<p>I've got some sliced models that represent the <strong>right</strong>-side arms and legs of a robot. I'm really happy with how they printed, so now I'd like to print the <strong>left</strong>-side arms and legs.</p>
<p>I was thinking it would be pretty trivial to parse the G-code file using Python and change the value of all the <code>Xn</code> commands from <code>n</code> to <code>2*h - n</code>, where <code>h</code> is in the middle of the bed, say 110 or 120 mm for an Ender 3.</p>
<p>Before I fire up my favorite IDE, are there any major gotchas I might encounter from such a naïve approach to mirroring the G-code like this? I originally sliced in Cura 4.9.1.</p>

[{'answer_id': 16660, 'author': 'John Meacham', 'author_id': 634, 'author_profile': 'https://3dprinting.stackexchange.com/users/634', 'pm_score': 3, 'selected': True, 'text': "<p>If your slicer does not have a mirror operation or a scale that allows negative values then mirroring in the G-code should be straightforward.</p>\n<p>As long as your printer doesn't have certain specific tool change or homing or purge positions that are done in the G-code you can just transform it, otherwise you would want to skip these sections and just do the model data (it should be obvious looking at the code where model data starts).</p>\n<p>In order to mirror it you just need to swap out the X coordinates in your G-code, If (0,0) is the center of your bed, as is often (but not always) the case for delta printers you will just want to negate the X, so <code>G1 X30 Y-3 Z2</code> becomes <code>G1 X-30 Y-3 Z2</code>.  If your coordinates have (0,0) in a corner  (often the case for orthogonal printers) then you want to subtract X from the maximum X value. for instance if your bed is 250\xa0mm wide then in <code>G1 X30 Y10 Z3  X30</code> becomes <code>X(250-30)</code> or <code>G1 X220 Y10 Z3</code>.</p>\n<p>There is only one caveat, some slicers will switch to relative movement using <code>G91</code> for certain operations and then back to absolute with <code>G90</code>, so you will want to look out for these. Between a <code>G91</code> and a <code>G90</code> you will want to negate the X, no matter where the origin of your printer is.</p>\n<p>When writing your script, I'd keep track of the minimum and maximum values encountered and the new minimum and maximum values and print them at the end as a sanity check so you can see if anything is wonky.</p>\n"}, {'answer_id': 16662, 'author': 'Ron Jensen', 'author_id': 28935, 'author_profile': 'https://3dprinting.stackexchange.com/users/28935', 'pm_score': 2, 'selected': False, 'text': '<p>Based on @John Mecham\'s comprehensive answer, I whipped up a quick proof of concept. In the image below, the left arrow (top) is the original and the right arrow (bottom) is the reversed clone. Cura does generate a little relative offset code at the end, I think I handled it correctly.</p>\n<p><a href="https://i.stack.imgur.com/ncnZP.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/ncnZP.jpg" alt="Left and right arrows" /></a></p>\n<pre><code>import re\n\ndata_start = re.compile(\'^;LAYER_COUNT:[0-9]+\')\n\nifilename = \'c:/Users/jentron128/Downloads/ThingVerse/Tools/arrow.gcode\'\nofilename = \'c:/Users/jentron128/Downloads/ThingVerse/Tools/rev_arrow.gcode\'\n\nnew_data =[]\nBEDX = float(235)\nh = BEDX # Absolute Positioning is default\n\nmode=\'Skip\'\n\nwith open(ifilename, \'r\') as ifp:\n    for d in ifp:\n        new_d = \'\'\n        tokens = d.split()\n        if len(tokens) == 0:\n            pass\n        elif mode == \'Skip\':\n            new_d = d[0:-1]\n            if data_start .match(d):\n                mode = \'Go\'\n        else:\n            if tokens[0] == \'G91\': # Relative Positioning\n                h = 0 \n            elif tokens[0] == \'G90\':# Absolute Positioning\n                h = BEDX\n                \n            for t in tokens:\n                if t.startswith(\'X\'):\n                    if len(t) &gt; 1:\n                        x = h - float(t[1:])\n                        t = \'X\'+f\'{x:.3f}\'\n                new_d += t+\' \'\n        \n        new_data.append(new_d)\n\nwith open(ofilename, \'w\') as ofp:\n    for d in new_data:\n        ofp.write(d+\'\\n\') # how does writelines not support a line separator?\n    \n</code></pre>\n'}, {'answer_id': 20040, 'author': 'Onza', 'author_id': 35611, 'author_profile': 'https://3dprinting.stackexchange.com/users/35611', 'pm_score': 0, 'selected': False, 'text': "<p>Usage: python3 [filename.gcode] [bed-width]</p>\n<pre><code>#!/usr/bin/env python3\n\nimport re\nfrom sys import argv\n\n#first arg is the file, second arg is the bed x width\nifilename = argv[1];\nofilename = ifilename.replace('.gcode', '-reversed.gcode');\n\nnew_data =[]\nBEDX = float(argv[2])\nh = BEDX # Absolute Positioning is default\n\nwith open(ifilename, 'r') as ifp:\n    for d in ifp:\n        new_d = ''\n        tokens = d.split()\n        if len(tokens) == 0:\n            pass\n        else:\n            if tokens[0] == 'G91': # Relative Positioning\n                h = 0\n                new_d += d\n            elif tokens[0] == 'G90':# Absolute Positioning\n                h = BEDX\n                new_d += d\n            elif tokens[0] == 'G1':\n                for t in tokens:\n                    if t.startswith('X'):\n                        if len(t) &gt; 1:\n                            x = h - float(t[1:])\n                            t = 'X'+f'{x:.3f}'\n                    new_d += t+' '\n            else:\n                new_d += d\n\n        new_data.append(new_d)\n\nwith open(ofilename, 'w') as ofp:\n    for d in new_data:\n        ofp.write(d+'\\n') # how does writelines not support a line separator?\n</code></pre>\n<p>For those that have a problem with the script above, I think this fixes it.</p>\n<p>Also there seems to be a bug with not writting G90 or G91 whenever it finds it.</p>\n"}]
['https://3dprinting.stackexchange.com/questions/16659', 'https://3dprinting.stackexchange.com', 'https://3dprinting.stackexchange.com/users/28935/']