| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | TOOLTIP = """ |
| | 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 Path.Post.Utils as PostUtils |
| | from builtins import open as pyopen |
| |
|
| |
|
| | |
| | def export(objectslist, filename, argstring): |
| | "Export objects as Roland Modela code." |
| |
|
| | code = "" |
| | for obj in objectslist: |
| | code += convertobject(obj) |
| |
|
| | gfile = pyopen(filename, "w") |
| | 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(): |
| | |
| | return "" |
| |
|
| |
|
| | def addheader(): |
| | return ["PA;PA;"] |
| |
|
| |
|
| | def addfooter(): |
| | return [] |
| |
|
| |
|
| | def mm2cord(mm): |
| | mm = float(mm) |
| | return int(40.0 * mm) |
| |
|
| |
|
| | def feed(x=None, y=None, z=None, state=None): |
| | c = [] |
| | if state is None: |
| | state = {} |
| |
|
| | 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: |
| | |
| | c.append("Z%d,%d,%d;" % (mm2cord(x), mm2cord(y), mm2cord(z))) |
| | elif x is not None and y is not None: |
| | |
| | c.append("PD%d,%d;" % (mm2cord(x), mm2cord(y))) |
| | elif z is not None: |
| | pass |
| | return c |
| |
|
| |
|
| | def jog(x=None, y=None, z=None, state=None): |
| | c = [] |
| | if state is None: |
| | state = {} |
| | 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;") |
| | state["Z"] = z |
| |
|
| | return c |
| |
|
| |
|
| | def xyarc(args, state): |
| | |
| | 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 |
| | points = arc.discretize(steps) |
| | |
| | |
| | for p in points: |
| | c += feed(p.x, p.y, state["Z"], state) |
| | return c |
| |
|
| |
|
| | def speed(xy=None, z=None, state=None): |
| | c = [] |
| | if state is None: |
| | state = {} |
| | 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": |
| | |
| | return jog(args["X"], args["Y"], args["Z"], state) |
| | elif cmd == "G1": |
| | |
| | c = [] |
| | |
| | c += speed(xy=args["F"], z=args["F"], state=state) |
| | |
| | c += feed(args["X"], args["Y"], args["Z"], state) |
| | return c |
| | elif cmd == "G2" or cmd == "G3": |
| | |
| | c = [] |
| | |
| | c += speed(xy=args["F"], state=state) |
| | |
| | if args["X"] and args["Y"] and args["Z"]: |
| | |
| | pass |
| | elif args["X"] and args["Y"]: |
| | |
| | c += xyarc(args, state) |
| | return c |
| | elif cmd == "G20": |
| | |
| | raise ValueError("rml_post: Inches mode not supported") |
| | elif cmd == "G21": |
| | |
| | return "" |
| | elif cmd == "G40": |
| | |
| | return "" |
| | elif cmd == "G80": |
| | |
| | return "PU;" |
| | elif cmd == "G81": |
| | c = [] |
| | |
| | c += speed(z=args["F"], state=state) |
| | |
| | c += jog(args["X"], args["Y"], state=state) |
| | c += feed(args["X"], args["Y"], args["Z"], state) |
| | return c |
| | elif cmd == "G90": |
| | |
| | return "" |
| | elif cmd == "G98": |
| | |
| | return "" |
| | else: |
| | raise NotImplementedError("rml_post: GCode command %s not understood" % (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 = [] |
| |
|
| | |
| | output += addheader() |
| | output += motoron() |
| |
|
| | output += speed(2.0, 1.0, state) |
| |
|
| | |
| |
|
| | |
| | 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 as e: |
| | print(e) |
| |
|
| | |
| | output += motoroff() |
| | output += home() |
| | output += addfooter() |
| |
|
| | return "\n".join(output) |
| |
|
| |
|
| | |
| |
|