@theolodian good point, I'll read up on the coordinate system method another time. The printer is running in FFF rather than CNC mode which may complicate things a little, but can't see why you'd limit that feature to CNC - even if the request came from there.
As it happens I'm just aiming for the middle of a circular-ish feature, so rotation not an issue this time thankfully.
Here was what I had in the macro:
; https://duet3d.dozuki.com/Wiki/GCode_Meta_Commands#Section_Variable_naming ; https://duet3d.dozuki.com/Wiki/Object_Model_of_RepRapFirmware#Section_Uses ; https://forum.duet3d.com/topic/26368/object-model-report-contents-of-object ; https://forum.duet3d.com/topic/26370/sanity-check-tool-offsets-g10-p0-x var num_x_pattern = 3 var num_y_pattern = 2 var x_pattern_space = 50.0 var y_pattern_space = 25.0 var tool_num = 0 var int_x = move.axes[0].machinePosition var int_y = move.axes[1].machinePosition var initial_x_offset = tools[var.tool_num].offsets[0] var initial_y_offset = tools[var.tool_num].offsets[0] M98 P"0:/macros/intro.g" while true if iterations = (var.num_x_pattern * var.num_y_pattern) break else var x_i = mod(iterations, var.num_x_pattern) var y_i = ((iterations-var.x_i)/var.num_x_pattern) var x_pos = var.int_x + (var.x_i * var.x_pattern_space) var y_pos = var.int_y + (var.y_i * var.y_pattern_space) G10 P0 X{(-1 * var.x_pos)} Y{(-1 * var.y_pos)} M98 P"0:/gcodes/sample_build.g" continue G10 P0 X{(-1 * var.initial_x_offset)} Y{(-1 * var.initial_y_offset)} M98 P"0:/macros/extro.g"