334 lines
11 KiB
Python
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
|
|
|