# G-code Macros Editing

Aurora™ supports custom G-code Macros, allowing you to define complex behaviors or override existing commands. Macros are processed using a Jinja2-like templating engine, enabling dynamic logic based on the command context.

Note: The templating engine is exclusively supported within macro/calibration definitions, not standard files. G-code files are capable of calling a macro or calibration containing templated content.

# Macro Matching Logic

When a command is sent to the machine, the system searches for a matching macro based on the following priority: Exact Match: The system first looks for a macro that matches both the command and the specific parameters provided. General Match: If no exact match is found, it looks for a macro defined without parameters (e.g., G28). Macros without parameters can be called with any combination of parameters (e.g., G28 X, G28 X Y Z will both trigger the G28 macro).

# Recursion and Nesting

Macros are permitted to call other macros or G-code commands. However, the following rules apply: Maximum Depth: There is a hard limit (30) on nesting depth to prevent system instability. Infinite Recursion: If a macro calls itself (directly or indirectly) in a way that exceeds the maximum depth, the execution will be aborted with an error.

# Available parameters

# The command Variable

Every macro has access to a command object containing the data from the call. You can use Jinja2-like syntax (e.g., {{ command.letterCode }}) to inject these values into your macro code.

Available keys:

  • letterCode: The command type (e.g., "M" or "G"). (Always uppercase)x
  • code: The numerical value of the command (e.g., 8894).
  • parameters: A map of the parameters passed to the macro.
    • To check if a parameter exists, use: existsIn(command.parameters, "X").
    • To access a value: {{ command.parameters.X.value }}.
    • The parameter letter will always be uppercase, regardless of the casing used in the command
  • originalParameters: The raw, unparsed parameter string provided when the command was invoked (e.g., for G999 X10 Y20, it would be "X10 Y20").
  • context: Current machine state:
    • defaultSpeed (linear/rapid)
    • isMoveUnitInch
    • isRelativeExtrusion
    • isRelativePosition
  • homedAxis: A boolean map indicating which axes (X, Y, Z, U, V, W, A, B, C) are currently homed.
  • currentToolNumber: The ID of the currently active tool.
  • layerNumber: The current print layer.
  • originalLineNumber: The line number from the source file.
  • comment: Any comment text appended to the G-code line.

Example value for command M8894 Xallo Y1.2 T"a" Q ; Some comment on the macro:

{
  "letterCode": "M",
  "code": 8894,
  "parameters": {
    "Q": {
      "letterCode": "Q",
      "valueType": "EMPTY"
    },
    "T": {
      "letterCode": "T",
      "value": "a",
      "valueType": "STRING"
    },
    "X": {
      "letterCode": "X",
      "value": "allo",
      "valueType": "STRING"
    },
    "Y": {
      "letterCode": "Y",
      "value": 1.2,
      "valueType": "DOUBLE"
    }
  },
  "originalParameters": "Xallo Y1.2 T\"a\" Q",
  "comment": " Some comment on the macro",
  "context": {
    "defaultSpeed": {
      "linear": 5000.0,
      "rapid": 5000.0
    },
    "isMoveUnitInch": false,
    "isRelativeExtrusion": true,
    "isRelativePosition": false
  },
  "homedAxis": {
    "A": false,
    "B": false,
    "C": false,
    "U": false,
    "V": false,
    "W": false,
    "X": false,
    "Y": false,
    "Z": false
  },
  "currentToolNumber": 0,
  "layerNumber": 0,
  "originalLineNumber": -1
}

# The WizardResponse Variable

After calling a W101 G-code in a macro, the macro has access to a WizardResponse object containing the data from the call. You can use Jinja2-like syntax (e.g., {{ WizardResponse.buttonClicked }}) to inject these values into your macro code.

Available keys:

  • buttonClicked: The button clicked by the user. It can be “ok”, “yes”, “no”, “cancel”
  • fields: A map of the fields and their value
    • The keys of this map correspond to the F parameter value provided to the W G-codes.
    • To access a value: {{ WizardResponse.fields.myField }}.
{
  "buttonClicked": "ok",
  "fields": {
    "myFloatField": 2.1,
    "myIntField": 2,
    "myStringField": "value",
    "myChoiceField": "firstChoice",
    "myCheckboxField": true
  }
}

# Other Variables

Other variables are available during rendering. DataBlocks that are available during rendering can be viewed in the “preview” section when editing a macro or calibration.

In addition to that, a special variable VALIDATION_DEFAULT can be used for parameters pointing to a device on the printer (ex: the tool in M104) and ensures that it passes the validation step.

The variable isCommandManual is used to indicate whether the original command was sent manually from the interface or if it’s part of a printing job/calibration.

ENUMS contains some useful enumerations used internally. It can be used for printer/job statuses for instance.

# Templating Features

The following logic and syntax can be utilized within your macros:

# Expressions

{{ ... }}

Used to output values directly into the G-code.

  • Math: {{ command.parameters.X.value + 10 }}
  • Comparisons: ==, !=, >, <, >=, <=
  • Logic: and, or, not

# Statements

{% ... %}

Used for logic that does not print text.

  • Assignments: {% set speed = command.context.defaultSpeed.linear %}
  • Conditionals:
    {% if existsIn(command.parameters, "S") %} 
        M104 S{{ command.parameters.S.value }} 
    {% else if existsIn(command.parameters, "T") %}
        M104 T{{ command.parameters.T.value }} S200
    {% else %}
        M104 S200
    {% endif %}
    
  • Loops:
    {% for axis in ['X', 'Y', 'Z'] %}
        {% if existsIn(command.homedAxis, axis) %}
            G1 {{ axis }}{{ at(command.parameters, axis).value}}
        {% endif %}
    {% endfor %}
    

# Comments

{# ... #}

Used to add comments in templates that won’t be in the G-code output. Regular G-code comments using semi-colon (;) won’t prevent template statements or variables from being evaluated. Which means that templates can be used to generate comments in G-code.

# Whitespace Control

By default, white spaces and newlines are stripped around templating blocks. But this can also be partially manually controlled blocks. By using {%+ at the start of a statement block, you can keep the leading whitespaces in front of the block. However, at the moment, we can’t specify to keep trailing whitespaces.

This can cause some issues when using G-codes right after a templating block.

Examples:

Normally this block

    {% if true %}G92{% endif %}
{% if true %}
    G1 X2
{% endif %}
G1 Y2

    {%+ if true %}G92{% endif %}

Would render to

G92    G1 X2
G1 Y2

    G92   

# Logic & Data Helpers

  • existsIn(object, key): Returns true if a key exists (Essential for checking parameters).
    • Example: {% if existsIn(command.parameters, "X") %}
  • length(item): Returns the number of elements in a map or array.
    • Example: {% if length(command.parameters) == 0 %}
  • at(object, key): Returns the value at a specific key.
  • default(value, default_value): Returns the default if the value is undefined.
  • value in list: Returns true if a value exists in a list.
  • assign(array, index, value): Assign the value in the array at the index
    • Example: {{ assign(myArray, 2, “bar”) }}
  • setGlobal(variable, value): Assign the value in a variable of the global context, where it can be retrieved in another macro execution
    • Example: {{ setGlobal(“myVariable”, “bar”) }}
  • getGlobal(variable): retrieve the value of a variable in the global context.
    • Example: {% set myVariable = getGlobal(“myVariable”) %}

# String Functions

  • upper(string): Converts to uppercase.
  • lower(string): Converts to lowercase.
  • capitalize(string): Capitalizes the first letter.
  • trim(string): Removes leading/trailing whitespace.
  • int(string): Converts to integer
  • float(string): Converts to float
  • str(var), string(var), toString(var): Converts var to string

# Number Functions

  • odd(number): Check if number is odd
  • even(number): Check if number is even
  • divisibleBy(number1, number2): Check if number1 is divisible by number2

# Mathematical Functions

  • abs(number): Absolute value.
  • round(number, precision): Rounds to N decimal places.
  • min(a, b) / max(a, b): Returns the smaller or larger value.
  • range(stop) or range(start, stop): Generates a sequence for loops.
  • avg(array): Returns the average value in the array or 0 if the array is empty.

# Flow Control Functions

  • abortPrint(“<message>”): Stops the print with the error message (will do a M2 F””). Also stops the rendering of the template
  • exitMacro(): Stops the processing of the macro/template

# Date Function

getDate

Returns a string representing the local date and time. You can optionally provide a custom format string to specify how the output should look.

Syntax: getDate() getDate(format)

Parameters:

  • format (string, optional): A custom format string. If omitted, the default format used is "%Y-%m-%d_%H-%M-%S".

The formatting uses standard date and time specifiers. Below is a detailed list of the available specifiers you can use to build your custom format:

Specifier Description Example
Year
%Y Year with century as a decimal number 2026
%y Year without century as a zero-padded decimal number 26
Month
%B Full month name July
%b / %h Abbreviated month name Jul
%m Month as a zero-padded decimal number (01-12) 07
Day
%d Day of the month as a zero-padded decimal number (01-31) 28
%A Full weekday name Tuesday
%a Abbreviated weekday name Tue
%j Day of the year as a zero-padded decimal number (001-366) 209
%w Weekday as a decimal number (0-6, Sunday is 0) 2
Time
%H Hour (24-hour clock) as a zero-padded decimal number (00-23) 12
%I Hour (12-hour clock) as a zero-padded decimal number (01-12) 12
%p AM or PM designation PM
%M Minute as a zero-padded decimal number (00-59) 57
%S Second as a zero-padded decimal number (00-60) 28
Other
%z Timezone offset from UTC +0200
%Z Timezone name or abbreviation EST
%% A literal % character %

Examples:

{# Using the default format #}
Current Date: {{ getDate() }}
{# Output: Current Date: 2026-07-28_12-57-28 #}

{# Using a custom date format #}
Today is {{ getDate("%d/%m/%Y") }}
{# Output: Today is 28/07/2026 #}

{# Using a more verbose format #}
Time: {{ getDate("%A, %B %d, %Y at %I:%M %p") }}
{# Output: Time: Tuesday, July 28, 2026 at 12:57 PM #}

# Settings Functions

  • getSettings(sectionName): Returns the object representing the whole setting section.
    • Example: {% set generalSettings = getSettings(“general”) }}
  • getSettings(sectionName, key): Returns the object that has the key in the section
    • Example: {% set servo1 = getSettings(“motors”, “Servo-1”) }}
  • getSettingsAll(sectionName): Returns an array of all objects of a section.
    • Example: {% set motors = getSettingsAll(“motors”) }}
  • getSettingsAll(sectionName, property, expectedValue): Returns an array of objects that has the right expectedValue for the property.
    • Example: {% set tool1 = getSettingsAll(“tools”, “toolNumber”, 1) %}
  • getProbeSettings() or getProbeSettings(Endstop or accessoryNumber): Returns an object representing the settings of the probe. If no parameter is provided, the Mesh probe is returned.
    • Example:
      {% set probeSet = getProbeSettings() %}
      G1 X{{-probeSet.xOffset} Y{{-probeSet.yOffset}
      G1 Z-10 P{{probeSet.probe}}
      

Available settings sections :

  • general
  • axis or axes
  • motors
  • customSensors
  • heaters
  • fans
  • Accessories
  • tools

# Bed Tilt Calibration Function

  • calculateBedCalibration(probePoints): Returns the offset to apply to each Z axis motor and the offset of the Bed current height after applying the offset.

Example:

{% set calibrationResult = calculateBedCalibration(probePoints) %}
M922 Z{{calibrationResult.motorOffsets}}
G92 Z{{calibrationResult.zOffsetAfter}}

# Example: Homing Macro (G28)

The following example demonstrates a macro that handles homing for all axes if no parameters are passed, or specific axes if they are provided:

M120
G91
{% set home_all=(length(command.parameters) == 0) %}
{% if home_all %}
    G1 Z5
{% endif %}
{% for axis in ["X", "Y", "Z"] %}
    {% if home_all or existsIn(command.parameters, axis) %}
        G1 {{ axis }}4000 H1 F1500
        G1 {{ axis }}5 F1500 H-1
        G1 {{ axis }}10 H1 F600
    {% endif %}
{% endfor %}
M121