Has anyone coded an export script for PLT or HPGL in Grasshopper?
I am looking to send 2d lines from Rhino layers to a cutter, which uses a HPGL / PLT language. Has anyone created a GH definition which includes such a script? As I want to refine it. So I can send flattened geometry from a complex 3D model to get cut.
In the bigger picture all an HPGL file is just a text file that describes a set of machine movements in X, Y and Z (Z being Pen Up and Pen Down) directions. It very closely related to G-code in this way - just slightly more simple than G-code overall.
For tool selection you use the Select Pen - SPx - command, x is the number of the pen you are using. As I'm using a vinyl cutter without a pen/tool changer I just use SP1 in the file header/ini of the cutter.
Without knowing the full spec of your machine it is hard to say for certain BUT all of my experience with CNC machines - of all sizes and spec levels - the actual control files are pretty much the same. Very simple text based HPGL or G-code text files run all motion control - even on things like 7 axis robot arms etc. For plotting I'd expect you'd be able to get a usable HPGL/PLT file without a lot of work - its just a matter of matching the file to what the machine is expecting.
To answer your question about getting the file to the printer its maybe best to explain it this way: there are two parts to this project 1/ Create the correctly formatted text/hpgl/plt file ready to send to the printer 2/ Send the file to printer
For part 1/ the procedure is:
Select the curves you want to print Convert the curves into a set of points Format these points into HPGL Save this HPGL as a text file
For 2/ we need a way to stream the text file to a printer port
To do this I've used an old dos command line technique that allows allow you to 'copy' a text file to a printer LPT or COM port:
copy /b c:\spool\ini.plt LPT1
Type the above into a DOS command line and it will send a text file called ini.plt to the printer on LPT1 port. As you'll see in my attached code I use os.system calls in my python code to send files when needed.
So your original code was doing some strange things with the conversion from curves to points. Lines/Polylines were OK - with the code just using the line end points. For curves and polycurves the code code was exploding these into segments and then dividing into set of points. However this led to two issues: - curves that started off as closed polycurves would end up being plotted as open curve segments - which is not very good for a cut file and not very smooth for a plot file. - the division of the curves to points was by distance - and if this wasn't an exact division of the length of the curve the end point would not match up with the next line - again not ideal for a cutting file which needs to be a closed curve.
To solve the above I changed to using rs.ConvertCurveToPolyline - with the tolerance set to match the HPGL resolution of 0.025mm - this converts all curves needed to plot to polylines, leaves everything closed and ends points line up perfectly.
I had one other problem with my setup - I ran into a file size/curve number/plotting points upper limit. A small number of curves would cut/plot fine, however at a certain number in one file the print driver would throw an error and the plotter would not even start plotting the file. I could not work out where is the system this limit was being imposed. The current working version of my code is attached - it gets around this file size limit by creating a separate print file for each curve required and sending them to the plotter in sequence. Not as completely tidy as I'd like as it flashes up a cmd window on every loop - but plots/cuts are perfect.
The final 'nice touch' for the project is I've created a custom tool bar button to run the script - all I have to do to cut a file is hit the button on the tool bar, select the curves and hit enter = SO EASY!
I've attached my latest code, a sample HPGL file to plot a rectangle, and a screen shot of setting up the custom toolbar button.
So, just to update everyone and put some records online - I've done a lot work on this code in the last week or so.
Back story is I picked up and old XP based desk top PC to use as a print server for both my plotter/cutter and wide format printer. I had been running the plotter from my main work laptop - a Win10 machine via the plotters USB port. As it turns out you can't get Win XP drivers for this USB connection so I needed another solution.
I tried to use the plotters DB25 serial port connection using an old DB9 to DB25 modem cable I had in my collection = no luck the plotter wouldn't talk. A bit more research and it turns out these plotters need a 'null modem' cross over cable to operate. I found a pic of the correct wiring online and made up my own with some cable and connectors from the local electronics hobby shop.
With this hooked up and using Hyperterminal I was able to fire some codes to the plotter directly and get a response back - winning!
At this point I got my original code working with the 'net use' redirect from LPT1 to COM1.
HOWEVER - being that the plotter was now on a COM port there are a few more interesting things you can do with it - one is being able to read the paper size/cut area from the printer.
So what I needed to to was find a way to send and receive data to/from the plotter using the serial port.
A bit of research into .NET's serial port interface and using a bunch of small pieces of test code I have manged to completely re-jig this driver.
Upgrades include:
- Direct Serial Port comms using Null Modem cable (a USB to serial adaptor + null modem should also work)
- Plot area read from the plotter - a rectangle the size of the plot area is placed on a separate layer and coloured red
- Testing to see if selected plotting curves are both closed and inside of the cutting area - with errors shown and exiting if they are not right.
- After plot 'parking' of the plot head at the end of the cut items + an adjustable offset (currently requires manual resetting of origin on the plotter before for next cut)
Great thing is it is now 100% running within Rhino Python - no DOS command line calls = no flashing up of the CMD wind. Also no temp files needed on the HDD and no limit to number of curves that can be plotted - tested with 200 or so with no issues.
Overall very happy with whole project - have learnt a LOT about Python and .NET interfacing AND ended up with a very handy/useful tool.
Cheers
DK
# This code is a WIP # It plots directly to a DGI Plotter # via the serial port
import System.IO.Ports as Ports import rhinoscriptsyntax as rs import time
#Some setup values com_port = 'COM1' #change to match plotter port baud_rate = 9600 #change to match plotter setting plotter_step = .025 #mm finsh_offset = 10 #mm
#Delete old cutting area and cut objects if rs.IsLayer('Cutting Area'): rs.PurgeLayer('Cutting Area') if rs.IsLayer('Cutting Objects'): rs.PurgeLayer('Cut Objects')
#Setup Serial Port Myport = Ports.SerialPort(com_port) Port_Write = Ports.SerialPort.Write Myport.BaudRate = baud_rate Myport.ReadTimeout=5000 #5 secs Myport.Close() Myport.Open()
#place cutting area curve on its own layer, make it red and lock it plane = rs.WorldXYPlane() cutting_area = rs.AddRectangle( plane, (rect1), (rect2)) rs.AddLayer (name='Cutting Area', color=(255,0,0), visible=True, locked=True, parent=None) rs.ObjectLayer(cutting_area, 'Cutting Area')
#get plotting objects
allCurves = rs.GetObjects("Select curves to plot", rs.filter.curve)
#test to see if these are closed curves - exit if not
for curve in allCurves: test_closed = rs.IsCurveClosed(curve) if test_closed == 0: print "One or move of these curves are not closed" Myport.Close() exit()
#test to see if these are inside cutting area - exit if not
for curve in allCurves: test_inside = rs.PlanarClosedCurveContainment(curve, cutting_area)
if test_inside==0 or test_inside==1: print "One or more of these curves are outside of cut area" Myport.Close() exit()
#All ok - convert to points and send data to printer
# PU to the first point x = points[0][0] y = points[0][1] Port_Write(Myport, 'PU' + str(int(x / plotter_step)) + ',' + str(int(y / plotter_step)) + ';\n')
# PD to every subsequent point i = 1 while i < len(points): x = points[i][0] y = points[i][1] Port_Write(Myport, 'PD' + str(int(x / plotter_step)) + ',' + str(int(y / plotter_step)) + ';\n') i += 1
Port_Write(Myport,'PU;\n')
#find the far end of the cut box = rs.BoundingBox(allCurves) far_end = str(box[1]) far_end = far_end.split(",") far_end = far_end[0] far_end = float(far_end)/plotter_step far_end = (int(far_end))+ finsh_offset far_end = str(far_end) print (far_end)
#return plotter home and close port Port_Write(Myport, 'PU;PA' + far_end + ',0;IN;\n') Port_Write(Myport, 'SP1;\n') Port_Write(Myport, 'PA;\n') Myport.Close() time.sleep(10)
I wasn't liking how the code would throw up and exception box on early exit - say if the com port wouldn't open or the plotter wasnt ready.
A bit of research into Pythons exception handling and most of the code now sits inside of:
try:
***My code***
except:
print 'exception message'
Has cleaned up the reactions to the issues quite nicely - actually really liking this language - can see myself using for more tools in the future. Well done on McNeel for choosing this as a scripting language for Rhino.
Anyway - cleaned up code is attached - I've now got two versions, the second one just gets the max plotting area from the plotter and puts it onto the XY work plane so you can check everything will be inside the before plotting/cutting.
I copy these two scripts into a tool bar button:
Left mouse click on the button and it plots, right click gets the plot area.
David Kay
Hi John,
I found the Wikipedia site for the HPGL format very handy in getting this project done:
https://en.wikipedia.org/wiki/HP-GL
In the bigger picture all an HPGL file is just a text file that describes a set of machine movements in X, Y and Z (Z being Pen Up and Pen Down) directions. It very closely related to G-code in this way - just slightly more simple than G-code overall.
For tool selection you use the Select Pen - SPx - command, x is the number of the pen you are using. As I'm using a vinyl cutter without a pen/tool changer I just use SP1 in the file header/ini of the cutter.
Without knowing the full spec of your machine it is hard to say for certain BUT all of my experience with CNC machines - of all sizes and spec levels - the actual control files are pretty much the same. Very simple text based HPGL or G-code text files run all motion control - even on things like 7 axis robot arms etc. For plotting I'd expect you'd be able to get a usable HPGL/PLT file without a lot of work - its just a matter of matching the file to what the machine is expecting.
To answer your question about getting the file to the printer its maybe best to explain it this way: there are two parts to this project
1/ Create the correctly formatted text/hpgl/plt file ready to send to the printer
2/ Send the file to printer
For part 1/ the procedure is:
Select the curves you want to print
Convert the curves into a set of points
Format these points into HPGL
Save this HPGL as a text file
For 2/ we need a way to stream the text file to a printer port
To do this I've used an old dos command line technique that allows allow you to 'copy' a text file to a printer LPT or COM port:
copy /b c:\spool\ini.plt LPT1
Type the above into a DOS command line and it will send a text file called ini.plt to the printer on LPT1 port. As you'll see in my attached code I use os.system calls in my python code to send files when needed.
So your original code was doing some strange things with the conversion from curves to points. Lines/Polylines were OK - with the code just using the line end points. For curves and polycurves the code code was exploding these into segments and then dividing into set of points. However this led to two issues:
- curves that started off as closed polycurves would end up being plotted as open curve segments - which is not very good for a cut file and not very smooth for a plot file.
- the division of the curves to points was by distance - and if this wasn't an exact division of the length of the curve the end point would not match up with the next line - again not ideal for a cutting file which needs to be a closed curve.
To solve the above I changed to using rs.ConvertCurveToPolyline - with the tolerance set to match the HPGL resolution of 0.025mm - this converts all curves needed to plot to polylines, leaves everything closed and ends points line up perfectly.
I had one other problem with my setup - I ran into a file size/curve number/plotting points upper limit. A small number of curves would cut/plot fine, however at a certain number in one file the print driver would throw an error and the plotter would not even start plotting the file. I could not work out where is the system this limit was being imposed. The current working version of my code is attached - it gets around this file size limit by creating a separate print file for each curve required and sending them to the plotter in sequence. Not as completely tidy as I'd like as it flashes up a cmd window on every loop - but plots/cuts are perfect.
The final 'nice touch' for the project is I've created a custom tool bar button to run the script - all I have to do to cut a file is hit the button on the tool bar, select the curves and hit enter = SO EASY!
I've attached my latest code, a sample HPGL file to plot a rectangle, and a screen shot of setting up the custom toolbar button.
Cheers
DK
Nov 1, 2017
David Kay
So, just to update everyone and put some records online - I've done a lot work on this code in the last week or so.
Back story is I picked up and old XP based desk top PC to use as a print server for both my plotter/cutter and wide format printer. I had been running the plotter from my main work laptop - a Win10 machine via the plotters USB port. As it turns out you can't get Win XP drivers for this USB connection so I needed another solution.
I tried to use the plotters DB25 serial port connection using an old DB9 to DB25 modem cable I had in my collection = no luck the plotter wouldn't talk. A bit more research and it turns out these plotters need a 'null modem' cross over cable to operate. I found a pic of the correct wiring online and made up my own with some cable and connectors from the local electronics hobby shop.
With this hooked up and using Hyperterminal I was able to fire some codes to the plotter directly and get a response back - winning!
At this point I got my original code working with the 'net use' redirect from LPT1 to COM1.
HOWEVER - being that the plotter was now on a COM port there are a few more interesting things you can do with it - one is being able to read the paper size/cut area from the printer.
So what I needed to to was find a way to send and receive data to/from the plotter using the serial port.
A bit of research into .NET's serial port interface and using a bunch of small pieces of test code I have manged to completely re-jig this driver.
Upgrades include:
- Direct Serial Port comms using Null Modem cable (a USB to serial adaptor + null modem should also work)
- Plot area read from the plotter - a rectangle the size of the plot area is placed on a separate layer and coloured red
- Testing to see if selected plotting curves are both closed and inside of the cutting area - with errors shown and exiting if they are not right.
- After plot 'parking' of the plot head at the end of the cut items + an adjustable offset (currently requires manual resetting of origin on the plotter before for next cut)
Great thing is it is now 100% running within Rhino Python - no DOS command line calls = no flashing up of the CMD wind. Also no temp files needed on the HDD and no limit to number of curves that can be plotted - tested with 200 or so with no issues.
Overall very happy with whole project - have learnt a LOT about Python and .NET interfacing AND ended up with a very handy/useful tool.
Cheers
DK
# This code is a WIP
# It plots directly to a DGI Plotter
# via the serial port
import System.IO.Ports as Ports
import rhinoscriptsyntax as rs
import time
#Some setup values
com_port = 'COM1' #change to match plotter port
baud_rate = 9600 #change to match plotter setting
plotter_step = .025 #mm
finsh_offset = 10 #mm
#Delete old cutting area and cut objects
if rs.IsLayer('Cutting Area'):
rs.PurgeLayer('Cutting Area')
if rs.IsLayer('Cutting Objects'):
rs.PurgeLayer('Cut Objects')
#Setup Serial Port
Myport = Ports.SerialPort(com_port)
Port_Write = Ports.SerialPort.Write
Myport.BaudRate = baud_rate
Myport.ReadTimeout=5000 #5 secs
Myport.Close()
Myport.Open()
#Setup Plotter
Port_Write(Myport, 'PU;PA0,0;IN;\n')
Port_Write(Myport, 'SP1;\n')
Port_Write(Myport, 'PA;\n')
time.sleep(2)
#Read the Paper size from Plotter
Port_Write(Myport, 'OH;') #HPGL read limits code
time.sleep(2)
return1 = ''
papersize = ''
count = 0
char_in_buffer = 0
chars_in_buffer = Ports.SerialPort.BytesToRead.GetValue(Myport)
if chars_in_buffer == 0:
print 'Plotter not ready'
Myport.Close()
exit()
while (count < chars_in_buffer):
return1 = Myport.ReadChar()
papersize = papersize + chr(return1)
count = count + 1
papersize = papersize.split(",")
rect1 = (float(papersize[2])*plotter_step)
rect2 = (float(papersize[3])*plotter_step)
print 'Cutting area = ' + str(rect1) + 'x' + str(rect2)
#place cutting area curve on its own layer, make it red and lock it
plane = rs.WorldXYPlane()
cutting_area = rs.AddRectangle( plane, (rect1), (rect2))
rs.AddLayer (name='Cutting Area', color=(255,0,0), visible=True, locked=True, parent=None)
rs.ObjectLayer(cutting_area, 'Cutting Area')
#get plotting objects
allCurves = rs.GetObjects("Select curves to plot", rs.filter.curve)
#test to see if these are closed curves - exit if not
for curve in allCurves:
test_closed = rs.IsCurveClosed(curve)
if test_closed == 0:
print "One or move of these curves are not closed"
Myport.Close()
exit()
#test to see if these are inside cutting area - exit if not
for curve in allCurves:
test_inside = rs.PlanarClosedCurveContainment(curve, cutting_area)
if test_inside==0 or test_inside==1:
print "One or more of these curves are outside of cut area"
Myport.Close()
exit()
#All ok - convert to points and send data to printer
rs.AddLayer (name='Cut Objects', color=(0,255,0), visible=False, locked=True, parent=None)
for curve in allCurves:
Port_Write(Myport, 'PU;PA;SP1;\n')
polyline = rs.ConvertCurveToPolyline(curve,angle_tolerance=5.0, tolerance=0.025, delete_input=False, min_edge_length=0, max_edge_length=0)
points = rs.CurveEditPoints(polyline)
rs.ObjectLayer(polyline, 'Cut Objects')
# PU to the first point
x = points[0][0]
y = points[0][1]
Port_Write(Myport, 'PU' + str(int(x / plotter_step)) + ',' + str(int(y / plotter_step)) + ';\n')
# PD to every subsequent point
i = 1
while i < len(points):
x = points[i][0]
y = points[i][1]
Port_Write(Myport, 'PD' + str(int(x / plotter_step)) + ',' + str(int(y / plotter_step)) + ';\n')
i += 1
Port_Write(Myport,'PU;\n')
#find the far end of the cut
box = rs.BoundingBox(allCurves)
far_end = str(box[1])
far_end = far_end.split(",")
far_end = far_end[0]
far_end = float(far_end)/plotter_step
far_end = (int(far_end))+ finsh_offset
far_end = str(far_end)
print (far_end)
#return plotter home and close port
Port_Write(Myport, 'PU;PA' + far_end + ',0;IN;\n')
Port_Write(Myport, 'SP1;\n')
Port_Write(Myport, 'PA;\n')
Myport.Close()
time.sleep(10)
Nov 18, 2017
David Kay
So, did some more work on this project today.
I wasn't liking how the code would throw up and exception box on early exit - say if the com port wouldn't open or the plotter wasnt ready.
A bit of research into Pythons exception handling and most of the code now sits inside of:
try:
***My code***
except:
print 'exception message'
Has cleaned up the reactions to the issues quite nicely - actually really liking this language - can see myself using for more tools in the future. Well done on McNeel for choosing this as a scripting language for Rhino.
Anyway - cleaned up code is attached - I've now got two versions, the second one just gets the max plotting area from the plotter and puts it onto the XY work plane so you can check everything will be inside the before plotting/cutting.
I copy these two scripts into a tool bar button:
Left mouse click on the button and it plots, right click gets the plot area.
Cheers all
DK
Nov 19, 2017