Port EditGCodeDialog from PrusaSlicer (#3417)

This is a port of the EditGCodeDialog from PrusaSlicer 2.7.x. There were
a few changes made to make it a bit more functional. Also, it isn't
quite fully complete, but it should be in a very usable state.

General Changes:
- Implement UndoValueUIManager and EditValueUI in Field
- Implement EditGCodeDialog and add buttons to the tabs
- Other minor changes to accommodate the new classes

Differences from PrusaSlicer's Implementation:
- backported to wxWidgets 3.1.5 (reverse commit 8770c4b7 after updating
to 3.2.x)
- icons have been updated to use Orca colors
- improve the report that tells you if certain placeholders have not
been defined properly for the dialog. It now shows all issues at once
rather than having to fix then recompile to see the next issue.
- allow the use of the cmake option `ORCA_CHECK_GCODE_PLACEHOLDERS` to
toggle the above report since our workflow rarely uses debug mode.
- if a custom gcode value is not set when checking gcode placeholders, a
testing value is set. Custom gcode is not parsed if it is empty, and the
only way to check if the placeholders are all defined is by running the
placeholder operation on the custom gcode.
- some calls to `print.config()` in Gcode.cpp were changed to `m_config`
to support the above testing values feature (only m_config is modified
with the placeholders and `print.config()` would return an empty string)
- a macro has been added to quickly add a definition to
SlicingStatesConfigDefs with less boiler plate (it could technically be
used for any ConfigOptionDef, but that would hurt interoperability with
PS. I tried to not use the macro for too many PS defined definitions.)
- the presets are now also categorized by the page they are on in their
tab
<table>
<tr>
 <td>Prusa
 <td>Orca
<tr>
 <td>
<img
src="27cb4f48-d225-4563-9aeb-b2b461f8bff5"
/>
 <td>
<img
src="4fcd8cde-2427-4d1a-a0ed-1738b570b919"
/>
</table>

TODO:
- [x] Make sure all linux fixes have been applied
- [x] Finish adding "universal" gcode options
- [x] add search function to dataview (maybe?)
- [x] determine if any options are being left out of the preset
categories by getting options from Tab rather than Presets. If so,
consider adding outside of the groupings
This commit is contained in:
SoftFever 2024-01-24 19:56:18 +08:00 committed by GitHub
commit 5ff00fb48c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 2155 additions and 122 deletions

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g id="b"><path d="m8.02,7.18h3.98v3.08c0,2.6-1.23,3.72-4.05,3.72s-3.95-1.12-3.95-3.68v-4.75c0-2.42,1.09-3.54,3.95-3.54s4.05,1,4.05,3.54h-2.12c0-1.11-.28-1.66-1.92-1.66-1.54,0-1.83.69-1.83,1.77v4.65c0,1.12.29,1.77,1.83,1.77s2.08-.65,2.08-1.82v-1.16h-2.02v-1.92Z" style="fill:#808080;"/></g></svg>

After

Width:  |  Height:  |  Size: 402 B

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m13.6,5h-.6v3.5c0,.28-.22.5-.5.5s-.5-.22-.5-.5v-3.5h-2v3.5c0,.28-.22.5-.5.5s-.5-.22-.5-.5v-3.5h-2v3.5c0,.28-.22.5-.5.5s-.5-.22-.5-.5v-3.5h-2v3.5c0,.28-.22.5-.5.5s-.5-.22-.5-.5v-3.5h-.6c-.77,0-1.4.62-1.4,1.4v3.21c0,.77.62,1.4,1.4,1.4h11.21c.77,0,1.4-.62,1.4-1.4v-3.21c0-.77-.62-1.4-1.4-1.4Z" style="fill:#808080;"/></svg>

After

Width:  |  Height:  |  Size: 434 B

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path id="b" d="m8.54,4.31c-.53-.19-.81-.69-.73-1.32.08-.62.55-1.01,1.21-1.01.46,0,.82.21,1,.58.1.2.1.22.1.55,0,.43-.07.61-.31.87-.24.26-.46.35-.84.36-.21,0-.33,0-.43-.04Z" style="fill:#009688; isolation:isolate; opacity:1;"/><path id="c" d="m6.82,13.97c-.51-.14-.74-.4-.77-.86-.02-.28-.04-.15.57-3.12.22-1.07.44-2.13.48-2.34.13-.71.07-.77-.77-.81-.21-.01-.38-.03-.39-.04s0-.09.02-.18c.04-.18,0-.16.69-.31.62-.14,1.09-.19,1.63-.2.67,0,.9.07,1.13.36.09.11.09.13.09.41,0,.25-.06.57-.39,2.17-.78,3.74-.79,3.82-.68,3.99.03.04.08.1.12.13.12.08.47.14.8.14h.3s-.02.18-.02.18c-.01.1-.04.19-.05.2-.05.03-.88.2-1.24.26-.45.07-1.31.09-1.51.03h0Z" style="fill:#009688; isolation:isolate; opacity:1;"/><polyline points="14.5 3.5 14.5 1.5 12.5 1.5" style="fill:none; stroke:808080; stroke-linecap:round; stroke-miterlimit:10;"/><polyline points="3.5 1.5 1.5 1.5 1.5 3.5" style="fill:none; stroke:#808080; stroke-linecap:round; stroke-miterlimit:10;"/><polyline points="1.5 12.5 1.5 14.5 3.5 14.5" style="fill:none; stroke:#808080; stroke-linecap:round; stroke-miterlimit:10;"/><polyline points="12.5 14.5 14.5 14.5 14.5 12.5" style="fill:none; stroke:#808080; stroke-linecap:round; stroke-miterlimit:10;"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><circle cx="8" cy="8" r="2" style="fill:#808080;"/></svg>

After

Width:  |  Height:  |  Size: 162 B

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g id="b"><path d="m13.77,6.39c-.13-.47-.32-.92-.55-1.33l.43-1.3-1.41-1.41-1.3.43c-.42-.23-.86-.42-1.33-.55l-.61-1.23h-2l-.61,1.23c-.47.13-.92.31-1.33.55l-1.3-.43-1.42,1.41.43,1.3c-.23.41-.41.86-.54,1.33l-1.23.61v2l1.23.61c.13.47.32.92.55,1.33l-.43,1.3,1.41,1.41,1.3-.43c.42.23.86.42,1.33.55l.61,1.23h2l.61-1.23c.47-.13.92-.32,1.33-.55l1.3.43,1.41-1.41-.43-1.3c.23-.42.42-.86.55-1.33l1.23-.61v-2l-1.23-.61Zm-5.77,6.61c-2.76,0-5-2.24-5-5s2.24-5,5-5,5,2.24,5,5-2.24,5-5,5Z" style="fill:#808080;"/></g></svg>

After

Width:  |  Height:  |  Size: 610 B

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g id="b">
<path d="m13.77,6.39c-.13-.47-.32-.92-.55-1.33l.43-1.3-1.41-1.41-1.3.43c-.42-.23-.86-.42-1.33-.55l-.61-1.23h-2l-.61,1.23c-.47.13-.92.31-1.33.55l-1.3-.43-1.42,1.41.43,1.3c-.23.41-.41.86-.54,1.33l-1.23.61v2l1.23.61c.13.47.32.92.55,1.33l-.43,1.3,1.41,1.41,1.3-.43c.42.23.86.42,1.33.55l.61,1.23h2l.61-1.23c.47-.13.92-.32,1.33-.55l1.3.43,1.41-1.41-.43-1.3c.23-.42.42-.86.55-1.33l1.23-.61v-2l-1.23-.61Zm-5.77,6.61c-2.76,0-5-2.24-5-5s2.24-5,5-5,5,2.24,5,5-2.24,5-5,5Z" style="fill:#808080"/>
</g><circle cx="8" cy="8" r="2" style="fill:#009688; isolation:isolate; opacity:1;"/></svg>

After

Width:  |  Height:  |  Size: 694 B

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect x="1" y="7" width="2" height="6" rx="1" ry="1" style="fill:#009688;"/><rect x="5" y="2" width="2" height="11" rx="1" ry="1" style="fill:#009688;"/><rect x="9" y="3" width="2" height="10" rx="1" ry="1" style="fill:#009688;"/><rect x="13" y="5" width="2" height="8" rx="1" ry="1" style="fill:#009688;"/></svg>

After

Width:  |  Height:  |  Size: 418 B

View file

@ -0,0 +1,14 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8076_36010)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.69269 2.49023C4.69269 1.29516 5.66149 0.326355 6.85657 0.326355C8.05165 0.326355 9.02044 1.29516 9.02044 2.49023V6.92898C9.96736 7.60117 10.5871 8.70725 10.5871 9.95952C10.5871 12.0108 8.92419 13.6737 6.87289 13.6737C4.8216 13.6737 3.15869 12.0108 3.15869 9.95952C3.15869 8.72168 3.76428 7.62665 4.69269 6.95241V2.49023ZM5.69269 7.51465V2.49023C5.69269 1.84744 6.21378 1.32635 6.85657 1.32635C7.49936 1.32635 8.02044 1.84744 8.02044 2.49023V7.49913C8.94588 7.9315 9.58709 8.87063 9.58709 9.95952C9.58709 11.4585 8.3719 12.6737 6.87289 12.6737C5.37388 12.6737 4.15869 11.4585 4.15869 9.95952C4.15869 8.88348 4.78486 7.95369 5.69269 7.51465Z" fill="#009688"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.186 2.49249C10.186 2.21635 10.4099 1.99249 10.686 1.99249H11.8258C12.1019 1.99249 12.3258 2.21635 12.3258 2.49249C12.3258 2.76863 12.1019 2.99249 11.8258 2.99249H10.686C10.4099 2.99249 10.186 2.76863 10.186 2.49249Z" fill="#009688"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.186 4.83203C10.186 4.55589 10.4099 4.33203 10.686 4.33203H11.8258C12.1019 4.33203 12.3258 4.55589 12.3258 4.83203C12.3258 5.10817 12.1019 5.33203 11.8258 5.33203H10.686C10.4099 5.33203 10.186 5.10817 10.186 4.83203Z" fill="#009688"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.87305 2.49249C7.14919 2.49249 7.37305 2.71635 7.37305 2.99249V9.22844C7.37305 9.50458 7.14919 9.72844 6.87305 9.72844C6.5969 9.72844 6.37305 9.50458 6.37305 9.22844L6.37305 2.99249C6.37305 2.71635 6.5969 2.49249 6.87305 2.49249Z" fill="#009688"/>
<path d="M8.24858 9.90249C8.24858 10.6622 7.63274 11.278 6.87307 11.278C6.1134 11.278 5.49756 10.6622 5.49756 9.90249C5.49756 9.14281 6.1134 8.52698 6.87307 8.52698C7.63274 8.52698 8.24858 9.14281 8.24858 9.90249Z" fill="#009688"/>
</g>
<defs>
<clipPath id="clip0_8076_36010">
<rect width="14" height="14" fill="white" transform="translate(0 14) rotate(-90)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="m9,8v3.14c0,.47-.45.86-1,.86h0c-.55,0-1-.38-1-.86v-3.14h2Z" style="fill:#009688;"/><circle cx="8" cy="6" r="1" style="fill:#009688;"/><polyline points="4.5 2.5 2.5 2.5 2.5 13.5 4.5 13.5" style="fill:none; stroke:#808080; stroke-linecap:round; stroke-miterlimit:10;"/><polyline points="11.5 13.5 13.5 13.5 13.5 2.5 11.5 2.5" style="fill:none; stroke:#808080; stroke-linecap:round; stroke-miterlimit:10;"/></svg>

After

Width:  |  Height:  |  Size: 524 B

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><polyline points="4.5 2.5 2.5 2.5 2.5 13.5 4.5 13.5" style="fill:none; stroke:808080; stroke-linecap:round; stroke-miterlimit:10;"/><polyline points="11.5 13.5 13.5 13.5 13.5 2.5 11.5 2.5" style="fill:none; stroke:808080; stroke-linecap:round; stroke-miterlimit:10;"/><path d="m9,8v3.14c0,.47-.45.86-1,.86h0c-.55,0-1-.38-1-.86v-3.14h2Z" style="fill:#808080;"/><circle cx="8" cy="6" r="1" style="fill:#808080;"/></svg>

After

Width:  |  Height:  |  Size: 522 B

View file

@ -5329,6 +5329,7 @@ bool CLI::setup(int argc, char **argv)
set_var_dir((path_resources / "images").string());
set_local_dir((path_resources / "i18n").string());
set_sys_shapes_dir((path_resources / "shapes").string());
set_custom_gcodes_dir((path_resources / "custom_gcodes").string());
// Parse all command line options into a DynamicConfig.
// If any option is unsupported, print usage and abort immediately.

View file

@ -13,6 +13,10 @@ include(PrecompiledHeader)
string(TIMESTAMP COMPILE_TIME %Y%m%d-%H%M%S)
set(SLIC3R_BUILD_TIME ${COMPILE_TIME})
if(NOT DEFINED ORCA_CHECK_GCODE_PLACEHOLDERS)
set(ORCA_CHECK_GCODE_PLACEHOLDERS "0")
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY)
if (MINGW)

View file

@ -1786,6 +1786,8 @@ public:
// Create a default option to be inserted into a DynamicConfig.
ConfigOption* create_default_option() const;
bool is_scalar() const { return (int(this->type) & int(coVectorType)) == 0; }
template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
if (this->nullable) {
switch (this->type) {
@ -1973,6 +1975,7 @@ public:
out.push_back(kvp.first);
return out;
}
bool empty() { return options.empty(); }
// Iterate through all of the CLI options and write them to a stream.
std::ostream& print_cli_help(

View file

@ -1585,6 +1585,21 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
check_placeholder_parser_failed();
#if ORCA_CHECK_GCODE_PLACEHOLDERS
if (!m_placeholder_error_messages.empty()){
std::ostringstream message;
message << "Some EditGcodeDialog defs were not specified properly. Do so in PrintConfig under SlicingStatesConfigDef:" << std::endl;
for (const auto& error : m_placeholder_error_messages) {
message << std::endl << error.first << ": " << std::endl;
for (const auto& str : error.second)
message << str << ", ";
message.seekp(-2, std::ios_base::end);
message << std::endl;
}
throw Slic3r::PlaceholderParserError(message.str());
}
#endif
BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info();
// Post-process the G-code to update time stamps.
@ -2936,6 +2951,42 @@ void GCode::process_layers(
std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
{
// Orca: Added CMake config option since debug is rarely used in current workflow.
// Also changed from throwing error immediately to storing messages till slicing is completed
// to raise all errors at the same time.
#if !defined(NDEBUG) || ORCA_CHECK_GCODE_PLACEHOLDERS // CHECK_CUSTOM_GCODE_PLACEHOLDERS
if (config_override) {
const auto& custom_gcode_placeholders = custom_gcode_specific_placeholders();
// 1-st check: custom G-code "name" have to be present in s_CustomGcodeSpecificOptions;
//if (custom_gcode_placeholders.count(name) > 0) {
// const auto& placeholders = custom_gcode_placeholders.at(name);
if (auto it = custom_gcode_placeholders.find(name); it != custom_gcode_placeholders.end()) {
const auto& placeholders = it->second;
for (const std::string& key : config_override->keys()) {
// 2-nd check: "key" have to be present in s_CustomGcodeSpecificOptions for "name" custom G-code ;
if (std::find(placeholders.begin(), placeholders.end(), key) == placeholders.end()) {
auto& vector = m_placeholder_error_messages[name + " - option not specified for custom gcode type (s_CustomGcodeSpecificOptions)"];
if (std::find(vector.begin(), vector.end(), key) == vector.end())
vector.emplace_back(key);
}
// 3-rd check: "key" have to be present in CustomGcodeSpecificConfigDef for "key" placeholder;
if (!custom_gcode_specific_config_def.has(key)) {
auto& vector = m_placeholder_error_messages[name + " - option has no definition (CustomGcodeSpecificConfigDef)"];
if (std::find(vector.begin(), vector.end(), key) == vector.end())
vector.emplace_back(key);
}
}
}
else {
auto& vector = m_placeholder_error_messages[name + " - gcode type not found in s_CustomGcodeSpecificOptions"];
if (vector.empty())
vector.emplace_back("");
}
}
#endif
PlaceholderParserIntegration &ppi = m_placeholder_parser_integration;
try {
ppi.update_from_gcodewriter(m_writer);
@ -3528,7 +3579,7 @@ LayerResult GCode::process_layer(
m_last_height = height;
// Set new layer - this will change Z and force a retraction if retract_when_changing_layer is enabled.
if (! print.config().before_layer_change_gcode.value.empty()) {
if (! m_config.before_layer_change_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
@ -3551,7 +3602,7 @@ LayerResult GCode::process_layer(
auto insert_timelapse_gcode = [this, print_z, &print]() -> std::string {
std::string gcode_res;
if (!print.config().time_lapse_gcode.value.empty()) {
if (!m_config.time_lapse_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
@ -3579,7 +3630,7 @@ LayerResult GCode::process_layer(
}
}
} else {
if (!print.config().time_lapse_gcode.value.empty()) {
if (!m_config.time_lapse_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
@ -3589,7 +3640,7 @@ LayerResult GCode::process_layer(
"\n";
}
}
if (! print.config().layer_change_gcode.value.empty()) {
if (! m_config.layer_change_gcode.value.empty()) {
DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
@ -4287,6 +4338,33 @@ void GCode::apply_print_config(const PrintConfig &print_config)
m_writer.apply_print_config(print_config);
m_config.apply(print_config);
m_scaled_resolution = scaled<double>(print_config.resolution.value);
#if ORCA_CHECK_GCODE_PLACEHOLDERS
// If the gcode value is empty, set a value so that the check code within the parser is run
for (auto opt : std::initializer_list<ConfigOptionString*>{
&m_config.machine_start_gcode,
&m_config.machine_end_gcode,
&m_config.before_layer_change_gcode,
&m_config.layer_change_gcode,
&m_config.time_lapse_gcode,
&m_config.change_filament_gcode,
&m_config.change_extrusion_role_gcode,
&m_config.printing_by_object_gcode,
&m_config.machine_pause_gcode,
&m_config.template_custom_gcode,
}) {
if (opt->empty())
opt->set(new ConfigOptionString(";VALUE FOR TESTING"));
}
for (auto opt : std::initializer_list<ConfigOptionStrings*>{
&m_config.filament_start_gcode,
&m_config.filament_end_gcode
}) {
if (opt->empty())
for (int i = 0; i < opt->size(); ++i)
opt->set_at(new ConfigOptionString(";VALUE FOR TESTING"), i, 0);
}
#endif
}
void GCode::append_full_config(const Print &print, std::string &str)

View file

@ -158,6 +158,7 @@ struct LayerResult {
};
class GCode {
public:
GCode() :
m_origin(Vec2d::Zero()),
@ -522,6 +523,10 @@ private:
double m_last_mm3_per_mm;
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
#if ORCA_CHECK_GCODE_PLACEHOLDERS
std::map<std::string, std::vector<std::string>> m_placeholder_error_messages;
#endif
Point m_last_pos;
bool m_last_pos_defined;

View file

@ -2041,6 +2041,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
const Vec3d origin = this->get_plate_origin();
gcode.set_gcode_offset(origin(0), origin(1));
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
//BBS
result->conflict_result = m_conflict_result;
return path.c_str();

View file

@ -6614,6 +6614,386 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::
}
}
// SlicingStatesConfigDefs
// Create a new config definition with a label and tooltip
// Note: the L() macro is already used for LABEL and TOOLTIP
#define new_def(OPT_KEY, TYPE, LABEL, TOOLTIP) \
def = this->add(OPT_KEY, TYPE); \
def->label = L(LABEL); \
def->tooltip = L(TOOLTIP);
ReadOnlySlicingStatesConfigDef::ReadOnlySlicingStatesConfigDef()
{
ConfigOptionDef* def;
def = this->add("zhop", coFloat);
def->label = L("Current z-hop");
def->tooltip = L("Contains z-hop present at the beginning of the custom G-code block.");
}
ReadWriteSlicingStatesConfigDef::ReadWriteSlicingStatesConfigDef()
{
ConfigOptionDef* def;
def = this->add("position", coFloats);
def->label = L("Position");
def->tooltip = L("Position of the extruder at the beginning of the custom G-code block. If the custom G-code travels somewhere else, "
"it should write to this variable so PrusaSlicer knows where it travels from when it gets control back.");
def = this->add("e_retracted", coFloats);
def->label = L("Retraction");
def->tooltip = L("Retraction state at the beginning of the custom G-code block. If the custom G-code moves the extruder axis, "
"it should write to this variable so PrusaSlicer deretracts correctly when it gets control back.");
def = this->add("e_restart_extra", coFloats);
def->label = L("Extra deretraction");
def->tooltip = L("Currently planned extra extruder priming after deretraction.");
// Options from PS not used in Orca
// def = this->add("e_position", coFloats);
// def->label = L("Absolute E position");
// def->tooltip = L("Current position of the extruder axis. Only used with absolute extruder addressing.");
}
OtherSlicingStatesConfigDef::OtherSlicingStatesConfigDef()
{
ConfigOptionDef* def;
def = this->add("current_extruder", coInt);
def->label = L("Current extruder");
def->tooltip = L("Zero-based index of currently used extruder.");
def = this->add("current_object_idx", coInt);
def->label = L("Current object index");
def->tooltip = L("Specific for sequential printing. Zero-based index of currently printed object.");
def = this->add("has_wipe_tower", coBool);
def->label = L("Has wipe tower");
def->tooltip = L("Whether or not wipe tower is being generated in the print.");
def = this->add("initial_extruder", coInt);
def->label = L("Initial extruder");
def->tooltip = L("Zero-based index of the first extruder used in the print. Same as initial_tool.");
def = this->add("initial_tool", coInt);
def->label = L("Initial tool");
def->tooltip = L("Zero-based index of the first extruder used in the print. Same as initial_extruder.");
def = this->add("is_extruder_used", coBools);
def->label = L("Is extruder used?");
def->tooltip = L("Vector of bools stating whether a given extruder is used in the print.");
// Options from PS not used in Orca
// def = this->add("initial_filament_type", coString);
// def->label = L("Initial filament type");
// def->tooltip = L("String containing filament type of the first used extruder.");
// def = this->add("has_single_extruder_multi_material_priming", coBool);
// def->label = L("Has single extruder MM priming");
// def->tooltip = L("Are the extra multi-material priming regions used in this print?");
new_def("initial_no_support_extruder", coInt, "Initial no support extruder", "Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool.");
new_def("in_head_wrap_detect_zone", coBool, "In head wrap detect zone", "Indicates if the first layer overlaps with the head wrap zone.");
}
PrintStatisticsConfigDef::PrintStatisticsConfigDef()
{
ConfigOptionDef* def;
def = this->add("extruded_volume", coFloats);
def->label = L("Volume per extruder");
def->tooltip = L("Total filament volume extruded per extruder during the entire print.");
def = this->add("total_toolchanges", coInt);
def->label = L("Total toolchanges");
def->tooltip = L("Number of toolchanges during the print.");
def = this->add("extruded_volume_total", coFloat);
def->label = L("Total volume");
def->tooltip = L("Total volume of filament used during the entire print.");
def = this->add("extruded_weight", coFloats);
def->label = L("Weight per extruder");
def->tooltip = L("Weight per extruder extruded during the entire print. Calculated from filament_density value in Filament Settings.");
def = this->add("extruded_weight_total", coFloat);
def->label = L("Total weight");
def->tooltip = L("Total weight of the print. Calculated from filament_density value in Filament Settings.");
def = this->add("total_layer_count", coInt);
def->label = L("Total layer count");
def->tooltip = L("Number of layers in the entire print.");
// Options from PS not used in Orca
/* def = this->add("normal_print_time", coString);
def->label = L("Print time (normal mode)");
def->tooltip = L("Estimated print time when printed in normal mode (i.e. not in silent mode). Same as print_time.");
def = this->add("num_printing_extruders", coInt);
def->label = L("Number of printing extruders");
def->tooltip = L("Number of extruders used during the print.");
def = this->add("print_time", coString);
def->label = L("Print time (normal mode)");
def->tooltip = L("Estimated print time when printed in normal mode (i.e. not in silent mode). Same as normal_print_time.");
def = this->add("printing_filament_types", coString);
def->label = L("Used filament types");
def->tooltip = L("Comma-separated list of all filament types used during the print.");
def = this->add("silent_print_time", coString);
def->label = L("Print time (silent mode)");
def->tooltip = L("Estimated print time when printed in silent mode.");
def = this->add("total_cost", coFloat);
def->label = L("Total cost");
def->tooltip = L("Total cost of all material used in the print. Calculated from filament_cost value in Filament Settings.");
def = this->add("total_weight", coFloat);
def->label = L("Total weight");
def->tooltip = L("Total weight of the print. Calculated from filament_density value in Filament Settings.");
def = this->add("total_wipe_tower_cost", coFloat);
def->label = L("Total wipe tower cost");
def->tooltip = L("Total cost of the material wasted on the wipe tower. Calculated from filament_cost value in Filament Settings.");
def = this->add("total_wipe_tower_filament", coFloat);
def->label = L("Wipe tower volume");
def->tooltip = L("Total filament volume extruded on the wipe tower.");
def = this->add("used_filament", coFloat);
def->label = L("Used filament");
def->tooltip = L("Total length of filament used in the print.");*/
}
ObjectsInfoConfigDef::ObjectsInfoConfigDef()
{
ConfigOptionDef* def;
def = this->add("num_objects", coInt);
def->label = L("Number of objects");
def->tooltip = L("Total number of objects in the print.");
def = this->add("num_instances", coInt);
def->label = L("Number of instances");
def->tooltip = L("Total number of object instances in the print, summed over all objects.");
def = this->add("scale", coStrings);
def->label = L("Scale per object");
def->tooltip = L("Contains a string with the information about what scaling was applied to the individual objects. "
"Indexing of the objects is zero-based (first object has index 0).\n"
"Example: 'x:100% y:50% z:100'.");
def = this->add("input_filename_base", coString);
def->label = L("Input filename without extension");
def->tooltip = L("Source filename of the first object, without extension.");
new_def("input_filename", coString, "Full input filename", "Source filename of the first object.");
new_def("plate_name", coString, "Plate name", "Name of the plate sliced.");
}
DimensionsConfigDef::DimensionsConfigDef()
{
ConfigOptionDef* def;
const std::string point_tooltip = L("The vector has two elements: x and y coordinate of the point. Values in mm.");
const std::string bb_size_tooltip = L("The vector has two elements: x and y dimension of the bounding box. Values in mm.");
def = this->add("first_layer_print_convex_hull", coPoints);
def->label = L("First layer convex hull");
def->tooltip = L("Vector of points of the first layer convex hull. Each element has the following format:"
"'[x, y]' (x and y are floating-point numbers in mm).");
def = this->add("first_layer_print_min", coFloats);
def->label = L("Bottom-left corner of first layer bounding box");
def->tooltip = point_tooltip;
def = this->add("first_layer_print_max", coFloats);
def->label = L("Top-right corner of first layer bounding box");
def->tooltip = point_tooltip;
def = this->add("first_layer_print_size", coFloats);
def->label = L("Size of the first layer bounding box");
def->tooltip = bb_size_tooltip;
def = this->add("print_bed_min", coFloats);
def->label = L("Bottom-left corner of print bed bounding box");
def->tooltip = point_tooltip;
def = this->add("print_bed_max", coFloats);
def->label = L("Top-right corner of print bed bounding box");
def->tooltip = point_tooltip;
def = this->add("print_bed_size", coFloats);
def->label = L("Size of the print bed bounding box");
def->tooltip = bb_size_tooltip;
new_def("first_layer_center_no_wipe_tower", coFloats, "First layer center without wipe tower", point_tooltip);
new_def("first_layer_height", coFloat, "First layer height", "Height of the first layer.");
}
TemperaturesConfigDef::TemperaturesConfigDef()
{
ConfigOptionDef* def;
new_def("bed_temperature", coInts, "Bed temperature", "Vector of bed temperatures for each extruder/filament.")
new_def("bed_temperature_initial_layer", coInts, "Initial layer bed temperature", "Vector of initial layer bed temperatures for each extruder/filament. Provides the same value as first_layer_bed_temperature.")
new_def("bed_temperature_initial_layer_single", coInt, "Initial layer bed temperature (initial extruder)", "Initial layer bed temperature for the initial extruder. Same as bed_temperature_initial_layer[initial_extruder]")
new_def("chamber_temperature", coInts, "Chamber temperature", "Vector of chamber temperatures for each extruder/filament.")
new_def("overall_chamber_temperature", coInt, "Overall chamber temperature", "Overall chamber temperature. This value is the maximum chamber temperature of any extruder/filament used.")
new_def("first_layer_bed_temperature", coInts, "First layer bed temperature", "Vector of first layer bed temperatures for each extruder/filament. Provides the same value as bed_temperature_initial_layer.")
new_def("first_layer_temperature", coInts, "First layer temperature", "Vector of first layer temperatures for each extruder/filament.")
}
TimestampsConfigDef::TimestampsConfigDef()
{
ConfigOptionDef* def;
def = this->add("timestamp", coString);
def->label = L("Timestamp");
def->tooltip = L("String containing current time in yyyyMMdd-hhmmss format.");
def = this->add("year", coInt);
def->label = L("Year");
def = this->add("month", coInt);
def->label = L("Month");
def = this->add("day", coInt);
def->label = L("Day");
def = this->add("hour", coInt);
def->label = L("Hour");
def = this->add("minute", coInt);
def->label = L("Minute");
def = this->add("second", coInt);
def->label = L("Second");
}
OtherPresetsConfigDef::OtherPresetsConfigDef()
{
ConfigOptionDef* def;
def = this->add("print_preset", coString);
def->label = L("Print preset name");
def->tooltip = L("Name of the print preset used for slicing.");
def = this->add("filament_preset", coString);
def->label = L("Filament preset name");
def->tooltip = L("Names of the filament presets used for slicing. The variable is a vector "
"containing one name for each extruder.");
def = this->add("printer_preset", coString);
def->label = L("Printer preset name");
def->tooltip = L("Name of the printer preset used for slicing.");
def = this->add("physical_printer_preset", coString);
def->label = L("Physical printer name");
def->tooltip = L("Name of the physical printer used for slicing.");
// Options from PS not used in Orca
// def = this->add("num_extruders", coInt);
// def->label = L("Number of extruders");
// def->tooltip = L("Total number of extruders, regardless of whether they are used in the current print.");
}
static std::map<t_custom_gcode_key, t_config_option_keys> s_CustomGcodeSpecificPlaceholders{
// Machine Gcode
{"machine_start_gcode", {}},
{"machine_end_gcode", {"layer_num", "layer_z", "max_layer_z", "filament_extruder_id"}},
{"before_layer_change_gcode", {"layer_num", "layer_z", "max_layer_z"}},
{"layer_change_gcode", {"layer_num", "layer_z", "max_layer_z"}},
{"timelapse_gcode", {"layer_num", "layer_z", "max_layer_z"}},
{"change_filament_gcode", {"layer_num", "layer_z", "max_layer_z", "next_extruder", "previous_extruder", "fan_speed",
"first_flush_volume", "flush_length_1", "flush_length_2", "flush_length_3", "flush_length_4",
"new_filament_e_feedrate", "new_filament_temp", "new_retract_length",
"new_retract_length_toolchange", "old_filament_e_feedrate", "old_filament_temp", "old_retract_length",
"old_retract_length_toolchange", "relative_e_axis", "second_flush_volume", "toolchange_count", "toolchange_z",
"travel_point_1_x", "travel_point_1_y", "travel_point_2_x", "travel_point_2_y", "travel_point_3_x",
"travel_point_3_y", "x_after_toolchange", "y_after_toolchange", "z_after_toolchange"}},
{"change_extrusion_role_gcode", {"layer_num", "layer_z", "extrusion_role", "last_extrusion_role"}},
{"printing_by_object_gcode", {}},
{"machine_pause_gcode", {}},
{"template_custom_gcode", {}},
//Filament Gcode
{"filament_start_gcode", {"filament_extruder_id"}},
{"filament_end_gcode", {"layer_num", "layer_z", "max_layer_z", "filament_extruder_id"}},
};
const std::map<t_custom_gcode_key, t_config_option_keys>& custom_gcode_specific_placeholders()
{
return s_CustomGcodeSpecificPlaceholders;
}
CustomGcodeSpecificConfigDef::CustomGcodeSpecificConfigDef()
{
ConfigOptionDef* def;
// Common Defs
def = this->add("layer_num", coInt);
def->label = L("Layer number");
def->tooltip = L("Index of the current layer. One-based (i.e. first layer is number 1).");
def = this->add("layer_z", coFloat);
def->label = L("Layer z");
def->tooltip = L("Height of the current layer above the print bed, measured to the top of the layer.");
def = this->add("max_layer_z", coFloat);
def->label = L("Maximal layer z");
def->tooltip = L("Height of the last layer above the print bed.");
def = this->add("filament_extruder_id", coInt);
def->label = L("Filament extruder ID");
def->tooltip = L("The current extruder ID. The same as current_extruder.");
// change_filament_gcode
new_def("previous_extruder", coInt, "Previous extruder", "Index of the extruder that is being unloaded. The index is zero based (first extruder has index 0).");
new_def("next_extruder", coInt, "Next extruder", "Index of the extruder that is being loaded. The index is zero based (first extruder has index 0).");
new_def("relative_e_axis", coBool, "Relative e-axis", "Indicates if relative positioning is being used");
new_def("toolchange_count", coInt, "Toolchange count", "The number of toolchanges throught the print");
new_def("fan_speed", coNone, "", ""); //Option is no longer used and is zeroed by placeholder parser for compatability
new_def("old_retract_length", coFloat, "Old retract length", "The retraction length of the previous filament");
new_def("new_retract_length", coFloat, "New retract length", "The retraction lenght of the new filament");
new_def("old_retract_length_toolchange", coFloat, "Old retract length toolchange", "The toolchange retraction length of the previous filament");
new_def("new_retract_length_toolchange", coFloat, "New retract length toolchange", "The toolchange retraction length of the new filament");
new_def("old_filament_temp", coInt, "Old filament temp", "The old filament temp");
new_def("new_filament_temp", coInt, "New filament temp", "The new filament temp");
new_def("x_after_toolchange", coFloat, "X after toolchange", "The x pos after toolchange");
new_def("y_after_toolchange", coFloat, "Y after toolchange", "The y pos after toolchange");
new_def("z_after_toolchange", coFloat, "Z after toolchange", "The z pos after toolchange");
new_def("first_flush_volume", coFloat, "First flush volume", "The first flush volume");
new_def("second_flush_volume", coFloat, "Second flush volume", "The second flush volume");
new_def("old_filament_e_feedrate", coInt, "Old filament e feedrate", "The old filament extruder feedrate");
new_def("new_filament_e_feedrate", coInt, "New filament e feedrate", "The new filament extruder feedrate");
new_def("travel_point_1_x", coFloat, "Travel point 1 x", "The travel point 1 x");
new_def("travel_point_1_y", coFloat, "Travel point 1 y", "The travel point 1 y");
new_def("travel_point_2_x", coFloat, "Travel point 2 x", "The travel point 2 x");
new_def("travel_point_2_y", coFloat, "Travel point 2 y", "The travel point 2 y");
new_def("travel_point_3_x", coFloat, "Travel point 3 x", "The travel point 3 x");
new_def("travel_point_3_y", coFloat, "Travel point 3 y", "The travel point 3 y");
new_def("flush_length_1", coFloat, "Flush Length 1", "The first flush length");
new_def("flush_length_2", coFloat, "Flush Length 2", "The second flush length");
new_def("flush_length_3", coFloat, "Flush Length 3", "The third flush length");
new_def("flush_length_4", coFloat, "Flush Length 4", "The fourth flush length");
// change_extrusion_role_gcode
std::string extrusion_role_types = "Possible Values:\n[\"Perimeter\", \"ExternalPerimeter\", "
"\"OverhangPerimeter\", \"InternalInfill\", \"SolidInfill\", \"TopSolidInfill\", \"BottomSurface\", \"BridgeInfill\", \"GapFill\", \"Ironing\", "
"\"Skirt\", \"Brim\", \"SupportMaterial\", \"SupportMaterialInterface\", \"SupportTransition\", \"WipeTower\", \"Mixed\"]";
new_def("extrusion_role", coString, "Extrusion role", "The new extrusion role/type that is going to be used\n" + extrusion_role_types);
new_def("last_extrusion_role", coString, "Last extrusion role", "The previously used extrusion role/type\nPossible Values:\n" + extrusion_role_types);
}
const CustomGcodeSpecificConfigDef custom_gcode_specific_config_def;
#undef new_def
uint64_t ModelConfig::s_last_timestamp = 1;
static Points to_points(const std::vector<Vec2d> &dpts)

View file

@ -1464,6 +1464,74 @@ public:
CLIMiscConfigDef();
};
typedef std::string t_custom_gcode_key;
// This map containes list of specific placeholders for each custom G-code, if any exist
const std::map<t_custom_gcode_key, t_config_option_keys>& custom_gcode_specific_placeholders();
// Next classes define placeholders used by GUI::EditGCodeDialog.
class ReadOnlySlicingStatesConfigDef : public ConfigDef
{
public:
ReadOnlySlicingStatesConfigDef();
};
class ReadWriteSlicingStatesConfigDef : public ConfigDef
{
public:
ReadWriteSlicingStatesConfigDef();
};
class OtherSlicingStatesConfigDef : public ConfigDef
{
public:
OtherSlicingStatesConfigDef();
};
class PrintStatisticsConfigDef : public ConfigDef
{
public:
PrintStatisticsConfigDef();
};
class ObjectsInfoConfigDef : public ConfigDef
{
public:
ObjectsInfoConfigDef();
};
class DimensionsConfigDef : public ConfigDef
{
public:
DimensionsConfigDef();
};
class TemperaturesConfigDef : public ConfigDef
{
public:
TemperaturesConfigDef();
};
class TimestampsConfigDef : public ConfigDef
{
public:
TimestampsConfigDef();
};
class OtherPresetsConfigDef : public ConfigDef
{
public:
OtherPresetsConfigDef();
};
// This classes defines all custom G-code specific placeholders.
class CustomGcodeSpecificConfigDef : public ConfigDef
{
public:
CustomGcodeSpecificConfigDef();
};
extern const CustomGcodeSpecificConfigDef custom_gcode_specific_config_def;
// This class defines the command line options representing actions.
extern const CLIActionsConfigDef cli_actions_config_def;

View file

@ -144,6 +144,11 @@ const std::string& sys_shapes_dir();
// Return a full path to the custom shapes gallery directory.
std::string custom_shapes_dir();
// Set a path with shapes gallery files.
void set_custom_gcodes_dir(const std::string &path);
// Return a full path to the system shapes gallery directory.
const std::string& custom_gcodes_dir();
// Set a path with preset files.
void set_data_dir(const std::string &path);
// Return a full path to the GUI resource files.

View file

@ -10,5 +10,6 @@
//#define SLIC3R_RC_VERSION "@SLIC3R_VERSION@"
#define BBL_RELEASE_TO_PUBLIC @BBL_RELEASE_TO_PUBLIC@
#define BBL_INTERNAL_TESTING @BBL_INTERNAL_TESTING@
#define ORCA_CHECK_GCODE_PLACEHOLDERS @ORCA_CHECK_GCODE_PLACEHOLDERS@
#endif /* __SLIC3R_VERSION_H */

View file

@ -265,6 +265,18 @@ const std::string& sys_shapes_dir()
return g_sys_shapes_dir;
}
static std::string g_custom_gcodes_dir;
void set_custom_gcodes_dir(const std::string &dir)
{
g_custom_gcodes_dir = dir;
}
const std::string& custom_gcodes_dir()
{
return g_custom_gcodes_dir;
}
// Translate function callback, to call wxWidgets translate function to convert non-localized UTF8 string to a localized one.
Slic3r::I18N::translate_fn_type Slic3r::I18N::translate_fn = nullptr;
static std::string g_data_dir;

View file

@ -212,6 +212,8 @@ set(SLIC3R_GUI_SOURCES
GUI/PresetComboBoxes.cpp
GUI/BitmapComboBox.hpp
GUI/BitmapComboBox.cpp
GUI/EditGCodeDialog.hpp
GUI/EditGCodeDialog.cpp
GUI/SavePresetDialog.hpp
GUI/SavePresetDialog.cpp
GUI/GUI_Colors.hpp

View file

@ -0,0 +1,976 @@
#include "EditGCodeDialog.hpp"
#include <vector>
#include <string>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/button.h>
#include <wx/wupdlock.h>
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "Tab.hpp"
#include "wxExtensions.hpp"
#include "BitmapCache.hpp"
#include "ExtraRenderers.hpp"
#include "MsgDialog.hpp"
#include "Plater.hpp"
#include "libslic3r/PlaceholderParser.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/Print.hpp"
#define BTN_GAP FromDIP(20)
#define BTN_SIZE wxSize(FromDIP(58), FromDIP(24))
namespace Slic3r {
namespace GUI {
//------------------------------------------
// EditGCodeDialog
//------------------------------------------
EditGCodeDialog::EditGCodeDialog(wxWindow* parent, const std::string& key, const std::string& value) :
DPIDialog(parent, wxID_ANY, format_wxstr(_L("Edit Custom G-code (%1%)"), key), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
{
SetFont(wxGetApp().normal_font());
SetBackgroundColour(*wxWHITE);
wxGetApp().UpdateDarkUI(this);
wxGetApp().UpdateDlgDarkUI(this);
int border = 10;
int em = em_unit();
wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Built-in placeholders (Double click item to add to G-code)") + ":");
auto* grid_sizer = new wxFlexGridSizer(1, 3, 5, 15);
grid_sizer->SetFlexibleDirection(wxBOTH);
auto* param_sizer = new wxBoxSizer(wxVERTICAL);
m_search_bar = new wxSearchCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
m_search_bar->ShowSearchButton(true);
m_search_bar->ShowCancelButton(true);
m_search_bar->SetDescriptiveText(_L("Search gcode placeholders"));
m_search_bar->SetForegroundColour(*wxBLACK);
wxGetApp().UpdateDarkUI(m_search_bar);
m_search_bar->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent&) {
// this->on_search_update();
});
m_search_bar->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this](wxCommandEvent&) {
this->on_search_update();
});
param_sizer->Add(m_search_bar, 0, wxEXPAND | wxALL, border);
m_params_list = new ParamsViewCtrl(this, wxSize(em * 45, em * 70));
m_params_list->SetFont(wxGetApp().code_font());
wxGetApp().UpdateDarkUI(m_params_list);
param_sizer->Add(m_params_list, 0, wxEXPAND | wxALL, border);
m_add_btn = new ScalableButton(this, wxID_ANY, "add_copies");
m_add_btn->SetToolTip(_L("Add selected placeholder to G-code"));
m_gcode_editor = new wxTextCtrl(this, wxID_ANY, value, wxDefaultPosition, wxSize(em * 75, em * 70), wxTE_MULTILINE
#ifdef _WIN32
| wxBORDER_SIMPLE
#endif
);
m_gcode_editor->SetFont(wxGetApp().code_font());
m_gcode_editor->SetInsertionPointEnd();
wxGetApp().UpdateDarkUI(m_gcode_editor);
grid_sizer->Add(param_sizer, 1, wxEXPAND);
grid_sizer->Add(m_add_btn, 0, wxTOP, m_params_list->GetSize().y/2);
grid_sizer->Add(m_gcode_editor, 2, wxEXPAND);
grid_sizer->AddGrowableRow(0, 1);
grid_sizer->AddGrowableCol(0, 1);
grid_sizer->AddGrowableCol(2, 1);
m_param_label = new wxStaticText(this, wxID_ANY, _L("Select placeholder"));
m_param_label->SetFont(wxGetApp().bold_font());
m_param_description = new wxStaticText(this, wxID_ANY, wxEmptyString);
//Orca: use custom buttons
auto btn_sizer = create_btn_sizer(wxOK | wxCANCEL);
for(auto btn : m_button_list)
wxGetApp().UpdateDarkUI(btn.second);
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(grid_sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(m_param_label , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(m_param_description , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(btn_sizer , 0, wxEXPAND | wxALL, border);
SetSizer(topSizer);
topSizer->SetSizeHints(this);
this->Fit();
this->Layout();
this->CenterOnScreen();
init_params_list(key);
bind_list_and_button();
}
EditGCodeDialog::~EditGCodeDialog()
{
// To avoid redundant process of wxEVT_DATAVIEW_SELECTION_CHANGED after dialog distroing (on Linux)
// unbind this event from params_list
m_params_list->Unbind(wxEVT_DATAVIEW_SELECTION_CHANGED, &EditGCodeDialog::selection_changed, this);
}
std::string EditGCodeDialog::get_edited_gcode() const
{
return into_u8(m_gcode_editor->GetValue());
}
void EditGCodeDialog::on_search_update()
{
wxString search_text = m_search_bar->GetValue().Lower();
if (search_text.empty())
m_params_list->model->FinishSearch();
else
m_params_list->model->RefreshSearch(search_text);
}
static ParamType get_type(const std::string& opt_key, const ConfigOptionDef& opt_def)
{
return opt_def.is_scalar() ? ParamType::Scalar : ParamType::Vector;
}
void EditGCodeDialog::init_params_list(const std::string& custom_gcode_name)
{
const auto& custom_gcode_placeholders = custom_gcode_specific_placeholders();
const auto& specific_params = custom_gcode_placeholders.count(custom_gcode_name) > 0 ?
custom_gcode_placeholders.at(custom_gcode_name) : t_config_option_keys({});
// Add slicing states placeholders
wxDataViewItem slicing_state = m_params_list->AppendGroup(_L("[Global] Slicing State"), "custom-gcode_slicing-state_global");
if (!cgp_ro_slicing_states_config_def.empty()) {
wxDataViewItem read_only = m_params_list->AppendSubGroup(slicing_state, _L("Read Only"), "lock_closed");
for (const auto& [opt_key, def]: cgp_ro_slicing_states_config_def.options)
m_params_list->AppendParam(read_only, get_type(opt_key, def), opt_key);
}
if (!cgp_rw_slicing_states_config_def.empty()) {
wxDataViewItem read_write = m_params_list->AppendSubGroup(slicing_state, _L("Read Write"), "lock_open");
for (const auto& [opt_key, def] : cgp_rw_slicing_states_config_def.options)
m_params_list->AppendParam(read_write, get_type(opt_key, def), opt_key);
}
// add other universal params, which are related to slicing state
if (!cgp_other_slicing_states_config_def.empty()) {
slicing_state = m_params_list->AppendGroup(_L("Slicing State"), "custom-gcode_slicing-state");
for (const auto& [opt_key, def] : cgp_other_slicing_states_config_def.options)
m_params_list->AppendParam(slicing_state, get_type(opt_key, def), opt_key);
}
// Add universal placeholders
{
// Add print statistics subgroup
if (!cgp_print_statistics_config_def.empty()) {
wxDataViewItem statistics = m_params_list->AppendGroup(_L("Print Statistics"), "custom-gcode_stats");
for (const auto& [opt_key, def] : cgp_print_statistics_config_def.options)
m_params_list->AppendParam(statistics, get_type(opt_key, def), opt_key);
}
// Add objects info subgroup
if (!cgp_objects_info_config_def.empty()) {
wxDataViewItem objects_info = m_params_list->AppendGroup(_L("Objects Info"), "custom-gcode_object-info");
for (const auto& [opt_key, def] : cgp_objects_info_config_def.options)
m_params_list->AppendParam(objects_info, get_type(opt_key, def), opt_key);
}
// Add dimensions subgroup
if (!cgp_dimensions_config_def.empty()) {
wxDataViewItem dimensions = m_params_list->AppendGroup(_L("Dimensions"), "custom-gcode_measure");
for (const auto& [opt_key, def] : cgp_dimensions_config_def.options)
m_params_list->AppendParam(dimensions, get_type(opt_key, def), opt_key);
}
// Add temperature subgroup
if (!cgp_temperatures_config_def.empty()) {
wxDataViewItem temperatures = m_params_list->AppendGroup(_L("Temperatures"), "custom-gcode_temperature");
for (const auto& [opt_key, def] : cgp_temperatures_config_def.options)
m_params_list->AppendParam(temperatures, get_type(opt_key, def), opt_key);
}
// Add timestamp subgroup
if (!cgp_timestamps_config_def.empty()) {
wxDataViewItem dimensions = m_params_list->AppendGroup(_L("Timestamps"), "print-time");
for (const auto& [opt_key, def] : cgp_timestamps_config_def.options)
m_params_list->AppendParam(dimensions, get_type(opt_key, def), opt_key);
}
}
// Add specific placeholders
if (!specific_params.empty()) {
wxDataViewItem group = m_params_list->AppendGroup(format_wxstr(_L("Specific for %1%"), custom_gcode_name), "custom-gcode_gcode");
for (const auto& opt_key : specific_params)
if (auto def = custom_gcode_specific_config_def.get(opt_key); def && def->type != coNone) {
m_params_list->AppendParam(group, get_type(opt_key, *def), opt_key);
}
m_params_list->Expand(group);
}
// Add placeholders from presets
wxDataViewItem presets = add_presets_placeholders();
// add other params which are related to presets
if (!cgp_other_presets_config_def.empty())
for (const auto& [opt_key, def] : cgp_other_presets_config_def.options)
m_params_list->AppendParam(presets, get_type(opt_key, def), opt_key);
}
wxDataViewItem EditGCodeDialog::add_presets_placeholders()
{
auto get_set_from_vec = [](const std::vector<std::string>&vec) {
return std::set(vec.begin(), vec.end());
};
const bool is_fff = wxGetApp().plater()->printer_technology() == ptFFF;
const std::set<std::string> print_options = get_set_from_vec(is_fff ? Preset::print_options() : Preset::sla_print_options());
const std::set<std::string> material_options = get_set_from_vec(is_fff ? Preset::filament_options() : Preset::sla_material_options());
const std::set<std::string> printer_options = get_set_from_vec(is_fff ? Preset::printer_options() : Preset::sla_printer_options());
const auto& full_config = wxGetApp().preset_bundle->full_config();
const auto& tab_list = wxGetApp().tabs_list;
Tab* tab_print;
Tab* tab_filament;
Tab* tab_printer;
for (const auto tab : tab_list) {
if (tab->m_type == Preset::TYPE_PRINT)
tab_print = tab;
else if (tab->m_type == Preset::TYPE_FILAMENT)
tab_filament = tab;
else if (tab->m_type == Preset::TYPE_PRINTER)
tab_printer = tab;
}
// Orca: create subgroups from the pages of the tabs
auto init_from_tab = [this, full_config](wxDataViewItem parent, Tab* tab, const set<string>& preset_keys){
set extra_keys(preset_keys);
for (const auto& page : tab->m_pages) {
wxDataViewItem subgroup = m_params_list->AppendSubGroup(parent, page->title(), "empty");
std::set<std::string> opt_keys;
for (const auto& optgroup : page->m_optgroups)
for (const auto& opt : optgroup->opt_map())
opt_keys.emplace(opt.first);
for (const auto& opt_key : opt_keys)
if (const ConfigOption* optptr = full_config.optptr(opt_key)) {
extra_keys.erase(opt_key);
m_params_list->AppendParam(subgroup, optptr->is_scalar() ? ParamType::Scalar : ParamType::Vector, opt_key);
}
}
for (auto opt_key : extra_keys)
if (const ConfigOption* optptr = full_config.optptr(opt_key))
m_params_list->AppendParam(parent, optptr->is_scalar() ? ParamType::Scalar : ParamType::Vector, opt_key);
};
wxDataViewItem group = m_params_list->AppendGroup(_L("Presets"), "cog");
wxDataViewItem print = m_params_list->AppendSubGroup(group, _L("Print settings"), "cog");
init_from_tab(print, tab_print, print_options);
wxDataViewItem material = m_params_list->AppendSubGroup(group, _(is_fff ? L("Filament settings") : L("SLA Materials settings")), is_fff ? "filament" : "resin");
init_from_tab(material, tab_filament, material_options);
wxDataViewItem printer = m_params_list->AppendSubGroup(group, _L("Printer settings"), is_fff ? "printer" : "sla_printer");
init_from_tab(printer, tab_printer, printer_options);
return group;
}
void EditGCodeDialog::add_selected_value_to_gcode()
{
const wxString val = m_params_list->GetSelectedValue();
if (val.IsEmpty())
return;
m_gcode_editor->WriteText(m_gcode_editor->GetInsertionPoint() == m_gcode_editor->GetLastPosition() ? "\n" + val : val);
if (val.Last() == ']') {
const long new_pos = m_gcode_editor->GetInsertionPoint();
if (val[val.Len() - 2] == '[')
m_gcode_editor->SetInsertionPoint(new_pos - 1); // set cursor into brackets
else
m_gcode_editor->SetSelection(new_pos - 17, new_pos - 1); // select "current_extruder"
}
m_gcode_editor->SetFocus();
}
void EditGCodeDialog::selection_changed(wxDataViewEvent& evt)
{
wxString label;
wxString description;
const std::string opt_key = m_params_list->GetSelectedParamKey();
if (!opt_key.empty()) {
const ConfigOptionDef* def { nullptr };
for (const ConfigDef* config: std::initializer_list<const ConfigDef*> {
&custom_gcode_specific_config_def,
&cgp_ro_slicing_states_config_def,
&cgp_rw_slicing_states_config_def,
&cgp_other_slicing_states_config_def,
&cgp_print_statistics_config_def,
&cgp_objects_info_config_def,
&cgp_dimensions_config_def,
&cgp_temperatures_config_def,
&cgp_timestamps_config_def,
&cgp_other_presets_config_def
}) {
if (config->has(opt_key)) {
def = config->get(opt_key);
break;
}
}
// Orca: move below checking for def in custom defined gcode placeholders
// This allows custom placeholders to override the default ones for this dialog
// Override custom def if selection is within the preset category
if (!def || m_params_list->GetSelectedTopLevelCategory() == "Presets") {
const auto& full_config = wxGetApp().preset_bundle->full_config();
if (const ConfigDef* config_def = full_config.def(); config_def && config_def->has(opt_key)) {
def = config_def->get(opt_key);
}
}
if (def) {
const ConfigOptionType scalar_type = def->is_scalar() ? def->type : static_cast<ConfigOptionType>(def->type - coVectorType);
wxString type_str = scalar_type == coNone ? "none" :
scalar_type == coFloat ? "float" :
scalar_type == coInt ? "integer" :
scalar_type == coString ? "string" :
scalar_type == coPercent ? "percent" :
scalar_type == coFloatOrPercent ? "float or percent" :
scalar_type == coPoint ? "point" :
scalar_type == coBool ? "bool" :
scalar_type == coEnum ? "enum" : "undef";
if (!def->is_scalar())
type_str += "[]";
label = (!def || (def->full_label.empty() && def->label.empty()) ) ? format_wxstr("%1%\n(%2%)", opt_key, type_str) :
(!def->full_label.empty() && !def->label.empty() ) ?
format_wxstr("%1% > %2%\n(%3%)", _(def->full_label), _(def->label), type_str) :
format_wxstr("%1%\n(%2%)", def->label.empty() ? _(def->full_label) : _(def->label), type_str);
if (def)
description = get_wraped_wxString(_(def->tooltip), 120);
}
else
label = "Undef optptr";
}
m_param_label->SetLabel(label);
m_param_description->SetLabel(description);
Layout();
}
void EditGCodeDialog::bind_list_and_button()
{
m_params_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &EditGCodeDialog::selection_changed, this);
m_params_list->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, [this](wxDataViewEvent& ) {
add_selected_value_to_gcode();
});
m_add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
add_selected_value_to_gcode();
});
}
void EditGCodeDialog::on_dpi_changed(const wxRect&suggested_rect)
{
const int& em = em_unit();
//Orca: use custom buttons
for (auto button_item : m_button_list)
{
if (button_item.first == wxOK) {
button_item.second->SetMinSize(BTN_SIZE);
button_item.second->SetCornerRadius(FromDIP(12));
}
if (button_item.first == wxCANCEL) {
button_item.second->SetMinSize(BTN_SIZE);
button_item.second->SetCornerRadius(FromDIP(12));
}
}
const wxSize& size = wxSize(45 * em, 35 * em);
SetMinSize(size);
Fit();
Refresh();
}
void EditGCodeDialog::on_sys_color_changed()
{
m_add_btn->msw_rescale();
}
//Orca
wxBoxSizer* EditGCodeDialog::create_btn_sizer(long flags)
{
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
btn_sizer->AddStretchSpacer();
StateColor ok_btn_bg(
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)
);
StateColor ok_btn_bd(
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)
);
StateColor ok_btn_text(
std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Normal)
);
StateColor cancel_btn_bg(
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal)
);
StateColor cancel_btn_bd_(
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Normal)
);
StateColor cancel_btn_text(
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Normal)
);
StateColor calc_btn_bg(
std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)
);
StateColor calc_btn_bd(
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)
);
StateColor calc_btn_text(
std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Normal)
);
if (flags & wxOK) {
Button* ok_btn = new Button(this, _L("OK"));
ok_btn->SetMinSize(BTN_SIZE);
ok_btn->SetCornerRadius(FromDIP(12));
ok_btn->SetBackgroundColor(ok_btn_bg);
ok_btn->SetBorderColor(ok_btn_bd);
ok_btn->SetTextColor(ok_btn_text);
ok_btn->SetFocus();
ok_btn->SetId(wxID_OK);
btn_sizer->Add(ok_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP);
m_button_list[wxOK] = ok_btn;
}
if (flags & wxCANCEL) {
Button* cancel_btn = new Button(this, _L("Cancel"));
cancel_btn->SetMinSize(BTN_SIZE);
cancel_btn->SetCornerRadius(FromDIP(12));
cancel_btn->SetBackgroundColor(cancel_btn_bg);
cancel_btn->SetBorderColor(cancel_btn_bd_);
cancel_btn->SetTextColor(cancel_btn_text);
cancel_btn->SetId(wxID_CANCEL);
btn_sizer->Add(cancel_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP / 2);
m_button_list[wxCANCEL] = cancel_btn;
}
return btn_sizer;
}
const std::map<ParamType, std::string> ParamsInfo {
// Type BitmapName
{ ParamType::Scalar, "custom-gcode_single" },
{ ParamType::Vector, "custom-gcode_vector" },
{ ParamType::FilamentVector,"custom-gcode_vector-index" },
};
static void make_bold(wxString& str)
{
#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)
str = format_wxstr("<b>%1%</b>", str);
#endif
}
static void highlight(wxString& str)
{
#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)
str = format_wxstr("<span bgcolor=\"#009688\">%1%</span>", str);
#endif
}
// ----------------------------------------------------------------------------
// ParamsModelNode: a node inside ParamsModel
// ----------------------------------------------------------------------------
ParamsNode::ParamsNode(const wxString& group_name, const std::string& icon_name, wxDataViewCtrl* ctrl)
: icon_name(icon_name)
, text(group_name)
, m_ctrl(ctrl)
, m_bold(true)
{
}
ParamsNode::ParamsNode( ParamsNode * parent,
const wxString& sub_group_name,
const std::string& icon_name,
wxDataViewCtrl* ctrl)
: m_parent(parent)
, icon_name(icon_name)
, text(sub_group_name)
, m_ctrl(ctrl)
, m_bold(true)
{
}
ParamsNode::ParamsNode( ParamsNode* parent,
ParamType param_type,
const std::string& param_key,
wxDataViewCtrl* ctrl)
: m_parent(parent)
, m_param_type(param_type)
, m_container(false)
, param_key(param_key)
, m_ctrl(ctrl)
{
text = from_u8(param_key);
if (param_type == ParamType::Vector)
text += "[]";
else if (param_type == ParamType::FilamentVector)
text += "[current_extruder]";
icon_name = ParamsInfo.at(param_type);
}
wxString ParamsNode::GetFormattedText()
{
wxString formatted_text(text);
if (m_highlight_index) {
wxString substr = formatted_text.substr(m_highlight_index->first, m_highlight_index->second);
formatted_text = formatted_text.Remove(m_highlight_index->first, m_highlight_index->second);
highlight(substr);
formatted_text.insert(m_highlight_index->first, substr);
}
if (m_bold)
make_bold(formatted_text);
return formatted_text;
}
void ParamsNode::StartSearch()
{
const wxDataViewItem item(this);
m_expanded_before_search = m_ctrl->IsExpanded(item);
if (!GetChildren().empty())
for (const auto& child : GetChildren())
child->StartSearch();
}
void ParamsNode::RefreshSearch(const wxString& search_text)
{
if (!GetChildren().empty())
for (auto& child : GetChildren())
child->RefreshSearch(search_text);
if (GetEnabledChildren().empty())
if (auto pos = text.find(search_text); IsParamNode() && pos != wxString::npos) {
m_highlight_index = make_unique<pair<int, int>>(pos, search_text.Len());
Enable();
} else {
Disable();
}
else
Enable();
}
void ParamsNode::FinishSearch()
{
Enable();
m_highlight_index.reset();
const wxDataViewItem item(this);
if (!GetChildren().empty())
for (const auto& child : GetChildren())
child->FinishSearch();
m_expanded_before_search ? m_ctrl->Expand(item) : m_ctrl->Collapse(item);
}
wxDataViewItemArray ParamsNode::GetEnabledChildren() {
wxDataViewItemArray array;
for (const std::unique_ptr<ParamsNode>& child : m_children)
if (child->IsEnabled())
array.Add(wxDataViewItem(child.get()));
return array;
}
// ----------------------------------------------------------------------------
// ParamsModel
// ----------------------------------------------------------------------------
ParamsModel::ParamsModel()
{
}
wxDataViewItem ParamsModel::AppendGroup(const wxString& group_name,
const std::string& icon_name)
{
m_group_nodes.emplace_back(std::make_unique<ParamsNode>(group_name, icon_name, m_ctrl));
wxDataViewItem parent(nullptr);
wxDataViewItem child((void*)m_group_nodes.back().get());
ItemAdded(parent, child);
m_ctrl->Expand(parent);
return child;
}
wxDataViewItem ParamsModel::AppendSubGroup(wxDataViewItem parent,
const wxString& sub_group_name,
const std::string& icon_name)
{
ParamsNode* parent_node = static_cast<ParamsNode*>(parent.GetID());
if (!parent_node)
return wxDataViewItem(0);
parent_node->Append(std::make_unique<ParamsNode>(parent_node, sub_group_name, icon_name, m_ctrl));
const wxDataViewItem sub_group_item((void*)parent_node->GetChildren().back().get());
ItemAdded(parent, sub_group_item);
return sub_group_item;
}
wxDataViewItem ParamsModel::AppendParam(wxDataViewItem parent,
ParamType param_type,
const std::string& param_key)
{
ParamsNode* parent_node = static_cast<ParamsNode*>(parent.GetID());
if (!parent_node)
return wxDataViewItem(0);
parent_node->Append(std::make_unique<ParamsNode>(parent_node, param_type, param_key, m_ctrl));
const wxDataViewItem child_item((void*)parent_node->GetChildren().back().get());
ItemAdded(parent, child_item);
return child_item;
}
wxString ParamsModel::GetParamName(wxDataViewItem item)
{
if (item.IsOk()) {
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (node->IsParamNode())
return node->text;
}
return wxEmptyString;
}
std::string ParamsModel::GetParamKey(wxDataViewItem item)
{
if (item.IsOk()) {
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
return node->param_key;
}
return std::string();
}
std::string ParamsModel::GetTopLevelCategory(wxDataViewItem item)
{
if (item.IsOk()) {
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
while (!node->IsGroupNode())
node = node->GetParent();
return node->text.ToStdString();
}
return std::string();
}
void ParamsModel::RefreshSearch(const wxString& search_text)
{
if (!m_currently_searching) { // if not currently searching, save expansion state for all items
for (const auto& node : m_group_nodes)
node->StartSearch();
m_currently_searching = true;
}
for (const auto& node : m_group_nodes)
node->RefreshSearch(search_text); //Enable/Disable node based on search
Cleared(); //Reload the model into the control
for (const auto& node : m_group_nodes) // (re)expand all
m_ctrl->ExpandChildren(wxDataViewItem(node.get()));
}
void ParamsModel::FinishSearch()
{
RefreshSearch("");
Cleared();
if (m_currently_searching) {
for (const auto& node : m_group_nodes)
node->FinishSearch();
m_currently_searching = false;
}
}
wxDataViewItem ParamsModel::Delete(const wxDataViewItem& item)
{
auto ret_item = wxDataViewItem(nullptr);
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (!node) // happens if item.IsOk()==false
return ret_item;
// first remove the node from the parent's array of children;
// NOTE: m_group_nodes is only a vector of _pointers_
// thus removing the node from it doesn't result in freeing it
ParamsNodePtrArray& children = node->GetChildren();
// Delete all children
while (!children.empty())
Delete(wxDataViewItem(children.back().get()));
auto node_parent = node->GetParent();
ParamsNodePtrArray& parents_children = node_parent ? node_parent->GetChildren() : m_group_nodes;
auto it = find_if(parents_children.begin(), parents_children.end(),
[node](std::unique_ptr<ParamsNode>& child) { return child.get() == node; });
assert(it != parents_children.end());
it = parents_children.erase(it);
if (it != parents_children.end())
ret_item = wxDataViewItem(it->get());
wxDataViewItem parent(node_parent);
// set m_container to FALSE if parent has no child
if (node_parent) {
#ifndef __WXGTK__
if (node_parent->GetChildren().empty())
node_parent->SetContainer(false);
#endif //__WXGTK__
ret_item = parent;
}
// notify control
ItemDeleted(parent, item);
return ret_item;
}
void ParamsModel::Clear()
{
while (!m_group_nodes.empty())
Delete(wxDataViewItem(m_group_nodes.back().get()));
}
void ParamsModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
{
assert(item.IsOk());
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (col == (unsigned int)0)
#ifdef __linux__
// variant << wxDataViewIconText(node->GetFormattedText(), get_bmp_bundle(node->icon_name)->GetIconFor(m_ctrl->GetParent())); //TODO: update to bundle with wx update
{
wxIcon icon;
icon.CopyFromBitmap(create_scaled_bitmap(node->icon_name, m_ctrl->GetParent()));
variant << wxDataViewIconText(node->GetFormattedText(), icon);
}
#else
// variant << DataViewBitmapText(node->GetFormattedText(), get_bmp_bundle(node->icon_name)->GetBitmapFor(m_ctrl->GetParent())); //TODO: update to bundle with wx update
variant << DataViewBitmapText(node->GetFormattedText(), create_scaled_bitmap(node->icon_name, m_ctrl->GetParent()));
#endif //__linux__
else
wxLogError("DiffModel::GetValue: wrong column %d", col);
}
bool ParamsModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
{
assert(item.IsOk());
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (col == (unsigned int)0) {
#ifdef __linux__
wxDataViewIconText data;
data << variant;
node->icon = data.GetIcon();
#else
DataViewBitmapText data;
data << variant;
node->icon = data.GetBitmap();
#endif
node->text = data.GetText();
return true;
}
wxLogError("DiffModel::SetValue: wrong column");
return false;
}
wxDataViewItem ParamsModel::GetParent(const wxDataViewItem&item) const
{
// the invisible root node has no parent
if (!item.IsOk())
return wxDataViewItem(nullptr);
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
if (node->IsGroupNode())
return wxDataViewItem(nullptr);
return wxDataViewItem((void*)node->GetParent());
}
bool ParamsModel::IsContainer(const wxDataViewItem& item) const
{
// the invisble root node can have children
if (!item.IsOk())
return true;
ParamsNode* node = static_cast<ParamsNode*>(item.GetID());
return node->IsContainer();
}
unsigned int ParamsModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
{
ParamsNode* parent_node = (ParamsNode*)parent.GetID();
if (parent_node == nullptr) {
for (const auto& group : m_group_nodes)
if (group->IsEnabled())
array.Add(wxDataViewItem((void*)group.get()));
}
else {
const ParamsNodePtrArray& children = parent_node->GetChildren();
for (const std::unique_ptr<ParamsNode>& child : children)
if (child->IsEnabled())
array.Add(wxDataViewItem((void*)child.get()));
}
return array.Count();
}
unsigned int ParamsModel::GetColumnCount() const { return 1; }
wxString ParamsModel::GetColumnType(unsigned int col) const {
#ifdef __linux__
return wxT("wxDataViewIconText");
#else
return wxT("DataViewBitmapText");
#endif
}
// ----------------------------------------------------------------------------
// ParamsViewCtrl
// ----------------------------------------------------------------------------
ParamsViewCtrl::ParamsViewCtrl(wxWindow *parent, wxSize size)
: wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_SINGLE | wxDV_NO_HEADER// | wxDV_ROW_LINES
#ifdef _WIN32
| wxBORDER_SIMPLE
#endif
),
m_em_unit(em_unit(parent))
{
wxGetApp().UpdateDVCDarkUI(this);
model = new ParamsModel();
this->AssociateModel(model);
model->SetAssociatedControl(this);
#ifdef __linux__
wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer();
#ifdef SUPPORTS_MARKUP
rd->EnableMarkup(true);
#endif
wxDataViewColumn* column = new wxDataViewColumn("", rd, 0, 20 * m_em_unit, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT);
#else
wxDataViewColumn* column = new wxDataViewColumn("", new BitmapTextRenderer(true, wxDATAVIEW_CELL_INERT), 0, 20 * m_em_unit, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE);
#endif //__linux__
this->AppendColumn(column);
this->SetExpanderColumn(column);
}
wxDataViewItem ParamsViewCtrl::AppendGroup(const wxString& group_name, const std::string& icon_name)
{
return model->AppendGroup(group_name, icon_name);
}
wxDataViewItem ParamsViewCtrl::AppendSubGroup( wxDataViewItem parent,
const wxString& sub_group_name,
const std::string& icon_name)
{
return model->AppendSubGroup(parent, sub_group_name, icon_name);
}
wxDataViewItem ParamsViewCtrl::AppendParam( wxDataViewItem parent,
ParamType param_type,
const std::string& param_key)
{
return model->AppendParam(parent, param_type, param_key);
}
wxString ParamsViewCtrl::GetValue(wxDataViewItem item)
{
return model->GetParamName(item);
}
wxString ParamsViewCtrl::GetSelectedValue()
{
return model->GetParamName(this->GetSelection());
}
std::string ParamsViewCtrl::GetSelectedParamKey()
{
return model->GetParamKey(this->GetSelection());
}
std::string ParamsViewCtrl::GetSelectedTopLevelCategory()
{
return model->GetTopLevelCategory(this->GetSelection());
}
void ParamsViewCtrl::CheckAndDeleteIfEmpty(wxDataViewItem item)
{
wxDataViewItemArray children;
model->GetChildren(item, children);
if (children.IsEmpty())
model->Delete(item);
}
void ParamsViewCtrl::Clear()
{
model->Clear();
}
void ParamsViewCtrl::Rescale(int em/* = 0*/)
{
// model->Rescale();
Refresh();
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,269 @@
#ifndef slic3r_EditGCodeDialog_hpp_
#define slic3r_EditGCodeDialog_hpp_
#include <vector>
#include <wx/gdicmn.h>
#include <slic3r/GUI/Widgets/Button.hpp>
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <wx/srchctrl.h>
class wxListBox;
class wxTextCtrl;
class ScalableButton;
namespace Slic3r {
namespace GUI {
class ParamsViewCtrl;
//------------------------------------------
// EditGCodeDialog
//------------------------------------------
class EditGCodeDialog : public DPIDialog
{
ParamsViewCtrl* m_params_list {nullptr};
ScalableButton* m_add_btn {nullptr};
wxTextCtrl* m_gcode_editor {nullptr};
wxStaticText* m_param_label {nullptr};
wxStaticText* m_param_description {nullptr};
wxSearchCtrl* m_search_bar {nullptr};
ReadOnlySlicingStatesConfigDef cgp_ro_slicing_states_config_def;
ReadWriteSlicingStatesConfigDef cgp_rw_slicing_states_config_def;
OtherSlicingStatesConfigDef cgp_other_slicing_states_config_def;
PrintStatisticsConfigDef cgp_print_statistics_config_def;
ObjectsInfoConfigDef cgp_objects_info_config_def;
DimensionsConfigDef cgp_dimensions_config_def;
TemperaturesConfigDef cgp_temperatures_config_def;
TimestampsConfigDef cgp_timestamps_config_def;
OtherPresetsConfigDef cgp_other_presets_config_def;
public:
EditGCodeDialog(wxWindow*parent, const std::string&key, const std::string&value);
~EditGCodeDialog();
std::string get_edited_gcode() const;
void on_search_update();
void init_params_list(const std::string& custom_gcode_name);
wxDataViewItem add_presets_placeholders();
void add_selected_value_to_gcode();
void bind_list_and_button();
protected:
std::unordered_map<int, Button *> m_button_list;
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override;
void selection_changed(wxDataViewEvent& evt);
wxBoxSizer* create_btn_sizer(long flags);
};
// ----------------------------------------------------------------------------
// ParamsModelNode: a node inside ParamsModel
// ----------------------------------------------------------------------------
class ParamsNode;
using ParamsNodePtrArray = std::vector<std::unique_ptr<ParamsNode>>;
enum class ParamType {
Undef,
Scalar,
Vector,
FilamentVector,
};
// On all of 3 different platforms Bitmap+Text icon column looks different
// because of Markup text is missed or not implemented.
// As a temporary workaround, we will use:
// MSW - DataViewBitmapText (our custom renderer wxBitmap + wxString, supported Markup text)
// OSX - -//-, but Markup text is not implemented right now
// GTK - wxDataViewIconText (wxWidgets for GTK renderer wxIcon + wxString, supported Markup text)
class ParamsNode
{
ParamsNode* m_parent{ nullptr };
ParamsNodePtrArray m_children;
wxDataViewCtrl* m_ctrl;
ParamType m_param_type{ ParamType::Undef };
// TODO/FIXME:
// the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded)
// needs to know in advance if a node is or _will be_ a container.
// Thus implementing:
// bool IsContainer() const
// { return m_children.size()>0; }
// doesn't work with wxGTK when DiffModel::AddToClassical is called
// AND the classical node was removed (a new node temporary without children
// would be added to the control)
bool m_container{ true };
bool m_expanded_before_search{false};
bool m_enabled{true};
bool m_bold{false};
// first is pos, second is length
std::unique_ptr<std::pair<int, int>> m_highlight_index{nullptr};
public:
#ifdef __linux__
wxIcon icon;
#else
wxBitmap icon;
#endif //__linux__
std::string icon_name;
std::string param_key;
wxString text;
// Group params(root) node
ParamsNode(const wxString& group_name, const std::string& icon_name, wxDataViewCtrl* ctrl);
// sub SlicingState node
ParamsNode(ParamsNode* parent,
const wxString& sub_group_name,
const std::string& icon_name,
wxDataViewCtrl* ctrl);
// parametre node
ParamsNode( ParamsNode* parent,
ParamType param_type,
const std::string& param_key,
wxDataViewCtrl* ctrl);
wxString GetFormattedText();
bool IsContainer() const { return m_container; }
bool IsGroupNode() const { return m_parent == nullptr; }
bool IsParamNode() const { return m_param_type != ParamType::Undef; }
void SetContainer(bool is_container) { m_container = is_container; }
bool IsEnabled() { return m_enabled; }
void Enable(bool enable = true) { m_enabled = enable; }
void Disable() { Enable(false); }
void StartSearch();
void RefreshSearch(const wxString& search_text);
void FinishSearch();
ParamsNode* GetParent() { return m_parent; }
ParamsNodePtrArray& GetChildren() { return m_children; }
wxDataViewItemArray GetEnabledChildren();
void Append(std::unique_ptr<ParamsNode> child) { m_children.emplace_back(std::move(child)); }
};
// ----------------------------------------------------------------------------
// ParamsModel
// ----------------------------------------------------------------------------
class ParamsModel : public wxDataViewModel
{
ParamsNodePtrArray m_group_nodes;
wxDataViewCtrl* m_ctrl{ nullptr };
bool m_currently_searching{false};
public:
ParamsModel();
~ParamsModel() override = default;
void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; }
wxDataViewItem AppendGroup(const wxString& group_name,
const std::string& icon_name);
wxDataViewItem AppendSubGroup(wxDataViewItem parent,
const wxString& sub_group_name,
const std::string&icon_name);
wxDataViewItem AppendParam( wxDataViewItem parent,
ParamType param_type,
const std::string& param_key);
wxDataViewItem Delete(const wxDataViewItem& item);
wxString GetParamName(wxDataViewItem item);
std::string GetParamKey(wxDataViewItem item);
std::string GetTopLevelCategory(wxDataViewItem item);
void RefreshSearch(const wxString& search_text);
void FinishSearch();
void Clear();
wxDataViewItem GetParent(const wxDataViewItem& item) const override;
unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override;
unsigned int GetColumnCount() const override;
wxString GetColumnType(unsigned int col) const override;
void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override;
bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override;
bool IsContainer(const wxDataViewItem& item) const override;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
};
// ----------------------------------------------------------------------------
// ParamsViewCtrl
// ----------------------------------------------------------------------------
class ParamsViewCtrl : public wxDataViewCtrl
{
int m_em_unit;
public:
ParamsViewCtrl(wxWindow* parent, wxSize size);
~ParamsViewCtrl() override {
if (model) {
Clear();
model->DecRef();
}
}
ParamsModel* model{ nullptr };
wxDataViewItem AppendGroup(const wxString& group_name,
const std::string& icon_name);
wxDataViewItem AppendSubGroup(wxDataViewItem parent,
const wxString& sub_group_name,
const std::string&icon_name);
wxDataViewItem AppendParam( wxDataViewItem parent,
ParamType param_type,
const std::string& param_key);
wxString GetValue(wxDataViewItem item);
wxString GetSelectedValue();
std::string GetSelectedParamKey();
std::string GetSelectedTopLevelCategory();
void CheckAndDeleteIfEmpty(wxDataViewItem item);
void Clear();
void Rescale(int em = 0);
void set_em_unit(int em) { m_em_unit = em; }
};
} // namespace GUI
} // namespace Slic3r
#endif

View file

@ -196,6 +196,11 @@ void Field::on_back_to_sys_value()
m_back_to_sys_value(m_opt_id);
}
void Field::on_edit_value()
{
if (m_fn_edit_value)
m_fn_edit_value(m_opt_id);
}
/// Fires the enable or disable function, based on the input.
@ -206,7 +211,7 @@ wxString Field::get_tooltip_text(const wxString &default_string)
wxString tooltip_text("");
#ifdef NDEBUG
wxString tooltip = _(m_opt.tooltip);
edit_tooltip(tooltip);
::edit_tooltip(tooltip);
std::string opt_id = m_opt_id;
auto hash_pos = opt_id.find("#");

View file

@ -41,7 +41,120 @@ wxString double_to_string(double const value, const int max_precision = 4);
wxString get_thumbnail_string(const Vec2d& value);
wxString get_thumbnails_string(const std::vector<Vec2d>& values);
class Field {
class UndoValueUIManager
{
struct UndoValueUI {
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* undo_bitmap{ nullptr };
const wxString* undo_tooltip{ nullptr };
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* undo_to_sys_bitmap{ nullptr };
const wxString* undo_to_sys_tooltip{ nullptr };
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* label_color{ nullptr };
// State of the blinker icon
bool blink{ false };
bool set_undo_bitmap(const ScalableBitmap* bmp) {
if (undo_bitmap != bmp) {
undo_bitmap = bmp;
return true;
}
return false;
}
bool set_undo_to_sys_bitmap(const ScalableBitmap* bmp) {
if (undo_to_sys_bitmap != bmp) {
undo_to_sys_bitmap = bmp;
return true;
}
return false;
}
bool set_label_colour(const wxColour* clr) {
if (label_color != clr) {
label_color = clr;
}
return false;
}
bool set_undo_tooltip(const wxString* tip) {
if (undo_tooltip != tip) {
undo_tooltip = tip;
return true;
}
return false;
}
bool set_undo_to_sys_tooltip(const wxString* tip) {
if (undo_to_sys_tooltip != tip) {
undo_to_sys_tooltip = tip;
return true;
}
return false;
}
};
UndoValueUI m_undo_ui;
struct EditValueUI {
// Bitmap and Tooltip text for m_Edit_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* bitmap{ nullptr };
wxString tooltip { wxEmptyString };
bool set_bitmap(const ScalableBitmap* bmp) {
if (bitmap != bmp) {
bitmap = bmp;
return true;
}
return false;
}
bool set_tooltip(const wxString& tip) {
if (tooltip != tip) {
tooltip = tip;
return true;
}
return false;
}
};
EditValueUI m_edit_ui;
public:
UndoValueUIManager() {}
~UndoValueUIManager() {}
bool set_undo_bitmap(const ScalableBitmap* bmp) { return m_undo_ui.set_undo_bitmap(bmp); }
bool set_undo_to_sys_bitmap(const ScalableBitmap* bmp) { return m_undo_ui.set_undo_to_sys_bitmap(bmp); }
bool set_label_colour(const wxColour* clr) { return m_undo_ui.set_label_colour(clr); }
bool set_undo_tooltip(const wxString* tip) { return m_undo_ui.set_undo_tooltip(tip); }
bool set_undo_to_sys_tooltip(const wxString* tip) { return m_undo_ui.set_undo_to_sys_tooltip(tip); }
bool set_edit_bitmap(const ScalableBitmap* bmp) { return m_edit_ui.set_bitmap(bmp); }
bool set_edit_tooltip(const wxString& tip) { return m_edit_ui.set_tooltip(tip); }
// ui items used for revert line value
bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; }
const ScalableBitmap* undo_bitmap() const { return m_undo_ui.undo_bitmap; }
const wxString* undo_tooltip() const { return m_undo_ui.undo_tooltip; }
const ScalableBitmap* undo_to_sys_bitmap() const { return m_undo_ui.undo_to_sys_bitmap; }
const wxString* undo_to_sys_tooltip() const { return m_undo_ui.undo_to_sys_tooltip; }
const wxColour* label_color() const { return m_undo_ui.label_color; }
// Extentions
// Search blinker
const bool blink() const { return m_undo_ui.blink; }
bool* get_blink_ptr() { return &m_undo_ui.blink; }
// Edit field button
bool has_edit_ui() const { return !m_edit_ui.tooltip.IsEmpty(); }
const wxBitmap* edit_bitmap() const { return &m_edit_ui.bitmap->bmp(); }
const wxString* edit_tooltip() const { return &m_edit_ui.tooltip; }
};
class Field : public UndoValueUIManager {
protected:
// factory function to defer and enforce creation of derived type.
virtual void PostInitialize();
@ -70,6 +183,8 @@ public:
void on_back_to_initial_value();
/// Call the attached m_back_to_sys_value method.
void on_back_to_sys_value();
/// Call the attached m_fn_edit_value method.
void on_edit_value();
public:
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
@ -85,6 +200,9 @@ public:
t_back_to_init m_back_to_initial_value{ nullptr };
t_back_to_init m_back_to_sys_value{ nullptr };
/// Callback function to edit field value
t_back_to_init m_fn_edit_value{ nullptr };
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
bool m_disable_change_event {false};
bool m_is_modified_value {false};
@ -139,49 +257,6 @@ public:
return std::move(p); //!p;
}
bool set_undo_bitmap(const ScalableBitmap *bmp) {
if (m_undo_bitmap != bmp) {
m_undo_bitmap = bmp;
return true;
}
return false;
}
bool set_undo_to_sys_bitmap(const ScalableBitmap *bmp) {
if (m_undo_to_sys_bitmap != bmp) {
m_undo_to_sys_bitmap = bmp;
return true;
}
return false;
}
bool set_label_colour(const wxColour *clr) {
if (m_label_color != clr) {
m_label_color = clr;
}
return false;
}
bool set_undo_tooltip(const wxString *tip) {
if (m_undo_tooltip != tip) {
m_undo_tooltip = tip;
return true;
}
return false;
}
bool set_undo_to_sys_tooltip(const wxString *tip) {
if (m_undo_to_sys_tooltip != tip) {
m_undo_to_sys_tooltip = tip;
return true;
}
return false;
}
bool* get_blink_ptr() {
return &m_blink;
}
virtual void msw_rescale();
virtual void sys_color_changed();
@ -193,27 +268,9 @@ public:
static int def_width_wider() ;
static int def_width_thinner() ;
const ScalableBitmap* undo_bitmap() { return m_undo_bitmap; }
const wxString* undo_tooltip() { return m_undo_tooltip; }
const ScalableBitmap* undo_to_sys_bitmap() { return m_undo_to_sys_bitmap; }
const wxString* undo_to_sys_tooltip() { return m_undo_to_sys_tooltip; }
const wxColour* label_color() { return m_label_color; }
const bool blink() { return m_blink; }
const bool combine_side_text() { return m_combine_side_text; } // BBS: new param style
protected:
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* m_undo_bitmap = nullptr;
const wxString* m_undo_tooltip = nullptr;
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const ScalableBitmap* m_undo_to_sys_bitmap = nullptr;
const wxString* m_undo_to_sys_tooltip = nullptr;
bool m_blink{ false };
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_label_color = nullptr;
// current value
boost::any m_value;
// last maeningful value

View file

@ -3185,14 +3185,17 @@ void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
UpdateDarkUI(dvc, highlited ? dark_mode() : false);
#ifdef _MSW_DARK_MODE
//dvc->RefreshHeaderDarkMode(&m_normal_font);
HWND hwnd = (HWND)dvc->GenericGetHeader()->GetHandle();
hwnd = GetWindow(hwnd, GW_CHILD);
if (hwnd != NULL)
NppDarkMode::SetDarkListViewHeader(hwnd);
wxItemAttr attr;
attr.SetTextColour(NppDarkMode::GetTextColor());
attr.SetFont(m_normal_font);
dvc->SetHeaderAttr(attr);
HWND hwnd;
if (!dvc->HasFlag(wxDV_NO_HEADER)) {
hwnd = (HWND) dvc->GenericGetHeader()->GetHandle();
hwnd = GetWindow(hwnd, GW_CHILD);
if (hwnd != NULL)
NppDarkMode::SetDarkListViewHeader(hwnd);
wxItemAttr attr;
attr.SetTextColour(NppDarkMode::GetTextColor());
attr.SetFont(m_normal_font);
dvc->SetHeaderAttr(attr);
}
#endif //_MSW_DARK_MODE
if (dvc->HasFlag(wxDV_ROW_LINES))
dvc->SetAlternateRowColour(m_color_highlight_default);

View file

@ -195,7 +195,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
if (line.widget) {
#ifndef DISABLE_BLINKING
h_pos += blinking_button_width;
h_pos += (line.has_undo_ui() ? 3 : 1) * blinking_button_width;
#endif
for (auto child : line.widget_sizer->GetChildren())
@ -373,22 +373,31 @@ void OG_CustomCtrl::OnMotion(wxMouseEvent& event)
break;
}
for (size_t opt_idx = 0; opt_idx < line.rects_undo_icon.size(); opt_idx++)
size_t undo_icons_cnt = line.rects_undo_icon.size();
assert(line.rects_undo_icon.size() == line.rects_undo_to_sys_icon.size());
const std::vector<Option>& option_set = line.og_line.get_options();
for (size_t opt_idx = 0; opt_idx < undo_icons_cnt; opt_idx++) {
const std::string& opt_key = option_set[opt_idx].opt_id;
if (is_point_in_rect(pos, line.rects_undo_icon[opt_idx])) {
const std::vector<Option>& option_set = line.og_line.get_options();
Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
if (field)
if (line.og_line.has_undo_ui())
tooltip = *line.og_line.undo_tooltip();
else if (Field* field = opt_group->get_field(opt_key))
tooltip = *field->undo_tooltip();
break;
}
for (size_t opt_idx = 0; opt_idx < line.rects_undo_to_sys_icon.size(); opt_idx++)
if (is_point_in_rect(pos, line.rects_undo_to_sys_icon[opt_idx])) {
const std::vector<Option>& option_set = line.og_line.get_options();
Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
if (field)
if (line.og_line.has_undo_ui())
tooltip = *line.og_line.undo_to_sys_tooltip();
else if (Field* field = opt_group->get_field(opt_key))
tooltip = *field->undo_to_sys_tooltip();
break;
}
if (opt_idx < line.rects_edit_icon.size() && is_point_in_rect(pos, line.rects_edit_icon[opt_idx])) {
if (Field* field = opt_group->get_field(opt_key); field && field->has_edit_ui())
tooltip = *field->edit_tooltip();
break;
}
}
if (!tooltip.IsEmpty())
break;
}
@ -424,24 +433,40 @@ void OG_CustomCtrl::OnLeftDown(wxMouseEvent& event)
if (!line.is_visible) continue;
if (line.launch_browser())
return;
for (size_t opt_idx = 0; opt_idx < line.rects_undo_icon.size(); opt_idx++)
size_t undo_icons_cnt = line.rects_undo_icon.size();
assert(line.rects_undo_icon.size() == line.rects_undo_to_sys_icon.size());
const std::vector<Option>& option_set = line.og_line.get_options();
for (size_t opt_idx = 0; opt_idx < undo_icons_cnt; opt_idx++) {
const std::string& opt_key = option_set[opt_idx].opt_id;
if (is_point_in_rect(pos, line.rects_undo_icon[opt_idx])) {
const std::vector<Option>& option_set = line.og_line.get_options();
Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
if (field)
if (line.og_line.has_undo_ui()) {
if (ConfigOptionsGroup* conf_OG = dynamic_cast<ConfigOptionsGroup*>(line.ctrl->opt_group))
conf_OG->back_to_initial_value(opt_key);
}
else if (Field* field = opt_group->get_field(opt_key))
field->on_back_to_initial_value();
event.Skip();
return;
}
for (size_t opt_idx = 0; opt_idx < line.rects_undo_to_sys_icon.size(); opt_idx++)
if (is_point_in_rect(pos, line.rects_undo_to_sys_icon[opt_idx])) {
const std::vector<Option>& option_set = line.og_line.get_options();
Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
if (field)
if (line.og_line.has_undo_ui()) {
if (ConfigOptionsGroup* conf_OG = dynamic_cast<ConfigOptionsGroup*>(line.ctrl->opt_group))
conf_OG->back_to_sys_value(opt_key);
}
else if (Field* field = opt_group->get_field(opt_key))
field->on_back_to_sys_value();
event.Skip();
return;
}
if (opt_idx < line.rects_edit_icon.size() && is_point_in_rect(pos, line.rects_edit_icon[opt_idx])) {
if (Field* field = opt_group->get_field(opt_key))
field->on_edit_value();
event.Skip();
return;
}
}
}
SetFocusIgnoringChildren();
@ -745,10 +770,14 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord h_pos, wxCoord v_pos)
bool suppress_hyperlinks = false;
if (draw_just_act_buttons) {
//BBS: GUI refactor
if (field && field->undo_bitmap())
//if (field)
// BBS: new layout
draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink());
if (field && field->undo_bitmap()) {
// if (field)
// BBS: new layout
const wxPoint pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(),
field->undo_bitmap()->bmp(), field->blink());
if (field->has_edit_ui())
draw_edit_bmp(dc, pos, *field->edit_bitmap());
}
return;
}
@ -761,7 +790,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord h_pos, wxCoord v_pos)
wxColour blink_color = StateColor::darkModeColorFor("#009688");
bool is_url_string = false;
if (ctrl->opt_group->label_width != 0 && !label.IsEmpty()) {
const wxColour* text_clr = field ? field->label_color() : og_line.full_Label_color;
const wxColour* text_clr = field ? field->label_color() : og_line.label_color();
for (const Option& opt : option_set) {
Field* field = ctrl->opt_group->get_field(opt.opt_id);
if (field && field->blink()) {
@ -802,7 +831,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord h_pos, wxCoord v_pos)
auto draw_buttons = [&h_pos, &dc, &v_pos, this](Field* field, size_t bmp_rect_id = 0) {
if (field && field->undo_to_sys_bitmap()) {
h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink(), bmp_rect_id);
h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink(), bmp_rect_id).x;
}
#ifndef DISABLE_BLINKING
else if (field && !field->undo_to_sys_bitmap() && field->blink())
@ -945,7 +974,7 @@ wxPoint OG_CustomCtrl::CtrlLine::draw_blinking_bmp(wxDC& dc, wxPoint pos, bool i
return wxPoint(h_pos, v_pos);
}
wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id)
wxPoint OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id)
{
#ifndef DISABLE_BLINKING
pos = draw_blinking_bmp(dc, pos, is_blinking);
@ -979,7 +1008,19 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBi
h_pos += bmp_dim2 + ctrl->m_h_gap;
return h_pos;
return wxPoint(h_pos, v_pos);
}
wxCoord OG_CustomCtrl::CtrlLine::draw_edit_bmp(wxDC &dc, wxPoint pos, const wxBitmap& bmp_edit)
{
const wxCoord h_pos = pos.x + ctrl->m_h_gap;
const wxCoord v_pos = pos.y;
const int bmp_w = bmp_edit.GetWidth();
rects_edit_icon.emplace_back(wxRect(h_pos, v_pos, bmp_w, bmp_w));
dc.DrawBitmap(bmp_edit, h_pos, v_pos);
return h_pos + bmp_w + ctrl->m_h_gap;
}
bool OG_CustomCtrl::CtrlLine::launch_browser() const

View file

@ -64,12 +64,14 @@ class OG_CustomCtrl :public wxPanel
void render(wxDC& dc, wxCoord h_pos, wxCoord v_pos);
wxCoord draw_text (wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url = false, bool is_main = false);
wxPoint draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking);
wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id = 0);
wxPoint draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id = 0);
wxCoord draw_edit_bmp(wxDC& dc, wxPoint pos, const wxBitmap& bmp_edit);
bool launch_browser() const;
bool is_separator() const { return og_line.is_separator(); }
std::vector<wxRect> rects_undo_icon;
std::vector<wxRect> rects_undo_to_sys_icon;
std::vector<wxRect> rects_edit_icon;
wxRect rect_label;
};

View file

@ -102,6 +102,14 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
};
field->m_parent = parent();
if (edit_custom_gcode && opt.is_code) {
field->m_fn_edit_value = [this](std::string opt_id) {
if (!m_disabled)
this->edit_custom_gcode(opt_id);
};
field->set_edit_tooltip(_L("Edit Custom G-code"));
}
field->m_back_to_initial_value = [this](std::string opt_id) {
if (!m_disabled)
this->back_to_initial_value(opt_id);
@ -678,6 +686,7 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
opt_key == "thumbnails" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") {
value = get_config_value(config, opt_key);
this->change_opt_value(opt_key, value);
OptionsGroup::on_change_OG(opt_key, value);
return;
} else {
auto opt_id = m_opt_map.find(opt_key)->first;

View file

@ -48,7 +48,8 @@ struct Option {
using t_option = std::unique_ptr<Option>; //!
/// Represents option lines
class Line {
class Line : public UndoValueUIManager
{
bool m_is_separator{ false };
public:
wxString label;
@ -58,8 +59,6 @@ public:
bool toggle_visible{true}; // BBS: hide some line
size_t full_width {0};
wxColour* full_Label_color {nullptr};
bool blink {false};
widget_t widget {nullptr};
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
wxWindow* near_label_widget_win {nullptr};
@ -83,10 +82,10 @@ public:
Line() : m_is_separator(true) {}
bool is_separator() const { return m_is_separator; }
bool has_only_option(const std::string& opt_key) const { return m_options.size() == 1 && m_options[0].opt_id == opt_key; }
const std::vector<widget_t>& get_extra_widgets() const {return m_extra_widgets;}
const std::vector<Option>& get_options() const { return m_options; }
bool* get_blink_ptr() { return &blink; }
private:
std::vector<Option> m_options;//! {std::vector<Option>()};
@ -123,6 +122,8 @@ public:
std::function<void(wxWindow* win)> rescale_extra_column_item { nullptr };
std::function<void(wxWindow* win)> rescale_near_label_widget { nullptr };
std::function<void(const t_config_option_key& opt_key)> edit_custom_gcode { nullptr };
wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
int sidetext_width{ -1 };
@ -155,6 +156,7 @@ public:
if (m_fields.find(id) == m_fields.end()) return nullptr;
return m_fields.at(id).get();
}
bool set_value(const t_config_option_key& id, const boost::any& value, bool change_event = false) {
if (m_fields.find(id) == m_fields.end()) return false;
m_fields.at(id)->set_value(value, change_event);

View file

@ -38,6 +38,7 @@
#include "format.hpp"
#include "UnsavedChangesDialog.hpp"
#include "SavePresetDialog.hpp"
#include "EditGCodeDialog.hpp"
#include "MsgDialog.hpp"
#include "Notebook.hpp"
@ -255,6 +256,8 @@ void Tab::create_preset_tab()
// Bitmaps to be shown on the "Undo user changes" button next to each input field.
add_scaled_bitmap(this, m_bmp_value_revert, "undo");
add_scaled_bitmap(this, m_bmp_white_bullet, "dot");
// Bitmap to be shown on the "edit" button before to each editable input field.
add_scaled_bitmap(this, m_bmp_edit_value, "edit");
set_tooltips_text();
@ -708,8 +711,8 @@ void Tab::update_label_colours()
}
if (opt.first == "printable_area" ||
opt.first == "compatible_prints" || opt.first == "compatible_printers" ) {
if (m_colored_Label_colors.find(opt.first) != m_colored_Label_colors.end())
m_colored_Label_colors.at(opt.first) = *color;
if (Line* line = get_line(opt.first))
line->set_label_colour(color);
continue;
}
@ -748,13 +751,13 @@ void Tab::decorate()
for (const auto& opt : m_options_list)
{
Field* field = nullptr;
wxColour* colored_label_clr = nullptr;
bool option_without_field = false;
if (opt.first == "printable_area" ||
opt.first == "compatible_prints" || opt.first == "compatible_printers")
colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : &m_colored_Label_colors.at(opt.first);
option_without_field = true;
if (!colored_label_clr) {
if (!option_without_field) {
field = get_field(opt.first);
if (!field)
continue;
@ -789,8 +792,14 @@ void Tab::decorate()
tt = &m_tt_white_bullet;
}
if (colored_label_clr) {
*colored_label_clr = *color;
if (option_without_field) {
if (Line* line = get_line(opt.first)) {
line->set_undo_bitmap(icon);
line->set_undo_to_sys_bitmap(sys_icon);
line->set_undo_tooltip(tt);
line->set_undo_to_sys_tooltip(sys_tt);
line->set_label_colour(color);
}
continue;
}
@ -802,6 +811,10 @@ void Tab::decorate()
field->set_undo_tooltip(tt);
field->set_undo_to_sys_tooltip(sys_tt);
field->set_label_colour(color);
if (field->has_edit_ui())
field->set_edit_bitmap(&m_bmp_edit_value);
}
if (m_active_page)
@ -1082,7 +1095,7 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/)
m_postpone_update_ui = false;
// When all values are rolled, then we hane to update whole tab in respect to the reverted values
// When all values are rolled, then we have to update whole tab in respect to the reverted values
update();
// BBS: restore all pages in preset, update_dirty also update combobox
@ -1265,6 +1278,11 @@ Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/
return m_active_page ? m_active_page->get_field(opt_key, opt_index) : nullptr;
}
Line* Tab::get_line(const t_config_option_key& opt_key)
{
return m_active_page ? m_active_page->get_line(opt_key) : nullptr;
}
std::pair<OG_CustomCtrl*, bool*> Tab::get_custom_ctrl_with_blinking_ptr(const t_config_option_key& opt_key, int opt_index/* = -1*/)
{
if (!m_active_page)
@ -2880,6 +2898,43 @@ static void validate_custom_gcode_cb(Tab* tab, ConfigOptionsGroupShp opt_group,
tab->on_value_change(opt_key, value);
}
void Tab::edit_custom_gcode(const t_config_option_key& opt_key)
{
EditGCodeDialog dlg = EditGCodeDialog(this, opt_key, get_custom_gcode(opt_key));
if (dlg.ShowModal() == wxID_OK) {
set_custom_gcode(opt_key, dlg.get_edited_gcode());
update_dirty();
update();
}
}
const std::string& Tab::get_custom_gcode(const t_config_option_key& opt_key)
{
return m_config->opt_string(opt_key);
}
void Tab::set_custom_gcode(const t_config_option_key& opt_key, const std::string& value)
{
DynamicPrintConfig new_conf = *m_config;
new_conf.set_key_value(opt_key, new ConfigOptionString(value));
load_config(new_conf);
}
const std::string& TabFilament::get_custom_gcode(const t_config_option_key& opt_key)
{
return m_config->opt_string(opt_key, unsigned(0));
}
void TabFilament::set_custom_gcode(const t_config_option_key& opt_key, const std::string& value)
{
std::vector<std::string> gcodes = static_cast<const ConfigOptionStrings*>(m_config->option(opt_key))->values;
gcodes[0] = value;
DynamicPrintConfig new_conf = *m_config;
new_conf.set_key_value(opt_key, new ConfigOptionStrings(gcodes));
load_config(new_conf);
}
void TabFilament::add_filament_overrides_page()
{
//BBS
@ -3155,11 +3210,14 @@ void TabFilament::build()
const int gcode_field_height = 15; // 150
const int notes_field_height = 25; // 250
auto edit_custom_gcode_fn = [this](const t_config_option_key& opt_key) { edit_custom_gcode(opt_key); };
page = add_options_page(L("Advanced"), "advanced");
optgroup = page->new_optgroup(L("Filament start G-code"), L"param_gcode", 0);
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("filament_start_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3170,6 +3228,7 @@ void TabFilament::build()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("filament_end_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3471,6 +3530,8 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("support_chamber_temp_control", "chamber-temperature");
optgroup->append_single_option_line("support_air_filtration", "air-filtration");
auto edit_custom_gcode_fn = [this](const t_config_option_key& opt_key) { edit_custom_gcode(opt_key); };
const int gcode_field_height = 15; // 150
const int notes_field_height = 25; // 250
page = add_options_page(L("Machine gcode"), "cog");
@ -3478,6 +3539,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("machine_start_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3488,6 +3550,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("machine_end_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3498,6 +3561,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, optgroup](const t_config_option_key &opt_key, const boost::any &value) {
validate_custom_gcode_cb(this, optgroup, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("printing_by_object_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3509,6 +3573,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("before_layer_change_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3519,6 +3584,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("layer_change_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3529,6 +3595,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("time_lapse_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3539,6 +3606,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("change_filament_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3549,7 +3617,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key &opt_key, const boost::any &value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("change_extrusion_role_gcode");
option.opt.full_width = true;
option.opt.is_code = true;
@ -3560,6 +3628,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("machine_pause_gcode");
option.opt.is_code = true;
option.opt.height = gcode_field_height;//150;
@ -3569,6 +3638,7 @@ void TabPrinter::build_fff()
optgroup->m_on_change = [this, &optgroup_title = optgroup->title](const t_config_option_key& opt_key, const boost::any& value) {
validate_custom_gcode_cb(this, optgroup_title, opt_key, value);
};
optgroup->edit_custom_gcode = edit_custom_gcode_fn;
option = optgroup->get_option("template_custom_gcode");
option.opt.is_code = true;
option.opt.height = gcode_field_height;//150;
@ -5312,8 +5382,12 @@ void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::strin
line.widget = widget;
line.label_path = path;
m_colored_Label_colors[opt_key] = m_default_text_clr;
line.full_Label_color = &m_colored_Label_colors[opt_key];
// set default undo ui
line.set_undo_bitmap(&m_bmp_white_bullet);
line.set_undo_to_sys_bitmap(&m_bmp_white_bullet);
line.set_undo_tooltip(&m_tt_white_bullet);
line.set_undo_to_sys_tooltip(&m_tt_white_bullet);
line.set_label_colour(&m_default_text_clr);
optgroup->append_line(line);
}
@ -5671,12 +5745,10 @@ Field *Page::get_field(const t_config_option_key &opt_key, int opt_index /*= -1*
Line *Page::get_line(const t_config_option_key &opt_key)
{
Line *line = nullptr;
for (auto opt : m_optgroups) {
line = opt->get_line(opt_key);
if (line != nullptr) return line;
}
return line;
for (auto opt : m_optgroups)
if (Line* line = opt->get_line(opt_key))
return line;
return nullptr;
}
bool Page::set_value(const t_config_option_key &opt_key, const boost::any &value)

View file

@ -201,6 +201,8 @@ protected:
ScalableBitmap *m_bmp_non_system;
// Bitmaps to be shown on the "Undo user changes" button next to each input field.
ScalableBitmap m_bmp_value_revert;
// Bitmaps to be shown on the "Undo user changes" button next to each input field.
ScalableBitmap m_bmp_edit_value;
std::vector<ScalableButton*> m_scaled_buttons = {};
std::vector<ScalableBitmap*> m_scaled_bitmaps = {};
@ -374,6 +376,7 @@ public:
virtual void msw_rescale();
virtual void sys_color_changed();
Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const;
Line* get_line(const t_config_option_key& opt_key);
std::pair<OG_CustomCtrl*, bool*> get_custom_ctrl_with_blinking_ptr(const t_config_option_key& opt_key, int opt_index = -1);
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
@ -410,6 +413,10 @@ public:
bool validate_custom_gcodes_was_shown{ false };
void set_just_edit(bool just_edit);
void edit_custom_gcode(const t_config_option_key& opt_key);
virtual const std::string& get_custom_gcode(const t_config_option_key& opt_key);
virtual void set_custom_gcode(const t_config_option_key& opt_key, const std::string& value);
protected:
void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget);
wxSizer* compatible_widget_create(wxWindow* parent, PresetDependencies &deps);
@ -427,6 +434,7 @@ protected:
ConfigManipulation m_config_manipulation;
ConfigManipulation get_config_manipulation();
friend class EditGCodeDialog;
};
class TabPrint : public Tab
@ -562,6 +570,9 @@ public:
void update() override;
void clear_pages() override;
bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; }
const std::string& get_custom_gcode(const t_config_option_key& opt_key) override;
void set_custom_gcode(const t_config_option_key& opt_key, const std::string& value) override;
};
class TabPrinter : public Tab