#*************************************************************************** #* (c) Jon Nordby (jononor@gmail.com) 2015 * #* * #* This file is part of the FreeCAD CAx development system. * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* FreeCAD is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Lesser General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with FreeCAD; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #***************************************************************************/ ''' FreeCAD Path post-processor to output code for the Roland Modela MDX-## machines. The machine speaks RML-1, specified in 'Roland RML-1 Programming Guidelines' https://itp.nyu.edu/classes/cdp-spring2014/files/2014/09/RML1_Manual.pdf http://altlab.org/d/content/m/pangelo/ideas/rml_command_guide_en_v100.pdf The format has some overlap with HPGL: https://en.wikipedia.org/wiki/HPGL http://paulbourke.net/dataformats/hpgl/ ''' import FreeCAD import Part import PostUtils # to distinguish python built-in open function from the one declared below if open.__module__ == '__builtin__': pythonopen = open # Entrypoint used by FreeCAD def export(objectslist, filename): "Export objects as Roland Modela code." code = "" for obj in objectslist: code += convertobject(obj) gfile = pythonopen(filename,"wb") gfile.write(code) gfile.close() def convertobject(obj): gcode = obj.Path.toGCode() gcode = parse(gcode) return gcode def motoron(): return [ "!MC1;" ] def motoroff(): return [ "!MC0;" ] def home(): return [ "H;" ] def setjog(): # "!PZ%d,%d;",iz_down,iz_up); // set z down, jog return "" def addheader(): return [ "PA;PA;" ] # absolute positioning def addfooter(): return [] def mm2cord(mm): mm = float(mm) return int(40.0*mm) def feed(x=None, y=None, z=None, state={}): c = [] if x is not None: x = float(x) state['X'] = x if y is not None: y = float(y) state['Y'] = y if z is not None: z = float(z) state['Z'] = z if x is not None and y is not None and z is not None: # 3d motion c.append("Z%d,%d,%d;" % (mm2cord(x), mm2cord(y), mm2cord(z))) elif x is not None and y is not None: # 2d in XY plane c.append("PD%d,%d;" % (mm2cord(x), mm2cord(y))) elif z is not None: pass # XXX: is this used? return c def jog(x=None, y=None, z=None, state={}): c = [] if x is not None and y is not None: x, y = float(x), float(y) c.append("PU%d,%d;" % (mm2cord(x), mm2cord(y))) state['X'] = x state['Y'] = y if z is not None: z = float(z) c.append("PU;") # TODO: use !ZZ command state['Z'] = z return c def xyarc(args, state): # no native support in RML/Modela, convert to linear line segments c = [] lastPoint = FreeCAD.Vector(state['X'], state['Y']) newPoint = FreeCAD.Vector(float(args['X']), float(args['Y'])) centerOffset = FreeCAD.Vector(float(args['I']), float(args['J'])) center = lastPoint + centerOffset radius = (center - lastPoint).Length xyNormal = FreeCAD.Vector(0, 0, 1) circle = Part.Circle(center, xyNormal, radius) p0 = circle.parameter(lastPoint) p1 = circle.parameter(newPoint) arc = Part.ArcOfCircle(circle, p0, p1) steps = 64 # TODO: specify max error instead points = arc.discretize(steps) # TODO: consider direction #print 'p = Part.ArcOfCircle(Part.Circle(FreeCAD.Vector(%f, %f), FreeCAD.Vector(0, 0, 1), %f), %f, %f)' % (center.x, center.y, radius, p0, p1) for p in points: #print 'p', p.x, p.y c += feed(p.x, p.y, state['Z'], state) return c def speed(xy=None, z=None, state={}): c = [] print xy, z, state if xy is not None: xy = float(xy) if xy > 0.0 and xy != state['XYspeed']: c.append("VS%.1f;" % xy) state['XYspeed'] = xy if z is not None: z = float(z) if z > 0.0 and z != state['Zspeed']: c.append("!VZ%.1f;" % z) state['Zspeed'] = z return c def convertgcode(cmd, args, state): """Convert a single gcode command to equivalent Roland code""" if cmd == 'G0': # jog return jog(args['X'], args['Y'], args['Z'], state) elif cmd == 'G1': # linear feed c = [] # feedrate c += speed(xy=args['F'], z=args['F'], state=state) # motion c += feed(args['X'], args['Y'], args['Z'], state) return c elif cmd == 'G2' or cmd == 'G3': # arc feed c = [] # feedrate c += speed(xy=args['F'], state=state) # motion if args['X'] and args['Y'] and args['Z']: # helical motion pass elif args['X'] and args['Y']: # arc in plane c += xyarc(args, state) return c elif cmd == 'G20': # inches mode raise ValueError("rml_post: Inches mode not supported") elif cmd == 'G21': # millimeter mode return "" elif cmd == 'G40': # tool compensation off return "" elif cmd == 'G80': # cancel all cycles (drill normally) return "PU;" elif cmd == 'G81': c = [] # feedrate c += speed(z=args['F'], state=state) # motion c += jog(args['X'], args['Y'], state=state) c += feed(args['X'], args['Y'], args['Z'], state) return c elif cmd == 'G90': # absolute mode? return "" elif cmd == 'G98': # feedrate return "" else: raise NotImplementedError("rml_post: GCode command %s not understood" % (cmd,)) raise Exception("should not be reached. cmd=%s" % (cmd,)) def parse(inputstring): "parse(inputstring): returns a parsed output string" state = { 'X': 0.0, 'Y': 0.0, 'Z': 0.0, 'XYspeed': -1.0, 'Zspeed': -1.0 } output = [] # header output += addheader() output += motoron() output += speed(2.0, 1.0, state) # defaults # TODO: respect clearance height # treat the input line by line lines = inputstring.split("\n") for line in lines: if not line: continue parsed = PostUtils.stringsplit(line) command = parsed['command'] print 'cmd', line try: if command: code = convertgcode(command, parsed, state) if not isinstance(code, list): code = [ code ] if len(code) and code[0]: output += code except NotImplementedError, e: print e # footer output += motoroff() output += home() output += addfooter() return '\n'.join(output) print __name__ + " gcode postprocessor loaded."