I finally decided to try some Rhino (6/mac) scripting, with a simple tool to export paths as gcode. It mostly works well, but I have had a lot of difficulty understanding how to get the selection behavior I want.
It would be great if anyone could suggest a resource that explains this topic in general. The RhinoCommon documentation lists a ton of methods and properties related to selection, but it is not at all clear how they interact, and I managed to crash Rhino quite a lot by trying to experiment.
My specific, current problem is that I need the user to pick objects one by one in a specific order, and I want to select the objects as they are picked – partly to provide feedback, and partly because it shouldn’t be possible to pick the same object twice. The OneByOnePostSelect
property gives me part of this behavior, but the objects are not visibly selected when I pick them, and if I call rhinoscriptsyntax.SelectObject()
on the picked ObjectRef
, nothing happens.
I’ve attached my script below – hopefully it is understandable, but it will probably be obvious to Rhino experts that I haven’t used these APIs (or python) before!
import rhinoscriptsyntax as rs
import Rhino
gcodeFile = None
feedrate = 1500.0
spindle = 10000.0
retractHeight = 6.0
tool = 0
jobOrigin = 0
firstPath = True
gcodeState = dict(
x = 0,
y = 0,
z = 0,
f = 0
)
# prompt for an output location and setup origin, and then repeatedly
# prompt for toolpaths and machine settings to write to the file in order
def event_loop():
global gcodeFile
global feedrate
global spindle
global retractHeight
global tool
global jobOrigin
global firstPath
filename = rs.SaveFileName(
title = "Export G Code",
filter = "G Code File (*.nc)|*.nc|All files (*.*)|*.*||",
filename = "Untitled",
extension = "nc"
)
if not filename: return
gcodeFile = open(filename, "w")
jobOrigin = rs.GetPoint("Select toolpath origin")
prompt = Rhino.Input.Custom.GetObject()
feedrateOption = Rhino.Input.Custom.OptionDouble(feedrate, 1.0, 5000)
prompt.AddOptionDouble("Feed", feedrateOption, "New feed rate (mm/min)")
spindleOption = Rhino.Input.Custom.OptionDouble(spindle, 0, 10000.0)
prompt.AddOptionDouble("Speed", spindleOption, "New spindle speed (rpm)")
retractOption = Rhino.Input.Custom.OptionDouble(retractHeight, 0, 100.0)
prompt.AddOptionDouble("RetractHeight", retractOption, "Safe retract height (mm)")
toolOption = Rhino.Input.Custom.OptionInteger(tool, 0, 1000)
prompt.AddOptionInteger("Tool", toolOption, "Tool number")
prompt.AcceptNothing(True)
prompt.EnablePreSelect(False, True)
prompt.OneByOnePostSelect = True
prompt.SetCommandPrompt("Select first toolpath curve")
prompt.GeometryFilter = Rhino.DocObjects.ObjectType.Curve
while True:
rc = prompt.Get()
if prompt.CommandResult() != Rhino.Commands.Result.Success:
return prompt.CommandResult()
if rc == Rhino.Input.GetResult.Object:
curveRef = prompt.Object(0)
if not curveRef: return Rhino.Commands.Result.Failure
append_toolpath(curveRef)
if firstPath:
firstPath = False
prompt.SetCommandPrompt("Select next toolpath curve")
continue
elif rc == Rhino.Input.GetResult.Option:
opt = prompt.OptionIndex()
if feedrateOption.CurrentValue != feedrate:
feedrate = feedrateOption.CurrentValue
if spindleOption.CurrentValue != spindle:
spindle = spindleOption.CurrentValue
if not firstPath: gcodeFile.write("M3 S{}\n".format(gcs(spindle)))
if retractHeight != retractOption.CurrentValue:
retractHeight = retractOption.CurrentValue
if tool != toolOption.CurrentValue:
tool = toolOption.CurrentValue
gcodeFile.write("M6 T{:d}\n".format(tool))
continue
elif rc == Rhino.Input.GetResult.Nothing:
close_file()
print ("Exported GCode to "+filename)
break
elif rc == Rhino.Input.GetResult.Cancel:
#TODO use a temp file so it's actually possible to cancel export...
close_file()
print ("GCode Export cancelled")
break
return Rhino.Commands.Result.Success
#format numbers for CNC without wasting bytes
def gcs(num): return "{:.99g}".format(round(num,3))
#convert Rhino units to mm
def rtomm(n): return n * rs.UnitScale(2)
#convert mm to Rhino units
def mmtor(n): return n / rs.UnitScale(2)
# write out G code for initial setup and tool positioning
# (once we know where the first toolpath begins)
def write_prefix(startX=0, startY=0):
global gcodeState
gcodeFile.write("\n".join((
"G21 (distances in mm)",
"G90 (absolute coords)",
"G17 (use XY plane for arcs)",
"G0 Z" + gcs(retractHeight) + " (rapid to safe height)",
"G0 X" + gcs(startX) + " Y" + gcs(startY) + " (rapid to start XY)",
"M3 S" + gcs(spindle) + " (run spindle clockwise)",
"",
)))
gcodeState['x'] = startX
gcodeState['y'] = startY
gcodeState['z'] = retractHeight
return
# write out G code for a toolpath, prepending commands to
# move the tool safely (you hope) from its previous position
def append_toolpath(curveRef):
global gcodeState
curve = curveRef.Curve()
if rs.IsPolyline(curve):
polyline = curve
else:
polyline = rs.ConvertCurveToPolyline(curve, 5.0, mmtor(0.01), False)
points = rs.PolylineVertices(polyline)
point = rs.PointSubtract(points[0], jobOrigin)
if firstPath:
write_prefix(point.X, point.Y)
else:
gcodeFile.write("G1 Z{}\nG0 X{} Y{}\n".format(
gcs(retractHeight),
gcs(point.X),
gcs(point.Y)
))
gcodeFile.write("\n(toolpath {}mm)\n".format(gcs(rs.CurveLength(curve))))
for i in range(len(points)):
point = rs.PointSubtract(points[i], jobOrigin)
write_G1(point)
if not rs.IsPolyline(curve): rs.DeleteObject(polyline)
rs.SelectObject(curveRef)
return
# write out a G1 command (single linear move) in a compact form,
# setting the X,Y,Z and F parameters only where they have changed
def write_G1(dest):
epsilon = mmtor(0.001)
if rs.Distance(dest, (gcodeState['x'],gcodeState['y'],gcodeState['z'])) < epsilon:
return
cmd = "G1 "
sep = ""
if abs(dest.X - gcodeState['x']) > epsilon:
cmd += "X" + gcs(rtomm(dest.X))
gcodeState['x'] = dest.X
sep = " "
if abs(dest.Y - gcodeState['y']) > epsilon:
cmd += sep + "Y" + gcs(rtomm(dest.Y))
gcodeState['y'] = dest.Y
sep = " "
if abs(dest.Z - gcodeState['z']) > epsilon:
cmd += sep + "Z" + gcs(rtomm(dest.Z))
gcodeState['z'] = dest.Z
sep = " "
if feedrate != gcodeState['f']:
cmd += sep + "F" + gcs(feedrate)
gcodeState['f'] = feedrate
gcodeFile.write(cmd + "\n")
return
# move tool to a safe height, write final commands and close file
def close_file():
gcodeFile.write ("G1 Z" + gcs(retractHeight) + "\n\n")
gcodeFile.write ("M5 (stop spindle)\nM30 (program end)\n")
gcodeFile.close()
return
if(__name__ == "__main__"):
event_loop()
gcExport.py (6.2 KB)
2 posts - 2 participants