diff --git a/resources/images/custom-gcode_gcode.svg b/resources/images/custom-gcode_gcode.svg new file mode 100644 index 0000000000..38bcd21729 --- /dev/null +++ b/resources/images/custom-gcode_gcode.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/images/custom-gcode_measure.svg b/resources/images/custom-gcode_measure.svg new file mode 100644 index 0000000000..3c13dd7cc7 --- /dev/null +++ b/resources/images/custom-gcode_measure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/custom-gcode_object-info.svg b/resources/images/custom-gcode_object-info.svg new file mode 100644 index 0000000000..2c24bdc8a2 --- /dev/null +++ b/resources/images/custom-gcode_object-info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/custom-gcode_single.svg b/resources/images/custom-gcode_single.svg new file mode 100644 index 0000000000..d177860bc9 --- /dev/null +++ b/resources/images/custom-gcode_single.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/custom-gcode_slicing-state.svg b/resources/images/custom-gcode_slicing-state.svg new file mode 100644 index 0000000000..4b4bef6ecf --- /dev/null +++ b/resources/images/custom-gcode_slicing-state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/custom-gcode_slicing-state_global.svg b/resources/images/custom-gcode_slicing-state_global.svg new file mode 100644 index 0000000000..7f4e685a1b --- /dev/null +++ b/resources/images/custom-gcode_slicing-state_global.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/images/custom-gcode_stats.svg b/resources/images/custom-gcode_stats.svg new file mode 100644 index 0000000000..96dfe8decf --- /dev/null +++ b/resources/images/custom-gcode_stats.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/custom-gcode_temperature.svg b/resources/images/custom-gcode_temperature.svg new file mode 100644 index 0000000000..d14cd1a31c --- /dev/null +++ b/resources/images/custom-gcode_temperature.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/resources/images/custom-gcode_vector-index.svg b/resources/images/custom-gcode_vector-index.svg new file mode 100644 index 0000000000..68aef590bb --- /dev/null +++ b/resources/images/custom-gcode_vector-index.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/custom-gcode_vector.svg b/resources/images/custom-gcode_vector.svg new file mode 100644 index 0000000000..396f0e7b82 --- /dev/null +++ b/resources/images/custom-gcode_vector.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 9bf81790e4..1bebecfb96 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -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. diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2d8abfabfd..ebabfab426 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -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) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 959519e60e..a9973b4682 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -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 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( diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 89a68997c6..626d3d6d72 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -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(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{ + &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{ + &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) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 935cb95fc9..2260920dc7 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -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> m_placeholder_error_messages; +#endif + Point m_last_pos; bool m_last_pos_defined; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b8dacbcf91..7993f6a7ba 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -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(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 0059a8d5b2..792d8815d4 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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 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& 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 &dpts) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 7638471c56..f894a24ca8 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -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& 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; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index eb1599baf0..3fd171528e 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -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. diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in index 78809f1f6d..90383c93af 100644 --- a/src/libslic3r/libslic3r_version.h.in +++ b/src/libslic3r/libslic3r_version.h.in @@ -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 */ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 387d367385..1a5422b7bb 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -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; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c55ff32741..5386aad933 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -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 diff --git a/src/slic3r/GUI/EditGCodeDialog.cpp b/src/slic3r/GUI/EditGCodeDialog.cpp new file mode 100644 index 0000000000..54ec6db8bc --- /dev/null +++ b/src/slic3r/GUI/EditGCodeDialog.cpp @@ -0,0 +1,976 @@ +#include "EditGCodeDialog.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#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&vec) { + return std::set(vec.begin(), vec.end()); + }; + + const bool is_fff = wxGetApp().plater()->printer_technology() == ptFFF; + const std::set print_options = get_set_from_vec(is_fff ? Preset::print_options() : Preset::sla_print_options()); + const std::set material_options = get_set_from_vec(is_fff ? Preset::filament_options() : Preset::sla_material_options()); + const std::set 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& 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 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 { + &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(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(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal) + ); + + StateColor ok_btn_bd( + std::pair(wxColour(0, 150, 136), StateColor::Normal) + ); + + StateColor ok_btn_text( + std::pair(wxColour(255, 255, 254), StateColor::Normal) + ); + + StateColor cancel_btn_bg( + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(255, 255, 255), StateColor::Normal) + ); + + StateColor cancel_btn_bd_( + std::pair(wxColour(38, 46, 48), StateColor::Normal) + ); + + StateColor cancel_btn_text( + std::pair(wxColour(38, 46, 48), StateColor::Normal) + ); + + + StateColor calc_btn_bg( + std::pair(wxColour(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal) + ); + + StateColor calc_btn_bd( + std::pair(wxColour(0, 150, 136), StateColor::Normal) + ); + + StateColor calc_btn_text( + std::pair(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 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("%1%", str); +#endif +} + +static void highlight(wxString& str) +{ +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) + str = format_wxstr("%1%", 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>(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& 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(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(parent.GetID()); + if (!parent_node) + return wxDataViewItem(0); + + parent_node->Append(std::make_unique(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(parent.GetID()); + if (!parent_node) + return wxDataViewItem(0); + + parent_node->Append(std::make_unique(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(item.GetID()); + if (node->IsParamNode()) + return node->text; + } + return wxEmptyString; +} + +std::string ParamsModel::GetParamKey(wxDataViewItem item) +{ + if (item.IsOk()) { + ParamsNode* node = static_cast(item.GetID()); + return node->param_key; + } + return std::string(); +} + +std::string ParamsModel::GetTopLevelCategory(wxDataViewItem item) +{ + if (item.IsOk()) { + ParamsNode* node = static_cast(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(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& 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(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(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(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(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& 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 diff --git a/src/slic3r/GUI/EditGCodeDialog.hpp b/src/slic3r/GUI/EditGCodeDialog.hpp new file mode 100644 index 0000000000..28cd5809f9 --- /dev/null +++ b/src/slic3r/GUI/EditGCodeDialog.hpp @@ -0,0 +1,269 @@ +#ifndef slic3r_EditGCodeDialog_hpp_ +#define slic3r_EditGCodeDialog_hpp_ + +#include + +#include +#include + +#include "GUI_Utils.hpp" +#include "wxExtensions.hpp" +#include "libslic3r/Preset.hpp" +#include "libslic3r/PrintConfig.hpp" +#include + +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 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>; + +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> 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 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 diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 235ba99509..65cac2cc5e 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -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("#"); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index a951c02132..d86a790eb4 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -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& 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 diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a2a72824a1..2273a91f2c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -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); diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 2aef316b48..dadde84ebc 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -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