From e91a636152499f95da04ba75badee132dbdd7cfd Mon Sep 17 00:00:00 2001
From: GregValiant <64202104+GregValiant@users.noreply.github.com>
Date: Wed, 1 Jan 2025 11:54:47 -0500
Subject: [PATCH 1/8] Update AddCoolingProfile.py

Add control for a Build Volume fan.

Update AddCoolingProfile.py

Oops.  Left in a debugging line.
---
 .../scripts/AddCoolingProfile.py              | 286 +++++++++++++-----
 1 file changed, 209 insertions(+), 77 deletions(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index 44709afd24..ad00fbd923 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -1,16 +1,18 @@
 # Designed in January 2023 by GregValiant (Greg Foresi)
-##   My design intent was to make this as full featured and "industrial strength" as I could.  People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print.  This script allows that.
+#   My design intent was to make this as full featured and "industrial strength" as I could.  People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print.  This script allows that.
 #    Functions:
-##    Remove all fan speed lines from the file (optional).  This should be enabled for the first instance of the script.  It is disabled by default in any following instances.
-##    "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print.  "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
-##    If 'By Feature' then a Start Layer and/or an End Layer can be defined.
-##    Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
-##    A minimum fan speed of 12% is enforced.  It is the slowest speed that my cooling fan will turn on so that's what I used.  'M106 S14' (as Cura might insert) was pretty useless.
-##    If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting.  There is support for up to 4 layer cooling fan circuits.
-##    My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir.  A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
-##    9/14/23  (Greg Foresi) Added support for One-at-a-Time print sequence.
-##    12/15/23  (Greg Foresi) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
-##    1/5/24  (Greg Foresi) Revised the regex replacements.
+#    Remove all fan speed lines from the file (optional).  This should be enabled for the first instance of the script.  It is disabled by default in any following instances.
+#    "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print.  "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
+#    If 'By Feature' then a Start Layer and/or an End Layer can be defined.
+#    Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
+#    A minimum fan speed of 12% is enforced.  It is the slowest speed that my cooling fan will turn on so that's what I used.  'M106 S14' (as Cura might insert) was pretty useless.
+#    If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting.  There is support for up to 4 layer cooling fan circuits.
+#    My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir.  A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
+#    09/14/23  (GV) Added support for One-at-a-Time print sequence.
+#    12/15/23  (GV) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
+#    01/05/24  (GV) Revised the regex replacements.
+#    12/11/24  (GV) Added 'off_fan_speed' for the idle nozzle layer cooling fan.  It does not have to go to 0%.
+#    01/01/25  (GV) Added 'Build Volume' fan control
 
 from ..Script import Script
 from UM.Application import Application
@@ -273,37 +275,120 @@ class AddCoolingProfile(Script):
                     "maximum_value": 100,
                     "unit": "%    ",
                     "enabled": "fan_enable_raft"
+                },
+                "enable_off_fan_speed":
+                {
+                    "label": "Enable 'Off speed' of the idle fan",
+                    "description": "For machines with independent layer cooling fans.  Leaving a fan running while the other nozzle is printing can help with oozing.  You can pick the speed % for the idle nozzle layer cooling fan to hold at.",
+                    "type": "bool",
+                    "default_value": false,
+                    "enabled": "enable_off_fan_speed_enable"
+                },
+                "off_fan_speed":
+                {
+                    "label": "    'Off' speed of idle nozzle fan",
+                    "description": "This is the speed that the 'idle nozzle' layer cooling fan will maintain rather than being turned off completely.",
+                    "type": "int",
+                    "default_value": 35,
+                    "minimum_value": 0,
+                    "maximum_value": 100,
+                    "unit": "%    ",
+                    "enabled": "enable_off_fan_speed_enable and enable_off_fan_speed"
+                },
+                "enable_off_fan_speed_enable":
+                {
+                    "label": "Hidden setting",
+                    "description": "For dual extruder printers, this enables 'enable_off_fan_speed'.",
+                    "type": "bool",
+                    "default_value": false,
+                    "enabled": false
+                },
+                "bv_fan_speed_control_enable":
+                {
+                    "label": "Enable 'Chamber Fan' control",
+                    "description": "Available if the 'Build Volume Fan Number' > 0 in 'Printer Settings'.  Provides: On layer, off layer, and PWM speed control of the Chamber fan.",
+                    "type": "bool",
+                    "default_value": false,
+                    "enabled": "enable_bv_fan"
+                },
+                "bv_fan_speed":
+                {
+                    "label": "    Chamber fan speed %",
+                    "description": "The speed of the Chamber Fan.  This will be converted to PWM Duty Cycle (0-255).",
+                    "type": "int",
+                    "unit": "%    ",
+                    "default_value": 50,
+                    "maximum_value": 100,
+                    "minimum_value": 0,
+                    "enabled": "enable_bv_fan and bv_fan_speed_control_enable"
+                },
+                "bv_fan_start_layer":
+                {
+                    "label": "        Start Layer",
+                    "description": "The layer number for Chamber Fan start.  Use the Cura preview layer number.  If you are using a raft the chanber fan will start when the raft finishes.",
+                    "type": "int",
+                    "default_value": 1,
+                    "minimum_value": 1,
+                    "enabled": "enable_bv_fan and bv_fan_speed_control_enable"
+                },
+                "bv_fan_end_layer":
+                {
+                    "label": "        End Layer",
+                    "description": "The layer number for Chamber Fan to turn off.  Use the Cura preview layer number or '-1' to indicate the end of the print.",
+                    "type": "int",
+                    "default_value": -1,
+                    "minimum_value": -1,
+                    "enabled": "enable_bv_fan and bv_fan_speed_control_enable"
+                },
+                "enable_bv_fan":
+                {
+                    "label": "Hidden setting",
+                    "description": "For printers with heated chambers and chamber fans, this enables 'bv_fan_speed_control_enable'.",
+                    "type": "bool",
+                    "default_value": false,
+                    "enabled": false
                 }
             }
         }"""
 
     def initialize(self) -> None:
         super().initialize()
-        scripts = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("post_processing_scripts")
+        curaApp = Application.getInstance().getGlobalContainerStack()
+        extruder = curaApp.extruderList
+        scripts = curaApp.getMetaDataEntry("post_processing_scripts")
         if scripts != None:
             script_count = scripts.count("AddCoolingProfile")
             if script_count > 0:
-                ## Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
+                # Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
                 self._instance.setProperty("delete_existing_m106", "value", False)
+        if curaApp.getProperty("machine_extruder_count", "value") > 1:
+            if extruder[0].getProperty("machine_extruder_cooling_fan_number", "value") != extruder[1].getProperty("machine_extruder_cooling_fan_number", "value"):
+                self._instance.setProperty("enable_off_fan_speed_enable", "value", True)
+        self.has_bv_fan = False
+        self.bv_fan_nr = -1
+        if int(curaApp.getProperty("build_volume_fan_nr", "value")) > 0:
+            self.has_bv_fan = True
+            self.bv_fan_nr = int(curaApp.getProperty("build_volume_fan_nr", "value"))
+            self._instance.setProperty("enable_bv_fan", "value", True)
 
     def execute(self, data):
         #Initialize variables that are buried in if statements.
-        mycura = Application.getInstance().getGlobalContainerStack()
+        curaApp = Application.getInstance().getGlobalContainerStack()
         t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
 
         #Get some information from Cura-----------------------------------
-        extruder = mycura.extruderList
+        extruder = curaApp.extruderList
 
         #This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
         fan_mode = True
-        ##For 4.x versions that don't have the 0-1 option
+        #For 4.x versions that don't have the 0-1 option
         try:
             fan_mode = not bool(extruder[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
         except:
             pass
         bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
-        extruder_count = mycura.getProperty("machine_extruder_count", "value")
-        print_sequence = str(mycura.getProperty("print_sequence", "value"))
+        extruder_count = curaApp.getProperty("machine_extruder_count", "value")
+        print_sequence = str(curaApp.getProperty("print_sequence", "value"))
 
         #Assign the fan numbers to the tools------------------------------
         if extruder_count == 1:
@@ -333,7 +418,7 @@ class AddCoolingProfile(Script):
         #Assign the variable values if "By Layer"-------------------------
         by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature")
         if  by_layer_or_feature == "by_layer":
-            ## By layer doesn't do any feature search so there is no need to look for combing moves
+            # By layer doesn't do any feature search so there is no need to look for combing moves
             feature_fan_combing = False
             fan_list[0] = self.getSettingValueByKey("layer_fan_1")
             fan_list[2] = self.getSettingValueByKey("layer_fan_2")
@@ -343,25 +428,25 @@ class AddCoolingProfile(Script):
             fan_list[10] = self.getSettingValueByKey("layer_fan_6")
             fan_list[12] = self.getSettingValueByKey("layer_fan_7")
             fan_list[14] = self.getSettingValueByKey("layer_fan_8")
-            ## If there is no '/' delimiter then ignore the line else put the settings in a list
+            # If there is no '/' delimiter then ignore the line else put the settings in a list
             for num in range(0,15,2):
                 if "/" in fan_list[num]:
                     fan_list[num + 1] = self._layer_checker(fan_list[num], "p", fan_mode)
                     fan_list[num] = self._layer_checker(fan_list[num], "l", fan_mode)
 
-        ## Assign the variable values if "By Feature"
+        # Assign the variable values if "By Feature"
         elif by_layer_or_feature == "by_feature":
             the_start_layer = self.getSettingValueByKey("feature_fan_start_layer") - 1
             the_end_layer = self.getSettingValueByKey("feature_fan_end_layer")
             try:
                 if int(the_end_layer) != -1:
-                    ## Catch a possible input error.
+                    # Catch a possible input error.
                     if the_end_layer < the_start_layer:
                         the_end_layer = the_start_layer
             except:
-                the_end_layer = -1    ## If there is an input error default to the entire gcode file.
+                the_end_layer = -1  # If there is an input error then default to the entire gcode file.
 
-            ## Get the speed for each feature
+            # Get the speed for each feature
             feature_name_list = []
             feature_speed_list = []
             feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_skirt"), fan_mode)); feature_name_list.append(";TYPE:SKIRT")
@@ -376,19 +461,28 @@ class AddCoolingProfile(Script):
             feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_feature_final"), fan_mode)); feature_name_list.append("FINAL_FAN")
             feature_fan_combing = self.getSettingValueByKey("feature_fan_combing")
             if the_end_layer > -1 and by_layer_or_feature == "by_feature":
-                ## Required so the final speed input can be determined
+                # Required so the final speed input can be determined
                 the_end_is_enabled = True
             else:
-                ## There is no ending layer so do the whole file
+                # There is no ending layer so do the whole file
                 the_end_is_enabled = False
             if the_end_layer == -1 or the_end_is_enabled == False:
                 the_end_layer = len(data) + 2
 
-        ## Find the Layer0Index and the RaftIndex
+        # For multi-extruder printers with separate fans the 'idle' nozzle fan can be left on for ooze control
+        off_fan_speed = 0
+        if Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") > 1:
+            if self.getSettingValueByKey("enable_off_fan_speed"):
+                if fan_mode:
+                    off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * 2.55)
+                else:
+                    off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * .01, 2)
+
+        # Find the Layer0Index and the RaftIndex
         raft_start_index = 0
         number_of_raft_layers = 0
         layer_0_index = 0
-        ## Catch the number of raft layers.
+        # Catch the number of raft layers.
         for l_num in range(1,10,1):
             layer = data[l_num]
             if ";LAYER:-" in layer:
@@ -399,13 +493,13 @@ class AddCoolingProfile(Script):
                 layer_0_index = l_num
                 break
 
-        ## Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used.
+        # Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used.
         if is_multi_fan:
             T0_used = False
             T1_used = False
             T2_used = False
             T3_used = False
-            ## Bypass the file header and ending gcode.
+            # Bypass the file header and ending gcode.
             for num in range(1,len(data)-1,1):
                 lines = data[num]
                 if "T0" in lines:
@@ -418,7 +512,7 @@ class AddCoolingProfile(Script):
                     T3_used = True
             is_multi_extr_print = True if sum([T0_used, T1_used, T2_used, T3_used]) > 1 else False
 
-            ## On a multi-extruder printer and single extruder print find out which extruder starts the file.
+            # On a multi-extruder printer and single extruder print find out which extruder starts the file.
             init_fan = t0_fan
             if not is_multi_extr_print:
                 startup = data[1]
@@ -431,7 +525,7 @@ class AddCoolingProfile(Script):
                     elif line == "T3":
                         t0_fan = t3_fan
             elif is_multi_extr_print:
-            ## On a multi-extruder printer and multi extruder print find out which extruder starts the file.
+            # On a multi-extruder printer and multi extruder print find out which extruder starts the file.
                 startup = data[1]
                 lines = startup.split("\n")
                 for line in lines:
@@ -445,7 +539,7 @@ class AddCoolingProfile(Script):
                         init_fan = t3_fan
         else:
             init_fan = ""
-        ## Assign the variable values if "Raft Enabled"
+        # Assign the variable values if "Raft Enabled"
         raft_enabled = self.getSettingValueByKey("fan_enable_raft")
         if raft_enabled and bed_adhesion == "raft":
             fan_sp_raft = self._feature_checker(self.getSettingValueByKey("fan_raft_percent"), fan_mode)
@@ -453,15 +547,15 @@ class AddCoolingProfile(Script):
             fan_sp_raft = "M106 S0"
 
         # Start to alter the data-----------------------------------------
-        ## Strip the existing M106 lines from the file up to the end of the last layer.  If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option.
+        # Strip the existing M106 lines from the file up to the end of the last layer.  If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option.
         delete_existing_m106 = self.getSettingValueByKey("delete_existing_m106")
         if delete_existing_m106:
-        ## Start deleting from the beginning
+        # Start deleting from the beginning
             start_from = int(raft_start_index)
         else:
             if by_layer_or_feature == "by_layer":
                 altered_start_layer = str(len(data))
-                ## The fan list layers don't need to be in ascending order.  Get the lowest.
+                # The fan list layers don't need to be in ascending order.  Get the lowest.
                 for num in range(0,15,2):
                     try:
                         if int(fan_list[num]) < int(altered_start_layer):
@@ -471,12 +565,12 @@ class AddCoolingProfile(Script):
             elif by_layer_or_feature == "by_feature":
                 altered_start_layer = int(the_start_layer) - 1
             start_from = int(layer_0_index) + int(altered_start_layer)
-        ## Strip the M106 and M107 lines from the file
+        # Strip the M106 and M107 lines from the file
         for l_index in range(int(start_from), len(data) - 1, 1):
             data[l_index] = re.sub(re.compile("M106(.*)\n"), "", data[l_index])
             data[l_index] = re.sub(re.compile("M107(.*)\n"), "", data[l_index])
 
-        ## Deal with a raft and with One-At-A-Time print sequence
+        # Deal with a raft and with One-At-A-Time print sequence
         if raft_enabled and bed_adhesion == "raft":
             if print_sequence == "one_at_a_time":
                 for r_index in range(2,len(data)-2,1):
@@ -486,9 +580,9 @@ class AddCoolingProfile(Script):
                             lines.insert(1, "M106 S0" + str(t0_fan))
                     if raft_enabled and bed_adhesion == "raft":
                         if ";LAYER:-" in data[r_index]:
-                        ## Turn the raft fan on
+                        # Turn the raft fan on
                             lines.insert(1, fan_sp_raft + str(t0_fan))
-                        ## Shut the raft fan off at layer 0
+                        # Shut the raft fan off at layer 0
                         if ";LAYER:0" in data[r_index]:
                             lines.insert(1,"M106 S0" + str(t0_fan))
                     data[r_index] = "\n".join(lines)
@@ -496,13 +590,13 @@ class AddCoolingProfile(Script):
                 layer = data[raft_start_index]
                 lines = layer.split("\n")
                 if ";LAYER:-" in layer:
-                    ## Turn the raft fan on
+                    # Turn the raft fan on
                     lines.insert(1, fan_sp_raft + str(init_fan))
                 layer = "\n".join(lines)
                 data[raft_start_index] = layer
                 layer = data[layer_0_index]
                 lines = layer.split("\n")
-                ## Shut the raft fan off
+                # Shut the raft fan off
                 lines.insert(1, "M106 S0" + str(init_fan))
                 data[layer_0_index] = "\n".join(lines)
         else:
@@ -513,31 +607,34 @@ class AddCoolingProfile(Script):
                         lines.insert(1, "M106 S0" + str(t0_fan))
                 data[r_index] = "\n".join(lines)
 
-        ## Turn off all fans at the end of data[1].  If more than one instance of this script is running then this will result in multiple M106 lines.
+        # Turn off all fans at the end of data[1].  If more than one instance of this script is running then this will result in multiple M106 lines.
         temp_startup = data[1].split("\n")
         temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t0_fan))
-        ## If there are multiple cooling fans shut them all off
+        # If there are multiple cooling fans shut them all off
         if is_multi_fan:
             if extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
             if extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan))
             if extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan))
         data[1] = "\n".join(temp_startup)
 
-        ## If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
-        ## For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH.
+        # If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
+        # For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH.
         if feature_fan_combing:
             for layer_num in range(2,len(data)):
                 layer = data[layer_num]
                 data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
             data = self._add_travel_comment(data, layer_0_index)
-
+        # If there is a build volume fan
+        if self.has_bv_fan:
+            if self.getSettingValueByKey("bv_fan_speed_control_enable"):
+                data = self._control_bv_fan(data)
         # Single Fan "By Layer"--------------------------------------------
         if by_layer_or_feature == "by_layer" and not is_multi_fan:
             return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
 
         # Multi-Fan "By Layer"---------------------------------------------
         if by_layer_or_feature == "by_layer" and is_multi_fan:
-            return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan)
+            return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, fan_mode, off_fan_speed)
 
         #Single Fan "By Feature"------------------------------------------
         if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print):
@@ -545,7 +642,7 @@ class AddCoolingProfile(Script):
 
         #Multi Fan "By Feature"-------------------------------------------
         if by_layer_or_feature == "by_feature" and is_multi_fan:
-            return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing)
+            return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing, fan_mode, off_fan_speed)
 
     # The Single Fan "By Layer"----------------------------------------
     def _single_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str)->str:
@@ -557,7 +654,7 @@ class AddCoolingProfile(Script):
             for fan_line in fan_lines:
                 if ";LAYER:" in fan_line:
                     layer_number = str(fan_line.split(":")[1])
-                    ## If there is a match for the current layer number make the insertion
+                    # If there is a match for the current layer number make the insertion
                     for num in range(0,15,2):
                         if layer_number == str(fan_list[num]):
                             layer = layer.replace(fan_lines[0],fan_lines[0] + "\n" + fan_list[num + 1] + str(t0_fan))
@@ -565,7 +662,7 @@ class AddCoolingProfile(Script):
         return single_fan_data
 
     # Multi-Fan "By Layer"-----------------------------------------
-    def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str)->str:
+    def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, fan_mode: bool, off_fan_speed: str)->str:
         multi_fan_data = data
         layer_number = "0"
         current_fan_speed = "0"
@@ -573,15 +670,15 @@ class AddCoolingProfile(Script):
         this_fan = str(t0_fan)
         start_index = str(len(multi_fan_data))
         for num in range(0,15,2):
-        ## The fan_list may not be in ascending order.  Get the lowest layer number
+        # The fan_list may not be in ascending order.  Get the lowest layer number
             try:
                 if int(fan_list[num]) < int(start_index):
                     start_index = str(fan_list[num])
             except:
                 pass
-        ## Move the start point if delete_existing_m106 is false
+        # Move the start point if delete_existing_m106 is false
         start_index = int(start_index) + int(layer_0_index)
-        ## Track the tool number
+        # Track the tool number
         for num in range(1,int(start_index),1):
             layer = multi_fan_data[num]
             lines = layer.split("\n")
@@ -603,13 +700,13 @@ class AddCoolingProfile(Script):
             layer = multi_fan_data[l_index]
             fan_lines = layer.split("\n")
             for fan_line in fan_lines:
-                ## Prepare to shut down the previous fan and start the next one.
+                # Prepare to shut down the previous fan and start the next one.
                 if fan_line.startswith("T"):
                     if fan_line == "T0": this_fan = str(t0_fan)
                     if fan_line == "T1": this_fan = str(t1_fan)
                     if fan_line == "T2": this_fan = str(t2_fan)
                     if fan_line == "T3": this_fan = str(t3_fan)
-                    modified_data += "M106 S0" + prev_fan + "\n"
+                    modified_data += f"M106 S{off_fan_speed}" + prev_fan + "\n"
                     modified_data += fan_line + "\n"
                     modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
                     prev_fan = this_fan
@@ -620,11 +717,14 @@ class AddCoolingProfile(Script):
                         if layer_number == str(fan_list[num]):
                             modified_data += fan_list[num + 1] + this_fan + "\n"
                             current_fan_speed = str(fan_list[num + 1].split("S")[1])
-                            current_fan_speed = str(current_fan_speed.split(" ")[0]) ## Just in case
+                            current_fan_speed = str(current_fan_speed.split(" ")[0]) # Just in case
                 else:
                     modified_data += fan_line + "\n"
             if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
             multi_fan_data[l_index] = modified_data
+        # Insure the fans get shut off if 'off_fan_speed' was enabled
+        if Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+            multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
     # Single fan by feature-----------------------------------------------
@@ -632,7 +732,7 @@ class AddCoolingProfile(Script):
         single_fan_data = data
         layer_number = "0"
         index = 1
-        ## Start with layer:0
+        # Start with layer:0
         for l_index in range(layer_0_index,len(single_fan_data)-1,1):
             modified_data = ""
             layer = single_fan_data[l_index]
@@ -652,7 +752,7 @@ class AddCoolingProfile(Script):
                         if feature_fan_combing == True:
                             modified_data += "M106 S0" + t0_fan + "\n"
                 modified_data += line + "\n"
-                ## If an End Layer is defined and is less than the last layer then insert the Final Speed
+                # If an End Layer is defined and is less than the last layer then insert the Final Speed
                 if line == ";LAYER:" + str(the_end_layer) and the_end_is_enabled == True:
                     modified_data += feature_speed_list[len(feature_speed_list) - 1] + t0_fan + "\n"
             if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
@@ -660,7 +760,7 @@ class AddCoolingProfile(Script):
         return single_fan_data
 
     # Multi-fan by feature------------------------------------------------
-    def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
+    def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool, fan_mode: bool, off_fan_speed: str)->str:
         multi_fan_data = data
         layer_number = "0"
         start_index = 1
@@ -673,7 +773,7 @@ class AddCoolingProfile(Script):
             if ";LAYER:" + str(the_start_layer) + "\n" in layer:
                 start_index = int(my_index) - 1
                 break
-        ## Track the previous tool changes
+        # Track the previous tool changes
         for num in range(1,start_index,1):
             layer = multi_fan_data[num]
             lines = layer.split("\n")
@@ -690,7 +790,7 @@ class AddCoolingProfile(Script):
                 elif line == "T3":
                     prev_fan = this_fan
                     this_fan = t3_fan
-        ## Get the current tool.
+        # Get the current tool.
         for l_index in range(start_index,start_index + 1,1):
             layer = multi_fan_data[l_index]
             lines = layer.split("\n")
@@ -702,7 +802,7 @@ class AddCoolingProfile(Script):
                     if line == "T3": this_fan = t3_fan
                     prev_fan = this_fan
 
-        ## Start to make insertions-------------------------------------
+        # Start to make insertions-------------------------------------
         for l_index in range(start_index+1,len(multi_fan_data)-1,1):
             layer = multi_fan_data[l_index]
             lines = layer.split("\n")
@@ -712,10 +812,10 @@ class AddCoolingProfile(Script):
                     if line == "T1": this_fan = t1_fan
                     if line == "T2": this_fan = t2_fan
                     if line == "T3": this_fan = t3_fan
-                    ## Turn off the prev fan
-                    modified_data += "M106 S0" + prev_fan + "\n"
+                    # Turn off the prev fan
+                    modified_data += f"M106 S{off_fan_speed}" + prev_fan + "\n"
                     modified_data += line + "\n"
-                    ## Turn on the current fan
+                    # Turn on the current fan
                     modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
                     prev_fan = this_fan
                 if ";LAYER:" in line:
@@ -729,27 +829,29 @@ class AddCoolingProfile(Script):
                         name_index = -1
                     if name_index != -1:
                         modified_data += line + "\n" + feature_speed_list[name_index] + this_fan + "\n"
-                        #modified_data += feature_speed_list[name_index] + this_fan + "\n"
                         current_fan_speed = str(feature_speed_list[name_index].split("S")[1])
                     elif ";MESH:NONMESH" in line:
                         if feature_fan_combing == True:
                             modified_data += line + "\n"
-                            modified_data += "M106 S0" + this_fan + "\n"
+                            modified_data += f"M106 S{off_fan_speed}" + this_fan + "\n"
                             current_fan_speed = "0"
                         else:
                             modified_data += line + "\n"
-                    ## If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
-                    ## There cannot be a break here because if there are multiple fan numbers they still need to be shut off and turned on.
+                    # If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
+                    # There cannot be a 'break' here because if there are multiple fan numbers they still need to be shut off and turned on.
                     elif line == ";LAYER:" + str(the_end_layer):
                         modified_data += feature_speed_list[len(feature_speed_list) - 1] + this_fan + "\n"
                         for set_speed in range(0, len(feature_speed_list) - 2):
                             feature_speed_list[set_speed] = feature_speed_list[len(feature_speed_list) - 1]
                     else:
-                    ## Layer and Tool get inserted into modified_data above.  All other lines go into modified_data here
+                    # Layer and Tool get inserted into modified_data above.  All other lines go into modified_data here
                         if not line.startswith("T") and not line.startswith(";LAYER:"): modified_data += line + "\n"
             if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
             multi_fan_data[l_index] = modified_data
             modified_data = ""
+        # Insure the fans get shut off if 'off_fan_speed' was enabled
+        if Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+            multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
     #Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
@@ -768,7 +870,7 @@ class AddCoolingProfile(Script):
             if int(fan_string_p) > 100: fan_string_p = "100"
         except ValueError:
             fan_string_p = "0"
-        ## Set the minimum fan speed to 12%
+        # Set the minimum fan speed to 12%
         if int(fan_string_p) < 12 and int(fan_string_p) != 0:
             fan_string_p = "12"
         fan_layer_line = str(fan_string_l)
@@ -784,7 +886,7 @@ class AddCoolingProfile(Script):
     #Try to catch feature input errors, set the minimum speed to 12%, and put the strings together when 'By Feature'
     def _feature_checker(self, fan_feat_string: int, fan_mode: bool) -> str:
         if fan_feat_string < 0: fan_feat_string = 0
-        ## Set the minimum fan speed to 12%
+        # Set the minimum fan speed to 12%
         if fan_feat_string > 0 and fan_feat_string < 12: fan_feat_string = 12
         if fan_feat_string > 100: fan_feat_string = 100
         if fan_mode:
@@ -798,7 +900,7 @@ class AddCoolingProfile(Script):
         for lay_num in range(int(lay_0_index), len(comment_data)-1,1):
             layer = comment_data[lay_num]
             lines = layer.split("\n")
-            ## Copy the data to new_data and make the insertions there
+            # Copy the data to new_data and make the insertions there
             new_data = lines
             g0_count = 0
             g0_index = -1
@@ -818,12 +920,12 @@ class AddCoolingProfile(Script):
                     if g0_index == -1:
                         g0_index = lines.index(line)
                 elif not line.startswith("G0 ") and not is_travel:
-                ## Add additional 'NONMESH' lines to shut the fan off during long combing moves--------
+                # Add additional 'NONMESH' lines to shut the fan off during long combing moves--------
                     if g0_count > 5:
                         if not is_travel:
                             new_data.insert(g0_index + insert_index, ";MESH:NONMESH")
                             insert_index += 1
-                ## Add the feature_type at the end of the combing move to turn the fan back on
+                # Add the feature_type at the end of the combing move to turn the fan back on
                             new_data.insert(g0_index + g0_count + 1, feature_type)
                             insert_index += 1
                         g0_count = 0
@@ -834,4 +936,34 @@ class AddCoolingProfile(Script):
                         g0_index = -1
                         is_travel = False
             comment_data[lay_num] = "\n".join(new_data)
-        return comment_data
\ No newline at end of file
+        return comment_data
+
+    def _control_bv_fan(self, bv_data: str) -> str:
+        # Control the chamber fan
+        bv_start_layer = self.getSettingValueByKey("bv_fan_start_layer") - 1
+        bv_end_layer = self.getSettingValueByKey("bv_fan_end_layer")
+        if bv_end_layer != -1:
+            bv_end_layer -= 1
+        # Get the PWM speed or if RepRap then the 0-1 speed
+        if Application.getInstance().getGlobalContainerStack().extruderList[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
+            bv_fan_speed = round(self.getSettingValueByKey("bv_fan_speed") * .01, 1)
+        else:
+            bv_fan_speed = int(self.getSettingValueByKey("bv_fan_speed") * 2.55)
+        # Turn the chamber fan on
+        for index, layer in enumerate(bv_data):
+            if ";LAYER:" + str(bv_start_layer) + "\n" in layer:
+                bv_data[index] = re.sub(f";LAYER:{bv_start_layer}", f";LAYER:{bv_start_layer}\nM106 S{bv_fan_speed} P{self.bv_fan_nr}",layer)
+                break
+        # Turn the chamber fan off
+        if bv_end_layer == -1:
+            bv_data[len(bv_data)-2] += f"M106 S0 P{self.bv_fan_nr}\n"
+        else:
+            for index, layer in enumerate(bv_data):
+                if ";LAYER:" + str(bv_end_layer) + "\n" in layer:
+                    lines = layer.split("\n")
+                    for fdex, line in enumerate(lines):
+                        if ";TIME_ELAPSED:" in line:
+                            lines[fdex] = f"M106 S0 P{self.bv_fan_nr}\n" + line
+                    bv_data[index] = "\n".join(lines)
+                    break
+        return bv_data
\ No newline at end of file

From 248b5915db6d4e7de6a81e27936ba48b5906dd15 Mon Sep 17 00:00:00 2001
From: GregValiant <64202104+GregValiant@users.noreply.github.com>
Date: Fri, 3 Jan 2025 07:14:45 -0500
Subject: [PATCH 2/8] Update AddCoolingProfile.py

Made changes per request.
---
 .../scripts/AddCoolingProfile.py              | 34 +++++++++++--------
 1 file changed, 19 insertions(+), 15 deletions(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index ad00fbd923..370b0aff96 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -314,7 +314,7 @@ class AddCoolingProfile(Script):
                 "bv_fan_speed":
                 {
                     "label": "    Chamber fan speed %",
-                    "description": "The speed of the Chamber Fan.  This will be converted to PWM Duty Cycle (0-255).",
+                    "description": "The speed of the Chamber Fan.  This will be converted to PWM Duty Cycle (0-255) or (RepRap 0-1 if that is enabled in Cura).",
                     "type": "int",
                     "unit": "%    ",
                     "default_value": 50,
@@ -355,29 +355,32 @@ class AddCoolingProfile(Script):
         super().initialize()
         curaApp = Application.getInstance().getGlobalContainerStack()
         extruder = curaApp.extruderList
+        extruder_count = curaApp.getProperty("machine_extruder_count", "value")
         scripts = curaApp.getMetaDataEntry("post_processing_scripts")
         if scripts != None:
             script_count = scripts.count("AddCoolingProfile")
             if script_count > 0:
                 # Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
                 self._instance.setProperty("delete_existing_m106", "value", False)
-        if curaApp.getProperty("machine_extruder_count", "value") > 1:
+        if extruder_count > 1:
             if extruder[0].getProperty("machine_extruder_cooling_fan_number", "value") != extruder[1].getProperty("machine_extruder_cooling_fan_number", "value"):
                 self._instance.setProperty("enable_off_fan_speed_enable", "value", True)
-        self.has_bv_fan = False
-        self.bv_fan_nr = -1
-        if int(curaApp.getProperty("build_volume_fan_nr", "value")) > 0:
-            self.has_bv_fan = True
-            self.bv_fan_nr = int(curaApp.getProperty("build_volume_fan_nr", "value"))
+        self.has_bv_fan = bool(curaApp.getProperty("build_volume_fan_nr", "value"))
+        self.bv_fan_nr = int(curaApp.getProperty("build_volume_fan_nr", "value"))
+        if self.has_bv_fan:
             self._instance.setProperty("enable_bv_fan", "value", True)
 
     def execute(self, data):
+        # Exit if the gcode has been previously post-processed.
+        if ";POSTPROCESSED" in data[0]:
+            return data
         #Initialize variables that are buried in if statements.
-        curaApp = Application.getInstance().getGlobalContainerStack()
+        self.curaApp = Application.getInstance().getGlobalContainerStack()
         t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
 
         #Get some information from Cura-----------------------------------
-        extruder = curaApp.extruderList
+        extruder = self.curaApp.extruderList
+        extruder_count = self.curaApp.getProperty("machine_extruder_count", "value")
 
         #This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
         fan_mode = True
@@ -387,8 +390,7 @@ class AddCoolingProfile(Script):
         except:
             pass
         bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
-        extruder_count = curaApp.getProperty("machine_extruder_count", "value")
-        print_sequence = str(curaApp.getProperty("print_sequence", "value"))
+        print_sequence = str(self.curaApp.getProperty("print_sequence", "value"))
 
         #Assign the fan numbers to the tools------------------------------
         if extruder_count == 1:
@@ -471,7 +473,7 @@ class AddCoolingProfile(Script):
 
         # For multi-extruder printers with separate fans the 'idle' nozzle fan can be left on for ooze control
         off_fan_speed = 0
-        if Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") > 1:
+        if extruder_count > 1:
             if self.getSettingValueByKey("enable_off_fan_speed"):
                 if fan_mode:
                     off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * 2.55)
@@ -624,10 +626,12 @@ class AddCoolingProfile(Script):
                 layer = data[layer_num]
                 data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
             data = self._add_travel_comment(data, layer_0_index)
+        
         # If there is a build volume fan
         if self.has_bv_fan:
             if self.getSettingValueByKey("bv_fan_speed_control_enable"):
                 data = self._control_bv_fan(data)
+        
         # Single Fan "By Layer"--------------------------------------------
         if by_layer_or_feature == "by_layer" and not is_multi_fan:
             return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
@@ -723,7 +727,7 @@ class AddCoolingProfile(Script):
             if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
             multi_fan_data[l_index] = modified_data
         # Insure the fans get shut off if 'off_fan_speed' was enabled
-        if Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+        if self.curaApp.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
@@ -850,7 +854,7 @@ class AddCoolingProfile(Script):
             multi_fan_data[l_index] = modified_data
             modified_data = ""
         # Insure the fans get shut off if 'off_fan_speed' was enabled
-        if Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+        if self.curaApp.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
@@ -945,7 +949,7 @@ class AddCoolingProfile(Script):
         if bv_end_layer != -1:
             bv_end_layer -= 1
         # Get the PWM speed or if RepRap then the 0-1 speed
-        if Application.getInstance().getGlobalContainerStack().extruderList[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
+        if self.curaApp.extruderList[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
             bv_fan_speed = round(self.getSettingValueByKey("bv_fan_speed") * .01, 1)
         else:
             bv_fan_speed = int(self.getSettingValueByKey("bv_fan_speed") * 2.55)

From fba94ae2c48f1ec887c6c4caba04759a86b81e0d Mon Sep 17 00:00:00 2001
From: GregValiant <64202104+GregValiant@users.noreply.github.com>
Date: Wed, 8 Jan 2025 07:27:28 -0500
Subject: [PATCH 3/8] Update AddCoolingProfile.py

Changed variable name "curaApp" to "global_stack".

Update AddCoolingProfile.py

Add except for 'Build Volume Fan' for previous versions.
---
 .../scripts/AddCoolingProfile.py              | 32 +++++++++++--------
 1 file changed, 19 insertions(+), 13 deletions(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index 370b0aff96..c6ec748f96 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -353,10 +353,10 @@ class AddCoolingProfile(Script):
 
     def initialize(self) -> None:
         super().initialize()
-        curaApp = Application.getInstance().getGlobalContainerStack()
-        extruder = curaApp.extruderList
-        extruder_count = curaApp.getProperty("machine_extruder_count", "value")
-        scripts = curaApp.getMetaDataEntry("post_processing_scripts")
+        global_stack = Application.getInstance().getGlobalContainerStack()
+        extruder = global_stack.extruderList
+        extruder_count = global_stack.getProperty("machine_extruder_count", "value")
+        scripts = global_stack.getMetaDataEntry("post_processing_scripts")
         if scripts != None:
             script_count = scripts.count("AddCoolingProfile")
             if script_count > 0:
@@ -365,8 +365,14 @@ class AddCoolingProfile(Script):
         if extruder_count > 1:
             if extruder[0].getProperty("machine_extruder_cooling_fan_number", "value") != extruder[1].getProperty("machine_extruder_cooling_fan_number", "value"):
                 self._instance.setProperty("enable_off_fan_speed_enable", "value", True)
-        self.has_bv_fan = bool(curaApp.getProperty("build_volume_fan_nr", "value"))
-        self.bv_fan_nr = int(curaApp.getProperty("build_volume_fan_nr", "value"))
+        
+        self.has_bv_fan = False
+        self.bv_fan_nr = 0        
+        try:
+            self.has_bv_fan = bool(global_stack.getProperty("build_volume_fan_nr", "value"))
+            self.bv_fan_nr = int(global_stack.getProperty("build_volume_fan_nr", "value"))
+        except:
+            pass
         if self.has_bv_fan:
             self._instance.setProperty("enable_bv_fan", "value", True)
 
@@ -375,12 +381,12 @@ class AddCoolingProfile(Script):
         if ";POSTPROCESSED" in data[0]:
             return data
         #Initialize variables that are buried in if statements.
-        self.curaApp = Application.getInstance().getGlobalContainerStack()
+        self.global_stack = Application.getInstance().getGlobalContainerStack()
         t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
 
         #Get some information from Cura-----------------------------------
-        extruder = self.curaApp.extruderList
-        extruder_count = self.curaApp.getProperty("machine_extruder_count", "value")
+        extruder = self.global_stack.extruderList
+        extruder_count = self.global_stack.getProperty("machine_extruder_count", "value")
 
         #This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
         fan_mode = True
@@ -390,7 +396,7 @@ class AddCoolingProfile(Script):
         except:
             pass
         bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
-        print_sequence = str(self.curaApp.getProperty("print_sequence", "value"))
+        print_sequence = str(self.global_stack.getProperty("print_sequence", "value"))
 
         #Assign the fan numbers to the tools------------------------------
         if extruder_count == 1:
@@ -727,7 +733,7 @@ class AddCoolingProfile(Script):
             if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
             multi_fan_data[l_index] = modified_data
         # Insure the fans get shut off if 'off_fan_speed' was enabled
-        if self.curaApp.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+        if self.global_stack.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
@@ -854,7 +860,7 @@ class AddCoolingProfile(Script):
             multi_fan_data[l_index] = modified_data
             modified_data = ""
         # Insure the fans get shut off if 'off_fan_speed' was enabled
-        if self.curaApp.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+        if self.global_stack.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
@@ -949,7 +955,7 @@ class AddCoolingProfile(Script):
         if bv_end_layer != -1:
             bv_end_layer -= 1
         # Get the PWM speed or if RepRap then the 0-1 speed
-        if self.curaApp.extruderList[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
+        if self.global_stack.extruderList[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
             bv_fan_speed = round(self.getSettingValueByKey("bv_fan_speed") * .01, 1)
         else:
             bv_fan_speed = int(self.getSettingValueByKey("bv_fan_speed") * 2.55)

From 001dfc30af53f0154dc562cd4ddfc0200a7c347c Mon Sep 17 00:00:00 2001
From: GregValiant <64202104+GregValiant@users.noreply.github.com>
Date: Sat, 22 Mar 2025 11:10:23 -0400
Subject: [PATCH 4/8] Update AddCoolingProfile.py

Re-worked the Build Volume fan code to include a printers Auxiliary fan if there is one.
---
 .../scripts/AddCoolingProfile.py              | 126 ++++++++++--------
 1 file changed, 67 insertions(+), 59 deletions(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index c6ec748f96..e30e83b653 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -1,18 +1,22 @@
-# Designed in January 2023 by GregValiant (Greg Foresi)
-#   My design intent was to make this as full featured and "industrial strength" as I could.  People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print.  This script allows that.
-#    Functions:
-#    Remove all fan speed lines from the file (optional).  This should be enabled for the first instance of the script.  It is disabled by default in any following instances.
-#    "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print.  "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
-#    If 'By Feature' then a Start Layer and/or an End Layer can be defined.
-#    Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
-#    A minimum fan speed of 12% is enforced.  It is the slowest speed that my cooling fan will turn on so that's what I used.  'M106 S14' (as Cura might insert) was pretty useless.
-#    If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting.  There is support for up to 4 layer cooling fan circuits.
-#    My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir.  A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
-#    09/14/23  (GV) Added support for One-at-a-Time print sequence.
-#    12/15/23  (GV) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
-#    01/05/24  (GV) Revised the regex replacements.
-#    12/11/24  (GV) Added 'off_fan_speed' for the idle nozzle layer cooling fan.  It does not have to go to 0%.
-#    01/01/25  (GV) Added 'Build Volume' fan control
+"""
+Designed in January 2023 by GregValiant (Greg Foresi)
+    My design intent was to make this as full featured and "industrial strength" as I could.  People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print.  This script allows that.
+    Functions:
+        Remove all fan speed lines from the file (optional).  This should be enabled for the first instance of the script.  It is disabled by default in any following instances.
+        "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print.  "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
+        If 'By Feature' then a Start Layer and/or an End Layer can be defined.
+        Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
+        A minimum fan speed of 12% is enforced.  It is the slowest speed that my cooling fan will turn on so that's what I used.  'M106 S14' (as Cura might insert) was pretty useless.
+        If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting.  There is support for up to 4 layer cooling fan circuits.
+        My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir.  A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
+    Changes:
+        09/14/23  (GV) Added support for One-at-a-Time print sequence.
+        12/15/23  (GV) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
+        01/05/24  (GV) Revised the regex replacements.
+        12/11/24  (GV) Added 'off_fan_speed' for the idle nozzle layer cooling fan.  It does not have to go to 0%.
+        01/01/25  (GV) Added 'Build Volume' fan control
+        03/15/25  (GV) Added 'Chamber Cooling Fan' control
+"""
 
 from ..Script import Script
 from UM.Application import Application
@@ -45,7 +49,8 @@ class AddCoolingProfile(Script):
                     "type": "bool",
                     "enabled": true,
                     "value": true,
-                    "default_value": true
+                    "default_value": true,
+                    "read_only": true
                 },
                 "feature_fan_start_layer":
                 {
@@ -305,16 +310,26 @@ class AddCoolingProfile(Script):
                 },
                 "bv_fan_speed_control_enable":
                 {
-                    "label": "Enable 'Chamber Fan' control",
-                    "description": "Available if the 'Build Volume Fan Number' > 0 in 'Printer Settings'.  Provides: On layer, off layer, and PWM speed control of the Chamber fan.",
+                    "label": "Enable 'Chamber/Aux Fan' control",
+                    "description": "Controls the 'Build Volume Fan' or an 'Auxiliary Fan' on printers with that hardware.  Provides: 'On' layer, 'Off' layer, and PWM speed control of a secondary fan.",
                     "type": "bool",
                     "default_value": false,
                     "enabled": "enable_bv_fan"
                 },
+                "bv_fan_nr":
+                {
+                    "label": "    Chamber/Aux Fan Number",
+                    "description": "The mainboard circuit number of the Chamber or Auxiliary Fan.",
+                    "type": "int",
+                    "unit": "#    ",
+                    "default_value": 0,
+                    "minimum_value": 0,
+                    "enabled": "enable_bv_fan and bv_fan_speed_control_enable"
+                },
                 "bv_fan_speed":
                 {
-                    "label": "    Chamber fan speed %",
-                    "description": "The speed of the Chamber Fan.  This will be converted to PWM Duty Cycle (0-255) or (RepRap 0-1 if that is enabled in Cura).",
+                    "label": "    Chamber/Aux Fan Speed %",
+                    "description": "The speed of the Chamber or Auxiliary Fan.  This will be converted to PWM Duty Cycle (0-255) or (RepRap 0-1 if that is enabled in Cura).  If your specified fan does not operate on variable speeds then set this to '100'.",
                     "type": "int",
                     "unit": "%    ",
                     "default_value": 50,
@@ -324,18 +339,20 @@ class AddCoolingProfile(Script):
                 },
                 "bv_fan_start_layer":
                 {
-                    "label": "        Start Layer",
-                    "description": "The layer number for Chamber Fan start.  Use the Cura preview layer number.  If you are using a raft the chanber fan will start when the raft finishes.",
+                    "label": "    Chamber/Aux Fan Start Layer",
+                    "description": "The layer to start the Chamber or Auxiliary Fan.  Use the Cura preview layer number and the fan will start at the beginning of the layer.",
                     "type": "int",
+                    "unit": "Layer#    ",
                     "default_value": 1,
                     "minimum_value": 1,
                     "enabled": "enable_bv_fan and bv_fan_speed_control_enable"
                 },
                 "bv_fan_end_layer":
                 {
-                    "label": "        End Layer",
-                    "description": "The layer number for Chamber Fan to turn off.  Use the Cura preview layer number or '-1' to indicate the end of the print.",
+                    "label": "    Chamber/Aux Fan End Layer",
+                    "description": "The layer number for Chamber or Auxiliary Fan to turn off.  Use the Cura preview layer number or '-1' to indicate the end of the print.  The fan will run until the end of the layer",
                     "type": "int",
+                    "unit": "Layer#    ",
                     "default_value": -1,
                     "minimum_value": -1,
                     "enabled": "enable_bv_fan and bv_fan_speed_control_enable"
@@ -343,7 +360,7 @@ class AddCoolingProfile(Script):
                 "enable_bv_fan":
                 {
                     "label": "Hidden setting",
-                    "description": "For printers with heated chambers and chamber fans, this enables 'bv_fan_speed_control_enable'.",
+                    "description": "This is enabled when machine_heated_bed is true, and in turn this enables 'bv_fan_speed_control_enable'.",
                     "type": "bool",
                     "default_value": false,
                     "enabled": false
@@ -364,18 +381,10 @@ class AddCoolingProfile(Script):
                 self._instance.setProperty("delete_existing_m106", "value", False)
         if extruder_count > 1:
             if extruder[0].getProperty("machine_extruder_cooling_fan_number", "value") != extruder[1].getProperty("machine_extruder_cooling_fan_number", "value"):
-                self._instance.setProperty("enable_off_fan_speed_enable", "value", True)
-        
-        self.has_bv_fan = False
-        self.bv_fan_nr = 0        
-        try:
-            self.has_bv_fan = bool(global_stack.getProperty("build_volume_fan_nr", "value"))
-            self.bv_fan_nr = int(global_stack.getProperty("build_volume_fan_nr", "value"))
-        except:
-            pass
-        if self.has_bv_fan:
+                self._instance.setProperty("enable_off_fan_speed_enable", "value", True)                
+        if bool(global_stack.getProperty("machine_heated_bed", "value")):
             self._instance.setProperty("enable_bv_fan", "value", True)
-
+ 
     def execute(self, data):
         # Exit if the gcode has been previously post-processed.
         if ";POSTPROCESSED" in data[0]:
@@ -384,7 +393,7 @@ class AddCoolingProfile(Script):
         self.global_stack = Application.getInstance().getGlobalContainerStack()
         t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
 
-        #Get some information from Cura-----------------------------------
+        #Get some information from Cura
         extruder = self.global_stack.extruderList
         extruder_count = self.global_stack.getProperty("machine_extruder_count", "value")
 
@@ -398,14 +407,14 @@ class AddCoolingProfile(Script):
         bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
         print_sequence = str(self.global_stack.getProperty("print_sequence", "value"))
 
-        #Assign the fan numbers to the tools------------------------------
+        #Assign the fan numbers to the tools
         if extruder_count == 1:
             is_multi_fan = False
             is_multi_extr_print = False
             if int((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0:
                 t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
             else:
-        #No P parameter if there is a single fan circuit------------------
+        # No P parameter if there is a single fan circuit
                 t0_fan = ""
 
         #Get the cooling fan numbers for each extruder if the printer has multiple extruders
@@ -417,13 +426,13 @@ class AddCoolingProfile(Script):
             if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value")))
             if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value")))
 
-        #Initialize the fan_list with defaults----------------------------
+        #Initialize the fan_list with defaults
         fan_list = ["z"] * 16
         for num in range(0,15,2):
             fan_list[num] = len(data)
             fan_list[num + 1] = "M106 S0"
 
-        #Assign the variable values if "By Layer"-------------------------
+        #Assign the variable values if "By Layer"
         by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature")
         if  by_layer_or_feature == "by_layer":
             # By layer doesn't do any feature search so there is no need to look for combing moves
@@ -554,7 +563,7 @@ class AddCoolingProfile(Script):
         else:
             fan_sp_raft = "M106 S0"
 
-        # Start to alter the data-----------------------------------------
+        # Start to alter the data
         # Strip the existing M106 lines from the file up to the end of the last layer.  If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option.
         delete_existing_m106 = self.getSettingValueByKey("delete_existing_m106")
         if delete_existing_m106:
@@ -633,28 +642,26 @@ class AddCoolingProfile(Script):
                 data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
             data = self._add_travel_comment(data, layer_0_index)
         
-        # If there is a build volume fan
-        if self.has_bv_fan:
-            if self.getSettingValueByKey("bv_fan_speed_control_enable"):
-                data = self._control_bv_fan(data)
+        if bool(self.getSettingValueByKey("bv_fan_speed_control_enable")):
+            data = self._control_bv_fan(data)
         
-        # Single Fan "By Layer"--------------------------------------------
+        # Single Fan "By Layer"
         if by_layer_or_feature == "by_layer" and not is_multi_fan:
             return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
 
-        # Multi-Fan "By Layer"---------------------------------------------
+        # Multi-Fan "By Layer"
         if by_layer_or_feature == "by_layer" and is_multi_fan:
             return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, fan_mode, off_fan_speed)
 
-        #Single Fan "By Feature"------------------------------------------
+        #Single Fan "By Feature"
         if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print):
             return self._single_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, feature_speed_list, feature_name_list, feature_fan_combing)
 
-        #Multi Fan "By Feature"-------------------------------------------
+        #Multi Fan "By Feature"
         if by_layer_or_feature == "by_feature" and is_multi_fan:
             return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing, fan_mode, off_fan_speed)
 
-    # The Single Fan "By Layer"----------------------------------------
+    # The Single Fan "By Layer"
     def _single_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str)->str:
         layer_number = "0"
         single_fan_data = data
@@ -671,7 +678,7 @@ class AddCoolingProfile(Script):
                             single_fan_data[l_index] = layer
         return single_fan_data
 
-    # Multi-Fan "By Layer"-----------------------------------------
+    # Multi-Fan "By Layer"
     def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, fan_mode: bool, off_fan_speed: str)->str:
         multi_fan_data = data
         layer_number = "0"
@@ -737,7 +744,7 @@ class AddCoolingProfile(Script):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
-    # Single fan by feature-----------------------------------------------
+    # Single fan by feature
     def _single_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
         single_fan_data = data
         layer_number = "0"
@@ -769,7 +776,7 @@ class AddCoolingProfile(Script):
             single_fan_data[l_index] = modified_data
         return single_fan_data
 
-    # Multi-fan by feature------------------------------------------------
+    # Multi-fan by feature
     def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool, fan_mode: bool, off_fan_speed: str)->str:
         multi_fan_data = data
         layer_number = "0"
@@ -812,7 +819,7 @@ class AddCoolingProfile(Script):
                     if line == "T3": this_fan = t3_fan
                     prev_fan = this_fan
 
-        # Start to make insertions-------------------------------------
+        # Start to make insertions
         for l_index in range(start_index+1,len(multi_fan_data)-1,1):
             layer = multi_fan_data[l_index]
             lines = layer.split("\n")
@@ -930,7 +937,7 @@ class AddCoolingProfile(Script):
                     if g0_index == -1:
                         g0_index = lines.index(line)
                 elif not line.startswith("G0 ") and not is_travel:
-                # Add additional 'NONMESH' lines to shut the fan off during long combing moves--------
+                # Add additional 'NONMESH' lines to shut the fan off during long combing moves
                     if g0_count > 5:
                         if not is_travel:
                             new_data.insert(g0_index + insert_index, ";MESH:NONMESH")
@@ -952,6 +959,7 @@ class AddCoolingProfile(Script):
         # Control the chamber fan
         bv_start_layer = self.getSettingValueByKey("bv_fan_start_layer") - 1
         bv_end_layer = self.getSettingValueByKey("bv_fan_end_layer")
+        bv_fan_nr = self.getSettingValueByKey("bv_fan_nr")
         if bv_end_layer != -1:
             bv_end_layer -= 1
         # Get the PWM speed or if RepRap then the 0-1 speed
@@ -962,18 +970,18 @@ class AddCoolingProfile(Script):
         # Turn the chamber fan on
         for index, layer in enumerate(bv_data):
             if ";LAYER:" + str(bv_start_layer) + "\n" in layer:
-                bv_data[index] = re.sub(f";LAYER:{bv_start_layer}", f";LAYER:{bv_start_layer}\nM106 S{bv_fan_speed} P{self.bv_fan_nr}",layer)
+                bv_data[index] = re.sub(f";LAYER:{bv_start_layer}", f";LAYER:{bv_start_layer}\nM106 S{bv_fan_speed} P{bv_fan_nr}",layer)
                 break
         # Turn the chamber fan off
         if bv_end_layer == -1:
-            bv_data[len(bv_data)-2] += f"M106 S0 P{self.bv_fan_nr}\n"
+            bv_data[len(bv_data)-2] += f"M106 S0 P{bv_fan_nr}\n"
         else:
             for index, layer in enumerate(bv_data):
                 if ";LAYER:" + str(bv_end_layer) + "\n" in layer:
                     lines = layer.split("\n")
                     for fdex, line in enumerate(lines):
                         if ";TIME_ELAPSED:" in line:
-                            lines[fdex] = f"M106 S0 P{self.bv_fan_nr}\n" + line
+                            lines[fdex] = f"M106 S0 P{bv_fan_nr}\n" + line
                     bv_data[index] = "\n".join(lines)
                     break
         return bv_data
\ No newline at end of file

From a94f5e0f28f29d8589a76f7623ed7dcdb7ff9d2d Mon Sep 17 00:00:00 2001
From: HellAholic <alireza.doustdar@gmail.com>
Date: Sat, 22 Mar 2025 17:53:48 +0100
Subject: [PATCH 5/8] Add space after # for the comments

---
 .../scripts/AddCoolingProfile.py              | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index e30e83b653..bbf90b764c 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -389,17 +389,17 @@ class AddCoolingProfile(Script):
         # Exit if the gcode has been previously post-processed.
         if ";POSTPROCESSED" in data[0]:
             return data
-        #Initialize variables that are buried in if statements.
+        # Initialize variables that are buried in if statements.
         self.global_stack = Application.getInstance().getGlobalContainerStack()
         t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
 
-        #Get some information from Cura
+        # Get some information from Cura
         extruder = self.global_stack.extruderList
         extruder_count = self.global_stack.getProperty("machine_extruder_count", "value")
 
-        #This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
+        # This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
         fan_mode = True
-        #For 4.x versions that don't have the 0-1 option
+        # For 4.x versions that don't have the 0-1 option
         try:
             fan_mode = not bool(extruder[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
         except:
@@ -407,7 +407,7 @@ class AddCoolingProfile(Script):
         bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
         print_sequence = str(self.global_stack.getProperty("print_sequence", "value"))
 
-        #Assign the fan numbers to the tools
+        # Assign the fan numbers to the tools
         if extruder_count == 1:
             is_multi_fan = False
             is_multi_extr_print = False
@@ -417,7 +417,7 @@ class AddCoolingProfile(Script):
         # No P parameter if there is a single fan circuit
                 t0_fan = ""
 
-        #Get the cooling fan numbers for each extruder if the printer has multiple extruders
+        # Get the cooling fan numbers for each extruder if the printer has multiple extruders
         elif extruder_count > 1:
             is_multi_fan = True
             t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
@@ -426,13 +426,13 @@ class AddCoolingProfile(Script):
             if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value")))
             if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value")))
 
-        #Initialize the fan_list with defaults
+        # Initialize the fan_list with defaults
         fan_list = ["z"] * 16
         for num in range(0,15,2):
             fan_list[num] = len(data)
             fan_list[num + 1] = "M106 S0"
 
-        #Assign the variable values if "By Layer"
+        # Assign the variable values if "By Layer"
         by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature")
         if  by_layer_or_feature == "by_layer":
             # By layer doesn't do any feature search so there is no need to look for combing moves
@@ -653,11 +653,11 @@ class AddCoolingProfile(Script):
         if by_layer_or_feature == "by_layer" and is_multi_fan:
             return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, fan_mode, off_fan_speed)
 
-        #Single Fan "By Feature"
+        # Single Fan "By Feature"
         if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print):
             return self._single_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, feature_speed_list, feature_name_list, feature_fan_combing)
 
-        #Multi Fan "By Feature"
+        # Multi Fan "By Feature"
         if by_layer_or_feature == "by_feature" and is_multi_fan:
             return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing, fan_mode, off_fan_speed)
 
@@ -871,7 +871,7 @@ class AddCoolingProfile(Script):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
-    #Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
+    # Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
     def _layer_checker(self, fan_string: str, ty_pe: str, fan_mode: bool) -> str:
         fan_string_l = str(fan_string.split("/")[0])
         try:

From 99b198339ac3447ee5d419ab8b08a4ff40ce1159 Mon Sep 17 00:00:00 2001
From: GregValiant <64202104+GregValiant@users.noreply.github.com>
Date: Sat, 22 Mar 2025 15:41:39 -0400
Subject: [PATCH 6/8] Update AddCoolingProfile.py

Update per requested changes.
Comments are consistent with "# ".
global_stack, extruder_list, extruder_count are assigned to "self".
---
 .../scripts/AddCoolingProfile.py              | 60 +++++++++----------
 1 file changed, 27 insertions(+), 33 deletions(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index bbf90b764c..6e43e98969 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -14,8 +14,7 @@ Designed in January 2023 by GregValiant (Greg Foresi)
         12/15/23  (GV) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
         01/05/24  (GV) Revised the regex replacements.
         12/11/24  (GV) Added 'off_fan_speed' for the idle nozzle layer cooling fan.  It does not have to go to 0%.
-        01/01/25  (GV) Added 'Build Volume' fan control
-        03/15/25  (GV) Added 'Chamber Cooling Fan' control
+        03/22/25  (GV) Added 'Chamber Cooling Fan / Auxiliary Fan' control.
 """
 
 from ..Script import Script
@@ -370,19 +369,19 @@ class AddCoolingProfile(Script):
 
     def initialize(self) -> None:
         super().initialize()
-        global_stack = Application.getInstance().getGlobalContainerStack()
-        extruder = global_stack.extruderList
-        extruder_count = global_stack.getProperty("machine_extruder_count", "value")
-        scripts = global_stack.getMetaDataEntry("post_processing_scripts")
+        self.global_stack = Application.getInstance().getGlobalContainerStack()
+        self.extruder_list = self.global_stack.extruderList
+        self.extruder_count = self.global_stack.getProperty("machine_extruder_count", "value")
+        scripts = self.global_stack.getMetaDataEntry("post_processing_scripts")
         if scripts != None:
             script_count = scripts.count("AddCoolingProfile")
             if script_count > 0:
                 # Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
                 self._instance.setProperty("delete_existing_m106", "value", False)
-        if extruder_count > 1:
-            if extruder[0].getProperty("machine_extruder_cooling_fan_number", "value") != extruder[1].getProperty("machine_extruder_cooling_fan_number", "value"):
+        if self.extruder_count > 1:
+            if self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value") != self.extruder_list[1].getProperty("machine_extruder_cooling_fan_number", "value"):
                 self._instance.setProperty("enable_off_fan_speed_enable", "value", True)                
-        if bool(global_stack.getProperty("machine_heated_bed", "value")):
+        if bool(self.global_stack.getProperty("machine_heated_bed", "value")):
             self._instance.setProperty("enable_bv_fan", "value", True)
  
     def execute(self, data):
@@ -390,41 +389,36 @@ class AddCoolingProfile(Script):
         if ";POSTPROCESSED" in data[0]:
             return data
         # Initialize variables that are buried in if statements.
-        self.global_stack = Application.getInstance().getGlobalContainerStack()
         t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
 
-        # Get some information from Cura
-        extruder = self.global_stack.extruderList
-        extruder_count = self.global_stack.getProperty("machine_extruder_count", "value")
-
         # This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
         fan_mode = True
         # For 4.x versions that don't have the 0-1 option
         try:
-            fan_mode = not bool(extruder[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
+            fan_mode = not bool(self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
         except:
             pass
-        bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
+        bed_adhesion = (self.extruder_list[0].getProperty("adhesion_type", "value"))
         print_sequence = str(self.global_stack.getProperty("print_sequence", "value"))
 
         # Assign the fan numbers to the tools
-        if extruder_count == 1:
+        if self.extruder_count == 1:
             is_multi_fan = False
             is_multi_extr_print = False
-            if int((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0:
-                t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
+            if int((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0:
+                t0_fan = " P" + str((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value")))
             else:
         # No P parameter if there is a single fan circuit
                 t0_fan = ""
 
         # Get the cooling fan numbers for each extruder if the printer has multiple extruders
-        elif extruder_count > 1:
+        elif self.extruder_count > 1:
             is_multi_fan = True
-            t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
+            t0_fan = " P" + str((self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value")))
         if is_multi_fan:
-            if extruder_count > 1: t1_fan = " P" + str((extruder[1].getProperty("machine_extruder_cooling_fan_number", "value")))
-            if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value")))
-            if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value")))
+            if self.extruder_count > 1: t1_fan = " P" + str((self.extruder_list[1].getProperty("machine_extruder_cooling_fan_number", "value")))
+            if self.extruder_count > 2: t2_fan = " P" + str((self.extruder_list[2].getProperty("machine_extruder_cooling_fan_number", "value")))
+            if self.extruder_count > 3: t3_fan = " P" + str((self.extruder_list[3].getProperty("machine_extruder_cooling_fan_number", "value")))
 
         # Initialize the fan_list with defaults
         fan_list = ["z"] * 16
@@ -488,7 +482,7 @@ class AddCoolingProfile(Script):
 
         # For multi-extruder printers with separate fans the 'idle' nozzle fan can be left on for ooze control
         off_fan_speed = 0
-        if extruder_count > 1:
+        if self.extruder_count > 1:
             if self.getSettingValueByKey("enable_off_fan_speed"):
                 if fan_mode:
                     off_fan_speed = round(int(self.getSettingValueByKey("off_fan_speed")) * 2.55)
@@ -629,9 +623,9 @@ class AddCoolingProfile(Script):
         temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t0_fan))
         # If there are multiple cooling fans shut them all off
         if is_multi_fan:
-            if extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
-            if extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan))
-            if extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan))
+            if self.extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
+            if self.extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan))
+            if self.extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan))
         data[1] = "\n".join(temp_startup)
 
         # If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
@@ -740,7 +734,7 @@ class AddCoolingProfile(Script):
             if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
             multi_fan_data[l_index] = modified_data
         # Insure the fans get shut off if 'off_fan_speed' was enabled
-        if self.global_stack.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+        if self.extruder_count > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
@@ -867,7 +861,7 @@ class AddCoolingProfile(Script):
             multi_fan_data[l_index] = modified_data
             modified_data = ""
         # Insure the fans get shut off if 'off_fan_speed' was enabled
-        if self.global_stack.getProperty("machine_extruder_count", "value") > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
+        if self.extruder_count > 1 and self.getSettingValueByKey("enable_off_fan_speed"):
             multi_fan_data[-1] += "M106 S0 P1\nM106 S0 P0\n"
         return multi_fan_data
 
@@ -963,13 +957,13 @@ class AddCoolingProfile(Script):
         if bv_end_layer != -1:
             bv_end_layer -= 1
         # Get the PWM speed or if RepRap then the 0-1 speed
-        if self.global_stack.extruderList[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
+        if self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"):
             bv_fan_speed = round(self.getSettingValueByKey("bv_fan_speed") * .01, 1)
         else:
             bv_fan_speed = int(self.getSettingValueByKey("bv_fan_speed") * 2.55)
         # Turn the chamber fan on
         for index, layer in enumerate(bv_data):
-            if ";LAYER:" + str(bv_start_layer) + "\n" in layer:
+            if f";LAYER:{bv_start_layer}\n" in layer:
                 bv_data[index] = re.sub(f";LAYER:{bv_start_layer}", f";LAYER:{bv_start_layer}\nM106 S{bv_fan_speed} P{bv_fan_nr}",layer)
                 break
         # Turn the chamber fan off
@@ -977,7 +971,7 @@ class AddCoolingProfile(Script):
             bv_data[len(bv_data)-2] += f"M106 S0 P{bv_fan_nr}\n"
         else:
             for index, layer in enumerate(bv_data):
-                if ";LAYER:" + str(bv_end_layer) + "\n" in layer:
+                if f";LAYER:{bv_end_layer}\n" in layer:
                     lines = layer.split("\n")
                     for fdex, line in enumerate(lines):
                         if ";TIME_ELAPSED:" in line:

From 03aa64448a5a410b040e8fb550cf4e22abf0ff0f Mon Sep 17 00:00:00 2001
From: GregValiant <64202104+GregValiant@users.noreply.github.com>
Date: Sun, 30 Mar 2025 18:28:58 -0400
Subject: [PATCH 7/8] Update AddCoolingProfile.py

Update AddCoolingProfile.py

Un-trapped ValueError in line 782.  It might also be an IndexError.  I left it open.

Update AddCoolingProfile.py

Change an IndexError to a ValueError
---
 .../scripts/AddCoolingProfile.py              | 59 ++++++++++++++-----
 1 file changed, 44 insertions(+), 15 deletions(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index 6e43e98969..64678189ab 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -286,7 +286,7 @@ class AddCoolingProfile(Script):
                     "description": "For machines with independent layer cooling fans.  Leaving a fan running while the other nozzle is printing can help with oozing.  You can pick the speed % for the idle nozzle layer cooling fan to hold at.",
                     "type": "bool",
                     "default_value": false,
-                    "enabled": "enable_off_fan_speed_enable"
+                    "enabled": "enable_off_fan_speed_enable and self.extruder_count > 1"
                 },
                 "off_fan_speed":
                 {
@@ -297,7 +297,7 @@ class AddCoolingProfile(Script):
                     "minimum_value": 0,
                     "maximum_value": 100,
                     "unit": "%    ",
-                    "enabled": "enable_off_fan_speed_enable and enable_off_fan_speed"
+                    "enabled": "enable_off_fan_speed_enable and enable_off_fan_speed and self.extruder_count > 1"
                 },
                 "enable_off_fan_speed_enable":
                 {
@@ -378,13 +378,35 @@ class AddCoolingProfile(Script):
             if script_count > 0:
                 # Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
                 self._instance.setProperty("delete_existing_m106", "value", False)
+        self._instance.setProperty("enable_off_fan_speed_enable", "value", False)
         if self.extruder_count > 1:
             if self.extruder_list[0].getProperty("machine_extruder_cooling_fan_number", "value") != self.extruder_list[1].getProperty("machine_extruder_cooling_fan_number", "value"):
-                self._instance.setProperty("enable_off_fan_speed_enable", "value", True)                
+                self._instance.setProperty("enable_off_fan_speed_enable", "value", True)
         if bool(self.global_stack.getProperty("machine_heated_bed", "value")):
             self._instance.setProperty("enable_bv_fan", "value", True)
- 
+
     def execute(self, data):
+        """
+        Collect the settings from Cura and from this script
+            params:
+                t0_fan thru t3_fan:  The fan numbers for up to 4 layer cooling circuits
+                fan_mode:  Whether the fan scale will be 0-255 PWM (when true) or 0-1 RepRap (when false)
+                bed_adhesion:  Is only important if a raft is enabled
+                print_seuence:  Options are slightly different if in One-at-a-Time mode
+                is_multi-fan:  Used to distinguish between a multi-extruder with a single fan for each nozzle, or one fan for both nozzles.
+                is_multi_extr_print:  For the slight difference in handling a multi-extruder printer and a print that only uses one of the extruders.
+                fan_list:  A list of fan speeds (even numbered items) and layer numbers (odd numbered items)
+                feature_speed_list:  A list of the speeds for each ';TYPE:' in the gcode
+                feature_name_list:  The list of each 'TYPE' in the gcode
+                off_fan_speed:  The speed that will be maintained by the fan for the inactive extruder (for an anti-oozing effect)
+                init_fan:  The fan number of the first extruder used in a print
+                delete_existing_m106: The first instance of the script in the post processing list should remove the CUra M106 lines.  Following instances should not delete the changes made by the first instance.
+                feature_fan_combing:  Whether or not to shut the cooling fan off during travel moves.
+                the_start_layer:  When in By Feature this is the user selected start of the fan changes.
+                the_end_layer:  When in By Feature this is the user selected end of the fan changes
+                the_end_is_enabled:  When in By Feature, if the fan control ends before the print ends, then this will enable the Final Fan Speed to carry through to the print end.                
+                
+        """
         # Exit if the gcode has been previously post-processed.
         if ";POSTPROCESSED" in data[0]:
             return data
@@ -396,8 +418,9 @@ class AddCoolingProfile(Script):
         # For 4.x versions that don't have the 0-1 option
         try:
             fan_mode = not bool(self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
-        except:
+        except AttributeError:
             pass
+
         bed_adhesion = (self.extruder_list[0].getProperty("adhesion_type", "value"))
         print_sequence = str(self.global_stack.getProperty("print_sequence", "value"))
 
@@ -454,7 +477,7 @@ class AddCoolingProfile(Script):
                     # Catch a possible input error.
                     if the_end_layer < the_start_layer:
                         the_end_layer = the_start_layer
-            except:
+            except ValueError:
                 the_end_layer = -1  # If there is an input error then default to the entire gcode file.
 
             # Get the speed for each feature
@@ -480,7 +503,7 @@ class AddCoolingProfile(Script):
             if the_end_layer == -1 or the_end_is_enabled == False:
                 the_end_layer = len(data) + 2
 
-        # For multi-extruder printers with separate fans the 'idle' nozzle fan can be left on for ooze control
+        # For multi-extruder printers with separate cooling fans the 'idle' nozzle fan can be left on for ooze control
         off_fan_speed = 0
         if self.extruder_count > 1:
             if self.getSettingValueByKey("enable_off_fan_speed"):
@@ -494,7 +517,7 @@ class AddCoolingProfile(Script):
         number_of_raft_layers = 0
         layer_0_index = 0
         # Catch the number of raft layers.
-        for l_num in range(1,10,1):
+        for l_num in range(1,len(data) - 1):
             layer = data[l_num]
             if ";LAYER:-" in layer:
                 number_of_raft_layers += 1
@@ -511,7 +534,7 @@ class AddCoolingProfile(Script):
             T2_used = False
             T3_used = False
             # Bypass the file header and ending gcode.
-            for num in range(1,len(data)-1,1):
+            for num in range(1,len(data)-1):
                 lines = data[num]
                 if "T0" in lines:
                     T0_used = True
@@ -635,10 +658,10 @@ class AddCoolingProfile(Script):
                 layer = data[layer_num]
                 data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
             data = self._add_travel_comment(data, layer_0_index)
-        
+
         if bool(self.getSettingValueByKey("bv_fan_speed_control_enable")):
             data = self._control_bv_fan(data)
-        
+
         # Single Fan "By Layer"
         if by_layer_or_feature == "by_layer" and not is_multi_fan:
             return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
@@ -681,11 +704,12 @@ class AddCoolingProfile(Script):
         this_fan = str(t0_fan)
         start_index = str(len(multi_fan_data))
         for num in range(0,15,2):
+
         # The fan_list may not be in ascending order.  Get the lowest layer number
             try:
                 if int(fan_list[num]) < int(start_index):
                     start_index = str(fan_list[num])
-            except:
+            except ValueError:
                 pass
         # Move the start point if delete_existing_m106 is false
         start_index = int(start_index) + int(layer_0_index)
@@ -706,12 +730,13 @@ class AddCoolingProfile(Script):
                 elif line == "T3":
                     prev_fan = this_fan
                     this_fan = t3_fan
+        # With Active Tool tracked - now the body of changes can start
         for l_index in range(int(start_index),len(multi_fan_data)-1,1):
             modified_data = ""
             layer = multi_fan_data[l_index]
             fan_lines = layer.split("\n")
             for fan_line in fan_lines:
-                # Prepare to shut down the previous fan and start the next one.
+                # Prepare to turn off the previous fan and start the next one.
                 if fan_line.startswith("T"):
                     if fan_line == "T0": this_fan = str(t0_fan)
                     if fan_line == "T1": this_fan = str(t1_fan)
@@ -751,6 +776,7 @@ class AddCoolingProfile(Script):
             for line in lines:
                 if ";LAYER:" in line:
                     layer_number = str(line.split(":")[1])
+                    continue
                 if int(layer_number) >= int(the_start_layer) and int(layer_number) < int(the_end_layer)-1:
                     temp = line.split(" ")[0]
                     try:
@@ -836,8 +862,9 @@ class AddCoolingProfile(Script):
                     temp = line.split(" ")[0]
                     try:
                         name_index = feature_name_list.index(temp)
-                    except:
+                    except IndexError:
                         name_index = -1
+
                     if name_index != -1:
                         modified_data += line + "\n" + feature_speed_list[name_index] + this_fan + "\n"
                         current_fan_speed = str(feature_speed_list[name_index].split("S")[1])
@@ -848,6 +875,7 @@ class AddCoolingProfile(Script):
                             current_fan_speed = "0"
                         else:
                             modified_data += line + "\n"
+
                     # If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
                     # There cannot be a 'break' here because if there are multiple fan numbers they still need to be shut off and turned on.
                     elif line == ";LAYER:" + str(the_end_layer):
@@ -857,6 +885,7 @@ class AddCoolingProfile(Script):
                     else:
                     # Layer and Tool get inserted into modified_data above.  All other lines go into modified_data here
                         if not line.startswith("T") and not line.startswith(";LAYER:"): modified_data += line + "\n"
+
             if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
             multi_fan_data[l_index] = modified_data
             modified_data = ""
@@ -950,7 +979,7 @@ class AddCoolingProfile(Script):
         return comment_data
 
     def _control_bv_fan(self, bv_data: str) -> str:
-        # Control the chamber fan
+        # Control any secondary fan.  Can be used for an Auxilliary/Chamber fan
         bv_start_layer = self.getSettingValueByKey("bv_fan_start_layer") - 1
         bv_end_layer = self.getSettingValueByKey("bv_fan_end_layer")
         bv_fan_nr = self.getSettingValueByKey("bv_fan_nr")

From 1e18e0e63d7a305a4d795955e2d90577e708f0b7 Mon Sep 17 00:00:00 2001
From: GregValiant <64202104+GregValiant@users.noreply.github.com>
Date: Wed, 16 Apr 2025 09:22:30 -0400
Subject: [PATCH 8/8] Update AddCoolingProfile.py

Found a bug.  The ";LAYER:" line was not being added to the "modified data" string when in "single_fan_by_feature" mode.

Update AddCoolingProfile.py

bug fix for the bug fix.
---
 plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
index 64678189ab..b046a77c2f 100644
--- a/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
+++ b/plugins/PostProcessingPlugin/scripts/AddCoolingProfile.py
@@ -776,7 +776,6 @@ class AddCoolingProfile(Script):
             for line in lines:
                 if ";LAYER:" in line:
                     layer_number = str(line.split(":")[1])
-                    continue
                 if int(layer_number) >= int(the_start_layer) and int(layer_number) < int(the_end_layer)-1:
                     temp = line.split(" ")[0]
                     try:
@@ -789,6 +788,7 @@ class AddCoolingProfile(Script):
                         if feature_fan_combing == True:
                             modified_data += "M106 S0" + t0_fan + "\n"
                 modified_data += line + "\n"
+                
                 # If an End Layer is defined and is less than the last layer then insert the Final Speed
                 if line == ";LAYER:" + str(the_end_layer) and the_end_is_enabled == True:
                     modified_data += feature_speed_list[len(feature_speed_list) - 1] + t0_fan + "\n"