# # # Klipper Adaptive Meshing # # #

# Heads up! If you have any other BED_MESH_CALIBRATE macros defined elsewhere in your config, you will need to comment out / remove them for this to work. (Klicky/Euclid Probe)
# You will also need to be sure that [exclude_object] is defined in printer.cfg, and your slicer is labeling objects.
# This macro will parse information from objects in your gcode to define a min and max mesh area to probe, creating an adaptive mesh!
# This macro will not increase probe_count values in your [bed_mesh] config. If you want richer meshes, be sure to increase probe_count. We recommend at least 5,5.

[gcode_macro BED_MESH_CALIBRATE]
rename_existing: _BED_MESH_CALIBRATE
gcode:

    {% set all_points = printer.exclude_object.objects | map(attribute='polygon') | sum(start=[]) %}                                # Gather all object points
    {% set bed_mesh_min = printer.configfile.settings.bed_mesh.mesh_min %}                                                          # Get bed mesh min from printer.cfg
    {% set bed_mesh_max = printer.configfile.settings.bed_mesh.mesh_max %}                                                          # Get bed mesh max from printer.cfg
    {% set probe_count = printer.configfile.settings.bed_mesh.probe_count %}                                                        # Get probe count from printer.cfg
    {% set kamp_settings = printer["gcode_macro _KAMP_Settings"] %}                                                                 # Pull variables from _KAMP_Settings
    {% set verbose_enable = kamp_settings.verbose_enable | abs %}                                                                   # Pull verbose setting from _KAMP_Settings
    {% set probe_dock_enable = kamp_settings.probe_dock_enable | abs %}                                                             # Pull probe dockable probe settings from _KAMP_Settings
    {% set attach_macro = kamp_settings.attach_macro | string %}                                                                    # Pull attach probe command from _KAMP_Settings
    {% set detach_macro = kamp_settings.detach_macro | string %}                                                                    # Pull detach probe command from _KAMP_Settings
    {% set mesh_margin = kamp_settings.mesh_margin | float %}                                                                       # Pull mesh margin setting from _KAMP_Settings
    {% set fuzz_amount = kamp_settings.fuzz_amount | float %}                                                                       # Pull fuzz amount setting from _KAMP_Settings
    {% set probe_count = probe_count if probe_count|length > 1 else probe_count * 2  %}                                             # If probe count is only a single number, convert it to 2. E.g. probe_count:7 = 7,7
    {% set max_probe_point_distance_x = ( bed_mesh_max[0] - bed_mesh_min[0] ) / (probe_count[0] - 1)  %}                            # Determine max probe point distance
    {% set max_probe_point_distance_y = ( bed_mesh_max[1] - bed_mesh_min[1] ) / (probe_count[1] - 1)  %}                            # Determine max probe point distance
    {% set x_min = all_points | map(attribute=0) | min | default(bed_mesh_min[0]) %}                                                # Set x_min from smallest object x point
    {% set y_min = all_points | map(attribute=1) | min | default(bed_mesh_min[1]) %}                                                # Set y_min from smallest object y point
    {% set x_max = all_points | map(attribute=0) | max | default(bed_mesh_max[0]) %}                                                # Set x_max from largest object x point
    {% set y_max = all_points | map(attribute=1) | max | default(bed_mesh_max[1]) %}                                                # Set y_max from largest object y point

    {% set fuzz_range = range((0) | int, (fuzz_amount * 100) | int + 1) %}                                                          # Set fuzz_range between 0 and fuzz_amount
    {% set adapted_x_min = x_min - mesh_margin - (fuzz_range | random / 100.0) %}                                                   # Adapt x_min to margin and fuzz constraints
    {% set adapted_y_min = y_min - mesh_margin - (fuzz_range | random / 100.0) %}                                                   # Adapt y_min to margin and fuzz constraints
    {% set adapted_x_max = x_max + mesh_margin + (fuzz_range | random / 100.0) %}                                                   # Adapt x_max to margin and fuzz constraints
    {% set adapted_y_max = y_max + mesh_margin + (fuzz_range | random / 100.0) %}                                                   # Adapt y_max to margin and fuzz constraints

    {% set adapted_x_min = [adapted_x_min , bed_mesh_min[0]] | max %}                                                               # Compare adjustments to defaults and choose max
    {% set adapted_y_min = [adapted_y_min , bed_mesh_min[1]] | max %}                                                               # Compare adjustments to defaults and choose max
    {% set adapted_x_max = [adapted_x_max , bed_mesh_max[0]] | min %}                                                               # Compare adjustments to defaults and choose min
    {% set adapted_y_max = [adapted_y_max , bed_mesh_max[1]] | min %}                                                               # Compare adjustments to defaults and choose min

    {% set points_x = (((adapted_x_max - adapted_x_min) / max_probe_point_distance_x) | round(method='ceil') | int) + 1 %}          # Define probe_count's x point count and round up
    {% set points_y = (((adapted_y_max - adapted_y_min) / max_probe_point_distance_y) | round(method='ceil') | int) + 1 %}          # Define probe_count's y point count and round up

    {% if (([points_x, points_y]|max) > 6) %}                                                                                       # 
        {% set algorithm = "bicubic" %}                                                                                             # 
        {% set min_points = 4 %}                                                                                                    # 
    {% else %}                                                                                                                      # Calculate if algorithm should be bicubic or lagrange
        {% set algorithm = "lagrange" %}                                                                                            # 
        {% set min_points = 3 %}                                                                                                    # 
    {% endif %}                                                                                                                     # 

    {% set points_x = [points_x , min_points]|max %}                                                                                # Set probe_count's x points to fit the calculated algorithm
    {% set points_y = [points_y , min_points]|max %}                                                                                # Set probe_count's y points to fit the calculated algorithm
    {% set points_x = [points_x , probe_count[0]]|min %}
    {% set points_y = [points_y , probe_count[1]]|min %}

    {% if verbose_enable == True %}                                                                                                 # If verbose is enabled, print information about KAMP's calculations
        {% if printer.exclude_object.objects != [] %}

            { action_respond_info( "Algorithm: {}.".format(                                                                              
                (algorithm),                                                                                                            
            )) }

            { action_respond_info("Default probe count: {},{}.".format(                                                                  
                (probe_count[0]),                                                                                                       
                (probe_count[1]),                                                                                                       
            )) }

            { action_respond_info("Adapted probe count: {},{}.".format(                                                                  
                (points_x),                                                                                                             
                (points_y),                                                                                                             
            )) }                                                                                                              

            {action_respond_info("Default mesh bounds: {}, {}.".format(                                                                  
                (bed_mesh_min[0],bed_mesh_min[1]),                                                                                      
                (bed_mesh_max[0],bed_mesh_max[1]),                                                                                      
            )) }

            {% if mesh_margin > 0 %}                                                                                                    
                {action_respond_info("Mesh margin is {}, mesh bounds extended by {}mm.".format(                                       
                    (mesh_margin),                                                                                                      
                    (mesh_margin),                                                                                       
                )) }                                                                                                                    
            {% else %}                                                                                                                  
                {action_respond_info("Mesh margin is 0, margin not increased.")}                                                        
            {% endif %}                                                                                                                 

            {% if fuzz_amount > 0 %}                                                                                                    
                {action_respond_info("Mesh point fuzzing enabled, points fuzzed up to {}mm.".format(                                     
                    (fuzz_amount),                                                                                                      
                )) }                                                                                                                    
            {% else %}                                                                                                                  
                {action_respond_info("Fuzz amount is 0, mesh points not fuzzed.")}                                                      
            {% endif %}                                                                                                                 

            { action_respond_info("Adapted mesh bounds: {}, {}.".format(                                                                 
                (adapted_x_min, adapted_y_min),                                                                                         
                (adapted_x_max, adapted_y_max),                                                                                         
            )) }

            {action_respond_info("KAMP adjustments successful. Happy KAMPing!")}

        {% else %}

            {action_respond_info("No objects detected! Check your gcode and make sure that EXCLUDE_OBJECT_DEFINE is happening before BED_MESH_CALIBRATE is called. Defaulting to regular meshing.")}
            G4 P5000                                                                                                                # Wait 5 seconds to make error more visible
        {% endif %}

    {% endif %}

    {% if probe_dock_enable == True %}
        {attach_macro}                                                                                                              # Attach/deploy a probe if the probe is stored somewhere outside of the print area
    {% endif %}

    _BED_MESH_CALIBRATE mesh_min={adapted_x_min},{adapted_y_min} mesh_max={adapted_x_max},{adapted_y_max} ALGORITHM={algorithm} PROBE_COUNT={points_x},{points_y}

    {% if probe_dock_enable == True %}
        {detach_macro}                                                                                                              # Detach/stow a probe if the probe is stored somewhere outside of the print area
    {% endif %}                                                                                                                     # End of verbose