-
Notifications
You must be signed in to change notification settings - Fork 30
Path module
This is a temporary place for the documentation of the path module. See also the Path module workplan
The Path workbench offers tools to import, create, manipulate and export machine toolpaths in FreeCAD. With it, the user is able to import, visualize and modify existing gcode programs, generate toolpaths from 3D shapes, and export these toolpaths to gcode.
In its current state, though, the Path workbench is still in pretty early stages of development, and will not offer you the very advanced functionality found in some commercial alternatives. However, its broad python scripting interface makes it easy to modify or develop more powerful tools, and is therefore at the moment more directed to users with some knowledge of python scripting than to end users.
Below you will find a description of the available GUI tools, and a more in-depth description of the python scripting API.
Just compile the path-module branch at https://github.com/yorikvanhavre/FreeCAD/tree/path-module
>>> import Path
>>> c1 = Path.Command("g1x1")
>>> c2 = Path.Command("g1y4")
>>> c3 = Path.Command("g1 x2 y2") # spaces end newlines are not considered
>>> p = Path.Path([c1,c2,c3])
>>> o = App.ActiveDocument.addObject("Path::Feature","mypath")
>>> o.Path = p
>>> print p.toGCode()
Example file: http://www.uncreated.net/cloud/public.php?service=files&t=3fbc9b7a5fd9288925ebbe9cebcb0ae3
- Profile : Creates a profile path around a selected face or wire
- Pocket : Creates a pocket path from a selected face or closed loop of edges
- Drilling : Creates a drilling operation
- From Shape : Creates a Path from a selected Wire shape
- Compound : Creates a Path compound that is a combination of one or more selected paths
- Project : Creates a Path project which is a compound with several additional project-wide properties
- Dressup : Modifies another path by inserting new commands at a certain position
A preliminary concept is important to grasp. Most of the implementation below relies heavily on motion commands that have the same names as GCode commands, but aren't meant to be close to a particular controller's implementation. We chose names such as 'G0' to represent 'rapid' move or 'G1' to represent 'feed' move for performance (efficient file saving) and to minimize the work needed to translate to/from other GCode formats. Since the CNC world speaks thousands of GCode dialects, we chose to stick with a very simplified subset of it.
This has the following strict implications:
- Only commands starting with G or M are taken into account, the rest is considered a parameter
- No repeated parameters after a G command are allowed (for example, G1 X1 Y1 X2 Y2 will not work)
- Parameters after a G or M command are always sorted by alphabetic order. This means for example that G1 X1 Y1 F300 will be stored as G1 F300 X1 Y1
- Subroutines (O commands) are not supported
- Both G1 and G01 forms are supported
- You can omit X, Y or Z parameters (last ones will be reused)
- I, J , K coordinates are relative (Arc's R parameter is not supported)
- X, Y , Z, A, B, C are absolute by default, but changing with G90/G91is supported
All translations to/from dialects to FreeCAD GCode are done through pre and post scripts. That means that if you want to work with a machine that uses a specific LinuxCNC, Fanuc, Mitusubishi, or HAAS controller etc, you will have to use (or write if inexistant) a post processor for that particular control (see the "Importing and exporting GCode" section below).
The Command object represents a gcode command. It has three attributes: Name, Parameters and Placement, and two methods: toGCode() and setFromGCode(). Internally, it contains only a name and a dictionary of parameters. The rest (placement and gcode) is computed to/from this data.
>>> import Path
>>> c=Path.Command()
>>> c
Command ( )
>>> c.Name = "G1"
>>> c
Command G1 ( )
>>> c.Parameters= {"X":1,"Y":0}
>>> c
Command G1 ( X:1 Y:0 )
>>> c.Parameters
{'Y': 0.0, 'X': 1.0}
>>> c.Parameters= {"X":1,"Y":0.5}
>>> c
Command G1 ( X:1 Y:0.5 )
>>> c.toGCode()
'G1X1Y0.5'
>>> c2=Path.Command("G2")
>>> c2
Command G2 ( )
>>> c3=Path.Command("G1",{"X":34,"Y":1.2})
>>> c3
Command G1 ( X:34 Y:1.2 )
>>> c3.Placement
Placement [Pos=(34,1.2,0), Yaw-Pitch-Roll=(0,0,0)]
>>> c3.toGCode()
'G1X34Y1.2'
>>> c3.setFromGCode("G1X1Y0")
>>> c3
Command G1 [ X:1 Y:0 ]
>>> c4 = Path.Command("G1X4Y5")
>>> c4
Command G1 [ X:4 Y:5 ]
>>> p1 = App.Placement()
>>> p1.Base = App.Vector(3,2,1)
>>> p1
Placement [Pos=(3,2,1), Yaw-Pitch-Roll=(0,0,0)]
>>> c5=Path.Command("g1",p1)
>>> c5
Command G1 [ X:3 Y:2 Z:1 ]
>>> p2=App.Placement()
>>> p2.Base = App.Vector(5,0,0)
>>> c5
Command G1 [ X:3 Y:2 Z:1 ]
>>> c5.Placement=p2
>>> c5
Command G1 [ X:5 ]
>>> c5.x
5.0
>>> c5.x=10
>>> c5
Command G1 [ X:10 ]
>>> c5.y=2
>>> c5
Command G1 [ X:10 Y:2 ]
The Path object holds a list of commands
>>> import Path
>>> c1=Path.Command("g1",{"x":1,"y":0})
>>> c2=Path.Command("g1",{"x":0,"y":2})
>>> p=Path.Path([c1,c2])
>>> p
Path [ size:2 length:3 ]
>>> p.Commands
[Command G1 [ X:1 Y:0 ], Command G1 [ X:0 Y:2 ]]
>>> p.Length
3.0
>>> p.addCommands(c1)
Path [ size:3 length:4 ]
>>> p.toGCode()
'G1X1G1Y2G1X1'
lines = '''
G0X-0.5905Y-0.3937S3000M03
G0Z0.125
G1Z-0.004F3
G1X0.9842Y-0.3937F14.17
G1X0.9842Y0.433
G1X-0.5905Y0.433
G1X-0.5905Y-0.3937
G0Z0.5
'''
slines = lines.split('\n')
p = Path.Path()
for line in slines:
p.addCommands(Path.Command(line))
o = App.ActiveDocument.addObject("Path::Feature","mypath")
o.Path = p
# but you can also create a path directly form a piece of gcode. The commands
# will be created automatically:
p = Path.Path()
p.setFromGCode(lines)
The Path feature is a FreeCAD document object, that holds a path, and represents it in the 3D view.
>>> pf = App.ActiveDocument.addObject("Path::Feature","mypath")
>>> pf
<Document object>
>>> pf.Path = p
>>> pf.Path
Path [ size:2 length:2 ]
The Path feature also holds a Placement property. Changing the value of that placement will change the position of the Feature in the 3D view, although the Path information itself won't be modified. The transformation is purely visual.
The Tool object contains the definitions of a CNC tool. The Tooltable object contains an ordered list of tools. Tooltables are attached as a property to Path Project features, and can also be edited via the GUI, by double-clicking a project in the tree view, and clicking the "Edit tooltable" button in the task views that opens.
From that dialog, tooltables can be imported from FreeCAD's .xml and HeeksCad's .tooltable formats, and exported to FreeCAD's .xml format.
>>> import Path
>>> t1=Path.Tool()
>>> t1
Tool Default tool
>>> t1.Name = "12.7mm Drill Bit"
>>> t1
Tool 12.7mm Drill Bit
>>> t1.ToolType
'Undefined'
>>> t1.ToolType = "Drill"
>>> t1.Diameter= 12.7
>>> t1.LengthOffset = 127
>>> t1.CuttingEdgeAngle = 59
>>> t1.CuttingEdgeHeight = 50.8
>>> t2 = Path.Tool("my other tool",tooltype="EndMill",diameter=10)
>>> t2
Tool my other tool
>>> t2.Diameter
10.0
>>> table = Path.Tooltable()
>>> table
Tooltable containing 0 tools
>>> table.addTools(t1)
Tooltable containing 1 tools
>>> table.addTools(t2)
Tooltable containing 2 tools
>>> table.Tools
{1: Tool 12.7mm Drill Bit, 2: Tool my other tool}
>>>
The aim of this feature is to gather one or more toolpaths and associate it (them) with a tooltable. The Compound feature also behaves like a standard FreeCAD group, so you can add or remove objects to/from it directly from the tree view. You can also reorder items by double-clicking the Compound object in the Tree view, and reorder its elements in the Task view that opens.
>>> import Path
>>> p1 = Path.Path("G1X1")
>>> o1 = App.ActiveDocument.addObject("Path::Feature","path1")
>>> o1.Path=p1
>>> p2 = Path.Path("G1Y1")
>>> o2 = App.ActiveDocument.addObject("Path::Feature","path2")
>>> o2.Path=p2
>>> o3 = App.ActiveDocument.addObject("Path::FeatureCompound","compound")
>>> o3.Group=[o1,o2]
The Path project is an extended kind of Compound, that has a couple of additional machine-related properties such as a tooltable. It is made mainly to be the main object type you'll want to export to gcode once your whole path setup is ready. The Project object is now coded in python, so its creation mechanism is a bit different:
>>> from PathScripts import PathProject
>>> o4 = App.ActiveDocument.addObject("Path::FeatureCompoundPython","project")
>>> PathProject.ObjectPathProject(o4)
>>> o4.Group = [o3]
>>> o4.Tooltable
Tooltable containing 0 tools
The Path module also features a tooltable editor:
>>> from PathScripts import TooltableEditor
>>> TooltableEditor.edit(o4)
This feature is a normal Path object with an additional Shape property. By giving that property a Wire shape, its path will be automatically calculated from the shape. Note that in this case the placement is automatically set to the first point of the wire, and the object is therefore not movable anymore by changing its placement. To move it, the shape itself must be moved.
>>> import Part
>>> v1 = FreeCAD.Vector(0,0,0)
>>> v2 = FreeCAD.Vector(0,2,0)
>>> v3 = FreeCAD.Vector(2,2,0)
>>> v4 = FreeCAD.Vector(3,3,0)
>>> wire = Part.makePolygon([v1,v2,v3,v4])
>>> o = FreeCAD.ActiveDocument.addObject("Path::FeatureShape","myPath2")
>>> o.Shape = wire
>>> FreeCAD.ActiveDocument.recompute()
Both Path::Feature and Path::FeatureShape features have a python version, respectively named Path::FeaturePython and Path::FeatureShapePython, that can be used in python code to create more advanced parametric objects.
GCode files can be directly imported and exported via the GUI, by using the "open", "insert" or "export" menu items. After the file name is acquired, a dialog pops up to ask which processing script must be used. It can also be done from python:
Path information is stored into Path objects using a subset of gcode described in the "Visualizing Path in FreeCAD"section above. This subset can be imported or exported "as is", or converted to/from a particular version of GCode suited for your machine.
If you have a very simple and standard GCode program, that complies to the rules described in the "FreeCAD Path Visualization" section above, for example the boomerang from http://www.cnccookbook.com/GWESampleFiles.html (shown on the screenshot above), it can be imported directly into a Path object, without translation (this is equivalent to using the "None" option of the GUI dialog):
import Path
f = open("/path/to/boomerangv4.ncc")
s = f.read()
p = Path.Path(s)
o = App.ActiveDocument.addObject("Path::Feature","boomerang")
o.Path = p
In the same manner, you can obtain the path information as "agnostic" gcode, and store it manually in a file:
text = o.Path.toGCode()
print text
myfile = open("/path/to/newfile.ngc")
myfile.write(text)
myfile.close()
If you need a different output, though, you will need to convert this agnostic GCode into a format suited for your machine. That is the job of post-processing scripts.
If you have a gcode file written for a particular machine, which doesn't comply to the internal rules used by FreeCAD, described in the "FreeCAD Path Visualization" section above, it might fail to import and/or render properly in the 3D view. To remedy to this, you must use a pre-processing script, which will convert from your machine-specific format to the FreeCAD format.
If you know the name of the pre-processing script to use, you can import your file using it, from the python console like this:
import example_pre
example_pre.insert("/path/to/myfile.ncc","DocumentName")
In the same manner, you can output a path object to GCode, using a post_processor script like this:
import example_post
example_post.export (myObjectName,"/path/to/outputFile.ncc")
Pre- and post-processing scripts behave like other common FreeCAD imports/exporters. When choosing a pre/post processing script from the dialog, the import/export process will be redirected to the specified given script. Preprocessing scripts must contain at least the following methods open(filename) and insert(filename,docname). Postprocessing scripts need to implement export(objectslist,filename).
Scripts are placed into either the Mod/Path/PathScripts folder or the user's macro path directory. You can give them any name you like but by convention, and to be picked by the GUI dialog, pre-processing scripts names must end with "_pre", post-processing scripts with "_post" (make sure to use the underscore, not the hyphen, otherwise python cannot import it). This is an example of a very, very simple preprocessor. More complex examples are found in the Mod/Path/PathScripts folder:
def open(filename):
gfile = __builtins__.open(filename)
inputstring = gfile.read()
# the whole gcode program will come in as one string,
# for example: "G0 X1 Y1\nG1 X2 Y2"
output = ""
# we add a comment
output += "(This is my first parsed output!)\n"
# we split the input string by lines
lines = inputstring.split("\n")
for line in lines:
output += line
# we must insert the "end of line" character again
# because the split removed it
output += "\n"
# another comment
output += "(End of program)"
import Path
p = Path.Path(output)
myPath = FreeCAD.ActiveDocument.addObject("Path::Feature","ImportedPath")
myPath.Path = p
FreeCAD.ActiveDocument.recompute()
Pre- and post-processors work exactly the same way. They just do the contrary: The pre scripts convert from specific GCode to FreeCAD's "agnostic" GCode, while post scripts convert from FreeCAD's "agnostic" GCode to machine-specific GCode.