Merge pull request #27964 from petterreinholdtsen/cam-fanuc-new-improved-1.1

CAM: Improved Fanuc support (crash, thread tapping, header, python warnings) - 1.1 backport
This commit is contained in:
sliptonic
2026-03-02 10:58:58 -06:00
committed by GitHub
+132 -85
View File
@@ -1,5 +1,11 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
"""
CAM post processor for CNC machines with a Fanuc controller.
"""
# ***************************************************************************
# * Copyright (c) 2014 sliptonic <[email protected]> *
# * Copyright (c) 2021 shadowbane1000 <[email protected]> *
@@ -33,7 +39,7 @@ import shlex
import os.path
import Path.Base.Util as PathUtil
import Path.Post.Utils as PostUtils
import PathScripts.PathUtils as PathUtils
from PathScripts import PathUtils
from builtins import open as pyopen
TOOLTIP = """
@@ -49,6 +55,16 @@ import fanuc_post
fanuc_post.export(object,"/path/to/file.ncc","")
"""
# Preamble text will appear at the beginning of the GCODE output file.
DEFAULT_PREAMBLE = """G17 G54 G40 G49 G80 G90 G94
"""
# Postamble text will appear following the last operation.
DEFAULT_POSTAMBLE = """M05
G17 G54 G90 G80 G40
M30
"""
now = datetime.datetime.now()
parser = argparse.ArgumentParser(prog="fanuc", add_help=False)
@@ -63,11 +79,16 @@ parser.add_argument(
parser.add_argument("--precision", help="number of digits of precision, default=3 (mm) or 4 (in)")
parser.add_argument(
"--preamble",
help='set commands to be issued before the first command, default="G17 G54 G40 G49 G80 G90\\n"',
help='set commands to be issued before the first command, default="'
+ DEFAULT_PREAMBLE.replace("\n", "\\n")
+ '"',
)
parser.add_argument(
"--postamble",
help='set commands to be issued after the last command, default="M05\\nG17 G54 G90 G80 G40\\nM30\\n"',
help="set commands to be issued after the last command, "
+ 'default="'
+ DEFAULT_POSTAMBLE.replace("\n", "\\n")
+ '"',
)
parser.add_argument(
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
@@ -122,15 +143,8 @@ PRECISION = 3
# rigid tapping.
tapSpeed = 0
# Preamble text will appear at the beginning of the GCODE output file.
DEFAULT_PREAMBLE = """G17 G54 G40 G49 G80 G90
"""
# Postamble text will appear following the last operation.
DEFAULT_POSTAMBLE = """M05
G17 G54 G90 G80 G40
M30
"""
PREAMBLE = DEFAULT_PREAMBLE
POSTAMBLE = DEFAULT_POSTAMBLE
# Pre operation text will be inserted before every operation
PRE_OPERATION = """"""
@@ -145,16 +159,27 @@ TOOL_CHANGE = """G28 G91 Z0
# List of drill G codes where some parameters are required and their
# required parameters.
DRILL_OPERATION = ("G73", "G81", "G82", "G83", "G84", "G85")
DRILL_OPERATION = ("G73", "G81", "G82", "G83", "G85")
DRILL_PARAM_REQ = ("L", "P", "Q", "R", "Z")
# The settings shared between methods
PREAMBLE = None
POSTAMBLE = None
def processArguments(argstring):
"""
Apply default values and command line arguments before
processing commands.
"""
global OUTPUT_HEADER
global OUTPUT_COMMENTS
global OUTPUT_LINE_NUMBERS
global SHOW_EDITOR
global PRECISION
global DEFAULT_PREAMBLE
global DEFAULT_POSTAMBLE
global PREAMBLE
global POSTAMBLE
global UNITS
@@ -185,7 +210,7 @@ def processArguments(argstring):
SHOW_EDITOR = False
else:
SHOW_EDITOR = True
print("Show editor = %s" % SHOW_EDITOR)
# print("Show editor = %s" % SHOW_EDITOR) # Commented to reduce test noise
if args.preamble is not None:
PREAMBLE = args.preamble.replace("\\n", "\n")
else:
@@ -245,7 +270,7 @@ def export(objectslist, filename, argstring):
)
return None
print("postprocessing...")
# print("postprocessing...") # Commented to reduce test noise
gcode = ""
gcode += "%\n"
@@ -256,9 +281,16 @@ def export(objectslist, filename, argstring):
major = int(FreeCAD.ConfigGet("BuildVersionMajor"))
minor = int(FreeCAD.ConfigGet("BuildVersionMinor"))
# the filename variable always contain "-", so unable to
# provide more accurate information.
gcode += "(" + "FREECAD-FILENAME-GOES-HERE" + ", " + "JOB-NAME-GOES-HERE" + ")\n"
# the filename variable always contain "-", use more relevant
# information
job = PathUtils.findParentJob(objectslist[0])
if job:
body, job = job.FullName.split("#")
else:
# Workaround for the TestFanucPost code, where there is no
# job returned by findParentJob
body, job = ("FREECAD-FILENAME-GOES-HERE", "JOB-NAME-GOES-HERE")
gcode += "(" + body.upper() + ", " + job.upper() + ")\n"
gcode += (
linenumber() + "(POST PROCESSOR: FANUC USING FREECAD %d.%d" % (major, minor) + ")\n"
)
@@ -365,7 +397,7 @@ def export(objectslist, filename, argstring):
else:
final = gcode
print("done postprocessing.")
# print("done postprocessing.") # Commented to reduce test noise
if not filename == "-":
gfile = pyopen(filename, "w")
@@ -492,85 +524,100 @@ def parse(pathobj):
if command == "G0":
continue
# if tool a tap, we thread tap, so stop the spindle for now.
# This only trigger when pathobj is a ToolController.
# If tool a tap, we will thread tap, so stop the spindle
# for now as there is no point in starting it to stop it
# in the G74/G84 operation after S29. This only trigger
# when pathobj is a ToolController.
if command == "M03" or command == "M3":
if hasattr(pathobj, "Tool") and pathobj.Tool.ShapeName.lower() == "tap":
if (
hasattr(pathobj, "Tool")
and getattr(pathobj.Tool, "ShapeType", "").lower() == "tap"
):
tapSpeed = int(pathobj.SpindleSpeed)
continue
# Convert drill cycles to tap cycles if tool is a tap.
# Handle thread tapping cycles. Uses rigid tapping.
# This only trigger when pathobj is a Operation.
if command == "G81" or command == "G83":
if (
hasattr(pathobj, "ToolController")
and pathobj.ToolController.Tool.ShapeName.lower() == "tap"
):
command = "G84"
out += linenumber() + "G95\n"
paramstring = ""
for param in ["X", "Y"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
if paramstring != "":
out += linenumber() + "G00" + paramstring + "\n"
if "S" in c.Parameters:
tapSpeed = int(c.Parameters["S"])
out += "M29 S" + str(tapSpeed) + "\n"
for param in ["Z", "R"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
# in this mode, F is the distance per revolution of the thread (pitch)
# P is the dwell time in seconds at the bottom of the thread
# Q is the peck depth of the threading operation
for param in ["F", "P", "Q"]:
if param in c.Parameters:
value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
if command == "G74" or command == "G84":
pitch_mm = float(c.Parameters["F"])
# Convert pitch to inches if needed
if UNITS == "G20": # imperial
pitch = pitch_mm / 25.4
else:
pitch = pitch_mm
paramstring = ""
for param in ["X", "Y"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(value.getValueAs(UNIT_FORMAT)),
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
if paramstring != "":
out += linenumber() + "G00" + paramstring + "\n"
if "S" in c.Parameters:
tapSpeed = int(c.Parameters["S"])
out += "M29 S" + str(tapSpeed) + "\n"
for param in ["Z", "R"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
out += linenumber() + "G84" + paramstring + "\n"
out += linenumber() + "G80\n"
out += linenumber() + "G94\n"
continue
# Calculate feed rate as distance per minute
if tapSpeed is not None:
feed_rate = pitch * tapSpeed
speed = Units.Quantity(feed_rate, UNIT_SPEED_FORMAT)
paramstring += " F" + format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string
)
else:
# No spindle speed found, output pitch as F
paramstring += " F" + format(pitch, precision_string)
# P is the dwell time in seconds at the bottom of the thread
# Q is the peck depth of the threading operation
for param in ["P", "Q"]:
if param in c.Parameters:
value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(value.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
out += linenumber() + command + paramstring + "\n"
out += linenumber() + "G80\n" # End tapping cycle
continue
outstring.append(command)