FreeCAD-Postprocessor-for-KOSY/KOSY_post.py

334 lines
11 KiB
Python

# *****************************************************************************
# * 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 <String> ; Displays <String> 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 = "<None>"
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