diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 65264c9cd..b5ef3da5b 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -870,8 +870,11 @@ void GCode::_do_export(Print &print, FILE *file)
     this->apply_print_config(print.config());
     this->set_extruders(print.extruders());
     
-    // Initialize colorprint.
+    //  #ys_FIXME_COLOR // Initialize colorprint.
     m_colorprint_heights = cast<float>(print.config().colorprint_heights.values);
+    // Initialize custom gcode
+    Model* model = print.get_object(0)->model_object()->get_model();
+    m_custom_g_code_heights = model->custom_gcode_per_height;
 
     // Initialize autospeed.
     {
@@ -1676,8 +1679,15 @@ void GCode::process_layer(
     // In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all.
     // (Layers can be close to each other, model could have been resliced with bigger layer height, ...).
     bool colorprint_change = false;
-    while (!m_colorprint_heights.empty() && m_colorprint_heights.front()-EPSILON < layer.print_z) {
-        m_colorprint_heights.erase(m_colorprint_heights.begin());
+    //  #ys_FIXME_COLOR
+    // while (!m_colorprint_heights.empty() && m_colorprint_heights.front()-EPSILON < layer.print_z) {
+    //     m_colorprint_heights.erase(m_colorprint_heights.begin());
+    //     colorprint_change = true;
+    // } 
+    std::string custom_code = "";
+    while (!m_custom_g_code_heights.empty() && m_custom_g_code_heights.front().height-EPSILON < layer.print_z) {
+        custom_code = m_custom_g_code_heights.front().gcode;
+        m_custom_g_code_heights.erase(m_custom_g_code_heights.begin());
         colorprint_change = true;
     }
 
@@ -1688,7 +1698,9 @@ void GCode::process_layer(
         gcode += "; " + GCodeAnalyzer::Color_Change_Tag + "\n";
         // add tag for time estimator
         gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n";
-        gcode += "M600\n";
+        //  #ys_FIXME_COLOR
+        // gcode += "M600\n";
+        gcode += custom_code + "\n";
     }
 
 
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
index 45ff7eda6..a1a83983e 100644
--- a/src/libslic3r/GCode.hpp
+++ b/src/libslic3r/GCode.hpp
@@ -353,6 +353,8 @@ protected:
     // Layer heights for colorprint - updated before the export and erased during the process
     // so no toolchange occurs twice.
     std::vector<float> m_colorprint_heights;
+    // extensions for colorprint - now it's not a just color_print, there can be some custom gcode
+    std::vector<Model::CustomGCode> m_custom_g_code_heights;
 
     // Time estimators
     GCodeTimeEstimator m_normal_time_estimator;
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 410c2d3ef..2a1edd0ca 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -745,6 +745,32 @@ public:
     ModelObjectPtrs     objects;
     // Wipe tower object.
     ModelWipeTower	    wipe_tower;
+
+    // Extensions for 
+    struct CustomGCode
+    {
+        CustomGCode(double height, const std::string& code, int extruder) :
+            height(height), gcode(code), extruder(extruder) {}
+
+        bool operator<(const CustomGCode& other) const { return other.height > this->height; }
+        bool operator==(const CustomGCode& other) const
+        {
+            return (other.height    == this->height)     && 
+                   (other.gcode     == this->gcode)      && 
+                   (other.extruder  == this->extruder   );
+        }
+        bool operator!=(const CustomGCode& other) const
+        {
+            return (other.height    != this->height)     || 
+                   (other.gcode     != this->gcode)      || 
+                   (other.extruder  != this->extruder   );
+        }
+        
+        double      height;
+        std::string gcode;
+        int         extruder;
+    };
+    std::vector<CustomGCode> custom_gcode_per_height;
     
     // Default constructor assigns a new ID to the model.
     Model() { assert(this->id().valid()); }
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 88645df15..3212082a3 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -749,6 +749,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
                     delete model_object;
             }
         }
+        if (model.custom_gcode_per_height != m_model.custom_gcode_per_height)
+        {
+            update_apply_status(this->invalidate_step(psGCodeExport));
+            m_model.custom_gcode_per_height = model.custom_gcode_per_height;
+        }
     }
 
     // 2) Map print objects including their transformation matrices.
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index c55d64b47..2aa10a9bb 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -839,6 +839,8 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePrevie
     if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint && 
         wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets
     {
+        /*
+        // #ys_FIXME_COLOR
         auto& config = wxGetApp().preset_bundle->project_config;
         const std::vector<double>& color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values;
         
@@ -854,6 +856,27 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePrevie
                 double current_z    = *lower_b;
                 double previous_z   = lower_b == print_zs.begin() ? 0.0 : *(--lower_b);
 
+                // to avoid duplicate values, check adding values
+                if (cp_legend_values.empty() || 
+                    !(cp_legend_values.back().first == previous_z && cp_legend_values.back().second == current_z) )
+                    cp_legend_values.push_back(std::pair<double, double>(previous_z, current_z));
+            }
+        }
+        */
+        std::vector<Model::CustomGCode> custom_gcode_per_height = wxGetApp().plater()->model().custom_gcode_per_height;
+        
+        if (!custom_gcode_per_height.empty()) {
+            std::vector<double> print_zs = canvas.get_current_print_zs(true);
+            for (auto custom_code : custom_gcode_per_height)
+            {
+                auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.height - DoubleSlider::epsilon());
+
+                if (lower_b == print_zs.end())
+                    continue;
+
+                double current_z    = *lower_b;
+                double previous_z   = lower_b == print_zs.begin() ? 0.0 : *(--lower_b);
+
                 // to avoid duplicate values, check adding values
                 if (cp_legend_values.empty() || 
                     !(cp_legend_values.back().first == previous_z && cp_legend_values.back().second == current_z) )
diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp
index 98f6a8ee6..df7b5cf54 100644
--- a/src/slic3r/GUI/GUI_Preview.cpp
+++ b/src/slic3r/GUI/GUI_Preview.cpp
@@ -562,12 +562,22 @@ void Preview::update_view_type()
 {
     const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config;
 
+    /*
+    // #ys_FIXME_COLOR
     const wxString& choice = !config.option<ConfigOptionFloats>("colorprint_heights")->values.empty() && 
                              wxGetApp().extruders_edited_cnt()==1 ? 
                                 _(L("Color Print")) :
                                 config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ?
                                     _(L("Tool")) : 
                                     _(L("Feature type"));
+    */
+
+    const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_height.empty() &&
+                             wxGetApp().extruders_edited_cnt()==1 ? 
+                                _(L("Color Print")) :
+                                config.option<ConfigOptionFloats>("wiping_volumes_matrix")->values.size() > 1 ?
+                                    _(L("Tool")) : 
+                                    _(L("Feature type"));
 
     int type = m_choice_view_type->FindString(choice);
     if (m_choice_view_type->GetSelection() != type) {
@@ -609,7 +619,11 @@ void Preview::create_double_slider()
 
 
     Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
-        wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights")->values = m_slider->GetTicksValues();
+        // #ys_FIXME_COLOR
+        // wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights")->values = m_slider->GetTicksValues();
+
+        Model& model = wxGetApp().plater()->model();
+        model.custom_gcode_per_height = m_slider->GetTicksValues_();
         m_schedule_background_process();
 
         update_view_type();
@@ -645,6 +659,24 @@ static int find_close_layer_idx(const std::vector<double>& zs, double &z, double
     return -1;
 }
 
+void Preview::check_slider_values(std::vector<Model::CustomGCode>& ticks_from_model,
+                                  const std::vector<double>& layers_z)
+{
+    // All ticks that would end up outside the slider range should be erased.
+    // TODO: this should be placed into more appropriate part of code,
+    // this function is e.g. not called when the last object is deleted
+    unsigned int old_size = ticks_from_model.size();
+    ticks_from_model.erase(std::remove_if(ticks_from_model.begin(), ticks_from_model.end(),
+                     [layers_z](Model::CustomGCode val)
+        {
+            auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.height - DoubleSlider::epsilon());
+            return it == layers_z.end();
+        }),
+        ticks_from_model.end());
+    if (ticks_from_model.size() != old_size)
+        m_schedule_background_process();
+}
+
 void Preview::update_double_slider(const std::vector<double>& layers_z, bool keep_z_range)
 {
     // Save the initial slider span.
@@ -660,8 +692,11 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
     bool   snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min();
 	bool   snap_to_max  = force_sliders_full_range || m_slider->is_higher_at_max();
 
-    std::vector<double> &ticks_from_config = (wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights"))->values;
-    check_slider_values(ticks_from_config, layers_z);
+    // #ys_FIXME_COLOR
+    // std::vector<double> &ticks_from_config = (wxGetApp().preset_bundle->project_config.option<ConfigOptionFloats>("colorprint_heights"))->values;
+    // check_slider_values(ticks_from_config, layers_z);
+    std::vector<Model::CustomGCode> &ticks_from_model = wxGetApp().plater()->model().custom_gcode_per_height;
+    check_slider_values(ticks_from_model, layers_z);
 
     m_slider->SetSliderValues(layers_z);
     assert(m_slider->GetMinValue() == 0);
@@ -683,7 +718,9 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
     }
     m_slider->SetSelectionSpan(idx_low, idx_high);
 
-    m_slider->SetTicksValues(ticks_from_config);
+    // #ys_FIXME_COLOR
+    // m_slider->SetTicksValues(ticks_from_config);
+    m_slider->SetTicksValues_(ticks_from_model);
 
     bool color_print_enable = (wxGetApp().plater()->printer_technology() == ptFFF);
     if (color_print_enable) {
@@ -693,7 +730,7 @@ void Preview::update_double_slider(const std::vector<double>& layers_z, bool kee
     }
     m_slider->EnableTickManipulation(color_print_enable);
 }
-
+//  #ys_FIXME_COLOR
 void Preview::check_slider_values(std::vector<double>& ticks_from_config,
                                  const std::vector<double> &layers_z)
 {
@@ -799,10 +836,13 @@ void Preview::load_print_as_fff(bool keep_z_range)
     {
         colors = GCodePreviewData::ColorPrintColors();
         if (! gcode_preview_data_valid) {
-            //FIXME accessing full_config() is pretty expensive.
-            // Only initialize color_print_values for the initial preview, not for the full preview where the color_print_values is extracted from the G-code.
-            const auto& config = wxGetApp().preset_bundle->project_config;
-            color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values;
+            // #ys_FIXME_COLOR
+            // const auto& config = wxGetApp().preset_bundle->project_config;
+            // color_print_values = config.option<ConfigOptionFloats>("colorprint_heights")->values;
+            const std::vector<Model::CustomGCode>& custom_codes = wxGetApp().plater()->model().custom_gcode_per_height;
+            color_print_values.reserve(custom_codes.size());
+            for (const Model::CustomGCode& code : custom_codes)
+                color_print_values.push_back(code.height);
         }
     }
     else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) )
diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp
index 08d5991b4..551900277 100644
--- a/src/slic3r/GUI/GUI_Preview.hpp
+++ b/src/slic3r/GUI/GUI_Preview.hpp
@@ -5,6 +5,7 @@
 #include "libslic3r/Point.hpp"
 
 #include <string>
+#include "libslic3r/Model.hpp"
 
 class wxNotebook;
 class wxGLCanvas;
@@ -154,9 +155,11 @@ private:
 
     // Create/Update/Reset double slider on 3dPreview
     void create_double_slider();
+    void check_slider_values(std::vector<Model::CustomGCode> &ticks_from_model,
+                             const std::vector<double> &layers_z);
     void update_double_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
     void check_slider_values(std::vector<double> &ticks_from_config,
-                            const std::vector<double> &layers_z);
+                            const std::vector<double> &layers_z); //  #ys_FIXME_COLOR
     void reset_double_slider();
     // update DoubleSlider after keyDown in canvas
     void update_double_slider_from_canvas(wxKeyEvent& event);
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 50621caa8..6812c2b81 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -2688,8 +2688,10 @@ void Plater::priv::reset()
     // The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
     this->sidebar->show_sliced_info_sizer(false);
 
-    auto& config = wxGetApp().preset_bundle->project_config;
-    config.option<ConfigOptionFloats>("colorprint_heights")->values.clear();
+    // #ys_FIXME_COLOR
+    // auto& config = wxGetApp().preset_bundle->project_config;
+    // config.option<ConfigOptionFloats>("colorprint_heights")->values.clear();
+    model.custom_gcode_per_height.clear();
 }
 
 void Plater::priv::mirror(Axis axis)
diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp
index 9536e3560..aaa8c86fd 100644
--- a/src/slic3r/GUI/wxExtensions.cpp
+++ b/src/slic3r/GUI/wxExtensions.cpp
@@ -2469,6 +2469,44 @@ std::vector<double> DoubleSlider::GetTicksValues() const
     return values;
 }
 
+using t_custom_code = Slic3r::Model::CustomGCode;
+std::vector<t_custom_code> DoubleSlider::GetTicksValues_() const
+{
+    std::vector<t_custom_code> values;
+
+    const int val_size = m_values.size();
+    if (!m_values.empty())
+        for (const TICK_CODE& tick : m_ticks_) {
+            if (tick.tick > val_size)
+                break;
+            values.push_back(t_custom_code(m_values[tick.tick], tick.gcode, tick.extruder));
+        }
+
+    return values;
+}
+
+void DoubleSlider::SetTicksValues_(const std::vector<t_custom_code>& heights)
+{
+    if (m_values.empty())
+        return;
+
+    const bool was_empty = m_ticks_.empty();
+
+    m_ticks_.clear();
+    for (auto h : heights) {
+        auto it = std::lower_bound(m_values.begin(), m_values.end(), h.height - epsilon());
+
+        if (it == m_values.end())
+            continue;
+
+        m_ticks_.insert(TICK_CODE(it-m_values.begin(), h.gcode, h.extruder));
+    }
+    
+    if (!was_empty && m_ticks_.empty())
+        // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
+        wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
+}
+
 void DoubleSlider::SetTicksValues(const std::vector<double>& heights)
 {
     if (m_values.empty())
@@ -2577,19 +2615,12 @@ void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoin
     if (tick == 0)
         return;
 
+    wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp();
     // #ys_FIXME_COLOR
-    // wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp();
     // if (m_ticks.find(tick) != m_ticks.end())
     //     icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp();
-    wxBitmap* icon = m_action_icon_focesed > 0 ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp();
-    auto tick_code_it = m_ticks_.find(tick);
-    if (tick_code_it != m_ticks_.end()) {
-        icon = m_action_icon_focesed > 0 ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp();
-
-        if (m_action_icon_focesed > 0)
-            m_action_icon_focesed = tick_code_it->gcode == "M600" ? fiDelColorChange :
-                                    tick_code_it->gcode == "M25"  ? fiDelPause : fiDelCustomCode;
-    }
+    if (m_ticks_.find(tick) != m_ticks_.end())
+        icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp();
 
     wxCoord x_draw, y_draw;
     is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim;
@@ -3017,10 +3048,9 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
     bool is_revert_icon_focused = false;
 
     if (!m_is_left_down && !m_is_one_layer) {
+        m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action);
         // #ys_FIXME_COLOR
-        // m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action);
         // is_revert_icon_focused = !m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon);
-        m_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action) ? fiAdd : fiNone;
         is_revert_icon_focused = !m_ticks_.empty() && is_point_in_rect(pos, m_rect_revert_icon);
     }
     else if (m_is_left_down || m_is_right_down) {
@@ -3042,13 +3072,25 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
     event.Skip();
 
     // Set tooltips with information for each icon
-    const wxString tooltip = m_is_one_layer_icon_focesed    ? _(L("One layer mode"))    :
-                             // m_is_action_icon_focesed       ? _(L("Add/Del color change")) :
-                             m_action_icon_focesed == fiAdd             ? _(L("Add color change")) :
-                             m_action_icon_focesed == fiDelColorChange  ? _(L("Delete color change")) :
-                             m_action_icon_focesed == fiDelPause        ? _(L("Delete pause")) :
-                             m_action_icon_focesed == fiDelCustomCode   ? _(L("Delete custom code")) :
-                             is_revert_icon_focused         ? _(L("Discard all color changes")) : "";
+    // #ys_FIXME_COLOR
+    // const wxString tooltip = m_is_one_layer_icon_focesed    ? _(L("One layer mode"))    :
+    //                          m_is_action_icon_focesed       ? _(L("Add/Del color change")) :
+    //                          is_revert_icon_focused         ? _(L("Discard all color changes")) : "";
+    wxString tooltip(wxEmptyString);
+    if (m_is_one_layer_icon_focesed)
+        tooltip = _(L("One layer mode"));
+    if (is_revert_icon_focused)
+        tooltip = _(L("Discard all custom changes"));
+    else if (m_is_action_icon_focesed)
+    {
+        const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
+        const auto tick_code_it = m_ticks_.find(tick);
+        tooltip = tick_code_it == m_ticks_.end()    ? _(L("Add color change")) :
+                  tick_code_it->gcode == "M600"     ? _(L("Delete color change")) :
+                  tick_code_it->gcode == "M25"      ? _(L("Delete pause")) : 
+                  from_u8((boost::format(_utf8(L("Delete \"%1%\" code"))) % tick_code_it->gcode).str());
+    }
+
     this->SetToolTip(tooltip);
 
     if (action)
diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp
index a41fe1379..0ee451f44 100644
--- a/src/slic3r/GUI/wxExtensions.hpp
+++ b/src/slic3r/GUI/wxExtensions.hpp
@@ -16,6 +16,7 @@
 #include <vector>
 #include <set>
 #include <functional>
+#include "libslic3r/Model.hpp"
 
 namespace Slic3r {
     enum class ModelVolumeType : int;
@@ -795,6 +796,8 @@ public:
     }
     void ChangeOneLayerLock();
     std::vector<double> GetTicksValues() const;
+    std::vector<Slic3r::Model::CustomGCode> GetTicksValues_() const;
+    void SetTicksValues_(const std::vector<Slic3r::Model::CustomGCode> &heights);
     void SetTicksValues(const std::vector<double>& heights);
     void EnableTickManipulation(bool enable = true) {
         m_is_enabled_tick_manipulation = enable;
@@ -883,14 +886,6 @@ private:
     bool        m_is_one_layer = false;
     bool        m_is_focused = false;
     bool        m_is_action_icon_focesed = false;
-    enum FocusedIcon
-    {
-        fiNone = 0,
-        fiAdd,
-        fiDelColorChange,
-        fiDelPause,
-        fiDelCustomCode
-    } m_action_icon_focesed { fiNone };
     bool        m_is_one_layer_icon_focesed = false;
     bool        m_is_enabled_tick_manipulation = true;
     bool        m_show_context_menu = false;