# 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)xcode: 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
- To check if a parameter exists, use:
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)isMoveUnitInchisRelativeExtrusionisRelativePosition
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): Returnstrueif a key exists (Essential for checking parameters).- Example:
{% if existsIn(command.parameters, "X") %}
- Example:
length(item): Returns the number of elements in a map or array.- Example:
{% if length(command.parameters) == 0 %}
- Example:
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: Returnstrueif a value exists in a list.assign(array, index, value): Assign the value in the array at the index- Example:
{{ assign(myArray, 2, “bar”) }}
- Example:
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”) }}
- Example:
getGlobal(variable): retrieve the value of a variable in the global context.- Example:
{% set myVariable = getGlobal(“myVariable”) %}
- Example:
# 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 integerfloat(string): Converts to floatstr(var), string(var), toString(var): Converts var to string
# Number Functions
odd(number): Check if number is oddeven(number): Check if number is evendivisibleBy(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)orrange(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 aM2 F””). Also stops the rendering of the templateexitMacro(): 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”) }}
- Example:
getSettings(sectionName, key): Returns the object that has the key in the section- Example:
{% set servo1 = getSettings(“motors”, “Servo-1”) }}
- Example:
getSettingsAll(sectionName): Returns an array of all objects of a section.- Example:
{% set motors = getSettingsAll(“motors”) }}
- Example:
getSettingsAll(sectionName, property, expectedValue): Returns an array of objects that has the right expectedValue for the property.- Example:
{% set tool1 = getSettingsAll(“tools”, “toolNumber”, 1) %}
- Example:
getProbeSettings()orgetProbeSettings(EndstoporaccessoryNumber): 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}}
- Example:
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