# ***************************************************************************** # * Copyright (c) 2024 Marcel Dahl * # * * # * 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 * # * * # ****************************************************************************/ """Postprocessor to output real GCode for Max Computer GmbH nccad7.5.""" import FreeCAD import Path.Post.Utils as PostUtils import PathScripts.PathUtils as PathUtils import datetime TOOLTIP = """ This is a postprocessor file for the Path workbench. It is used to output real G-code suitable for the KOSY CNC devices. Some G and M codes are not supported. Please review result. Preconditions: metric values (check settings) Entered filename without suffix. Will be stored as .knc (nccad file extension for gcode) """ MACHINE_NAME = """Max Computer GmbH nccad MCS/KOSY""" # gCode for changing tools # M01 ; Displays and waits for user interaction TOOL_CHANGE = """G77 ; Move to release position M10 O6.0 ; Stop spindle M01 Insert tool TOOL G76 ; Move to reference point / WNP M10 O6.1 ; Start spindle / relais 6 on""" # gCode finishing the program POSTAMBLE = """G77 ; Move to release position fast M10 O6.0 ; Stop spindle relais 6 off G99 ;end program module""" # gCode header with information about CAD-software, post-processor # and date/time if FreeCAD.ActiveDocument: cam_file = FreeCAD.ActiveDocument.FileName else: cam_file = "" HEADER = """;Exported by FreeCAD ;Post Processor: {} ;CAM file: {} ;Output Time: {} """.format( __name__, cam_file, str(datetime.datetime.now()) ) # *************************************************************************** # * Internal global variables # *************************************************************************** RAPID_MOVES = ["G0", "G00"] # Rapid moves gCode commands definition # Global variables storing current position CURRENT_X = 0 CURRENT_Y = 0 CURRENT_Z = 0 CURRENT_F = 0 def export(objectslist, filename, argstring): """Export the list of objects into a filename. Parameters ---------- objectslists: list List of objects. filename: str Name of the output file without """ gcode = HEADER for obj in objectslist: gcode+= parse(obj) gcode += "\n" gcode += POSTAMBLE + "\n" # Open editor window if FreeCAD.GuiUp: dia = PostUtils.GCodeEditorDialog() dia.editor.setText(gcode) result = dia.exec_() if result: gcode = dia.editor.toPlainText() # Save to file if filename != "-": nccadFileEnding = ".knc" if filename.endswith(".nc"): # freecad file suffix filename = nccadFileEnding.join(filename.rsplit(".nc", 1)) gfile = open(filename, "w") gfile.write(gcode) gfile.close() return filename def parse(pathobj): global CURRENT_X global CURRENT_Y global CURRENT_Z global CURRENT_F out = "" lastcommand = None precision_string = ".2f" # Kosy support 0.01 mm steps params = [ "X", "Y", "Z", "A", "B", "C", "U", "V", "W", "I", "J", "K", "F", "S", "T", "Q", "R", "L", "P", ] # see KOSY help file NC-Kurzbefehlliste validCommands = [ "G0", "G1", "G2", "G3", "G20", "G24", "G54", "G60", "G61", "G74", "G76", "G77", "G79", # G80 start manufacture specific custom g-codes "G81", "G87", # G89 end custom g-codes "G88", "G89", "G90", # "G91", # KOSY valid, but not checked if input of postprocessor / Freecad delivers relative points "G98", "G99", "M1", #"M2", #"M3", #"M4", "M5", "M10", "M20", "M25", "M30", "M35", ] if hasattr(pathobj, "Group"): # We have a compound or project. out += "(Compound: " + pathobj.Label + ")\n" for p in pathobj.Group: out += parse(p) return out else: # parsing simple path if not hasattr( pathobj, "Path" ): # groups might contain non-path things like stock. return out for c in PathUtils.getPathWithPlacement(pathobj).Commands: outstring = [] command = c.Name if command[0] == ";(" : # command is a comment out += command + "\n" continue if not command in validCommands: out += ";(Unsupported command <"+ command + "> for KOSY maschine. Check simulation! )\n" continue # print the command if it is not the same as the last one if command == lastcommand: outstring.append(" ") # align / multiple spaces are ignored by machine else: outstring.append(command) # Now add the remaining parameters in order for param in params: if param in c.Parameters: if param == "F": if command not in RAPID_MOVES: speed = c.Parameters[param] if speed > 0.0 and speed != CURRENT_F: CURRENT_F = speed # Multiply F parameter value by 10, # FreeCAD = mm/s, nccad = 1/10 mm/s # speed *= 10 # Add command parameters and values and round float # as nccad9 does not support exponents outstring.append( param + format( float(speed), precision_string, ) ) elif param in ["T", "H", "S"]: outstring.append(param + str(int(c.Parameters[param]))) elif param in ["D", "P", "L"]: outstring.append(param + str(c.Parameters[param])) elif param in ["A", "B", "C"]: outstring.append( param + format(c.Parameters[param], precision_string) ) elif param == "K": # 2,5D KOSY doesnt support milling Z axis continue else: # [X, Y, Z, U, V, W, I, J, R, Q] pos = format( float(c.Parameters[param]), precision_string ) skip = False # improve code with # skip = sameCoordinate(pos, CURRENT_X) if param == "X": if pos == CURRENT_X: skip = True # same X coordinate else: CURRENT_X = pos # store new position if param == "Y": if pos == CURRENT_Y: skip = True # same coordinate else: CURRENT_Y = pos # store new position if param == "Z": if pos == CURRENT_Z: skip = True # same coordinate else: CURRENT_Z = pos # store new position if not skip or ( command in ["G2", "G3"] and param != "Z"): # while rotating movements KOSY needs all x y parameter outstring.append(param + format( pos ) ) # store the latest command lastcommand = command # TODO # if command in ("G98", "G99"): # DRILL_RETRACT_MODE = command # # # if TRANSLATE_DRILL_CYCLES: # if command in ("G81", "G82", "G83"): # out += drill_translate(outstring, command, c.Parameters) # # Erase the line we just translated # outstring = [] # TODO # if SPINDLE_WAIT > 0: # if command in ("M3", "M03", "M4", "M04"): # out += format_outstring(outstring) + "\n" # out += ( # linenumber() # + format_outstring(["G4", "P%s" % SPINDLE_WAIT]) # + "\n" # ) # outstring = [] # # Check for Tool Change: # if command in ("M6", "M06"): # if OUTPUT_COMMENTS: # out += "(Begin toolchange)\n" # if not OUTPUT_TOOL_CHANGE: # outstring.insert(0, "(") # outstring.append(")") # else: # for line in TOOL_CHANGE.splitlines(True): # out += line # prepend a line number and append a newline if len(outstring) >= 1: out += format_outstring(outstring) + "\n" return out # construct the line for the final output def format_outstring(strTable): s = "" for w in strTable: s += w + " " s = s.rstrip() return s #store into global variable? how by reference? def sameCoordinate(position, lastPosition): if position == lastPosition: return True else: lastPosition = position return False