Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2020-06-12 10:35:18 +02:00
commit ab60499298
37 changed files with 1441 additions and 955 deletions

View file

@ -107,7 +107,7 @@ namespace ImGui
const char ColorMarkerStart = 0x2; // STX const char ColorMarkerStart = 0x2; // STX
const char ColorMarkerEnd = 0x3; // ETX const char ColorMarkerEnd = 0x3; // ETX
// Special ASCII characters are used here as a ikons markers // Special ASCII characters are used here as an ikons markers
const char PrintIconMarker = 0x4; const char PrintIconMarker = 0x4;
const char PrinterIconMarker = 0x5; const char PrinterIconMarker = 0x5;
const char PrinterSlaIconMarker = 0x6; const char PrinterSlaIconMarker = 0x6;

View file

@ -192,6 +192,7 @@ add_library(libslic3r STATIC
MTUtils.hpp MTUtils.hpp
VoronoiOffset.cpp VoronoiOffset.cpp
VoronoiOffset.hpp VoronoiOffset.hpp
VoronoiVisualUtils.hpp
Zipper.hpp Zipper.hpp
Zipper.cpp Zipper.cpp
MinAreaBoundingBox.hpp MinAreaBoundingBox.hpp

View file

@ -31,7 +31,7 @@ extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrint
info.gcodes.reserve(colorprint_values.size()); info.gcodes.reserve(colorprint_values.size());
int i = 0; int i = 0;
for (auto val : colorprint_values) for (auto val : colorprint_values)
info.gcodes.emplace_back(Item{ val, ColorChangeCode, 1, colors[(++i)%7] }); info.gcodes.emplace_back(Item{ val, ColorChange, 1, colors[(++i)%7] });
info.mode = SingleExtruder; info.mode = SingleExtruder;
} }
@ -51,11 +51,11 @@ extern void check_mode_for_custom_gcode_per_print_z(Info& info)
bool is_single_extruder = true; bool is_single_extruder = true;
for (auto item : info.gcodes) for (auto item : info.gcodes)
{ {
if (item.gcode == ToolChangeCode) { if (item.type == ToolChange) {
info.mode = MultiAsSingle; info.mode = MultiAsSingle;
return; return;
} }
if (item.gcode == ColorChangeCode && item.extruder > 1) if (item.type == ColorChange && item.extruder > 1)
is_single_extruder = false; is_single_extruder = false;
} }
@ -68,7 +68,7 @@ std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& cus
{ {
std::vector<std::pair<double, unsigned int>> custom_tool_changes; std::vector<std::pair<double, unsigned int>> custom_tool_changes;
for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes) for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes)
if (custom_gcode.gcode == ToolChangeCode) { if (custom_gcode.type == ToolChange) {
// If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
assert(custom_gcode.extruder >= 0); assert(custom_gcode.extruder >= 0);
custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(size_t(custom_gcode.extruder) > num_extruders ? 1 : custom_gcode.extruder)); custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(size_t(custom_gcode.extruder) > num_extruders ? 1 : custom_gcode.extruder));

View file

@ -8,38 +8,40 @@ namespace Slic3r {
class DynamicPrintConfig; class DynamicPrintConfig;
// Additional Codes which can be set by user using DoubleSlider
static constexpr char ColorChangeCode[] = "M600";
static constexpr char PausePrintCode[] = "M601";
static constexpr char ToolChangeCode[] = "tool_change";
enum CustomGcodeType
{
cgtColorChange,
cgtPausePrint,
};
namespace CustomGCode { namespace CustomGCode {
enum Type
{
ColorChange,
PausePrint,
ToolChange,
Template,
Custom
};
struct Item struct Item
{ {
bool operator<(const Item& rhs) const { return this->print_z < rhs.print_z; } bool operator<(const Item& rhs) const { return this->print_z < rhs.print_z; }
bool operator==(const Item& rhs) const bool operator==(const Item& rhs) const
{ {
return (rhs.print_z == this->print_z ) && return (rhs.print_z == this->print_z ) &&
(rhs.gcode == this->gcode ) && (rhs.type == this->type ) &&
(rhs.extruder == this->extruder ) && (rhs.extruder == this->extruder ) &&
(rhs.color == this->color ); (rhs.color == this->color ) &&
(rhs.extra == this->extra );
} }
bool operator!=(const Item& rhs) const { return ! (*this == rhs); } bool operator!=(const Item& rhs) const { return ! (*this == rhs); }
double print_z; double print_z;
std::string gcode; Type type;
int extruder; // Informative value for ColorChangeCode and ToolChangeCode int extruder; // Informative value for ColorChangeCode and ToolChangeCode
// "gcode" == ColorChangeCode => M600 will be applied for "extruder" extruder // "gcode" == ColorChangeCode => M600 will be applied for "extruder" extruder
// "gcode" == ToolChangeCode => for whole print tool will be switched to "extruder" extruder // "gcode" == ToolChangeCode => for whole print tool will be switched to "extruder" extruder
std::string color; // if gcode is equal to PausePrintCode, std::string color; // if gcode is equal to PausePrintCode,
// this field is used for save a short message shown on Printer display // this field is used for save a short message shown on Printer display
std::string extra; // this field is used for the extra data like :
// - G-code text for the Type::Custom
// - message text for the Type::PausePrint
}; };
enum Mode enum Mode

View file

@ -1197,13 +1197,32 @@ namespace Slic3r {
} }
if (code.first != "code") if (code.first != "code")
continue; continue;
pt::ptree tree = code.second;
double print_z = tree.get<double> ("<xmlattr>.print_z" );
std::string gcode = tree.get<std::string> ("<xmlattr>.gcode" );
int extruder = tree.get<int> ("<xmlattr>.extruder" );
std::string color = tree.get<std::string> ("<xmlattr>.color" );
m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, gcode, extruder, color}) ; pt::ptree tree = code.second;
double print_z = tree.get<double> ("<xmlattr>.print_z" );
int extruder = tree.get<int> ("<xmlattr>.extruder");
std::string color = tree.get<std::string> ("<xmlattr>.color" );
CustomGCode::Type type;
std::string extra;
if (tree.find("type") == tree.not_found())
{
// It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
// read old data ...
std::string gcode = tree.get<std::string> ("<xmlattr>.gcode");
// ... and interpret them to the new data
type = gcode == "M600" ? CustomGCode::ColorChange :
gcode == "M601" ? CustomGCode::PausePrint :
gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom;
extra = type == CustomGCode::PausePrint ? color :
type == CustomGCode::Custom ? gcode : "";
}
else
{
type = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
extra = tree.get<std::string>("<xmlattr>.extra");
}
m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ;
} }
} }
} }
@ -1982,7 +2001,7 @@ namespace Slic3r {
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
}; };
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
@ -2082,7 +2101,7 @@ namespace Slic3r {
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"). // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
// All custom gcode per height of whole Model are stored here // All custom gcode per height of whole Model are stored here
if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model)) if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config))
{ {
close_zip_writer(&archive); close_zip_writer(&archive);
boost::filesystem::remove(filename); boost::filesystem::remove(filename);
@ -2702,7 +2721,7 @@ namespace Slic3r {
return true; return true;
} }
bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model) bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config)
{ {
std::string out = ""; std::string out = "";
@ -2714,11 +2733,20 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes)
{ {
pt::ptree& code_tree = main_tree.add("code", ""); pt::ptree& code_tree = main_tree.add("code", "");
// store minX and maxZ
// store data of custom_gcode_per_print_z
code_tree.put("<xmlattr>.print_z" , code.print_z ); code_tree.put("<xmlattr>.print_z" , code.print_z );
code_tree.put("<xmlattr>.gcode" , code.gcode ); code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder ); code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color ); code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.extra" , code.extra );
// add gcode field data for the old version of the PrusaSlicer
std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") :
code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
code_tree.put("<xmlattr>.gcode" , gcode );
} }
pt::ptree& mode_tree = main_tree.add("mode", ""); pt::ptree& mode_tree = main_tree.add("mode", "");

View file

@ -240,7 +240,7 @@ struct AMFParserContext
// Current instance allocated for an amf/constellation/instance subtree. // Current instance allocated for an amf/constellation/instance subtree.
Instance *m_instance; Instance *m_instance;
// Generic string buffer for vertices, face indices, metadata etc. // Generic string buffer for vertices, face indices, metadata etc.
std::string m_value[4]; std::string m_value[5];
// Pointer to config to update if config data are stored inside the amf file // Pointer to config to update if config data are stored inside the amf file
DynamicPrintConfig *m_config; DynamicPrintConfig *m_config;
@ -314,9 +314,26 @@ void AMFParserContext::startElement(const char *name, const char **atts)
if (strcmp(name, "code") == 0) { if (strcmp(name, "code") == 0) {
node_type_new = NODE_TYPE_GCODE_PER_HEIGHT; node_type_new = NODE_TYPE_GCODE_PER_HEIGHT;
m_value[0] = get_attribute(atts, "print_z"); m_value[0] = get_attribute(atts, "print_z");
m_value[1] = get_attribute(atts, "gcode"); m_value[1] = get_attribute(atts, "extruder");
m_value[2] = get_attribute(atts, "extruder"); m_value[2] = get_attribute(atts, "color");
m_value[3] = get_attribute(atts, "color"); if (get_attribute(atts, "type"))
{
m_value[3] = get_attribute(atts, "type");
m_value[4] = get_attribute(atts, "extra");
}
else
{
// It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
// read old data ...
std::string gcode = get_attribute(atts, "gcode");
// ... and interpret them to the new data
CustomGCode::Type type= gcode == "M600" ? CustomGCode::ColorChange :
gcode == "M601" ? CustomGCode::PausePrint :
gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom;
m_value[3] = std::to_string(static_cast<int>(type));
m_value[4] = type == CustomGCode::PausePrint ? m_value[2] :
type == CustomGCode::Custom ? gcode : "";
}
} }
else if (strcmp(name, "mode") == 0) { else if (strcmp(name, "mode") == 0) {
node_type_new = NODE_TYPE_CUSTOM_GCODE_MODE; node_type_new = NODE_TYPE_CUSTOM_GCODE_MODE;
@ -640,12 +657,13 @@ void AMFParserContext::endElement(const char * /* name */)
break; break;
case NODE_TYPE_GCODE_PER_HEIGHT: { case NODE_TYPE_GCODE_PER_HEIGHT: {
double print_z = double(atof(m_value[0].c_str())); double print_z = double(atof(m_value[0].c_str()));
const std::string& gcode = m_value[1]; int extruder = atoi(m_value[1].c_str());
int extruder = atoi(m_value[2].c_str()); const std::string& color= m_value[2];
const std::string& color = m_value[3]; CustomGCode::Type type = static_cast<CustomGCode::Type>(atoi(m_value[3].c_str()));
const std::string& extra= m_value[4];
m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, gcode, extruder, color}); m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra});
for (std::string& val: m_value) for (std::string& val: m_value)
val.clear(); val.clear();
@ -1253,9 +1271,17 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
pt::ptree& code_tree = main_tree.add("code", ""); pt::ptree& code_tree = main_tree.add("code", "");
// store custom_gcode_per_print_z gcodes information // store custom_gcode_per_print_z gcodes information
code_tree.put("<xmlattr>.print_z" , code.print_z ); code_tree.put("<xmlattr>.print_z" , code.print_z );
code_tree.put("<xmlattr>.gcode" , code.gcode ); code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder ); code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color ); code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.extra" , code.extra );
// add gcode field data for the old version of the PrusaSlicer
std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") :
code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
code_tree.put("<xmlattr>.gcode" , gcode );
} }
pt::ptree& mode_tree = main_tree.add("mode", ""); pt::ptree& mode_tree = main_tree.add("mode", "");

View file

@ -1824,17 +1824,18 @@ namespace ProcessLayer
const CustomGCode::Item *custom_gcode, const CustomGCode::Item *custom_gcode,
// ID of the first extruder printing this layer. // ID of the first extruder printing this layer.
unsigned int first_extruder_id, unsigned int first_extruder_id,
bool single_extruder_printer) const PrintConfig &config)
{ {
std::string gcode; std::string gcode;
bool single_extruder_printer = config.nozzle_diameter.size() == 1;
if (custom_gcode != nullptr) { if (custom_gcode != nullptr) {
// Extruder switches are processed by LayerTools, they should be filtered out. // Extruder switches are processed by LayerTools, they should be filtered out.
assert(custom_gcode->gcode != ToolChangeCode); assert(custom_gcode->type != CustomGCode::ToolChange);
const std::string &custom_code = custom_gcode->gcode; CustomGCode::Type gcode_type = custom_gcode->type;
bool color_change = custom_code == ColorChangeCode; bool color_change = gcode_type == CustomGCode::ColorChange;
bool tool_change = custom_code == ToolChangeCode; bool tool_change = gcode_type == CustomGCode::ToolChange;
// Tool Change is applied as Color Change for a single extruder printer only. // Tool Change is applied as Color Change for a single extruder printer only.
assert(! tool_change || single_extruder_printer); assert(! tool_change || single_extruder_printer);
@ -1842,8 +1843,8 @@ namespace ProcessLayer
int m600_extruder_before_layer = -1; int m600_extruder_before_layer = -1;
if (color_change && custom_gcode->extruder > 0) if (color_change && custom_gcode->extruder > 0)
m600_extruder_before_layer = custom_gcode->extruder - 1; m600_extruder_before_layer = custom_gcode->extruder - 1;
else if (custom_code == PausePrintCode) else if (gcode_type == CustomGCode::PausePrint)
pause_print_msg = custom_gcode->color; pause_print_msg = custom_gcode->extra;
// we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
if (color_change || tool_change) if (color_change || tool_change)
@ -1864,17 +1865,18 @@ namespace ProcessLayer
// && !MMU1 // && !MMU1
) { ) {
//! FIXME_in_fw show message during print pause //! FIXME_in_fw show message during print pause
gcode += "M601\n"; // pause print gcode += config.pause_print_gcode;// pause print
gcode += "\n";
gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
} }
else { else {
gcode += ColorChangeCode; gcode += config.color_change_gcode;//ColorChangeCode;
gcode += "\n"; gcode += "\n";
} }
} }
else else
{ {
if (custom_code == PausePrintCode) // Pause print if (gcode_type == CustomGCode::PausePrint) // Pause print
{ {
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
// add tag for processor // add tag for processor
@ -1888,8 +1890,9 @@ namespace ProcessLayer
gcode += "M117 " + pause_print_msg + "\n"; gcode += "M117 " + pause_print_msg + "\n";
// add tag for time estimator // add tag for time estimator
gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n"; gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n";
gcode += config.pause_print_gcode;
} }
else // custom Gcode else
{ {
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
// add tag for processor // add tag for processor
@ -1900,8 +1903,13 @@ namespace ProcessLayer
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
// add tag for time estimator // add tag for time estimator
//gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n"; //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n";
if (gcode_type == CustomGCode::Template) // Template Cistom Gcode
gcode += config.template_custom_gcode;
else // custom Gcode
gcode += custom_gcode->extra;
} }
gcode += custom_code + "\n"; gcode += "\n";
} }
} }
@ -2078,7 +2086,7 @@ void GCode::process_layer(
if (single_object_instance_idx == size_t(-1)) { if (single_object_instance_idx == size_t(-1)) {
// Normal (non-sequential) print. // Normal (non-sequential) print.
gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config().nozzle_diameter.size() == 1); gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config());
} }
// Extrude skirt at the print_z of the raft layers and normal object layers // Extrude skirt at the print_z of the raft layers and normal object layers
// not at the print_z of the interlaced support material layers. // not at the print_z of the interlaced support material layers.

View file

@ -492,7 +492,7 @@ void ToolOrdering::assign_custom_gcodes(const Print &print)
for (unsigned int i : lt.extruders) for (unsigned int i : lt.extruders)
extruder_printing_above[i] = true; extruder_printing_above[i] = true;
// Skip all custom G-codes above this layer and skip all extruder switches. // Skip all custom G-codes above this layer and skip all extruder switches.
for (; custom_gcode_it != custom_gcode_per_print_z.gcodes.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->gcode == ToolChangeCode); ++ custom_gcode_it); for (; custom_gcode_it != custom_gcode_per_print_z.gcodes.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->type == CustomGCode::ToolChange); ++ custom_gcode_it);
if (custom_gcode_it == custom_gcode_per_print_z.gcodes.rend()) if (custom_gcode_it == custom_gcode_per_print_z.gcodes.rend())
// Custom G-codes were processed. // Custom G-codes were processed.
break; break;
@ -504,8 +504,8 @@ void ToolOrdering::assign_custom_gcodes(const Print &print)
print_z_below = it_lt_below->print_z; print_z_below = it_lt_below->print_z;
if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) { if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) {
// The custom G-code applies to the current layer. // The custom G-code applies to the current layer.
bool color_change = custom_gcode.gcode == ColorChangeCode; bool color_change = custom_gcode.type == CustomGCode::ColorChange;
bool tool_change = custom_gcode.gcode == ToolChangeCode; bool tool_change = custom_gcode.type == CustomGCode::ToolChange;
bool pause_or_custom_gcode = ! color_change && ! tool_change; bool pause_or_custom_gcode = ! color_change && ! tool_change;
bool apply_color_change = ! ignore_tool_and_color_changes && bool apply_color_change = ! ignore_tool_and_color_changes &&
// If it is color change, it will actually be useful as the exturder above will print. // If it is color change, it will actually be useful as the exturder above will print.

View file

@ -188,7 +188,7 @@ namespace Slic3r {
_calculate_time(0); _calculate_time(0);
if (m_needs_custom_gcode_times && (m_custom_gcode_time_cache != 0.0f)) if (m_needs_custom_gcode_times && (m_custom_gcode_time_cache != 0.0f))
m_custom_gcode_times.push_back({ cgtColorChange, m_custom_gcode_time_cache }); m_custom_gcode_times.push_back({CustomGCode::ColorChange, m_custom_gcode_time_cache });
#if ENABLE_MOVE_STATS #if ENABLE_MOVE_STATS
_log_moves_stats(); _log_moves_stats();
@ -678,7 +678,7 @@ namespace Slic3r {
return _get_time_minutes(get_time()); return _get_time_minutes(get_time());
} }
std::vector<std::pair<CustomGcodeType, float>> GCodeTimeEstimator::get_custom_gcode_times() const std::vector<std::pair<CustomGCode::Type, float>> GCodeTimeEstimator::get_custom_gcode_times() const
{ {
return m_custom_gcode_times; return m_custom_gcode_times;
} }
@ -722,9 +722,9 @@ namespace Slic3r {
return ret; return ret;
} }
std::vector<std::pair<CustomGcodeType, std::string>> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const std::vector<std::pair<CustomGCode::Type, std::string>> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const
{ {
std::vector<std::pair<CustomGcodeType, std::string>> ret; std::vector<std::pair<CustomGCode::Type, std::string>> ret;
float total_time = 0.0f; float total_time = 0.0f;
for (auto t : m_custom_gcode_times) for (auto t : m_custom_gcode_times)
@ -1470,7 +1470,7 @@ namespace Slic3r {
size_t pos = comment.find(Color_Change_Tag); size_t pos = comment.find(Color_Change_Tag);
if (pos != comment.npos) if (pos != comment.npos)
{ {
_process_custom_gcode_tag(cgtColorChange); _process_custom_gcode_tag(CustomGCode::ColorChange);
return true; return true;
} }
@ -1478,14 +1478,14 @@ namespace Slic3r {
pos = comment.find(Pause_Print_Tag); pos = comment.find(Pause_Print_Tag);
if (pos != comment.npos) if (pos != comment.npos)
{ {
_process_custom_gcode_tag(cgtPausePrint); _process_custom_gcode_tag(CustomGCode::PausePrint);
return true; return true;
} }
return false; return false;
} }
void GCodeTimeEstimator::_process_custom_gcode_tag(CustomGcodeType code) void GCodeTimeEstimator::_process_custom_gcode_tag(CustomGCode::Type code)
{ {
PROFILE_FUNC(); PROFILE_FUNC();
m_needs_custom_gcode_times = true; m_needs_custom_gcode_times = true;

View file

@ -234,7 +234,7 @@ namespace Slic3r {
// data to calculate custom code times // data to calculate custom code times
bool m_needs_custom_gcode_times; bool m_needs_custom_gcode_times;
std::vector<std::pair<CustomGcodeType, float>> m_custom_gcode_times; std::vector<std::pair<CustomGCode::Type, float>> m_custom_gcode_times;
float m_custom_gcode_time_cache; float m_custom_gcode_time_cache;
#if ENABLE_MOVE_STATS #if ENABLE_MOVE_STATS
@ -358,7 +358,7 @@ namespace Slic3r {
std::string get_time_minutes() const; std::string get_time_minutes() const;
// Returns the estimated time, in seconds, for each custom gcode // Returns the estimated time, in seconds, for each custom gcode
std::vector<std::pair<CustomGcodeType, float>> get_custom_gcode_times() const; std::vector<std::pair<CustomGCode::Type, float>> get_custom_gcode_times() const;
// Returns the estimated time, in format DDd HHh MMm SSs, for each color // Returns the estimated time, in format DDd HHh MMm SSs, for each color
// If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)"
@ -370,7 +370,7 @@ namespace Slic3r {
// Returns the estimated time, in format DDd HHh MMm, for each custom_gcode // Returns the estimated time, in format DDd HHh MMm, for each custom_gcode
// If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)" // If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)"
std::vector<std::pair<CustomGcodeType, std::string>> get_custom_gcode_times_dhm(bool include_remaining) const; std::vector<std::pair<CustomGCode::Type, std::string>> get_custom_gcode_times_dhm(bool include_remaining) const;
// Return an estimate of the memory consumed by the time estimator. // Return an estimate of the memory consumed by the time estimator.
size_t memory_used() const; size_t memory_used() const;
@ -453,7 +453,7 @@ namespace Slic3r {
bool _process_tags(const GCodeReader::GCodeLine& line); bool _process_tags(const GCodeReader::GCodeLine& line);
// Processes ColorChangeTag and PausePrintTag // Processes ColorChangeTag and PausePrintTag
void _process_custom_gcode_tag(CustomGcodeType code); void _process_custom_gcode_tag(CustomGCode::Type code);
// Simulates firmware st_synchronize() call // Simulates firmware st_synchronize() call
void _simulate_st_synchronize(float additional_time); void _simulate_st_synchronize(float additional_time);

View file

@ -135,4 +135,4 @@ BoundingBox get_extents(const Lines &lines)
return bbox; return bbox;
} }
} } // namespace Slic3r

View file

@ -103,7 +103,7 @@ public:
Vec3d b; Vec3d b;
}; };
extern BoundingBox get_extents(const Lines &lines); BoundingBox get_extents(const Lines &lines);
} // namespace Slic3r } // namespace Slic3r
@ -125,4 +125,4 @@ namespace boost { namespace polygon {
} } } }
// end Boost // end Boost
#endif #endif // slic3r_Line_hpp_

View file

@ -499,12 +499,12 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<Custo
auto it_a = va.begin(); auto it_a = va.begin();
auto it_b = vb.begin(); auto it_b = vb.begin();
while (it_a != va.end() || it_b != vb.end()) { while (it_a != va.end() || it_b != vb.end()) {
if (it_a != va.end() && it_a->gcode != ToolChangeCode) { if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) {
// Skip any CustomGCode items, which are not tool changes. // Skip any CustomGCode items, which are not tool changes.
++ it_a; ++ it_a;
continue; continue;
} }
if (it_b != vb.end() && it_b->gcode != ToolChangeCode) { if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) {
// Skip any CustomGCode items, which are not tool changes. // Skip any CustomGCode items, which are not tool changes.
++ it_b; ++ it_b;
continue; continue;
@ -512,8 +512,8 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector<Custo
if (it_a == va.end() || it_b == vb.end()) if (it_a == va.end() || it_b == vb.end())
// va or vb contains more Tool Changes than the other. // va or vb contains more Tool Changes than the other.
return true; return true;
assert(it_a->gcode == ToolChangeCode); assert(it_a->type == CustomGCode::ToolChange);
assert(it_b->gcode == ToolChangeCode); assert(it_b->type == CustomGCode::ToolChange);
if (*it_a != *it_b) if (*it_a != *it_b)
// The two Tool Changes differ. // The two Tool Changes differ.
return true; return true;

View file

@ -305,8 +305,8 @@ struct PrintStatistics
PrintStatistics() { clear(); } PrintStatistics() { clear(); }
std::string estimated_normal_print_time; std::string estimated_normal_print_time;
std::string estimated_silent_print_time; std::string estimated_silent_print_time;
std::vector<std::pair<CustomGcodeType, std::string>> estimated_normal_custom_gcode_print_times; std::vector<std::pair<CustomGCode::Type, std::string>> estimated_normal_custom_gcode_print_times;
std::vector<std::pair<CustomGcodeType, std::string>> estimated_silent_custom_gcode_print_times; std::vector<std::pair<CustomGCode::Type, std::string>> estimated_silent_custom_gcode_print_times;
double total_used_filament; double total_used_filament;
double total_extruded_volume; double total_extruded_volume;
double total_cost; double total_cost;

View file

@ -1909,6 +1909,33 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionStrings { "; Filament gcode\n" }); def->set_default_value(new ConfigOptionStrings { "; Filament gcode\n" });
def = this->add("color_change_gcode", coString);
def->label = L("Color change G-code");
def->tooltip = L("This G-code will be used as a code for the color change");
def->multiline = true;
def->full_width = true;
def->height = 12;
def->mode = comExpert;
def->set_default_value(new ConfigOptionString("M600"));
def = this->add("pause_print_gcode", coString);
def->label = L("Pause Print G-code");
def->tooltip = L("This G-code will be used as a code for the pause print");
def->multiline = true;
def->full_width = true;
def->height = 12;
def->mode = comExpert;
def->set_default_value(new ConfigOptionString("M601"));
def = this->add("template_custom_gcode", coString);
def->label = L("Custom G-code");
def->tooltip = L("This G-code will be used as a custom code");
def->multiline = true;
def->full_width = true;
def->height = 12;
def->mode = comExpert;
def->set_default_value(new ConfigOptionString(""));
def = this->add("single_extruder_multi_material", coBool); def = this->add("single_extruder_multi_material", coBool);
def->label = L("Single Extruder Multi Material"); def->label = L("Single Extruder Multi Material");
def->tooltip = L("The printer multiplexes filaments into a single hot end."); def->tooltip = L("The printer multiplexes filaments into a single hot end.");

View file

@ -699,6 +699,9 @@ public:
ConfigOptionBool remaining_times; ConfigOptionBool remaining_times;
ConfigOptionBool silent_mode; ConfigOptionBool silent_mode;
ConfigOptionFloat extra_loading_move; ConfigOptionFloat extra_loading_move;
ConfigOptionString color_change_gcode;
ConfigOptionString pause_print_gcode;
ConfigOptionString template_custom_gcode;
std::string get_extrusion_axis() const std::string get_extrusion_axis() const
{ {
@ -772,6 +775,9 @@ protected:
OPT_PTR(remaining_times); OPT_PTR(remaining_times);
OPT_PTR(silent_mode); OPT_PTR(silent_mode);
OPT_PTR(extra_loading_move); OPT_PTR(extra_loading_move);
OPT_PTR(color_change_gcode);
OPT_PTR(pause_print_gcode);
OPT_PTR(template_custom_gcode);
} }
}; };

View file

@ -97,7 +97,7 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
_generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
hc.closing_distance)); hc.closing_distance));
if (meshptr) { if (meshptr && !meshptr->empty()) {
// This flips the normals to be outward facing... // This flips the normals to be outward facing...
meshptr->require_shared_vertices(); meshptr->require_shared_vertices();

View file

@ -28,17 +28,25 @@ void reproject_support_points(const EigenMesh3D &mesh, std::vector<PointType> &p
inline void reproject_points_and_holes(ModelObject *object) inline void reproject_points_and_holes(ModelObject *object)
{ {
bool has_sppoints = !object->sla_support_points.empty(); bool has_sppoints = !object->sla_support_points.empty();
bool has_holes = !object->sla_drain_holes.empty();
if (!object || (!has_holes && !has_sppoints)) return; // Disabling reprojection of holes as they have a significant offset away
// from the model body which tolerates minor geometrical changes.
//
// TODO: uncomment and ensure the right offset of the hole points if
// reprojection would still be necessary.
// bool has_holes = !object->sla_drain_holes.empty();
EigenMesh3D emesh{object->raw_mesh()}; if (!object || (/*!has_holes &&*/ !has_sppoints)) return;
TriangleMesh rmsh = object->raw_mesh();
rmsh.require_shared_vertices();
EigenMesh3D emesh{rmsh};
if (has_sppoints) if (has_sppoints)
reproject_support_points(emesh, object->sla_support_points); reproject_support_points(emesh, object->sla_support_points);
if (has_holes) // if (has_holes)
reproject_support_points(emesh, object->sla_drain_holes); // reproject_support_points(emesh, object->sla_drain_holes);
} }
}} }}

View file

@ -1,11 +1,15 @@
// Polygon offsetting code inspired by OpenVoronoi by Anders Wallin // Polygon offsetting using Voronoi diagram prodiced by boost::polygon.
// https://github.com/aewallin/openvoronoi
// This offsetter uses results of boost::polygon Voronoi.
#include "VoronoiOffset.hpp" #include "VoronoiOffset.hpp"
#include <cmath> #include <cmath>
// #define VORONOI_DEBUG_OUT
#ifdef VORONOI_DEBUG_OUT
#include <libslic3r/VoronoiVisualUtils.hpp>
#endif
namespace Slic3r { namespace Slic3r {
using VD = Geometry::VoronoiDiagram; using VD = Geometry::VoronoiDiagram;
@ -48,6 +52,93 @@ namespace detail {
} }
} }
struct Intersections
{
int count;
Vec2d pts[2];
};
// Return maximum two points, that are at distance "d" from both points
Intersections point_point_equal_distance_points(const Point &pt1, const Point &pt2, const double d)
{
// input points
const auto cx = double(pt1.x());
const auto cy = double(pt1.y());
const auto qx = double(pt2.x());
const auto qy = double(pt2.y());
// Calculating determinant.
auto x0 = 2. * qy;
auto cx2 = cx * cx;
auto cy2 = cy * cy;
auto x5 = 2 * cx * qx;
auto x6 = cy * x0;
auto qx2 = qx * qx;
auto qy2 = qy * qy;
auto x9 = qx2 + qy2;
auto x10 = cx2 + cy2 - x5 - x6 + x9;
auto x11 = - cx2 - cy2;
auto discr = x10 * (4. * d + x11 + x5 + x6 - qx2 - qy2);
if (discr < 0.)
// No intersection point found, the two circles are too far away.
return Intersections { 0, { Vec2d(), Vec2d() } };
// Some intersections are found.
int npoints = (discr > 0) ? 2 : 1;
auto x1 = 2. * cy - x0;
auto x2 = cx - qx;
auto x12 = 0.5 * x2 * sqrt(discr) / x10;
auto x13 = 0.5 * (cy + qy);
auto x14 = - x12 + x13;
auto x15 = x11 + x9;
auto x16 = 0.5 / x2;
auto x17 = x12 + x13;
return Intersections { npoints, { Vec2d(- x16 * (x1 * x14 + x15), x14),
Vec2d(- x16 * (x1 * x17 + x15), x17) } };
}
// Return maximum two points, that are at distance "d" from both the line and point.
Intersections line_point_equal_distance_points(const Line &line, const Point &pt, const double d)
{
assert(line.a != pt && line.b != pt);
// Calculating two points of distance "d" to a ray and a point.
// Point.
auto x0 = double(pt.x());
auto y0 = double(pt.y());
// Ray equation. Vector (a, b) is perpendicular to line.
auto a = double(line.a.y() - line.b.y());
auto b = double(line.b.x() - line.a.x());
// pt shall not lie on line.
assert(std::abs((x0 - line.a.x()) * a + (y0 - line.a.y()) * b) < SCALED_EPSILON);
// Orient line so that the vector (a, b) points towards pt.
if (a * (x0 - line.a.x()) + b * (y0 - line.a.y()) < 0.)
std::swap(x0, y0);
double c = - a * double(line.a.x()) - b * double(line.a.y());
// Calculate the two points.
double a2 = a * a;
double b2 = b * b;
double a2b2 = a2 + b2;
double d2 = d * d;
double s = a2*d2 - a2*sqr(x0) - 2*a*b*x0*y0 - 2*a*c*x0 + 2*a*d*x0 + b2*d2 - b2*sqr(y0) - 2*b*c*y0 + 2*b*d*y0 - sqr(c) + 2*c*d - d2;
if (s < 0.)
// Distance of pt from line is bigger than 2 * d.
return Intersections { 0 };
double u;
int cnt;
if (s == 0.) {
// Distance of pt from line is 2 * d.
cnt = 1;
u = 0.;
} else {
// Distance of pt from line is smaller than 2 * d.
cnt = 2;
u = a*sqrt(s)/a2b2;
}
double v = (-a2*y0 + a*b*x0 + b*c - b*d)/a2b2;
return Intersections { cnt, { Vec2d((b * ( u + v) - c + d) / a, - u - v),
Vec2d((b * (- u + v) - c + d) / a, u - v) } };
}
Vec2d voronoi_edge_offset_point( Vec2d voronoi_edge_offset_point(
const VD &vd, const VD &vd,
const Lines &lines, const Lines &lines,
@ -131,174 +222,384 @@ namespace detail {
} }
}; };
Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance, double discretization_error) static Vec2d foot_pt(const Line &iline, const Point &ipt)
{ {
// Distance of a VD vertex to the closest site (input polygon edge or vertex). Vec2d pt = iline.a.cast<double>();
std::vector<double> vertex_dist(vd.num_vertices(), std::numeric_limits<double>::max()); Vec2d dir = (iline.b - iline.a).cast<double>();
Vec2d v = ipt.cast<double>() - pt;
double l2 = dir.squaredNorm();
double t = (l2 == 0.) ? 0. : v.dot(dir) / l2;
return pt + dir * t;
}
// Minium distance of a VD edge to the closest site (input polygon edge or vertex). Polygons voronoi_offset(
// For a parabolic segment the distance may be smaller than the distance of the two end points. const Geometry::VoronoiDiagram &vd,
std::vector<double> edge_dist(vd.num_edges(), std::numeric_limits<double>::max()); const Lines &lines,
double offset_distance,
// Calculate minimum distance of input polygons to voronoi vertices and voronoi edges. double discretization_error)
for (const VD::edge_type &edge : vd.edges()) { {
const VD::vertex_type *v0 = edge.vertex0();
const VD::vertex_type *v1 = edge.vertex1();
const VD::cell_type *cell = edge.cell();
const VD::cell_type *cell2 = edge.twin()->cell();
const Line &line0 = lines[cell->source_index()];
const Line &line1 = lines[cell2->source_index()];
double d0, d1, dmin;
if (v0 == nullptr || v1 == nullptr) {
assert(edge.is_infinite());
if (cell->contains_point() && cell2->contains_point()) {
const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
d0 = d1 = std::numeric_limits<double>::max();
if (v0 == nullptr && v1 == nullptr) {
dmin = (pt1.cast<double>() - pt0.cast<double>()).norm();
} else {
Vec2d pt((pt0 + pt1).cast<double>() * 0.5);
Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x()));
Vec2d pt0d(pt0.x(), pt0.y());
if (v0) {
Vec2d a(v0->x(), v0->y());
d0 = (a - pt0d).norm();
dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d0;
vertex_dist[v0 - &vd.vertices().front()] = d0;
} else {
Vec2d a(v1->x(), v1->y());
d1 = (a - pt0d).norm();
dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d1;
vertex_dist[v1 - &vd.vertices().front()] = d1;
}
}
} else {
// Infinite edges could not be created by two segment sites.
assert(cell->contains_point() != cell2->contains_point());
// Linear edge goes through the endpoint of a segment.
assert(edge.is_linear());
assert(edge.is_secondary());
#ifndef NDEBUG #ifndef NDEBUG
if (cell->contains_segment()) { // Verify that twin halfedges are stored next to the other in vd.
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; for (size_t i = 0; i < vd.num_edges(); i += 2) {
assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || const VD::edge_type &e = vd.edges()[i];
(pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); const VD::edge_type &e2 = vd.edges()[i + 1];
assert(e.twin() == &e2);
assert(e2.twin() == &e);
assert(e.is_secondary() == e2.is_secondary());
if (e.is_secondary()) {
assert(e.cell()->contains_point() != e2.cell()->contains_point());
const VD::edge_type &ex = (e.cell()->contains_point() ? e : e2);
// Verify that the Point defining the cell left of ex is an end point of a segment
// defining the cell right of ex.
const Line &line0 = lines[ex.cell()->source_index()];
const Line &line1 = lines[ex.twin()->cell()->source_index()];
const Point &pt = (ex.cell()->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
assert(pt == line1.a || pt == line1.b);
}
}
#endif // NDEBUG
// Mark edges with outward vertex pointing outside the polygons, thus there is a chance
// that such an edge will have an intersection with our desired offset curve.
bool outside = offset_distance > 0.;
std::vector<char> edge_candidate(vd.num_edges(), 2); // unknown state
const VD::edge_type *front_edge = &vd.edges().front();
for (const VD::edge_type &edge : vd.edges())
if (edge.vertex1() == nullptr) {
// Infinite Voronoi edge separating two Point sites.
// Infinite edge is always outside and it has at least one valid vertex.
assert(edge.vertex0() != nullptr);
edge_candidate[&edge - front_edge] = outside;
// Opposite edge of an infinite edge is certainly not active.
edge_candidate[edge.twin() - front_edge] = 0;
} else if (edge.vertex1() != nullptr) {
// Finite edge.
const VD::cell_type *cell = edge.cell();
const Line *line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr;
if (line == nullptr) {
cell = edge.twin()->cell();
line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr;
}
if (line) {
const VD::vertex_type *v1 = edge.vertex1();
assert(v1);
Vec2d l0(line->a.cast<double>());
Vec2d lv((line->b - line->a).cast<double>());
double side = cross2(lv, Vec2d(v1->x(), v1->y()) - l0);
edge_candidate[&edge - front_edge] = outside ? (side < 0.) : (side > 0.);
}
}
for (const VD::edge_type &edge : vd.edges())
if (edge_candidate[&edge - front_edge] == 2) {
assert(edge.cell()->contains_point() && edge.twin()->cell()->contains_point());
// Edge separating two point sources, not yet classified as inside / outside.
const VD::edge_type *e = &edge;
char state;
do {
state = edge_candidate[e - front_edge];
if (state != 2)
break;
e = e->next();
} while (e != &edge);
e = &edge;
do {
char &s = edge_candidate[e - front_edge];
if (s == 2) {
assert(e->cell()->contains_point() && e->twin()->cell()->contains_point());
assert(edge_candidate[e->twin() - front_edge] == 2);
s = state;
edge_candidate[e->twin() - front_edge] = state;
}
e = e->next();
} while (e != &edge);
}
if (! outside)
offset_distance = - offset_distance;
#ifdef VORONOI_DEBUG_OUT
BoundingBox bbox;
{
bbox.merge(get_extents(lines));
bbox.min -= (0.01 * bbox.size().cast<double>()).cast<coord_t>();
bbox.max += (0.01 * bbox.size().cast<double>()).cast<coord_t>();
}
{
Lines helper_lines;
for (const VD::edge_type &edge : vd.edges())
if (edge_candidate[&edge - front_edge]) {
const VD::vertex_type *v0 = edge.vertex0();
const VD::vertex_type *v1 = edge.vertex1();
assert(v0 != nullptr);
Vec2d pt1(v0->x(), v0->y());
Vec2d pt2;
if (v1 == nullptr) {
// Unconstrained edge. Calculate a trimmed position.
assert(edge.is_linear());
const VD::cell_type *cell = edge.cell();
const VD::cell_type *cell2 = edge.twin()->cell();
const Line &line0 = lines[cell->source_index()];
const Line &line1 = lines[cell2->source_index()];
if (cell->contains_point() && cell2->contains_point()) {
const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
// Direction vector of this unconstrained Voronoi edge.
Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x()));
pt2 = Vec2d(v0->x(), v0->y()) + dir.normalized() * scale_(10.);
} else {
// Infinite edges could not be created by two segment sites.
assert(cell->contains_point() != cell2->contains_point());
// Linear edge goes through the endpoint of a segment.
assert(edge.is_secondary());
const Point &ipt = cell->contains_segment() ?
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) :
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b);
// Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve.
const Line &line = cell->contains_segment() ? line0 : line1;
assert(line.a == ipt || line.b == ipt);
// dir is perpendicular to line.
Vec2d dir(line.a.y() - line.b.y(), line.b.x() - line.a.x());
assert(dir.norm() > 0.);
if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr))
dir = - dir;
pt2 = ipt.cast<double>() + dir.normalized() * scale_(10.);
}
} else { } else {
pt2 = Vec2d(v1->x(), v1->y());
// Clip the line by the bounding box, so that the coloring of the line will be visible.
Geometry::liang_barsky_line_clipping(pt1, pt2, BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>()));
}
helper_lines.emplace_back(Line(Point(pt1.cast<coord_t>()), Point(((pt1 + pt2) * 0.5).cast<coord_t>())));
}
dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates1.svg").c_str(), vd, Points(), lines, Polygons(), helper_lines);
}
#endif // VORONOI_DEBUG_OUT
std::vector<Vec2d> edge_offset_point(vd.num_edges(), Vec2d());
const double offset_distance2 = offset_distance * offset_distance;
for (const VD::edge_type &edge : vd.edges()) {
assert(edge_candidate[&edge - front_edge] != 2);
size_t edge_idx = &edge - front_edge;
if (edge_candidate[edge_idx] == 1) {
// Edge candidate, intersection points were not calculated yet.
const VD::vertex_type *v0 = edge.vertex0();
const VD::vertex_type *v1 = edge.vertex1();
assert(v0 != nullptr);
const VD::cell_type *cell = edge.cell();
const VD::cell_type *cell2 = edge.twin()->cell();
const Line &line0 = lines[cell->source_index()];
const Line &line1 = lines[cell2->source_index()];
size_t edge_idx2 = edge.twin() - front_edge;
if (v1 == nullptr) {
assert(edge.is_infinite());
assert(edge_candidate[edge_idx2] == 0);
if (cell->contains_point() && cell2->contains_point()) {
const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
(pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); double dmin2 = (Vec2d(v0->x(), v0->y()) - pt0.cast<double>()).squaredNorm();
} if (dmin2 <= offset_distance2) {
const Point &pt = cell->contains_segment() ? // There shall be an intersection of this unconstrained edge with the offset curve.
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : // Direction vector of this unconstrained Voronoi edge.
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x()));
#endif /* NDEBUG */ Vec2d pt(v0->x(), v0->y());
if (v0) { double t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir);
assert((Point(v0->x(), v0->y()) - pt).cast<double>().norm() < SCALED_EPSILON); edge_offset_point[edge_idx] = pt + t * dir;
d0 = dmin = 0.; edge_candidate[edge_idx] = 3;
vertex_dist[v0 - &vd.vertices().front()] = d0; } else
edge_candidate[edge_idx] = 0;
} else { } else {
assert((Point(v1->x(), v1->y()) - pt).cast<double>().norm() < SCALED_EPSILON); // Infinite edges could not be created by two segment sites.
d1 = dmin = 0.; assert(cell->contains_point() != cell2->contains_point());
vertex_dist[v1 - &vd.vertices().front()] = d1; // Linear edge goes through the endpoint of a segment.
assert(edge.is_linear());
assert(edge.is_secondary());
const Point &ipt = cell->contains_segment() ?
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) :
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b);
#ifndef NDEBUG
if (cell->contains_segment()) {
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) ||
(pt1.x() == line0.b.x() && pt1.y() == line0.b.y()));
} else {
const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b;
assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) ||
(pt0.x() == line1.b.x() && pt0.y() == line1.b.y()));
}
assert((Vec2d(v0->x(), v0->y()) - ipt.cast<double>()).norm() < SCALED_EPSILON);
#endif /* NDEBUG */
// Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve.
const Line &line = cell->contains_segment() ? line0 : line1;
assert(line.a == ipt || line.b == ipt);
Vec2d pt = ipt.cast<double>();
Vec2d dir(line.a.y() - line.b.y(), line.b.x() - line.a.x());
assert(dir.norm() > 0.);
double t = offset_distance / dir.norm();
if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr))
t = - t;
edge_offset_point[edge_idx] = pt + t * dir;
edge_candidate[edge_idx] = 3;
} }
} // The other edge of an unconstrained edge starting with null vertex shall never be intersected.
} else { edge_candidate[edge_idx2] = 0;
// Finite edge has valid points at both sides. } else if (edge.is_secondary()) {
if (cell->contains_segment() && cell2->contains_segment()) { assert(cell->contains_point() != cell2->contains_point());
// This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. const Line &line0 = lines[edge.cell()->source_index()];
Vec2d pt(line0.a.cast<double>()); const Line &line1 = lines[edge.twin()->cell()->source_index()];
Vec2d dir(line0.b.cast<double>() - pt); const Point &pt = cell->contains_point() ?
Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt;
Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt;
double l2 = dir.squaredNorm();
assert(l2 > 0.);
d0 = (dir * (vec0.dot(dir) / l2) - vec0).norm();
d1 = (dir * (vec1.dot(dir) / l2) - vec1).norm();
dmin = std::min(d0, d1);
} else {
assert(cell->contains_point() || cell2->contains_point());
const Point &pt0 = cell->contains_point() ?
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) :
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b);
// Project p0 to line segment <v0, v1>. const Line &line = cell->contains_segment() ? line0 : line1;
Vec2d p0(v0->x(), v0->y()); assert(pt == line.a || pt == line.b);
Vec2d p1(v1->x(), v1->y()); assert((pt.cast<double>() - Vec2d(v0->x(), v0->y())).norm() < SCALED_EPSILON);
Vec2d px(pt0.x(), pt0.y()); Vec2d dir(v1->x() - v0->x(), v1->y() - v0->y());
Vec2d v = p1 - p0; double l2 = dir.squaredNorm();
d0 = (p0 - px).norm(); if (offset_distance2 <= l2) {
d1 = (p1 - px).norm(); edge_offset_point[edge_idx] = pt.cast<double>() + (offset_distance / sqrt(l2)) * dir;
double t = v.dot(px - p0); edge_candidate[edge_idx] = 3;
double l2 = v.squaredNorm(); } else {
if (t > 0. && t < l2) { edge_candidate[edge_idx] = 0;
// Foot point on the line segment. }
Vec2d foot = p0 + (t / l2) * v; edge_candidate[edge_idx2] = 0;
dmin = (foot - px).norm(); } else {
} else // Finite edge has valid points at both sides.
dmin = std::min(d0, d1); bool done = false;
} if (cell->contains_segment() && cell2->contains_segment()) {
vertex_dist[v0 - &vd.vertices().front()] = d0; // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments.
vertex_dist[v1 - &vd.vertices().front()] = d1; Vec2d pt(line0.a.cast<double>());
Vec2d dir(line0.b.cast<double>() - pt);
Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt;
Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt;
double l2 = dir.squaredNorm();
assert(l2 > 0.);
double dmin = (dir * (vec0.dot(dir) / l2) - vec0).squaredNorm();
double dmax = (dir * (vec1.dot(dir) / l2) - vec1).squaredNorm();
bool flip = dmin > dmax;
if (flip)
std::swap(dmin, dmax);
if (offset_distance2 >= dmin && offset_distance2 <= dmax) {
// Intersect. Maximum one intersection will be found.
// This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically.
dmin = sqrt(dmin);
dmax = sqrt(dmax);
assert(offset_distance > dmin - EPSILON && offset_distance < dmax + EPSILON);
double ddif = dmax - dmin;
if (ddif == 0.) {
// line, line2 are exactly parallel. This is a singular case, the offset curve should miss it.
} else {
if (flip) {
std::swap(edge_idx, edge_idx2);
std::swap(v0, v1);
}
double t = clamp(0., 1., (offset_distance - dmin) / ddif);
edge_offset_point[edge_idx] = Vec2d(lerp(v0->x(), v1->x(), t), lerp(v0->y(), v1->y(), t));
edge_candidate[edge_idx] = 3;
edge_candidate[edge_idx2] = 0;
done = true;
}
}
} else {
assert(cell->contains_point() || cell2->contains_point());
bool point_vs_segment = cell->contains_point() != cell2->contains_point();
const Point &pt0 = cell->contains_point() ?
((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) :
((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b);
// Project p0 to line segment <v0, v1>.
Vec2d p0(v0->x(), v0->y());
Vec2d p1(v1->x(), v1->y());
Vec2d px(pt0.x(), pt0.y());
double d0 = (p0 - px).squaredNorm();
double d1 = (p1 - px).squaredNorm();
double dmin = std::min(d0, d1);
double dmax = std::max(d0, d1);
bool has_intersection = false;
if (offset_distance2 <= dmax) {
if (offset_distance2 >= dmin) {
has_intersection = true;
} else {
double dmin_new;
if (point_vs_segment) {
Vec2d ft = foot_pt(cell->contains_segment() ? line0 : line1, pt0);
dmin_new = (ft - px).squaredNorm() * 0.25;
} else {
// point vs. point
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
dmin_new = (pt1.cast<double>() - px).squaredNorm() * 0.25;
}
assert(dmin_new < dmax + SCALED_EPSILON);
assert(dmin_new < dmin + SCALED_EPSILON);
dmin = dmin_new;
has_intersection = offset_distance2 >= dmin;
}
}
if (has_intersection) {
detail::Intersections intersections;
if (point_vs_segment) {
assert(cell->contains_point() || cell2->contains_point());
intersections = detail::line_point_equal_distance_points(cell->contains_segment() ? line0 : line1, pt0, offset_distance);
} else {
const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b;
intersections = detail::point_point_equal_distance_points(pt0, pt1, offset_distance);
}
if (intersections.count == 2) {
// Now decide which points fall on this Voronoi edge.
// Tangential points (single intersection) are ignored.
Vec2d v = p1 - p0;
double l2 = v.squaredNorm();
double t0 = v.dot(intersections.pts[0] - p0);
double t1 = v.dot(intersections.pts[1] - p0);
if (t0 > t1) {
std::swap(t0, t1);
std::swap(intersections.pts[0], intersections.pts[1]);
}
// Remove points outside of the line range.
if (t0 < 0. || t0 > l2) {
if (t1 < 0. || t1 > l2)
intersections.count = 0;
else {
-- intersections.count;
t0 = t1;
intersections.pts[0] = intersections.pts[1];
}
} else if (t1 < 0. || t1 > l2)
-- intersections.count;
if (intersections.count == 2) {
edge_candidate[edge_idx] = edge_candidate[edge_idx2] = 3;
edge_offset_point[edge_idx] = intersections.pts[0];
edge_offset_point[edge_idx2] = intersections.pts[1];
done = true;
} else if (intersections.count == 1) {
if (d1 > d0) {
std::swap(edge_idx, edge_idx2);
edge_candidate[edge_idx] = 3;
edge_candidate[edge_idx2] = 0;
edge_offset_point[edge_idx] = intersections.pts[0];
}
done = true;
}
}
if (! done)
edge_candidate[edge_idx] = edge_candidate[edge_idx2] = 0;
}
}
}
} }
edge_dist[&edge - &vd.edges().front()] = dmin; }
}
// Mark cells intersected by the offset curve.
std::vector<unsigned char> seed_cells(vd.num_cells(), false);
for (const VD::cell_type &cell : vd.cells()) {
const VD::edge_type *first_edge = cell.incident_edge();
const VD::edge_type *edge = first_edge;
do {
double dmin = edge_dist[edge - &vd.edges().front()];
double dmax = std::numeric_limits<double>::max();
const VD::vertex_type *v0 = edge->vertex0();
const VD::vertex_type *v1 = edge->vertex1();
if (v0 != nullptr)
dmax = vertex_dist[v0 - &vd.vertices().front()];
if (v1 != nullptr)
dmax = std::max(dmax, vertex_dist[v1 - &vd.vertices().front()]);
if (offset_distance >= dmin && offset_distance <= dmax) {
// This cell is being intersected by the offset curve.
seed_cells[&cell - &vd.cells().front()] = true;
break;
}
edge = edge->next();
} while (edge != first_edge);
}
auto edge_dir = [&vd, &vertex_dist, &edge_dist, offset_distance](const VD::edge_type *edge) { #ifdef VORONOI_DEBUG_OUT
const VD::vertex_type *v0 = edge->vertex0(); {
const VD::vertex_type *v1 = edge->vertex1(); Lines helper_lines;
double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits<double>::max(); for (const VD::edge_type &edge : vd.edges())
double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::max(); if (edge_candidate[&edge - front_edge] == 3)
if (d0 < offset_distance && offset_distance < d1) helper_lines.emplace_back(Line(Point(edge.vertex0()->x(), edge.vertex0()->y()), Point(edge_offset_point[&edge - front_edge].cast<coord_t>())));
return true; dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates2.svg").c_str(), vd, Points(), lines, Polygons(), helper_lines);
else if (d1 < offset_distance && offset_distance < d0) }
return false; #endif // VORONOI_DEBUG_OUT
else {
assert(false);
return false;
}
};
/// \brief starting at e, find the next edge on the face that brackets t auto next_offset_edge = [&edge_candidate, front_edge](const VD::edge_type *start_edge) -> const VD::edge_type* {
/// for (const VD::edge_type *edge = start_edge->next(); edge != start_edge; edge = edge->next())
/// we can be in one of two modes. if (edge_candidate[edge->twin() - front_edge] == 3)
/// if direction==false then we are looking for an edge where src_t < t < trg_t return edge->twin();
/// if direction==true we are looning for an edge where trg_t < t < src_t
auto next_offset_edge =
[&vd, &vertex_dist, &edge_dist, offset_distance]
(const VD::edge_type *start_edge, bool direction) -> const VD::edge_type* {
const VD::edge_type *edge = start_edge;
do {
const VD::vertex_type *v0 = edge->vertex0();
const VD::vertex_type *v1 = edge->vertex1();
double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits<double>::max();
double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits<double>::max();
if (direction ? (d1 < offset_distance && offset_distance < d0) : (d0 < offset_distance && offset_distance < d1))
return edge;
edge = edge->next();
} while (edge != start_edge);
assert(false); assert(false);
return nullptr; return nullptr;
}; };
@ -316,28 +617,20 @@ Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance
Polygons out; Polygons out;
double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance); double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance);
double sin_threshold = sin(angle_step) + EPSILON; double sin_threshold = sin(angle_step) + EPSILON;
for (size_t seed_cell_idx = 0; seed_cell_idx < vd.num_cells(); ++ seed_cell_idx) for (size_t seed_edge_idx = 0; seed_edge_idx < vd.num_edges(); ++ seed_edge_idx)
if (seed_cells[seed_cell_idx]) { if (edge_candidate[seed_edge_idx] == 3) {
seed_cells[seed_cell_idx] = false; const VD::edge_type *start_edge = &vd.edges()[seed_edge_idx];
// Initial direction should not matter, an offset curve shall intersect a cell at least at two points
// (if it is not just touching the cell at a single vertex), and such two intersection points shall have
// opposite direction.
bool direction = false;
// the first edge on the start-face
const VD::cell_type &cell = vd.cells()[seed_cell_idx];
const VD::edge_type *start_edge = next_offset_edge(cell.incident_edge(), direction);
assert(start_edge->cell() == &cell);
const VD::edge_type *edge = start_edge; const VD::edge_type *edge = start_edge;
Polygon poly; Polygon poly;
do { do {
direction = edge_dir(edge);
// find the next edge // find the next edge
const VD::edge_type *next_edge = next_offset_edge(edge->next(), direction); const VD::edge_type *next_edge = next_offset_edge(edge);
//std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n"; //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n";
// Interpolate a circular segment or insert a linear segment between edge and next_edge. // Interpolate a circular segment or insert a linear segment between edge and next_edge.
const VD::cell_type *cell = edge->cell(); const VD::cell_type *cell = edge->cell();
Vec2d p1 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *edge, offset_distance); edge_candidate[next_edge - front_edge] = 0;
Vec2d p2 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *next_edge, offset_distance); Vec2d p1 = edge_offset_point[edge - front_edge];
Vec2d p2 = edge_offset_point[next_edge - front_edge];
#ifndef NDEBUG #ifndef NDEBUG
{ {
double err = dist_to_site(*cell, p1) - offset_distance; double err = dist_to_site(*cell, p1) - offset_distance;
@ -380,9 +673,7 @@ Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance
} }
} }
poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y()))); poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y())));
// although we may revisit current_face (if it is non-convex), it seems safe to mark it "done" here. edge = next_edge;
seed_cells[cell - &vd.cells().front()] = false;
edge = next_edge->twin();
} while (edge != start_edge); } while (edge != start_edge);
out.emplace_back(std::move(poly)); out.emplace_back(std::move(poly));
} }

View file

@ -1,3 +1,5 @@
// Polygon offsetting using Voronoi diagram prodiced by boost::polygon.
#ifndef slic3r_VoronoiOffset_hpp_ #ifndef slic3r_VoronoiOffset_hpp_
#define slic3r_VoronoiOffset_hpp_ #define slic3r_VoronoiOffset_hpp_
@ -7,7 +9,16 @@
namespace Slic3r { namespace Slic3r {
Polygons voronoi_offset(const Geometry::VoronoiDiagram &vd, const Lines &lines, double offset_distance, double discretization_error); // Offset a polygon or a set of polygons possibly with holes by traversing a Voronoi diagram.
// The input polygons are stored in lines and lines are referenced by vd.
// Outer curve will be extracted for a positive offset_distance,
// inner curve will be extracted for a negative offset_distance.
// Circular arches will be discretized to achieve discretization_error.
Polygons voronoi_offset(
const Geometry::VoronoiDiagram &vd,
const Lines &lines,
double offset_distance,
double discretization_error);
} // namespace Slic3r } // namespace Slic3r

View file

@ -0,0 +1,407 @@
#include <stack>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Line.hpp>
#include <libslic3r/Polygon.hpp>
#include <libslic3r/SVG.hpp>
namespace boost { namespace polygon {
// The following code for the visualization of the boost Voronoi diagram is based on:
//
// Boost.Polygon library voronoi_graphic_utils.hpp header file
// Copyright Andrii Sydorchuk 2010-2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
template <typename CT>
class voronoi_visual_utils {
public:
// Discretize parabolic Voronoi edge.
// Parabolic Voronoi edges are always formed by one point and one segment
// from the initial input set.
//
// Args:
// point: input point.
// segment: input segment.
// max_dist: maximum discretization distance.
// discretization: point discretization of the given Voronoi edge.
//
// Template arguments:
// InCT: coordinate type of the input geometries (usually integer).
// Point: point type, should model point concept.
// Segment: segment type, should model segment concept.
//
// Important:
// discretization should contain both edge endpoints initially.
template <class InCT1, class InCT2,
template<class> class Point,
template<class> class Segment>
static
typename enable_if<
typename gtl_and<
typename gtl_if<
typename is_point_concept<
typename geometry_concept< Point<InCT1> >::type
>::type
>::type,
typename gtl_if<
typename is_segment_concept<
typename geometry_concept< Segment<InCT2> >::type
>::type
>::type
>::type,
void
>::type discretize(
const Point<InCT1>& point,
const Segment<InCT2>& segment,
const CT max_dist,
std::vector< Point<CT> >* discretization) {
// Apply the linear transformation to move start point of the segment to
// the point with coordinates (0, 0) and the direction of the segment to
// coincide the positive direction of the x-axis.
CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y;
// Compute x-coordinates of the endpoints of the edge
// in the transformed space.
CT projection_start = sqr_segment_length *
get_point_projection((*discretization)[0], segment);
CT projection_end = sqr_segment_length *
get_point_projection((*discretization)[1], segment);
// Compute parabola parameters in the transformed space.
// Parabola has next representation:
// f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y).
CT point_vec_x = cast(x(point)) - cast(x(low(segment)));
CT point_vec_y = cast(y(point)) - cast(y(low(segment)));
CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y;
CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x;
// Save the last point.
Point<CT> last_point = (*discretization)[1];
discretization->pop_back();
// Use stack to avoid recursion.
std::stack<CT> point_stack;
point_stack.push(projection_end);
CT cur_x = projection_start;
CT cur_y = parabola_y(cur_x, rot_x, rot_y);
// Adjust max_dist parameter in the transformed space.
const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length;
while (!point_stack.empty()) {
CT new_x = point_stack.top();
CT new_y = parabola_y(new_x, rot_x, rot_y);
// Compute coordinates of the point of the parabola that is
// furthest from the current line segment.
CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x;
CT mid_y = parabola_y(mid_x, rot_x, rot_y);
// Compute maximum distance between the given parabolic arc
// and line segment that discretize it.
CT dist = (new_y - cur_y) * (mid_x - cur_x) -
(new_x - cur_x) * (mid_y - cur_y);
dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) +
(new_x - cur_x) * (new_x - cur_x));
if (dist <= max_dist_transformed) {
// Distance between parabola and line segment is less than max_dist.
point_stack.pop();
CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) /
sqr_segment_length + cast(x(low(segment)));
CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) /
sqr_segment_length + cast(y(low(segment)));
discretization->push_back(Point<CT>(inter_x, inter_y));
cur_x = new_x;
cur_y = new_y;
} else {
point_stack.push(mid_x);
}
}
// Update last point.
discretization->back() = last_point;
}
private:
// Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b).
static CT parabola_y(CT x, CT a, CT b) {
return ((x - a) * (x - a) + b * b) / (b + b);
}
// Get normalized length of the distance between:
// 1) point projection onto the segment
// 2) start point of the segment
// Return this length divided by the segment length. This is made to avoid
// sqrt computation during transformation from the initial space to the
// transformed one and vice versa. The assumption is made that projection of
// the point lies between the start-point and endpoint of the segment.
template <class InCT,
template<class> class Point,
template<class> class Segment>
static
typename enable_if<
typename gtl_and<
typename gtl_if<
typename is_point_concept<
typename geometry_concept< Point<int> >::type
>::type
>::type,
typename gtl_if<
typename is_segment_concept<
typename geometry_concept< Segment<long> >::type
>::type
>::type
>::type,
CT
>::type get_point_projection(
const Point<CT>& point, const Segment<InCT>& segment) {
CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
CT point_vec_x = x(point) - cast(x(low(segment)));
CT point_vec_y = y(point) - cast(y(low(segment)));
CT sqr_segment_length =
segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y;
CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y;
return vec_dot / sqr_segment_length;
}
template <typename InCT>
static CT cast(const InCT& value) {
return static_cast<CT>(value);
}
};
} } // namespace boost::polygon
namespace Slic3r
{
// The following code for the visualization of the boost Voronoi diagram is based on:
//
// Boost.Polygon library voronoi_visualizer.cpp file
// Copyright Andrii Sydorchuk 2010-2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
namespace Voronoi { namespace Internal {
using VD = Geometry::VoronoiDiagram;
typedef double coordinate_type;
typedef boost::polygon::point_data<coordinate_type> point_type;
typedef boost::polygon::segment_data<coordinate_type> segment_type;
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
typedef VD::cell_type cell_type;
typedef VD::cell_type::source_index_type source_index_type;
typedef VD::cell_type::source_category_type source_category_type;
typedef VD::edge_type edge_type;
typedef VD::cell_container_type cell_container_type;
typedef VD::cell_container_type vertex_container_type;
typedef VD::edge_container_type edge_container_type;
typedef VD::const_cell_iterator const_cell_iterator;
typedef VD::const_vertex_iterator const_vertex_iterator;
typedef VD::const_edge_iterator const_edge_iterator;
static const std::size_t EXTERNAL_COLOR = 1;
inline void color_exterior(const VD::edge_type* edge)
{
if (edge->color() == EXTERNAL_COLOR)
return;
edge->color(EXTERNAL_COLOR);
edge->twin()->color(EXTERNAL_COLOR);
const VD::vertex_type* v = edge->vertex1();
if (v == NULL || !edge->is_primary())
return;
v->color(EXTERNAL_COLOR);
const VD::edge_type* e = v->incident_edge();
do {
color_exterior(e);
e = e->rot_next();
} while (e != v->incident_edge());
}
inline point_type retrieve_point(const Points &points, const std::vector<segment_type> &segments, const cell_type& cell)
{
assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT ||
cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT);
return cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT ?
Voronoi::Internal::point_type(double(points[cell.source_index()].x()), double(points[cell.source_index()].y())) :
(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ?
low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
}
inline void clip_infinite_edge(const Points &points, const std::vector<segment_type> &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector<point_type>* clipped_edge)
{
const cell_type& cell1 = *edge.cell();
const cell_type& cell2 = *edge.twin()->cell();
point_type origin, direction;
// Infinite edges could not be created by two segment sites.
if (! cell1.contains_point() && ! cell2.contains_point()) {
printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n");
return;
}
if (cell1.contains_point() && cell2.contains_point()) {
point_type p1 = retrieve_point(points, segments, cell1);
point_type p2 = retrieve_point(points, segments, cell2);
origin.x((p1.x() + p2.x()) * 0.5);
origin.y((p1.y() + p2.y()) * 0.5);
direction.x(p1.y() - p2.y());
direction.y(p2.x() - p1.x());
} else {
origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1);
segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()];
coordinate_type dx = high(segment).x() - low(segment).x();
coordinate_type dy = high(segment).y() - low(segment).y();
if ((low(segment) == origin) ^ cell1.contains_point()) {
direction.x(dy);
direction.y(-dx);
} else {
direction.x(-dy);
direction.y(dx);
}
}
coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y()));
if (edge.vertex0() == NULL) {
clipped_edge->push_back(point_type(
origin.x() - direction.x() * koef,
origin.y() - direction.y() * koef));
} else {
clipped_edge->push_back(
point_type(edge.vertex0()->x(), edge.vertex0()->y()));
}
if (edge.vertex1() == NULL) {
clipped_edge->push_back(point_type(
origin.x() + direction.x() * koef,
origin.y() + direction.y() * koef));
} else {
clipped_edge->push_back(
point_type(edge.vertex1()->x(), edge.vertex1()->y()));
}
}
inline void sample_curved_edge(const Points &points, const std::vector<segment_type> &segments, const edge_type& edge, std::vector<point_type> &sampled_edge, coordinate_type max_dist)
{
point_type point = edge.cell()->contains_point() ?
retrieve_point(points, segments, *edge.cell()) :
retrieve_point(points, segments, *edge.twin()->cell());
segment_type segment = edge.cell()->contains_point() ?
segments[edge.twin()->cell()->source_index()] :
segments[edge.cell()->source_index()];
::boost::polygon::voronoi_visual_utils<coordinate_type>::discretize(point, segment, max_dist, &sampled_edge);
}
} /* namespace Internal */ } // namespace Voronoi
BoundingBox get_extents(const Lines &lines);
static inline void dump_voronoi_to_svg(
const char *path,
const Geometry::VoronoiDiagram &vd,
const Points &points,
const Lines &lines,
const Polygons &offset_curves = Polygons(),
const Lines &helper_lines = Lines(),
const double scale = 0.7) // 0.2?
{
const std::string inputSegmentPointColor = "lightseagreen";
const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR);
const std::string inputSegmentColor = "lightseagreen";
const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR);
const std::string voronoiPointColor = "black";
const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR);
const std::string voronoiLineColorPrimary = "black";
const std::string voronoiLineColorSecondary = "green";
const std::string voronoiArcColor = "red";
const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR);
const std::string offsetCurveColor = "magenta";
const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR);
const std::string helperLineColor = "orange";
const coord_t helperLineWidth = coord_t(0.09 * scale / SCALING_FACTOR);
const bool internalEdgesOnly = false;
const bool primaryEdgesOnly = false;
BoundingBox bbox;
bbox.merge(get_extents(points));
bbox.merge(get_extents(lines));
bbox.merge(get_extents(offset_curves));
bbox.min -= (0.01 * bbox.size().cast<double>()).cast<coord_t>();
bbox.max += (0.01 * bbox.size().cast<double>()).cast<coord_t>();
::Slic3r::SVG svg(path, bbox);
// bbox.scale(1.2);
// For clipping of half-lines to some reasonable value.
// The line will then be clipped by the SVG viewer anyway.
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
// For the discretization of the Voronoi parabolic segments.
const double discretization_step = 0.05 * bbox_dim_max;
// Make a copy of the input segments with the double type.
std::vector<Voronoi::Internal::segment_type> segments;
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it)
segments.push_back(Voronoi::Internal::segment_type(
Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))),
Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1)))));
// Color exterior edges.
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
if (!it->is_finite())
Voronoi::Internal::color_exterior(&(*it));
// Draw the end points of the input polygon.
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius);
svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius);
}
// Draw the input polygon.
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it)
svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth);
#if 1
// Draw voronoi vertices.
for (boost::polygon::voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius);
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
if (primaryEdgesOnly && !it->is_primary())
continue;
if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR))
continue;
std::vector<Voronoi::Internal::point_type> samples;
std::string color = voronoiLineColorPrimary;
if (!it->is_finite()) {
Voronoi::Internal::clip_infinite_edge(points, segments, *it, bbox_dim_max, &samples);
if (! it->is_primary())
color = voronoiLineColorSecondary;
} else {
// Store both points of the segment into samples. sample_curved_edge will split the initial line
// until the discretization_step is reached.
samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y()));
samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y()));
if (it->is_curved()) {
Voronoi::Internal::sample_curved_edge(points, segments, *it, samples, discretization_step);
color = voronoiArcColor;
} else if (! it->is_primary())
color = voronoiLineColorSecondary;
}
for (std::size_t i = 0; i + 1 < samples.size(); ++i)
svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth);
}
#endif
svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth);
svg.draw(helper_lines, helperLineColor, helperLineWidth);
svg.Close();
}
} // namespace Slic3r

View file

@ -98,6 +98,9 @@ void AppConfig::set_defaults()
set("use_environment_map", "0"); set("use_environment_map", "0");
#endif // ENABLE_ENVIRONMENT_MAP #endif // ENABLE_ENVIRONMENT_MAP
if (get("use_inches").empty())
set("use_inches", "0");
// Remove legacy window positions/sizes // Remove legacy window positions/sizes
erase("", "main_frame_maximized"); erase("", "main_frame_maximized");
erase("", "main_frame_pos"); erase("", "main_frame_pos");

View file

@ -29,6 +29,7 @@
#include "GUI.hpp" #include "GUI.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "GUI_Utils.hpp" #include "GUI_Utils.hpp"
#include "GUI_ObjectManipulation.hpp"
#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Config/Snapshot.hpp"
#include "slic3r/Utils/PresetUpdater.hpp" #include "slic3r/Utils/PresetUpdater.hpp"
@ -840,6 +841,10 @@ PageMode::PageMode(ConfigWizard *parent)
append(radio_simple); append(radio_simple);
append(radio_advanced); append(radio_advanced);
append(radio_expert); append(radio_expert);
append_text("\n" + _L("The size of the object can be specified in inches"));
check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches"));
append(check_inch);
} }
void PageMode::on_activate() void PageMode::on_activate()
@ -850,6 +855,8 @@ void PageMode::on_activate()
if (mode == "advanced") { radio_advanced->SetValue(true); } if (mode == "advanced") { radio_advanced->SetValue(true); }
else if (mode == "expert") { radio_expert->SetValue(true); } else if (mode == "expert") { radio_expert->SetValue(true); }
else { radio_simple->SetValue(true); } else { radio_simple->SetValue(true); }
check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1");
} }
void PageMode::serialize_mode(AppConfig *app_config) const void PageMode::serialize_mode(AppConfig *app_config) const
@ -866,6 +873,7 @@ void PageMode::serialize_mode(AppConfig *app_config) const
return; return;
app_config->set("view_mode", mode); app_config->set("view_mode", mode);
app_config->set("use_inches", check_inch->GetValue() ? "1" : "0");
} }
PageVendors::PageVendors(ConfigWizard *parent) PageVendors::PageVendors(ConfigWizard *parent)
@ -2179,6 +2187,7 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
p->apply_config(app.app_config, app.preset_bundle, app.preset_updater); p->apply_config(app.app_config, app.preset_bundle, app.preset_updater);
app.app_config->set_legacy_datadir(false); app.app_config->set_legacy_datadir(false);
app.update_mode(); app.update_mode();
app.obj_manipul()->update_ui_from_settings();
BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
return true; return true;
} else { } else {

View file

@ -316,6 +316,8 @@ struct PageMode: ConfigWizardPage
wxRadioButton *radio_advanced; wxRadioButton *radio_advanced;
wxRadioButton *radio_expert; wxRadioButton *radio_expert;
wxCheckBox *check_inch;
PageMode(ConfigWizard *parent); PageMode(ConfigWizard *parent);
void serialize_mode(AppConfig *app_config) const; void serialize_mode(AppConfig *app_config) const;

View file

@ -41,6 +41,21 @@ namespace DoubleSlider {
wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
static std::string gcode(Type type)
{
const PrintConfig& config = GUI::wxGetApp().plater()->fff_print().config();
switch (type) {
case ColorChange:
return config.color_change_gcode;
case PausePrint:
return config.pause_print_gcode;
case Template:
return config.template_custom_gcode;
default:
return "";
}
}
Control::Control( wxWindow *parent, Control::Control( wxWindow *parent,
wxWindowID id, wxWindowID id,
int lowerValue, int lowerValue,
@ -297,18 +312,25 @@ double Control::get_double_value(const SelectedSlider& selection)
return m_values[selection == ssLower ? m_lower_value : m_higher_value]; return m_values[selection == ssLower ? m_lower_value : m_higher_value];
} }
using t_custom_code = CustomGCode::Item; Info Control::GetTicksValues() const
CustomGCode::Info Control::GetTicksValues() const
{ {
CustomGCode::Info custom_gcode_per_print_z; Info custom_gcode_per_print_z;
std::vector<t_custom_code>& values = custom_gcode_per_print_z.gcodes; #if ENABLE_GCODE_VIEWER
std::vector<CustomGCode::Item>& values = custom_gcode_per_print_z.gcodes;
#else
std::vector<Item>& values = custom_gcode_per_print_z.gcodes;
#endif // ENABLE_GCODE_VIEWER
const int val_size = m_values.size(); const int val_size = m_values.size();
if (!m_values.empty()) if (!m_values.empty())
for (const TickCode& tick : m_ticks.ticks) { for (const TickCode& tick : m_ticks.ticks) {
if (tick.tick > val_size) if (tick.tick > val_size)
break; break;
values.emplace_back(t_custom_code{m_values[tick.tick], tick.gcode, tick.extruder, tick.color}); #if ENABLE_GCODE_VIEWER
values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra });
#else
values.emplace_back(Item{m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra});
#endif // ENABLE_GCODE_VIEWER
} }
if (m_force_mode_apply) if (m_force_mode_apply)
@ -317,7 +339,7 @@ CustomGCode::Info Control::GetTicksValues() const
return custom_gcode_per_print_z; return custom_gcode_per_print_z;
} }
void Control::SetTicksValues(const CustomGCode::Info& custom_gcode_per_print_z) void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
{ {
if (m_values.empty()) if (m_values.empty())
{ {
@ -328,14 +350,18 @@ void Control::SetTicksValues(const CustomGCode::Info& custom_gcode_per_print_z)
const bool was_empty = m_ticks.empty(); const bool was_empty = m_ticks.empty();
m_ticks.ticks.clear(); m_ticks.ticks.clear();
const std::vector<t_custom_code>& heights = custom_gcode_per_print_z.gcodes; #if ENABLE_GCODE_VIEWER
const std::vector<CustomGCode::Item>& heights = custom_gcode_per_print_z.gcodes;
#else
const std::vector<Item>& heights = custom_gcode_per_print_z.gcodes;
#endif // ENABLE_GCODE_VIEWER
for (auto h : heights) { for (auto h : heights) {
auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon()); auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon());
if (it == m_values.end()) if (it == m_values.end())
continue; continue;
m_ticks.ticks.emplace(TickCode{int(it-m_values.begin()), h.gcode, h.extruder, h.color}); m_ticks.ticks.emplace(TickCode{int(it-m_values.begin()), h.type, h.extruder, h.color, h.extra});
} }
if (!was_empty && m_ticks.empty()) if (!was_empty && m_ticks.empty())
@ -358,14 +384,14 @@ void Control::SetDrawMode(bool is_sla_print, bool is_sequential_print)
void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder) void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder)
{ {
m_mode = !is_one_extruder_printed_model ? t_mode::MultiExtruder : m_mode = !is_one_extruder_printed_model ? MultiExtruder :
only_extruder < 0 ? t_mode::SingleExtruder : only_extruder < 0 ? SingleExtruder :
t_mode::MultiAsSingle; MultiAsSingle;
if (!m_ticks.mode) if (!m_ticks.mode)
m_ticks.mode = m_mode; m_ticks.mode = m_mode;
m_only_extruder = only_extruder; m_only_extruder = only_extruder;
UseDefaultColors(m_mode == t_mode::SingleExtruder); UseDefaultColors(m_mode == SingleExtruder);
} }
void Control::SetExtruderColors( const std::vector<std::string>& extruder_colors) void Control::SetExtruderColors( const std::vector<std::string>& extruder_colors)
@ -711,11 +737,11 @@ void Control::draw_ticks(wxDC& dc)
// if we have non-regular draw mode, all ticks should be marked with error icon // if we have non-regular draw mode, all ticks should be marked with error icon
if (m_draw_mode != dmRegular) if (m_draw_mode != dmRegular)
icon_name = focused_tick ? "error_tick_f" : "error_tick"; icon_name = focused_tick ? "error_tick_f" : "error_tick";
else if (tick.gcode == ColorChangeCode || tick.gcode == ToolChangeCode) { else if (tick.type == ColorChange || tick.type == ToolChange) {
if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick])) if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick]))
icon_name = focused_tick ? "error_tick_f" : "error_tick"; icon_name = focused_tick ? "error_tick_f" : "error_tick";
} }
else if (tick.gcode == PausePrintCode) else if (tick.type == PausePrint)
icon_name = focused_tick ? "pause_print_f" : "pause_print"; icon_name = focused_tick ? "pause_print_f" : "pause_print";
else else
icon_name = focused_tick ? "edit_gcode_f" : "edit_gcode"; icon_name = focused_tick ? "edit_gcode_f" : "edit_gcode";
@ -740,7 +766,7 @@ std::string Control::get_color_for_tool_change_tick(std::set<TickCode>::const_it
auto it_n = it; auto it_n = it;
while (it_n != m_ticks.ticks.begin()) { while (it_n != m_ticks.ticks.begin()) {
--it_n; --it_n;
if (it_n->gcode == ColorChangeCode && it_n->extruder == current_extruder) if (it_n->type == ColorChange && it_n->extruder == current_extruder)
return it_n->color; return it_n->color;
} }
@ -754,13 +780,13 @@ std::string Control::get_color_for_color_change_tick(std::set<TickCode>::const_i
bool is_tool_change = false; bool is_tool_change = false;
while (it_n != m_ticks.ticks.begin()) { while (it_n != m_ticks.ticks.begin()) {
--it_n; --it_n;
if (it_n->gcode == ToolChangeCode) { if (it_n->type == ToolChange) {
is_tool_change = true; is_tool_change = true;
if (it_n->extruder == it->extruder) if (it_n->extruder == it->extruder)
return it->color; return it->color;
break; break;
} }
if (it_n->gcode == ColorChangeCode && it_n->extruder == it->extruder) if (it_n->type == ColorChange && it_n->extruder == it->extruder)
return it->color; return it->color;
} }
if (!is_tool_change && it->extruder == def_extruder) if (!is_tool_change && it->extruder == def_extruder)
@ -798,7 +824,7 @@ void Control::draw_colored_band(wxDC& dc)
wxRect main_band = get_colored_band_rect(); wxRect main_band = get_colored_band_rect();
// don't color a band for MultiExtruder mode // don't color a band for MultiExtruder mode
if (m_ticks.empty() || m_mode == t_mode::MultiExtruder) if (m_ticks.empty() || m_mode == MultiExtruder)
{ {
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
#ifdef _WIN32 #ifdef _WIN32
@ -812,22 +838,22 @@ void Control::draw_colored_band(wxDC& dc)
return; return;
} }
const int default_color_idx = m_mode==t_mode::MultiAsSingle ? std::max<int>(m_only_extruder - 1, 0) : 0; const int default_color_idx = m_mode==MultiAsSingle ? std::max<int>(m_only_extruder - 1, 0) : 0;
draw_band(dc, wxColour(m_extruder_colors[default_color_idx]), main_band); draw_band(dc, wxColour(m_extruder_colors[default_color_idx]), main_band);
std::set<TickCode>::const_iterator tick_it = m_ticks.ticks.begin(); std::set<TickCode>::const_iterator tick_it = m_ticks.ticks.begin();
while (tick_it != m_ticks.ticks.end()) while (tick_it != m_ticks.ticks.end())
{ {
if ( (m_mode == t_mode::SingleExtruder && tick_it->gcode == ColorChangeCode ) || if ( (m_mode == SingleExtruder && tick_it->type == ColorChange ) ||
(m_mode == t_mode::MultiAsSingle && (tick_it->gcode == ToolChangeCode || tick_it->gcode == ColorChangeCode)) ) (m_mode == MultiAsSingle && (tick_it->type == ToolChange || tick_it->type == ColorChange)) )
{ {
const wxCoord pos = get_position_from_value(tick_it->tick); const wxCoord pos = get_position_from_value(tick_it->tick);
is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) : is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) :
main_band.SetBottom(pos - 1); main_band.SetBottom(pos - 1);
const std::string clr_str = m_mode == t_mode::SingleExtruder ? tick_it->color : const std::string clr_str = m_mode == SingleExtruder ? tick_it->color :
tick_it->gcode == ToolChangeCode ? tick_it->type == ToolChange ?
get_color_for_tool_change_tick(tick_it) : get_color_for_tool_change_tick(tick_it) :
get_color_for_color_change_tick(tick_it); get_color_for_color_change_tick(tick_it);
@ -1036,9 +1062,9 @@ wxString Control::get_tooltip(int tick/*=-1*/)
if (m_focus == fiNone) if (m_focus == fiNone)
return ""; return "";
if (m_focus == fiOneLayerIcon) if (m_focus == fiOneLayerIcon)
return _(L("One layer mode")); return _L("One layer mode");
if (m_focus == fiRevertIcon) if (m_focus == fiRevertIcon)
return _(L("Discard all custom changes")); return _L("Discard all custom changes");
if (m_focus == fiCogIcon) if (m_focus == fiCogIcon)
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
{ {
@ -1046,16 +1072,16 @@ wxString Control::get_tooltip(int tick/*=-1*/)
return _L("Jump to move") + " (Shift + G)"; return _L("Jump to move") + " (Shift + G)";
else else
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
return m_mode == t_mode::MultiAsSingle ? return m_mode == MultiAsSingle ?
GUI::from_u8((boost::format(_utf8(L("Jump to height %s or " GUI::from_u8((boost::format(_u8L("Jump to height %s or "
"Set extruder sequence for the entire print"))) % " (Shift + G)\n").str()) : "Set extruder sequence for the entire print")) % " (Shift + G)\n").str()) :
_(L("Jump to height")) + " (Shift + G)"; _L("Jump to height") + " (Shift + G)";
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
} }
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
if (m_focus == fiColorBand) if (m_focus == fiColorBand)
return m_mode != t_mode::SingleExtruder ? "" : return m_mode != SingleExtruder ? "" :
_(L("Edit current color - Right click the colored slider segment")); _L("Edit current color - Right click the colored slider segment");
if (m_draw_mode == dmSlaPrint) if (m_draw_mode == dmSlaPrint)
return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode
@ -1065,10 +1091,10 @@ wxString Control::get_tooltip(int tick/*=-1*/)
if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist
{ {
// Show mode as a first string of tooltop // Show mode as a first string of tooltop
tooltip = " " + _(L("Print mode")) + ": "; tooltip = " " + _L("Print mode") + ": ";
tooltip += (m_mode == t_mode::SingleExtruder ? CustomGCode::SingleExtruderMode : tooltip += (m_mode == SingleExtruder ? SingleExtruderMode :
m_mode == t_mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : m_mode == MultiAsSingle ? MultiAsSingleMode :
CustomGCode::MultiExtruderMode ); MultiExtruderMode );
tooltip += "\n\n"; tooltip += "\n\n";
/* Note: just on OSX!!! /* Note: just on OSX!!!
@ -1078,68 +1104,70 @@ wxString Control::get_tooltip(int tick/*=-1*/)
* */ * */
// Show list of actions with new tick // Show list of actions with new tick
tooltip += ( m_mode == t_mode::MultiAsSingle ? tooltip += ( m_mode == MultiAsSingle ?
_(L("Add extruder change - Left click")) : _L("Add extruder change - Left click") :
m_mode == t_mode::SingleExtruder ? m_mode == SingleExtruder ?
_(L("Add color change - Left click for predefined color or " _L("Add color change - Left click for predefined color or "
"Shift + Left click for custom color selection")) : "Shift + Left click for custom color selection") :
_(L("Add color change - Left click")) ) + " " + _L("Add color change - Left click") ) + " " +
_(L("or press \"+\" key")) + "\n" + ( _L("or press \"+\" key") + "\n" + (
is_osx ? is_osx ?
_(L("Add another code - Ctrl + Left click")) : _L("Add another code - Ctrl + Left click") :
_(L("Add another code - Right click")) ); _L("Add another code - Right click") );
} }
if (tick_code_it != m_ticks.ticks.end()) // tick exists if (tick_code_it != m_ticks.ticks.end()) // tick exists
{ {
if (m_draw_mode == dmSequentialFffPrint) if (m_draw_mode == dmSequentialFffPrint)
return _(L("The sequential print is on.\n" return _L("The sequential print is on.\n"
"It's impossible to apply any custom G-code for objects printing sequentually.\n" "It's impossible to apply any custom G-code for objects printing sequentually.\n"
"This code won't be processed during G-code generation.")); "This code won't be processed during G-code generation.");
// Show custom Gcode as a first string of tooltop // Show custom Gcode as a first string of tooltop
tooltip = " "; tooltip = " ";
tooltip += tooltip +=
tick_code_it->gcode == ColorChangeCode ? tick_code_it->type == ColorChange ?
(m_mode == t_mode::SingleExtruder ? (m_mode == SingleExtruder ?
format_wxstr(_L("Color change (\"%1%\")"), tick_code_it->gcode) : format_wxstr(_L("Color change (\"%1%\")"), gcode(ColorChange)) :
format_wxstr(_L("Color change (\"%1%\") for Extruder %2%"), tick_code_it->gcode, tick_code_it->extruder)) : format_wxstr(_L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) :
tick_code_it->gcode == PausePrintCode ? tick_code_it->type == PausePrint ?
format_wxstr(_L("Pause print (\"%1%\")"), tick_code_it->gcode) : format_wxstr(_L("Pause print (\"%1%\")"), gcode(PausePrint)) :
tick_code_it->gcode == ToolChangeCode ? tick_code_it->type == Template ?
format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) :
from_u8(tick_code_it->gcode); tick_code_it->type == ToolChange ?
format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) :
from_u8(tick_code_it->extra);// tick_code_it->type == Custom
// If tick is marked as a conflict (exclamation icon), // If tick is marked as a conflict (exclamation icon),
// we should to explain why // we should to explain why
ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]); ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]);
if (conflict != ctNone) if (conflict != ctNone)
tooltip += "\n\n" + _(L("Note")) + "! "; tooltip += "\n\n" + _L("Note") + "! ";
if (conflict == ctModeConflict) if (conflict == ctModeConflict)
tooltip += _(L("G-code associated to this tick mark is in a conflict with print mode.\n" tooltip += _L("G-code associated to this tick mark is in a conflict with print mode.\n"
"Editing it will cause changes of Slider data.")); "Editing it will cause changes of Slider data.");
else if (conflict == ctMeaninglessColorChange) else if (conflict == ctMeaninglessColorChange)
tooltip += _(L("There is a color change for extruder that won't be used till the end of print job.\n" tooltip += _L("There is a color change for extruder that won't be used till the end of print job.\n"
"This code won't be processed during G-code generation.")); "This code won't be processed during G-code generation.");
else if (conflict == ctMeaninglessToolChange) else if (conflict == ctMeaninglessToolChange)
tooltip += _(L("There is an extruder change set to the same extruder.\n" tooltip += _L("There is an extruder change set to the same extruder.\n"
"This code won't be processed during G-code generation.")); "This code won't be processed during G-code generation.");
else if (conflict == ctRedundant) else if (conflict == ctRedundant)
tooltip += _(L("There is a color change for extruder that has not been used before.\n" tooltip += _L("There is a color change for extruder that has not been used before.\n"
"Check your settings to avoid redundant color changes.")); "Check your settings to avoid redundant color changes.");
// Show list of actions with existing tick // Show list of actions with existing tick
if (m_focus == fiActionIcon) if (m_focus == fiActionIcon)
tooltip += "\n\n" + _(L("Delete tick mark - Left click or press \"-\" key")) + "\n" + ( tooltip += "\n\n" + _L("Delete tick mark - Left click or press \"-\" key") + "\n" + (
is_osx ? is_osx ?
_(L("Edit tick mark - Ctrl + Left click")) : _L("Edit tick mark - Ctrl + Left click") :
_(L("Edit tick mark - Right click")) ); _L("Edit tick mark - Right click") );
} }
return tooltip; return tooltip;
} }
int Control::get_edited_tick_for_position(const wxPoint pos, const std::string& gcode /*= ColorChangeCode*/) int Control::get_edited_tick_for_position(const wxPoint pos, Type type /*= ColorChange*/)
{ {
if (m_ticks.empty()) if (m_ticks.empty())
return -1; return -1;
@ -1149,7 +1177,7 @@ int Control::get_edited_tick_for_position(const wxPoint pos, const std::string&
while (it != m_ticks.ticks.begin()) { while (it != m_ticks.ticks.begin()) {
--it; --it;
if (it->gcode == gcode) if (it->type == type)
return it->tick; return it->tick;
} }
@ -1175,7 +1203,7 @@ void Control::OnMotion(wxMouseEvent& event)
m_focus = fiRevertIcon; m_focus = fiRevertIcon;
else if (is_point_in_rect(pos, m_rect_cog_icon)) else if (is_point_in_rect(pos, m_rect_cog_icon))
m_focus = fiCogIcon; m_focus = fiCogIcon;
else if (m_mode == t_mode::SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) && else if (m_mode == SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) &&
get_edited_tick_for_position(pos) >= 0 ) get_edited_tick_for_position(pos) >= 0 )
m_focus = fiColorBand; m_focus = fiColorBand;
else { else {
@ -1232,21 +1260,21 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
const wxString item_name = wxString::Format(_(L("Extruder %d")), i) + const wxString item_name = wxString::Format(_(L("Extruder %d")), i) +
(is_active_extruder ? " (" + _(L("active")) + ")" : ""); (is_active_extruder ? " (" + _(L("active")) + ")" : "");
if (m_mode == t_mode::MultiAsSingle) if (m_mode == MultiAsSingle)
append_menu_item(change_extruder_menu, wxID_ANY, item_name, "", append_menu_item(change_extruder_menu, wxID_ANY, item_name, "",
[this, i](wxCommandEvent&) { add_code_as_tick(ToolChangeCode, i); }, *icons[i-1], menu, [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, *icons[i-1], menu,
[is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater());
} }
const wxString change_extruder_menu_name = m_mode == t_mode::MultiAsSingle ? const wxString change_extruder_menu_name = m_mode == MultiAsSingle ?
(switch_current_code ? _(L("Switch code to Change extruder")) : _(L("Change extruder")) ) : (switch_current_code ? _L("Switch code to Change extruder") : _L("Change extruder") ) :
_(L("Change extruder (N/A)")); _L("Change extruder (N/A)");
wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder"))); wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder")));
change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder")); change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder"));
GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) { GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) {
enable_menu_item(evt, [this]() {return m_mode == t_mode::MultiAsSingle; }, change_extruder_menu_item, this); }, enable_menu_item(evt, [this]() {return m_mode == MultiAsSingle; }, change_extruder_menu_item, this); },
change_extruder_menu_item->GetId()); change_extruder_menu_item->GetId());
} }
} }
@ -1269,13 +1297,13 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren
(is_used_extruder ? " (" + _(L("used")) + ")" : ""); (is_used_extruder ? " (" + _(L("used")) + ")" : "");
append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", append_menu_item(add_color_change_menu, wxID_ANY, item_name, "",
[this, i](wxCommandEvent&) { add_code_as_tick(ColorChangeCode, i); }, "", menu, [this, i](wxCommandEvent&) { add_code_as_tick(ColorChange, i); }, "", menu,
[]() { return true; }, GUI::wxGetApp().plater()); []() { return true; }, GUI::wxGetApp().plater());
} }
const wxString menu_name = switch_current_code ? const wxString menu_name = switch_current_code ?
format_wxstr(_L("Switch code to Color change (%1%) for:"), ColorChangeCode) : format_wxstr(_L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) :
format_wxstr(_L("Add color change (%1%) for:"), ColorChangeCode); format_wxstr(_L("Add color change (%1%) for:"), gcode(ColorChange));
wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, "");
add_color_change_menu_item->SetBitmap(create_scaled_bitmap("colorchange_add_m")); add_color_change_menu_item->SetBitmap(create_scaled_bitmap("colorchange_add_m"));
} }
@ -1299,7 +1327,7 @@ void Control::OnLeftUp(wxMouseEvent& event)
add_current_tick(); add_current_tick();
break; break;
case maCogIconClick : case maCogIconClick :
if (m_mode == t_mode::MultiAsSingle && m_draw_mode == dmRegular) if (m_mode == MultiAsSingle && m_draw_mode == dmRegular)
show_cog_icon_context_menu(); show_cog_icon_context_menu();
else else
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
@ -1516,9 +1544,9 @@ void Control::OnRightDown(wxMouseEvent& event)
m_mouse = m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end() ? m_mouse = m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end() ?
maAddMenu : maEditMenu; maAddMenu : maEditMenu;
} }
else if (m_mode == t_mode::SingleExtruder && !detect_selected_slider(pos) && is_point_in_rect(pos, get_colored_band_rect())) else if (m_mode == SingleExtruder && !detect_selected_slider(pos) && is_point_in_rect(pos, get_colored_band_rect()))
m_mouse = maForceColorEdit; m_mouse = maForceColorEdit;
else if (m_mode == t_mode::MultiAsSingle && is_point_in_rect(pos, m_rect_cog_icon)) else if (m_mode == MultiAsSingle && is_point_in_rect(pos, m_rect_cog_icon))
m_mouse = maCogIconMenu; m_mouse = maCogIconMenu;
} }
if (m_mouse != maNone || !detect_selected_slider(pos)) if (m_mouse != maNone || !detect_selected_slider(pos))
@ -1539,11 +1567,11 @@ void Control::OnRightDown(wxMouseEvent& event)
// Get active extruders for tick. // Get active extruders for tick.
// Means one current extruder for not existing tick OR // Means one current extruder for not existing tick OR
// 2 extruders - for existing tick (extruder before ToolChangeCode and extruder of current existing tick) // 2 extruders - for existing tick (extruder before ToolChange and extruder of current existing tick)
// Use those values to disable selection of active extruders // Use those values to disable selection of active extruders
std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const
{ {
int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? std::max<int>(1, m_only_extruder) : 1; int default_initial_extruder = m_mode == MultiAsSingle ? std::max<int>(1, m_only_extruder) : 1;
std::array<int, 2> extruders = { default_initial_extruder, -1 }; std::array<int, 2> extruders = { default_initial_extruder, -1 };
if (m_ticks.empty()) if (m_ticks.empty())
return extruders; return extruders;
@ -1555,7 +1583,7 @@ std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const
while (it != m_ticks.ticks.begin()) { while (it != m_ticks.ticks.begin()) {
--it; --it;
if(it->gcode == ToolChangeCode) { if(it->type == ToolChange) {
extruders[0] = it->extruder; extruders[0] = it->extruder;
break; break;
} }
@ -1566,11 +1594,11 @@ std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const
// Get used extruders for tick. // Get used extruders for tick.
// Means all extruders(tools) which will be used during printing from current tick to the end // Means all extruders(tools) which will be used during printing from current tick to the end
std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z, t_mode force_mode/* = t_mode::Undef*/) const std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode/* = Undef*/) const
{ {
t_mode e_mode = !force_mode ? mode : force_mode; Mode e_mode = !force_mode ? mode : force_mode;
if (e_mode == t_mode::MultiExtruder) if (e_mode == MultiExtruder)
{ {
// #ys_FIXME: get tool ordering from _correct_ place // #ys_FIXME: get tool ordering from _correct_ place
const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering(); const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering();
@ -1591,15 +1619,15 @@ std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru
return used_extruders; return used_extruders;
} }
const int default_initial_extruder = e_mode == t_mode::MultiAsSingle ? std::max(only_extruder, 1) : 1; const int default_initial_extruder = e_mode == MultiAsSingle ? std::max(only_extruder, 1) : 1;
if (ticks.empty() || e_mode == t_mode::SingleExtruder) if (ticks.empty() || e_mode == SingleExtruder)
return {default_initial_extruder}; return {default_initial_extruder};
std::set<int> used_extruders; std::set<int> used_extruders;
auto it_start = ticks.lower_bound(TickCode{tick}); auto it_start = ticks.lower_bound(TickCode{tick});
auto it = it_start; auto it = it_start;
if (it == ticks.begin() && it->gcode == ToolChangeCode && if (it == ticks.begin() && it->type == ToolChange &&
tick != it->tick ) // In case of switch of ToolChange to ColorChange, when tick exists, tick != it->tick ) // In case of switch of ToolChange to ColorChange, when tick exists,
// we shouldn't change color for extruder, which will be deleted // we shouldn't change color for extruder, which will be deleted
{ {
@ -1610,7 +1638,7 @@ std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru
while (it != ticks.begin()) { while (it != ticks.begin()) {
--it; --it;
if (it->gcode == ToolChangeCode && tick != it->tick) { if (it->type == ToolChange && tick != it->tick) {
used_extruders.emplace(it->extruder); used_extruders.emplace(it->extruder);
break; break;
} }
@ -1620,7 +1648,7 @@ std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru
used_extruders.emplace(default_initial_extruder); used_extruders.emplace(default_initial_extruder);
for (it = it_start; it != ticks.end(); ++it) for (it = it_start; it != ticks.end(); ++it)
if (it->gcode == ToolChangeCode && tick != it->tick) if (it->type == ToolChange && tick != it->tick)
used_extruders.emplace(it->extruder); used_extruders.emplace(it->extruder);
return used_extruders; return used_extruders;
@ -1630,9 +1658,9 @@ void Control::show_add_context_menu()
{ {
wxMenu menu; wxMenu menu;
if (m_mode == t_mode::SingleExtruder) { if (m_mode == SingleExtruder) {
append_menu_item(&menu, wxID_ANY, _(L("Add color change")) + " (M600)", "", append_menu_item(&menu, wxID_ANY, _L("Add color change") + " (" + gcode(ColorChange) + ")", "",
[this](wxCommandEvent&) { add_code_as_tick(ColorChangeCode); }, "colorchange_add_m", &menu); [this](wxCommandEvent&) { add_code_as_tick(ColorChange); }, "colorchange_add_m", &menu);
UseDefaultColors(false); UseDefaultColors(false);
} }
@ -1641,11 +1669,15 @@ void Control::show_add_context_menu()
append_add_color_change_menu_item(&menu); append_add_color_change_menu_item(&menu);
} }
append_menu_item(&menu, wxID_ANY, _(L("Add pause print")) + " (M601)", "", append_menu_item(&menu, wxID_ANY, _L("Add pause print") + " (" + gcode(PausePrint) + ")", "",
[this](wxCommandEvent&) { add_code_as_tick(PausePrintCode); }, "pause_print", &menu); [this](wxCommandEvent&) { add_code_as_tick(PausePrint); }, "pause_print", &menu);
append_menu_item(&menu, wxID_ANY, _(L("Add custom G-code")), "", if (!gcode(Template).empty())
[this](wxCommandEvent&) { add_code_as_tick(""); }, "edit_gcode", &menu); append_menu_item(&menu, wxID_ANY, _L("Add custom template") + " (" + gcode(Template) + ")", "",
[this](wxCommandEvent&) { add_code_as_tick(Template); }, "edit_gcode", &menu);
append_menu_item(&menu, wxID_ANY, _L("Add custom G-code"), "",
[this](wxCommandEvent&) { add_code_as_tick(Custom); }, "edit_gcode", &menu);
GUI::wxGetApp().plater()->PopupMenu(&menu); GUI::wxGetApp().plater()->PopupMenu(&menu);
} }
@ -1656,23 +1688,23 @@ void Control::show_edit_context_menu()
std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value });
if (it->gcode == ToolChangeCode) { if (it->type == ToolChange) {
if (m_mode == t_mode::MultiAsSingle) if (m_mode == MultiAsSingle)
append_change_extruder_menu_item(&menu); append_change_extruder_menu_item(&menu);
append_add_color_change_menu_item(&menu, true); append_add_color_change_menu_item(&menu, true);
} }
else else
append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Edit color")) : append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _(L("Edit color")) :
it->gcode == PausePrintCode ? _(L("Edit pause print message")) : it->type == PausePrint ? _(L("Edit pause print message")) :
_(L("Edit custom G-code")), "", _(L("Edit custom G-code")), "",
[this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu); [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu);
if (it->gcode == ColorChangeCode && m_mode == t_mode::MultiAsSingle) if (it->type == ColorChange && m_mode == MultiAsSingle)
append_change_extruder_menu_item(&menu, true); append_change_extruder_menu_item(&menu, true);
append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Delete color change")) : append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _(L("Delete color change")) :
it->gcode == ToolChangeCode ? _(L("Delete tool change")) : it->type == ToolChange ? _(L("Delete tool change")) :
it->gcode == PausePrintCode ? _(L("Delete pause print")) : it->type == PausePrint ? _(L("Delete pause print")) :
_(L("Delete custom G-code")), "", _(L("Delete custom G-code")), "",
[this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu); [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu);
@ -1757,6 +1789,8 @@ static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0,
if (!textctrl) if (!textctrl)
return; return;
textctrl->SetInsertionPointEnd();
wxButton* btn_OK = static_cast<wxButton*>(dlg->FindWindowById(wxID_OK)); wxButton* btn_OK = static_cast<wxButton*>(dlg->FindWindowById(wxID_OK));
btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt) btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt)
{ {
@ -1833,13 +1867,13 @@ static double get_print_z_to_jump(double active_print_z, double min_z, double ma
return dlg.GetValue().ToCDouble(&value) ? value : -1.0; return dlg.GetValue().ToCDouble(&value) ? value : -1.0;
} }
void Control::add_code_as_tick(std::string code, int selected_extruder/* = -1*/) void Control::add_code_as_tick(Type type, int selected_extruder/* = -1*/)
{ {
if (m_selection == ssUndef) if (m_selection == ssUndef)
return; return;
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
if ( !check_ticks_changed_event(code) ) if ( !check_ticks_changed_event(type) )
return; return;
const int extruder = selected_extruder > 0 ? selected_extruder : std::max<int>(1, m_only_extruder); const int extruder = selected_extruder > 0 ? selected_extruder : std::max<int>(1, m_only_extruder);
@ -1847,18 +1881,18 @@ void Control::add_code_as_tick(std::string code, int selected_extruder/* = -1*/)
if ( it == m_ticks.ticks.end() ) { if ( it == m_ticks.ticks.end() ) {
// try to add tick // try to add tick
if (!m_ticks.add_tick(tick, code, extruder, m_values[tick])) if (!m_ticks.add_tick(tick, type, extruder, m_values[tick]))
return; return;
} }
else if (code == ToolChangeCode || code == ColorChangeCode) { else if (type == ToolChange || type == ColorChange) {
// try to switch tick code to ToolChangeCode or ColorChangeCode accordingly // try to switch tick code to ToolChange or ColorChange accordingly
if (!m_ticks.switch_code_for_tick(it, code, extruder)) if (!m_ticks.switch_code_for_tick(it, type, extruder))
return; return;
} }
else else
return; return;
post_ticks_changed_event(code); post_ticks_changed_event(type);
} }
void Control::add_current_tick(bool call_from_keyboard /*= false*/) void Control::add_current_tick(bool call_from_keyboard /*= false*/)
@ -1869,16 +1903,16 @@ void Control::add_current_tick(bool call_from_keyboard /*= false*/)
auto it = m_ticks.ticks.find(TickCode{ tick }); auto it = m_ticks.ticks.find(TickCode{ tick });
if (it != m_ticks.ticks.end() || // this tick is already exist if (it != m_ticks.ticks.end() || // this tick is already exist
!check_ticks_changed_event(m_mode == t_mode::MultiAsSingle ? ToolChangeCode : ColorChangeCode)) !check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange))
return; return;
if (m_mode == t_mode::SingleExtruder) if (m_mode == SingleExtruder)
add_code_as_tick(ColorChangeCode); add_code_as_tick(ColorChange);
else else
{ {
wxMenu menu; wxMenu menu;
if (m_mode == t_mode::MultiAsSingle) if (m_mode == MultiAsSingle)
append_change_extruder_menu_item(&menu); append_change_extruder_menu_item(&menu);
else else
append_add_color_change_menu_item(&menu); append_add_color_change_menu_item(&menu);
@ -1911,12 +1945,12 @@ void Control::delete_current_tick()
auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value });
if (it == m_ticks.ticks.end() || if (it == m_ticks.ticks.end() ||
!check_ticks_changed_event(it->gcode)) !check_ticks_changed_event(it->type))
return; return;
const std::string code = it->gcode; Type type = it->type;
m_ticks.ticks.erase(it); m_ticks.ticks.erase(it);
post_ticks_changed_event(code); post_ticks_changed_event(type);
} }
void Control::edit_tick(int tick/* = -1*/) void Control::edit_tick(int tick/* = -1*/)
@ -1926,12 +1960,12 @@ void Control::edit_tick(int tick/* = -1*/)
const std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ tick }); const std::set<TickCode>::iterator it = m_ticks.ticks.find(TickCode{ tick });
if (it == m_ticks.ticks.end() || if (it == m_ticks.ticks.end() ||
!check_ticks_changed_event(it->gcode)) !check_ticks_changed_event(it->type))
return; return;
const std::string code = it->gcode; Type type = it->type;
if (m_ticks.edit_tick(it, m_values[it->tick])) if (m_ticks.edit_tick(it, m_values[it->tick]))
post_ticks_changed_event(code); post_ticks_changed_event(type);
} }
// switch on/off one layer mode // switch on/off one layer mode
@ -1986,7 +2020,7 @@ void Control::move_current_thumb_to_pos(wxPoint pos)
void Control::edit_extruder_sequence() void Control::edit_extruder_sequence()
{ {
if (!check_ticks_changed_event(ToolChangeCode)) if (!check_ticks_changed_event(ToolChange))
return; return;
GUI::ExtruderSequenceDialog dlg(m_extruders_sequence); GUI::ExtruderSequenceDialog dlg(m_extruders_sequence);
@ -1994,7 +2028,7 @@ void Control::edit_extruder_sequence()
return; return;
m_extruders_sequence = dlg.GetValue(); m_extruders_sequence = dlg.GetValue();
m_ticks.erase_all_ticks_with_code(ToolChangeCode); m_ticks.erase_all_ticks_with_code(ToolChange);
int tick = 0; int tick = 0;
double value = 0.0; double value = 0.0;
@ -2007,7 +2041,7 @@ void Control::edit_extruder_sequence()
bool meaningless_tick = tick == 0.0 && cur_extruder == extruder; bool meaningless_tick = tick == 0.0 && cur_extruder == extruder;
if (!meaningless_tick) if (!meaningless_tick)
m_ticks.ticks.emplace(TickCode{tick, ToolChangeCode, cur_extruder + 1, m_extruder_colors[cur_extruder]}); m_ticks.ticks.emplace(TickCode{tick, ToolChange,cur_extruder + 1, m_extruder_colors[cur_extruder]});
extruder++; extruder++;
if (extruder == extr_cnt) if (extruder == extr_cnt)
@ -2026,7 +2060,7 @@ void Control::edit_extruder_sequence()
tick += m_extruders_sequence.interval_by_layers; tick += m_extruders_sequence.interval_by_layers;
} }
post_ticks_changed_event(ToolChangeCode); post_ticks_changed_event(ToolChange);
} }
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
@ -2063,64 +2097,64 @@ void Control::jump_to_print_z()
} }
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
void Control::post_ticks_changed_event(const std::string& gcode /*= ""*/) void Control::post_ticks_changed_event(Type type /*= Custom*/)
{ {
m_force_mode_apply = (gcode.empty() || gcode == ColorChangeCode || gcode == ToolChangeCode); m_force_mode_apply = type != ToolChange;
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
} }
bool Control::check_ticks_changed_event(const std::string& gcode) bool Control::check_ticks_changed_event(Type type)
{ {
if ( m_ticks.mode == m_mode || if ( m_ticks.mode == m_mode ||
(gcode != ColorChangeCode && gcode != ToolChangeCode) || (type != ColorChange && type != ToolChange) ||
(m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiAsSingle) || // All ColorChanges will be applied for 1st extruder (m_ticks.mode == SingleExtruder && m_mode == MultiAsSingle) || // All ColorChanges will be applied for 1st extruder
(m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::MultiAsSingle) ) // Just mark ColorChanges for all unused extruders (m_ticks.mode == MultiExtruder && m_mode == MultiAsSingle) ) // Just mark ColorChanges for all unused extruders
return true; return true;
if ((m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiExtruder ) || if ((m_ticks.mode == SingleExtruder && m_mode == MultiExtruder ) ||
(m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::SingleExtruder) ) (m_ticks.mode == MultiExtruder && m_mode == SingleExtruder) )
{ {
if (!m_ticks.has_tick_with_code(ColorChangeCode)) if (!m_ticks.has_tick_with_code(ColorChange))
return true; return true;
wxString message = (m_ticks.mode == t_mode::SingleExtruder ? wxString message = (m_ticks.mode == SingleExtruder ?
_(L("The last color change data was saved for a single extruder printing.")) : _L("The last color change data was saved for a single extruder printing.") :
_(L("The last color change data was saved for a multi extruder printing.")) _L("The last color change data was saved for a multi extruder printing.")
) + "\n" + ) + "\n" +
_(L("Your current changes will delete all saved color changes.")) + "\n\n\t" + _L("Your current changes will delete all saved color changes.") + "\n\n\t" +
_(L("Are you sure you want to continue?")); _L("Are you sure you want to continue?");
wxMessageDialog msg(this, message, _(L("Notice")), wxYES_NO); wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO);
if (msg.ShowModal() == wxID_YES) { if (msg.ShowModal() == wxID_YES) {
m_ticks.erase_all_ticks_with_code(ColorChangeCode); m_ticks.erase_all_ticks_with_code(ColorChange);
post_ticks_changed_event(ColorChangeCode); post_ticks_changed_event(ColorChange);
} }
return false; return false;
} }
// m_ticks_mode == t_mode::MultiAsSingle // m_ticks_mode == MultiAsSingle
if( m_ticks.has_tick_with_code(ToolChangeCode) ) if( m_ticks.has_tick_with_code(ToolChange) )
{ {
wxString message = m_mode == t_mode::SingleExtruder ? ( wxString message = m_mode == SingleExtruder ? (
_(L("The last color change data was saved for a multi extruder printing.")) + "\n\n" + _L("The last color change data was saved for a multi extruder printing.") + "\n\n" +
_(L("Select YES if you want to delete all saved tool changes, \n" _L("Select YES if you want to delete all saved tool changes, \n"
"NO if you want all tool changes switch to color changes, \n" "NO if you want all tool changes switch to color changes, \n"
"or CANCEL to leave it unchanged.")) + "\n\n\t" + "or CANCEL to leave it unchanged.") + "\n\n\t" +
_(L("Do you want to delete all saved tool changes?")) _L("Do you want to delete all saved tool changes?")
) : ( // t_mode::MultiExtruder ): ( // MultiExtruder
_(L("The last color change data was saved for a multi extruder printing with tool changes for whole print.")) + "\n\n" + _L("The last color change data was saved for a multi extruder printing with tool changes for whole print.") + "\n\n" +
_(L("Your current changes will delete all saved extruder (tool) changes.")) + "\n\n\t" + _L("Your current changes will delete all saved extruder (tool) changes.") + "\n\n\t" +
_(L("Are you sure you want to continue?")) ) ; _L("Are you sure you want to continue?") ) ;
wxMessageDialog msg(this, message, _(L("Notice")), wxYES_NO | (m_mode == t_mode::SingleExtruder ? wxCANCEL : 0)); wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO | (m_mode == SingleExtruder ? wxCANCEL : 0));
const int answer = msg.ShowModal(); const int answer = msg.ShowModal();
if (answer == wxID_YES) { if (answer == wxID_YES) {
m_ticks.erase_all_ticks_with_code(ToolChangeCode); m_ticks.erase_all_ticks_with_code(ToolChange);
post_ticks_changed_event(ToolChangeCode); post_ticks_changed_event(ToolChange);
} }
else if (m_mode == t_mode::SingleExtruder && answer == wxID_NO) { else if (m_mode == SingleExtruder && answer == wxID_NO) {
m_ticks.switch_code(ToolChangeCode, ColorChangeCode); m_ticks.switch_code(ToolChange, ColorChange);
post_ticks_changed_event(ColorChangeCode); post_ticks_changed_event(ColorChange);
} }
return false; return false;
} }
@ -2128,9 +2162,9 @@ bool Control::check_ticks_changed_event(const std::string& gcode)
return true; return true;
} }
std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& code, const int extruder) std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder)
{ {
if (mode == t_mode::SingleExtruder && code == ColorChangeCode && m_use_default_colors) if (mode == SingleExtruder && type == ColorChange && m_use_default_colors)
{ {
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
const std::vector<std::string>& colors = ColorPrintColors::get(); const std::vector<std::string>& colors = ColorPrintColors::get();
@ -2146,14 +2180,14 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& c
std::string color = (*m_colors)[extruder - 1]; std::string color = (*m_colors)[extruder - 1];
if (code == ColorChangeCode) if (type == ColorChange)
{ {
if (!ticks.empty()) if (!ticks.empty())
{ {
auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick ); auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick );
while (before_tick_it != ticks.begin()) { while (before_tick_it != ticks.begin()) {
--before_tick_it; --before_tick_it;
if (before_tick_it->gcode == ColorChangeCode && before_tick_it->extruder == extruder) { if (before_tick_it->type == ColorChange && before_tick_it->extruder == extruder) {
color = before_tick_it->color; color = before_tick_it->color;
break; break;
} }
@ -2165,63 +2199,67 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& c
return color; return color;
} }
bool TickCodeInfo::add_tick(const int tick, std::string& code, const int extruder, double print_z) bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, double print_z)
{ {
std::string color; std::string color;
if (code.empty()) // custom Gcode std::string extra;
if (type == Custom) // custom Gcode
{ {
code = get_custom_code(custom_gcode, print_z); extra = get_custom_code(custom_gcode, print_z);
if (code.empty()) if (extra.empty())
return false; return false;
custom_gcode = code; custom_gcode = extra;
} }
else if (code == PausePrintCode) else if (type == PausePrint)
{ {
/* PausePrintCode doesn't need a color, so extra = get_pause_print_msg(pause_print_msg, print_z);
* this field is used for save a short message shown on Printer display if (extra.empty())
* */
color = get_pause_print_msg(pause_print_msg, print_z);
if (color.empty())
return false; return false;
pause_print_msg = color; pause_print_msg = extra;
} }
else else
{ {
color = get_color_for_tick(TickCode{ tick }, code, extruder); color = get_color_for_tick(TickCode{ tick }, type, extruder);
if (color.empty()) if (color.empty())
return false; return false;
} }
if (mode == t_mode::SingleExtruder) if (mode == SingleExtruder)
m_use_default_colors = true; m_use_default_colors = true;
ticks.emplace(TickCode{ tick, code, extruder, color }); ticks.emplace(TickCode{ tick, type, extruder, color, extra });
return true; return true;
} }
bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z) bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
{ {
std::string edited_value; std::string edited_value;
if (it->gcode == ColorChangeCode) if (it->type == ColorChange)
edited_value = get_new_color(it->color); edited_value = get_new_color(it->color);
else if (it->gcode == PausePrintCode) else if (it->type == PausePrint)
edited_value = get_pause_print_msg(it->color, print_z); edited_value = get_pause_print_msg(it->extra, print_z);
else else
edited_value = get_custom_code(it->gcode, print_z); edited_value = get_custom_code(it->type == Template ? gcode(Template) : it->extra, print_z);
if (edited_value.empty()) if (edited_value.empty())
return false; return false;
TickCode changed_tick = *it; TickCode changed_tick = *it;
if (it->gcode == ColorChangeCode || it->gcode == PausePrintCode) { if (it->type == ColorChange) {
if (it->color == edited_value) if (it->color == edited_value)
return false; return false;
changed_tick.color = edited_value; changed_tick.color = edited_value;
} }
else { else if (it->type == Template) {
if (it->gcode == edited_value) if (gcode(Template) == edited_value)
return false; return false;
changed_tick.gcode = edited_value; changed_tick.extra = edited_value;
changed_tick.type = Custom;
}
else if (it->type == Custom || it->type == PausePrint) {
if (it->extra == edited_value)
return false;
changed_tick.extra = edited_value;
} }
ticks.erase(it); ticks.erase(it);
@ -2230,13 +2268,13 @@ bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
return true; return true;
} }
void TickCodeInfo::switch_code(const std::string& code_from, const std::string& code_to) void TickCodeInfo::switch_code(Type type_from, Type type_to)
{ {
for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; )
if (it->gcode == code_from) if (it->type == type_from)
{ {
TickCode tick = *it; TickCode tick = *it;
tick.gcode = code_to; tick.type = type_to;
tick.extruder = 1; tick.extruder = 1;
ticks.erase(it); ticks.erase(it);
it = ticks.emplace(tick).first; it = ticks.emplace(tick).first;
@ -2245,14 +2283,14 @@ void TickCodeInfo::switch_code(const std::string& code_from, const std::string&
++it; ++it;
} }
bool TickCodeInfo::switch_code_for_tick(std::set<TickCode>::iterator it, const std::string& code_to, const int extruder) bool TickCodeInfo::switch_code_for_tick(std::set<TickCode>::iterator it, Type type_to, const int extruder)
{ {
const std::string color = get_color_for_tick(*it, code_to, extruder); const std::string color = get_color_for_tick(*it, type_to, extruder);
if (color.empty()) if (color.empty())
return false; return false;
TickCode changed_tick = *it; TickCode changed_tick = *it;
changed_tick.gcode = code_to; changed_tick.type = type_to;
changed_tick.extruder = extruder; changed_tick.extruder = extruder;
changed_tick.color = color; changed_tick.color = color;
@ -2262,36 +2300,36 @@ bool TickCodeInfo::switch_code_for_tick(std::set<TickCode>::iterator it, const s
return true; return true;
} }
void TickCodeInfo::erase_all_ticks_with_code(const std::string& gcode) void TickCodeInfo::erase_all_ticks_with_code(Type type)
{ {
for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) { for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) {
if (it->gcode == gcode) if (it->type == type)
it = ticks.erase(it); it = ticks.erase(it);
else else
++it; ++it;
} }
} }
bool TickCodeInfo::has_tick_with_code(const std::string& gcode) bool TickCodeInfo::has_tick_with_code(Type type)
{ {
for (const TickCode& tick : ticks) for (const TickCode& tick : ticks)
if (tick.gcode == gcode) if (tick.type == type)
return true; return true;
return false; return false;
} }
ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z) ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z)
{ {
if ((tick.gcode == ColorChangeCode && ( if ((tick.type == ColorChange && (
(mode == t_mode::SingleExtruder && out_mode == t_mode::MultiExtruder ) || (mode == SingleExtruder && out_mode == MultiExtruder ) ||
(mode == t_mode::MultiExtruder && out_mode == t_mode::SingleExtruder) )) || (mode == MultiExtruder && out_mode == SingleExtruder) )) ||
(tick.gcode == ToolChangeCode && (tick.type == ToolChange &&
(mode == t_mode::MultiAsSingle && out_mode != t_mode::MultiAsSingle)) ) (mode == MultiAsSingle && out_mode != MultiAsSingle)) )
return ctModeConflict; return ctModeConflict;
// check ColorChange tick // check ColorChange tick
if (tick.gcode == ColorChangeCode) if (tick.type == ColorChange)
{ {
// We should mark a tick as a "MeaninglessColorChange", // We should mark a tick as a "MeaninglessColorChange",
// if it has a ColorChange for unused extruder from current print to end of the print // if it has a ColorChange for unused extruder from current print to end of the print
@ -2302,15 +2340,15 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod
// We should mark a tick as a "Redundant", // We should mark a tick as a "Redundant",
// if it has a ColorChange for extruder that has not been used before // if it has a ColorChange for extruder that has not been used before
if (mode == t_mode::MultiAsSingle && tick.extruder != std::max<int>(only_extruder, 1) ) if (mode == MultiAsSingle && tick.extruder != std::max<int>(only_extruder, 1) )
{ {
auto it = ticks.lower_bound( tick ); auto it = ticks.lower_bound( tick );
if (it == ticks.begin() && it->gcode == ToolChangeCode && tick.extruder == it->extruder) if (it == ticks.begin() && it->type == ToolChange && tick.extruder == it->extruder)
return ctNone; return ctNone;
while (it != ticks.begin()) { while (it != ticks.begin()) {
--it; --it;
if (it->gcode == ToolChangeCode && tick.extruder == it->extruder) if (it->type == ToolChange && tick.extruder == it->extruder)
return ctNone; return ctNone;
} }
@ -2319,7 +2357,7 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod
} }
// check ToolChange tick // check ToolChange tick
if (mode == t_mode::MultiAsSingle && tick.gcode == ToolChangeCode) if (mode == MultiAsSingle && tick.type == ToolChange)
{ {
// We should mark a tick as a "MeaninglessToolChange", // We should mark a tick as a "MeaninglessToolChange",
// if it has a ToolChange to the same extruder // if it has a ToolChange to the same extruder
@ -2329,7 +2367,7 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod
while (it != ticks.begin()) { while (it != ticks.begin()) {
--it; --it;
if (it->gcode == ToolChangeCode) if (it->type == ToolChange)
return tick.extruder == it->extruder ? ctMeaninglessToolChange : ctNone; return tick.extruder == it->extruder ? ctMeaninglessToolChange : ctNone;
} }
} }

View file

@ -19,6 +19,8 @@ class wxMenu;
namespace Slic3r { namespace Slic3r {
using namespace CustomGCode;
namespace DoubleSlider { namespace DoubleSlider {
/* For exporting GCode in GCodeWriter is used XYZF_NUM(val) = PRECISION(val, 3) for XYZ values. /* For exporting GCode in GCodeWriter is used XYZF_NUM(val) = PRECISION(val, 3) for XYZ values.
@ -78,17 +80,16 @@ enum DrawMode
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
}; };
using t_mode = CustomGCode::Mode;
struct TickCode struct TickCode
{ {
bool operator<(const TickCode& other) const { return other.tick > this->tick; } bool operator<(const TickCode& other) const { return other.tick > this->tick; }
bool operator>(const TickCode& other) const { return other.tick < this->tick; } bool operator>(const TickCode& other) const { return other.tick < this->tick; }
int tick = 0; int tick = 0;
std::string gcode = ColorChangeCode; Type type = ColorChange;
int extruder = 0; int extruder = 0;
std::string color; std::string color;
std::string extra;
}; };
class TickCodeInfo class TickCodeInfo
@ -102,27 +103,27 @@ class TickCodeInfo
std::vector<std::string>* m_colors {nullptr}; std::vector<std::string>* m_colors {nullptr};
std::string get_color_for_tick(TickCode tick, const std::string& code, const int extruder); std::string get_color_for_tick(TickCode tick, Type type, const int extruder);
public: public:
std::set<TickCode> ticks {}; std::set<TickCode> ticks {};
t_mode mode = t_mode::Undef; Mode mode = Undef;
bool empty() const { return ticks.empty(); } bool empty() const { return ticks.empty(); }
void set_pause_print_msg(const std::string& message) { pause_print_msg = message; } void set_pause_print_msg(const std::string& message) { pause_print_msg = message; }
bool add_tick(const int tick, std::string& code, int extruder, double print_z); bool add_tick(const int tick, Type type, int extruder, double print_z);
bool edit_tick(std::set<TickCode>::iterator it, double print_z); bool edit_tick(std::set<TickCode>::iterator it, double print_z);
void switch_code(const std::string& code_from, const std::string& code_to); void switch_code(Type type_from, Type type_to);
bool switch_code_for_tick(std::set<TickCode>::iterator it, const std::string& code_to, const int extruder); bool switch_code_for_tick(std::set<TickCode>::iterator it, Type type_to, const int extruder);
void erase_all_ticks_with_code(const std::string& gcode); void erase_all_ticks_with_code(Type type);
bool has_tick_with_code(const std::string& gcode); bool has_tick_with_code(Type type);
ConflictType is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z); ConflictType is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z);
// Get used extruders for tick. // Get used extruders for tick.
// Means all extruders(tools) which will be used during printing from current tick to the end // Means all extruders(tools) which will be used during printing from current tick to the end
std::set<int> get_used_extruders_for_tick(int tick, int only_extruder, double print_z, t_mode force_mode = t_mode::Undef) const; std::set<int> get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode = Undef) const;
void suppress_plus (bool suppress) { m_suppress_plus = suppress; } void suppress_plus (bool suppress) { m_suppress_plus = suppress; }
void suppress_minus(bool suppress) { m_suppress_minus = suppress; } void suppress_minus(bool suppress) { m_suppress_minus = suppress; }
@ -210,16 +211,16 @@ public:
void SetSliderValues(const std::vector<double>& values) { m_values = values; } void SetSliderValues(const std::vector<double>& values) { m_values = values; }
void ChangeOneLayerLock(); void ChangeOneLayerLock();
CustomGCode::Info GetTicksValues() const; Info GetTicksValues() const;
void SetTicksValues(const Slic3r::CustomGCode::Info &custom_gcode_per_print_z); void SetTicksValues(const Info &custom_gcode_per_print_z);
void SetDrawMode(bool is_sla_print, bool is_sequential_print); void SetDrawMode(bool is_sla_print, bool is_sequential_print);
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
void SetDrawMode(DrawMode mode) { m_draw_mode = mode; } void SetDrawMode(DrawMode mode) { m_draw_mode = mode; }
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
void SetManipulationMode(t_mode mode) { m_mode = mode; } void SetManipulationMode(Mode mode) { m_mode = mode; }
t_mode GetManipulationMode() const { return m_mode; } Mode GetManipulationMode() const { return m_mode; }
void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder);
void SetExtruderColors(const std::vector<std::string>& extruder_colors); void SetExtruderColors(const std::vector<std::string>& extruder_colors);
@ -243,7 +244,7 @@ public:
void OnRightDown(wxMouseEvent& event); void OnRightDown(wxMouseEvent& event);
void OnRightUp(wxMouseEvent& event); void OnRightUp(wxMouseEvent& event);
void add_code_as_tick(std::string code, int selected_extruder = -1); void add_code_as_tick(Type type, int selected_extruder = -1);
// add default action for tick, when press "+" // add default action for tick, when press "+"
void add_current_tick(bool call_from_keyboard = false); void add_current_tick(bool call_from_keyboard = false);
// delete current tick, when press "-" // delete current tick, when press "-"
@ -305,7 +306,7 @@ private:
void get_size(int* w, int* h) const; void get_size(int* w, int* h) const;
double get_double_value(const SelectedSlider& selection); double get_double_value(const SelectedSlider& selection);
wxString get_tooltip(int tick = -1); wxString get_tooltip(int tick = -1);
int get_edited_tick_for_position(wxPoint pos, const std::string& gcode = ColorChangeCode); int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange);
std::string get_color_for_tool_change_tick(std::set<TickCode>::const_iterator it) const; std::string get_color_for_tool_change_tick(std::set<TickCode>::const_iterator it) const;
std::string get_color_for_color_change_tick(std::set<TickCode>::const_iterator it) const; std::string get_color_for_color_change_tick(std::set<TickCode>::const_iterator it) const;
@ -317,8 +318,8 @@ private:
// Use those values to disable selection of active extruders // Use those values to disable selection of active extruders
std::array<int, 2> get_active_extruders_for_tick(int tick) const; std::array<int, 2> get_active_extruders_for_tick(int tick) const;
void post_ticks_changed_event(const std::string& gcode = ""); void post_ticks_changed_event(Type type = Custom);
bool check_ticks_changed_event(const std::string& gcode); bool check_ticks_changed_event(Type type);
void append_change_extruder_menu_item (wxMenu*, bool switch_current_code = false); void append_change_extruder_menu_item (wxMenu*, bool switch_current_code = false);
void append_add_color_change_menu_item(wxMenu*, bool switch_current_code = false); void append_add_color_change_menu_item(wxMenu*, bool switch_current_code = false);
@ -350,7 +351,7 @@ private:
DrawMode m_draw_mode = dmRegular; DrawMode m_draw_mode = dmRegular;
t_mode m_mode = t_mode::SingleExtruder; Mode m_mode = SingleExtruder;
int m_only_extruder = -1; int m_only_extruder = -1;
MouseAction m_mouse = maNone; MouseAction m_mouse = maNone;

View file

@ -1189,7 +1189,7 @@ void GCodeViewer::render_legend() const
cp_values.reserve(custom_gcode_per_print_z.size()); cp_values.reserve(custom_gcode_per_print_z.size());
for (auto custom_code : custom_gcode_per_print_z) { for (auto custom_code : custom_gcode_per_print_z) {
if (custom_code.gcode != ColorChangeCode) if (custom_code.type != ColorChange)
continue; continue;
auto lower_b = std::lower_bound(m_layers_zs.begin(), m_layers_zs.end(), custom_code.print_z - Slic3r::DoubleSlider::epsilon()); auto lower_b = std::lower_bound(m_layers_zs.begin(), m_layers_zs.end(), custom_code.print_z - Slic3r::DoubleSlider::epsilon());
@ -1258,7 +1258,7 @@ void GCodeViewer::render_legend() const
int color_change_idx = 1 + static_cast<int>(m_tool_colors.size()) - extruders_count; int color_change_idx = 1 + static_cast<int>(m_tool_colors.size()) - extruders_count;
size_t last_color_id = m_tool_colors.size() - 1; size_t last_color_id = m_tool_colors.size() - 1;
for (int i = static_cast<int>(custom_gcode_per_print_z.size()) - 1; i >= 0; --i) { for (int i = static_cast<int>(custom_gcode_per_print_z.size()) - 1; i >= 0; --i) {
if (custom_gcode_per_print_z[i].gcode == ColorChangeCode) { if (custom_gcode_per_print_z[i].type == ColorChange) {
// create label for color change item // create label for color change item
std::string id_str = " (" + std::to_string(color_change_idx--) + ")"; std::string id_str = " (" + std::to_string(color_change_idx--) + ")";

View file

@ -895,7 +895,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D
std::vector<double> print_zs = canvas.get_current_print_zs(true); std::vector<double> print_zs = canvas.get_current_print_zs(true);
for (auto custom_code : custom_gcode_per_print_z) for (auto custom_code : custom_gcode_per_print_z)
{ {
if (custom_code.gcode != ColorChangeCode) if (custom_code.type != CustomGCode::ColorChange)
continue; continue;
auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.print_z - Slic3r::DoubleSlider::epsilon()); auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.print_z - Slic3r::DoubleSlider::epsilon());
@ -968,7 +968,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D
int cnt = custom_gcode_per_print_z.size(); int cnt = custom_gcode_per_print_z.size();
int color_change_idx = color_cnt - extruders_cnt; int color_change_idx = color_cnt - extruders_cnt;
for (int i = cnt-1; i >= 0; --i) for (int i = cnt-1; i >= 0; --i)
if (custom_gcode_per_print_z[i].gcode == ColorChangeCode) { if (custom_gcode_per_print_z[i].type == CustomGCode::ColorChange) {
::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + color_in_pos), 4 * sizeof(float)); ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + color_in_pos), 4 * sizeof(float));
color_pos += 4; color_pos += 4;
color_in_pos -= 4; color_in_pos -= 4;
@ -6164,7 +6164,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
// For coloring by a color_print(M600), return a parsed color. // For coloring by a color_print(M600), return a parsed color.
bool color_by_color_print() const { return color_print_values!=nullptr; } bool color_by_color_print() const { return color_print_values!=nullptr; }
const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const {
const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, "", 0, ""}; const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""};
auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value);
return (it - color_print_values->begin()) % number_tools(); return (it - color_print_values->begin()) % number_tools();
} }
@ -6178,36 +6178,36 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
{ return fabs(code.print_z - print_z) < EPSILON; }); { return fabs(code.print_z - print_z) < EPSILON; });
if (it != color_print_values->end()) if (it != color_print_values->end())
{ {
const std::string& code = it->gcode; CustomGCode::Type type = it->type;
// pause print or custom Gcode // pause print or custom Gcode
if (code == PausePrintCode || if (type == CustomGCode::PausePrint ||
(code != ColorChangeCode && code != ToolChangeCode)) (type != CustomGCode::ColorChange && type != CustomGCode::ToolChange))
return number_tools()-1; // last color item is a gray color for pause print or custom G-code return number_tools()-1; // last color item is a gray color for pause print or custom G-code
// change tool (extruder) // change tool (extruder)
if (code == ToolChangeCode) if (type == CustomGCode::ToolChange)
return get_color_idx_for_tool_change(it, extruder); return get_color_idx_for_tool_change(it, extruder);
// change color for current extruder // change color for current extruder
if (code == ColorChangeCode) { if (type == CustomGCode::ColorChange) {
int color_idx = get_color_idx_for_color_change(it, extruder); int color_idx = get_color_idx_for_color_change(it, extruder);
if (color_idx >= 0) if (color_idx >= 0)
return color_idx; return color_idx;
} }
} }
const CustomGCode::Item value{print_z + EPSILON, "", 0, ""}; const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""};
it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value);
while (it != color_print_values->begin()) while (it != color_print_values->begin())
{ {
--it; --it;
// change color for current extruder // change color for current extruder
if (it->gcode == ColorChangeCode) { if (it->type == CustomGCode::ColorChange) {
int color_idx = get_color_idx_for_color_change(it, extruder); int color_idx = get_color_idx_for_color_change(it, extruder);
if (color_idx >= 0) if (color_idx >= 0)
return color_idx; return color_idx;
} }
// change tool (extruder) // change tool (extruder)
if (it->gcode == ToolChangeCode) if (it->type == CustomGCode::ToolChange)
return get_color_idx_for_tool_change(it, extruder); return get_color_idx_for_tool_change(it, extruder);
} }
@ -6220,7 +6220,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
int shift = 0; int shift = 0;
while (it != color_print_values->begin()) { while (it != color_print_values->begin()) {
--it; --it;
if (it->gcode == ColorChangeCode) if (it->type == CustomGCode::ColorChange)
shift++; shift++;
} }
return extruders_cnt + shift; return extruders_cnt + shift;
@ -6235,7 +6235,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
auto it_n = it; auto it_n = it;
while (it_n != color_print_values->begin()) { while (it_n != color_print_values->begin()) {
--it_n; --it_n;
if (it_n->gcode == ColorChangeCode && it_n->extruder == current_extruder) if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder)
return get_m600_color_idx(it_n); return get_m600_color_idx(it_n);
} }
@ -6251,7 +6251,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
bool is_tool_change = false; bool is_tool_change = false;
while (it_n != color_print_values->begin()) { while (it_n != color_print_values->begin()) {
--it_n; --it_n;
if (it_n->gcode == ToolChangeCode) { if (it_n->type == CustomGCode::ToolChange) {
is_tool_change = true; is_tool_change = true;
if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder))
return get_m600_color_idx(it); return get_m600_color_idx(it);

View file

@ -715,24 +715,16 @@ void GUI_App::update_ui_from_settings()
void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized)
{ {
const std::string name = into_u8(window->GetName()); const std::string name = into_u8(window->GetName());
wxTopLevelWindow* settings_dlg = dynamic_cast<MainFrame*>(window)->m_settings_dialog;
const std::string settings_dlg_name = "settings_dialog";
window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) {
window_pos_save(window, name); window_pos_save(window, name);
if (settings_dlg)
window_pos_save(settings_dlg, settings_dlg_name);
event.Skip(); event.Skip();
}); });
window_pos_restore(window, name, default_maximized); window_pos_restore(window, name, default_maximized);
if (settings_dlg)
window_pos_restore(settings_dlg, settings_dlg_name, default_maximized);
on_window_geometry(window, [=]() { on_window_geometry(window, [=]() {
window_pos_sanitize(window); window_pos_sanitize(window);
if (settings_dlg)
window_pos_sanitize(settings_dlg);
}); });
} }
@ -1408,7 +1400,9 @@ void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &na
return; return;
} }
window->SetSize(metrics->get_rect()); const wxRect& rect = metrics->get_rect();
window->SetPosition(rect.GetPosition());
window->SetSize(rect.GetSize());
window->Maximize(metrics->get_maximized()); window->Maximize(metrics->get_maximized());
} }

View file

@ -769,10 +769,10 @@ void Preview::update_view_type(bool slice_completed)
const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes.empty() /*&& const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes.empty() /*&&
(wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */? (wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */?
_(L("Color Print")) : _L("Color Print") :
config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ? config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ?
_(L("Tool")) : _L("Tool") :
_(L("Feature type")); _L("Feature type");
int type = m_choice_view_type->FindString(choice); int type = m_choice_view_type->FindString(choice);
if (m_choice_view_type->GetSelection() != type) { if (m_choice_view_type->GetSelection() != type) {
@ -786,6 +786,8 @@ void Preview::update_view_type(bool slice_completed)
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER
m_preferred_color_mode = "feature"; m_preferred_color_mode = "feature";
} }
reload_print();
} }
#if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER
@ -851,8 +853,6 @@ wxBoxSizer* Preview::create_layers_slider_sizer()
m_schedule_background_process(); m_schedule_background_process();
update_view_type(false); update_view_type(false);
reload_print();
}); });
return sizer; return sizer;
@ -885,9 +885,7 @@ void Preview::create_double_slider()
m_schedule_background_process(); m_schedule_background_process();
update_view_type(false); update_view_type(false);
});
reload_print();
});
} }
#endif // ENABLE_GCODE_VIEWER #endif // ENABLE_GCODE_VIEWER

View file

@ -341,7 +341,7 @@ public:
static WindowMetrics from_window(wxTopLevelWindow *window); static WindowMetrics from_window(wxTopLevelWindow *window);
static boost::optional<WindowMetrics> deserialize(const std::string &str); static boost::optional<WindowMetrics> deserialize(const std::string &str);
wxRect get_rect() const { return rect; } const wxRect& get_rect() const { return rect; }
bool get_maximized() const { return maximized; } bool get_maximized() const { return maximized; }
void sanitize_for_display(const wxRect &screen_rect); void sanitize_for_display(const wxRect &screen_rect);

View file

@ -223,6 +223,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
}); });
wxGetApp().persist_window_geometry(this, true); wxGetApp().persist_window_geometry(this, true);
if (m_settings_dialog != nullptr)
wxGetApp().persist_window_geometry(m_settings_dialog, true);
update_ui_from_settings(); // FIXME (?) update_ui_from_settings(); // FIXME (?)
@ -277,8 +279,14 @@ void MainFrame::shutdown()
// In addition, there were some crashes due to the Paint events sent to already destructed windows. // In addition, there were some crashes due to the Paint events sent to already destructed windows.
this->Show(false); this->Show(false);
if (m_settings_dialog) if (m_settings_dialog != nullptr)
{
if (m_settings_dialog->IsShown())
// call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
m_settings_dialog->Close();
m_settings_dialog->Destroy(); m_settings_dialog->Destroy();
}
if (m_plater != nullptr) { if (m_plater != nullptr) {
// restore sidebar if it was hidden when switching to gcode viewer mode // restore sidebar if it was hidden when switching to gcode viewer mode
@ -1713,7 +1721,8 @@ std::string MainFrame::get_dir_name(const wxString &full_name) const
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
SettingsDialog::SettingsDialog(MainFrame* mainframe) SettingsDialog::SettingsDialog(MainFrame* mainframe)
: DPIDialog(nullptr, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings")), : DPIDialog(nullptr, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings"), wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxDIALOG_NO_PARENT, "settings_dialog"),
m_main_frame(mainframe) m_main_frame(mainframe)
{ {
this->SetFont(wxGetApp().normal_font()); this->SetFont(wxGetApp().normal_font());

View file

@ -1330,22 +1330,22 @@ void Sidebar::update_sliced_info_sizer()
wxString str_color = _L("Color"); wxString str_color = _L("Color");
wxString str_pause = _L("Pause"); wxString str_pause = _L("Pause");
auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGcodeType, std::string>>& times, auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGCode::Type, std::string>>& times,
wxString& new_label, wxString& info_text) wxString& new_label, wxString& info_text)
{ {
int color_change_count = 0; int color_change_count = 0;
for (auto time : times) for (auto time : times)
if (time.first == cgtColorChange) if (time.first == CustomGCode::ColorChange)
color_change_count++; color_change_count++;
for (int i = (int)times.size() - 1; i >= 0; --i) for (int i = (int)times.size() - 1; i >= 0; --i)
{ {
if (i == 0 || times[i - 1].first == cgtPausePrint) if (i == 0 || times[i - 1].first == CustomGCode::PausePrint)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count); new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count);
else if (times[i - 1].first == cgtColorChange) else if (times[i - 1].first == CustomGCode::ColorChange)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--); new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--);
if (i != (int)times.size() - 1 && times[i].first == cgtPausePrint) if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint)
new_label += format_wxstr(" -> %1%", str_pause); new_label += format_wxstr(" -> %1%", str_pause);
info_text += format_wxstr("\n%1%", times[i].second); info_text += format_wxstr("\n%1%", times[i].second);
@ -5421,7 +5421,7 @@ std::vector<std::string> Plater::get_colors_for_color_print() const
colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size()); colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size());
for (const CustomGCode::Item& code : p->model.custom_gcode_per_print_z.gcodes) for (const CustomGCode::Item& code : p->model.custom_gcode_per_print_z.gcodes)
if (code.gcode == ColorChangeCode) if (code.type == CustomGCode::ColorChange)
colors.emplace_back(code.color); colors.emplace_back(code.color);
return colors; return colors;

View file

@ -461,6 +461,7 @@ const std::vector<std::string>& Preset::printer_options()
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"host_type", "print_host", "printhost_apikey", "printhost_cafile", "host_type", "print_host", "printhost_apikey", "printhost_cafile",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
"color_change_gcode", "pause_print_gcode", "template_custom_gcode",
"between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height",
"default_print_profile", "inherits", "default_print_profile", "inherits",

View file

@ -34,20 +34,6 @@ namespace Search {
#define SEARCH_SUPPORTS_MARKUP #define SEARCH_SUPPORTS_MARKUP
#endif #endif
static const std::vector<std::wstring>& NameByType()
{
static std::vector<std::wstring> data;
if (data.empty()) {
data.assign(Preset::TYPE_COUNT, std::wstring());
data[Preset::TYPE_PRINT ] = _L("Print" ).ToStdWstring();
data[Preset::TYPE_FILAMENT ] = _L("Filament" ).ToStdWstring();
data[Preset::TYPE_SLA_MATERIAL ] = _L("Material" ).ToStdWstring();
data[Preset::TYPE_SLA_PRINT ] = _L("Print" ).ToStdWstring();
data[Preset::TYPE_PRINTER ] = _L("Printer" ).ToStdWstring();
};
return data;
}
static char marker_by_type(Preset::Type type, PrinterTechnology pt) static char marker_by_type(Preset::Type type, PrinterTechnology pt)
{ {
switch(type) { switch(type) {

View file

@ -2218,6 +2218,21 @@ void TabPrinter::build_fff()
option.opt.height = gcode_field_height;//150; option.opt.height = gcode_field_height;//150;
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option);
optgroup = page->new_optgroup(L("Color Change G-code"), 0);
option = optgroup->get_option("color_change_gcode");
option.opt.height = gcode_field_height;//150;
optgroup->append_single_option_line(option);
optgroup = page->new_optgroup(L("Pause Print G-code"), 0);
option = optgroup->get_option("pause_print_gcode");
option.opt.height = gcode_field_height;//150;
optgroup->append_single_option_line(option);
optgroup = page->new_optgroup(L("Template Custom G-code"), 0);
option = optgroup->get_option("template_custom_gcode");
option.opt.height = gcode_field_height;//150;
optgroup->append_single_option_line(option);
page = add_options_page(L("Notes"), "note.png"); page = add_options_page(L("Notes"), "note.png");
optgroup = page->new_optgroup(L("Notes"), 0); optgroup = page->new_optgroup(L("Notes"), 0);
option = optgroup->get_option("printer_notes"); option = optgroup->get_option("printer_notes");

View file

@ -1,16 +1,18 @@
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
#include <test_utils.hpp> #include <test_utils.hpp>
#include <stack>
#include <libslic3r/Polygon.hpp> #include <libslic3r/Polygon.hpp>
#include <libslic3r/Polyline.hpp> #include <libslic3r/Polyline.hpp>
#include <libslic3r/EdgeGrid.hpp> #include <libslic3r/EdgeGrid.hpp>
#include <libslic3r/Geometry.hpp> #include <libslic3r/Geometry.hpp>
#include <libslic3r/VoronoiOffset.hpp> #include <libslic3r/VoronoiOffset.hpp>
#define BOOST_VORONOI_USE_GMP 1 // #define VORONOI_DEBUG_OUT
#include "boost/polygon/voronoi.hpp"
#ifdef VORONOI_DEBUG_OUT
#include <libslic3r/VoronoiVisualUtils.hpp>
#endif
using boost::polygon::voronoi_builder; using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram; using boost::polygon::voronoi_diagram;
@ -19,400 +21,6 @@ using namespace Slic3r;
using VD = Geometry::VoronoiDiagram; using VD = Geometry::VoronoiDiagram;
// #define VORONOI_DEBUG_OUT
#ifdef VORONOI_DEBUG_OUT
#include <libslic3r/SVG.hpp>
#endif
#ifdef VORONOI_DEBUG_OUT
namespace boost { namespace polygon {
// The following code for the visualization of the boost Voronoi diagram is based on:
//
// Boost.Polygon library voronoi_graphic_utils.hpp header file
// Copyright Andrii Sydorchuk 2010-2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
template <typename CT>
class voronoi_visual_utils {
public:
// Discretize parabolic Voronoi edge.
// Parabolic Voronoi edges are always formed by one point and one segment
// from the initial input set.
//
// Args:
// point: input point.
// segment: input segment.
// max_dist: maximum discretization distance.
// discretization: point discretization of the given Voronoi edge.
//
// Template arguments:
// InCT: coordinate type of the input geometries (usually integer).
// Point: point type, should model point concept.
// Segment: segment type, should model segment concept.
//
// Important:
// discretization should contain both edge endpoints initially.
template <class InCT1, class InCT2,
template<class> class Point,
template<class> class Segment>
static
typename enable_if<
typename gtl_and<
typename gtl_if<
typename is_point_concept<
typename geometry_concept< Point<InCT1> >::type
>::type
>::type,
typename gtl_if<
typename is_segment_concept<
typename geometry_concept< Segment<InCT2> >::type
>::type
>::type
>::type,
void
>::type discretize(
const Point<InCT1>& point,
const Segment<InCT2>& segment,
const CT max_dist,
std::vector< Point<CT> >* discretization) {
// Apply the linear transformation to move start point of the segment to
// the point with coordinates (0, 0) and the direction of the segment to
// coincide the positive direction of the x-axis.
CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y;
// Compute x-coordinates of the endpoints of the edge
// in the transformed space.
CT projection_start = sqr_segment_length *
get_point_projection((*discretization)[0], segment);
CT projection_end = sqr_segment_length *
get_point_projection((*discretization)[1], segment);
// Compute parabola parameters in the transformed space.
// Parabola has next representation:
// f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y).
CT point_vec_x = cast(x(point)) - cast(x(low(segment)));
CT point_vec_y = cast(y(point)) - cast(y(low(segment)));
CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y;
CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x;
// Save the last point.
Point<CT> last_point = (*discretization)[1];
discretization->pop_back();
// Use stack to avoid recursion.
std::stack<CT> point_stack;
point_stack.push(projection_end);
CT cur_x = projection_start;
CT cur_y = parabola_y(cur_x, rot_x, rot_y);
// Adjust max_dist parameter in the transformed space.
const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length;
while (!point_stack.empty()) {
CT new_x = point_stack.top();
CT new_y = parabola_y(new_x, rot_x, rot_y);
// Compute coordinates of the point of the parabola that is
// furthest from the current line segment.
CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x;
CT mid_y = parabola_y(mid_x, rot_x, rot_y);
// Compute maximum distance between the given parabolic arc
// and line segment that discretize it.
CT dist = (new_y - cur_y) * (mid_x - cur_x) -
(new_x - cur_x) * (mid_y - cur_y);
dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) +
(new_x - cur_x) * (new_x - cur_x));
if (dist <= max_dist_transformed) {
// Distance between parabola and line segment is less than max_dist.
point_stack.pop();
CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) /
sqr_segment_length + cast(x(low(segment)));
CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) /
sqr_segment_length + cast(y(low(segment)));
discretization->push_back(Point<CT>(inter_x, inter_y));
cur_x = new_x;
cur_y = new_y;
} else {
point_stack.push(mid_x);
}
}
// Update last point.
discretization->back() = last_point;
}
private:
// Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b).
static CT parabola_y(CT x, CT a, CT b) {
return ((x - a) * (x - a) + b * b) / (b + b);
}
// Get normalized length of the distance between:
// 1) point projection onto the segment
// 2) start point of the segment
// Return this length divided by the segment length. This is made to avoid
// sqrt computation during transformation from the initial space to the
// transformed one and vice versa. The assumption is made that projection of
// the point lies between the start-point and endpoint of the segment.
template <class InCT,
template<class> class Point,
template<class> class Segment>
static
typename enable_if<
typename gtl_and<
typename gtl_if<
typename is_point_concept<
typename geometry_concept< Point<int> >::type
>::type
>::type,
typename gtl_if<
typename is_segment_concept<
typename geometry_concept< Segment<long> >::type
>::type
>::type
>::type,
CT
>::type get_point_projection(
const Point<CT>& point, const Segment<InCT>& segment) {
CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
CT point_vec_x = x(point) - cast(x(low(segment)));
CT point_vec_y = y(point) - cast(y(low(segment)));
CT sqr_segment_length =
segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y;
CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y;
return vec_dot / sqr_segment_length;
}
template <typename InCT>
static CT cast(const InCT& value) {
return static_cast<CT>(value);
}
};
} } // namespace boost::polygon
// The following code for the visualization of the boost Voronoi diagram is based on:
//
// Boost.Polygon library voronoi_visualizer.cpp file
// Copyright Andrii Sydorchuk 2010-2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
namespace Voronoi { namespace Internal {
typedef double coordinate_type;
typedef boost::polygon::point_data<coordinate_type> point_type;
typedef boost::polygon::segment_data<coordinate_type> segment_type;
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
typedef boost::polygon::voronoi_diagram<coordinate_type> VD;
typedef VD::cell_type cell_type;
typedef VD::cell_type::source_index_type source_index_type;
typedef VD::cell_type::source_category_type source_category_type;
typedef VD::edge_type edge_type;
typedef VD::cell_container_type cell_container_type;
typedef VD::cell_container_type vertex_container_type;
typedef VD::edge_container_type edge_container_type;
typedef VD::const_cell_iterator const_cell_iterator;
typedef VD::const_vertex_iterator const_vertex_iterator;
typedef VD::const_edge_iterator const_edge_iterator;
static const std::size_t EXTERNAL_COLOR = 1;
inline void color_exterior(const VD::edge_type* edge)
{
if (edge->color() == EXTERNAL_COLOR)
return;
edge->color(EXTERNAL_COLOR);
edge->twin()->color(EXTERNAL_COLOR);
const VD::vertex_type* v = edge->vertex1();
if (v == NULL || !edge->is_primary())
return;
v->color(EXTERNAL_COLOR);
const VD::edge_type* e = v->incident_edge();
do {
color_exterior(e);
e = e->rot_next();
} while (e != v->incident_edge());
}
inline point_type retrieve_point(const Points &points, const std::vector<segment_type> &segments, const cell_type& cell)
{
assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT ||
cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT);
return cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT ?
Voronoi::Internal::point_type(double(points[cell.source_index()].x()), double(points[cell.source_index()].y())) :
(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ?
low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
}
inline void clip_infinite_edge(const Points &points, const std::vector<segment_type> &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector<point_type>* clipped_edge)
{
const cell_type& cell1 = *edge.cell();
const cell_type& cell2 = *edge.twin()->cell();
point_type origin, direction;
// Infinite edges could not be created by two segment sites.
if (! cell1.contains_point() && ! cell2.contains_point()) {
printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n");
return;
}
if (cell1.contains_point() && cell2.contains_point()) {
point_type p1 = retrieve_point(points, segments, cell1);
point_type p2 = retrieve_point(points, segments, cell2);
origin.x((p1.x() + p2.x()) * 0.5);
origin.y((p1.y() + p2.y()) * 0.5);
direction.x(p1.y() - p2.y());
direction.y(p2.x() - p1.x());
} else {
origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1);
segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()];
coordinate_type dx = high(segment).x() - low(segment).x();
coordinate_type dy = high(segment).y() - low(segment).y();
if ((low(segment) == origin) ^ cell1.contains_point()) {
direction.x(dy);
direction.y(-dx);
} else {
direction.x(-dy);
direction.y(dx);
}
}
coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y()));
if (edge.vertex0() == NULL) {
clipped_edge->push_back(point_type(
origin.x() - direction.x() * koef,
origin.y() - direction.y() * koef));
} else {
clipped_edge->push_back(
point_type(edge.vertex0()->x(), edge.vertex0()->y()));
}
if (edge.vertex1() == NULL) {
clipped_edge->push_back(point_type(
origin.x() + direction.x() * koef,
origin.y() + direction.y() * koef));
} else {
clipped_edge->push_back(
point_type(edge.vertex1()->x(), edge.vertex1()->y()));
}
}
inline void sample_curved_edge(const Points &points, const std::vector<segment_type> &segments, const edge_type& edge, std::vector<point_type> &sampled_edge, coordinate_type max_dist)
{
point_type point = edge.cell()->contains_point() ?
retrieve_point(points, segments, *edge.cell()) :
retrieve_point(points, segments, *edge.twin()->cell());
segment_type segment = edge.cell()->contains_point() ?
segments[edge.twin()->cell()->source_index()] :
segments[edge.cell()->source_index()];
::boost::polygon::voronoi_visual_utils<coordinate_type>::discretize(point, segment, max_dist, &sampled_edge);
}
} /* namespace Internal */ } // namespace Voronoi
static inline void dump_voronoi_to_svg(
const char *path,
/* const */ VD &vd,
const Points &points,
const Lines &lines,
const Polygons &offset_curves = Polygons(),
const double scale = 0.7) // 0.2?
{
const std::string inputSegmentPointColor = "lightseagreen";
const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR);
const std::string inputSegmentColor = "lightseagreen";
const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR);
const std::string voronoiPointColor = "black";
const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR);
const std::string voronoiLineColorPrimary = "black";
const std::string voronoiLineColorSecondary = "green";
const std::string voronoiArcColor = "red";
const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR);
const std::string offsetCurveColor = "magenta";
const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR);
const bool internalEdgesOnly = false;
const bool primaryEdgesOnly = false;
BoundingBox bbox;
bbox.merge(get_extents(points));
bbox.merge(get_extents(lines));
bbox.min -= (0.01 * bbox.size().cast<double>()).cast<coord_t>();
bbox.max += (0.01 * bbox.size().cast<double>()).cast<coord_t>();
::Slic3r::SVG svg(path, bbox);
// bbox.scale(1.2);
// For clipping of half-lines to some reasonable value.
// The line will then be clipped by the SVG viewer anyway.
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
// For the discretization of the Voronoi parabolic segments.
const double discretization_step = 0.05 * bbox_dim_max;
// Make a copy of the input segments with the double type.
std::vector<Voronoi::Internal::segment_type> segments;
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it)
segments.push_back(Voronoi::Internal::segment_type(
Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))),
Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1)))));
// Color exterior edges.
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
if (!it->is_finite())
Voronoi::Internal::color_exterior(&(*it));
// Draw the end points of the input polygon.
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius);
svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius);
}
// Draw the input polygon.
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it)
svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth);
#if 1
// Draw voronoi vertices.
for (boost::polygon::voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius);
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
if (primaryEdgesOnly && !it->is_primary())
continue;
if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR))
continue;
std::vector<Voronoi::Internal::point_type> samples;
std::string color = voronoiLineColorPrimary;
if (!it->is_finite()) {
Voronoi::Internal::clip_infinite_edge(points, segments, *it, bbox_dim_max, &samples);
if (! it->is_primary())
color = voronoiLineColorSecondary;
} else {
// Store both points of the segment into samples. sample_curved_edge will split the initial line
// until the discretization_step is reached.
samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y()));
samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y()));
if (it->is_curved()) {
Voronoi::Internal::sample_curved_edge(points, segments, *it, samples, discretization_step);
color = voronoiArcColor;
} else if (! it->is_primary())
color = voronoiLineColorSecondary;
}
for (std::size_t i = 0; i + 1 < samples.size(); ++i)
svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth);
}
#endif
svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth);
svg.Close();
}
#endif
// https://svn.boost.org/trac10/ticket/12067 // https://svn.boost.org/trac10/ticket/12067
// This bug seems to be confirmed. // This bug seems to be confirmed.
// Vojtech supposes that there may be no Voronoi edges produced for // Vojtech supposes that there may be no Voronoi edges produced for
@ -1586,7 +1194,7 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]")
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(), dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(),
vd, Points(), lines, Polygons(), 0.015); vd, Points(), lines, Polygons(), Lines(), 0.015);
#endif #endif
} }
@ -1606,12 +1214,19 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]")
Lines lines = to_lines(poly_with_hole); Lines lines = to_lines(poly_with_hole);
construct_voronoi(lines.begin(), lines.end(), &vd); construct_voronoi(lines.begin(), lines.end(), &vd);
Polygons offsetted_polygons = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); Polygons offsetted_polygons_out = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005));
REQUIRE(offsetted_polygons_out.size() == 1);
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset.svg").c_str(), dump_voronoi_to_svg(debug_out_path("voronoi-offset-out.svg").c_str(),
vd, Points(), lines, offsetted_polygons); vd, Points(), lines, offsetted_polygons_out);
#endif #endif
REQUIRE(offsetted_polygons.size() == 2); Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - scale_(0.2), scale_(0.005));
REQUIRE(offsetted_polygons_in.size() == 1);
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset-in.svg").c_str(),
vd, Points(), lines, offsetted_polygons_in);
#endif
} }