CAM scripting

Introduction

The CAM 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 G-code programs, generate toolpaths from 3D shapes, and export these toolpaths to G-code.

The CAM Workbench exposes its C core (`Path.Command`, `Path.Path`, `Path.Area`, `Path.Voronoi`) and its Python layer (the `Path.*` packages: `Path.Main`, `Path.Op`, `Path.Tool`, `Path.Post`, `Path.Dressup`, `Path.Base`) to scripting. [.since]#Since 26.3# the C bindings ship typed stubs (App/.pyi: Command.pyi, Path.pyi, Area.pyi, Voronoi.pyi, PathApp.module.pyi) under src/Mod/CAM/App, so IDEs provide completion and signature checking; the stubs are the authoritative reference for argument names and return types.

Quickstart

FreeCAD’s Path objects are made of a sequence of motion commands. A typical use is this:

>>> import Path
>>> c1 = Path.Command("g1x1")
>>> c2 = Path.Command("g1y4")
>>> c3 = Path.Command("g1 x2 y2") # spaces end newlines are ignored
>>> p = Path.Path([c1,c2,c3])
>>> o = App.ActiveDocument.addObject("Path::Feature","mypath")
>>> o.Path = p
>>> print (p.toGCode())

The FreeCAD Internal G-code Format

A preliminary concept is important to grasp. Most of the implementation below relies heavily on motion commands that have the same names as G-code commands, but aren’t meant to be close to a particular controller’s implementation. Names such as 'G0' to represent 'rapid' move or 'G1' were chosen to represent 'feed' move for performance (efficient file saving) and to minimize the work needed to translate to/from other G-code formats. Since the CNC world speaks thousands of G-code dialects, a very simplified subset of it was chosen. You could describe FreeCAD’s G-code format as a "machine-agnostic" form of G-code.

Inside .FCStd files, toolpath data is saved directly into that G-code form.

All translations to/from dialects to FreeCAD G-code 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 nonexistent) a post processor for that particular controller (see the "Importing and exporting G-code" section below).

G-code Reference

The following rules and guidelines define the G-code subset used internally in FreeCAD:

  • G-code data, inside FreeCAD Path objects, is separated into "Commands". A Command is defined by a command name, which must begin with G or M, and (optionally) arguments, which are in the form Letter plus a Float, for example X 0.02 or Y 3.5 or F 300. These are examples of typical G-code commands in FreeCAD:

G0 X2.5 Y0 (The command name is G0, the arguments are X=2.5 and Y=0)
G1 X30 (The command name is G1, the only argument is X=30)
G90 (The command name is G90, there are no arguments)
  • For the numeric part of a G or M command, both "G1" or "G01" forms are supported.

  • Only commands starting with G or M are supported at the moment.

  • Internal toolpaths are always stored in millimeters. When a G-code string is parsed with Path.Path(gcode) or setFromGCode(), a G20 switches the parser to inches and all following coordinates are multiplied by 25.4; G21 switches back. The G20/G21 commands themselves are not stored.

  • Arguments are always sorted alphabetically. This means that if you create a command with "G1 X2 Y4 F300", it will be stored as "G1 F300 X2 Y4"

  • Arguments cannot be repeated inside a same command. For example, "G1 X1 Y2 X2 Y3" will not work. You will need to split it into two commands, for example: "G1 X1 Y2, G1 X2 Y3"

  • X, Y, Z, A, B, C arguments are absolute or relative, depending on the current G90/G91 mode. Default (if not specified) is absolute.

  • I, J, K are always relative to the last point. K can be omitted.

  • X, Y, or Z (and A, B, C) can be omitted. In this case, the previous X-, Y- or Z-coordinates are maintained.

  • G-code commands other than the ones listed in the table below are supported, that is, they are saved inside the toolpath data (as long as they comply to the rules above, of course). Commands that the segment walker does not understand (for example M-codes or G4) are stored in the toolpath but produce no visible segment in the 3D view.

List of currently supported G-code commands

Command Description Supported Arguments Displayed

G0, G00

rapid move

X,Y,Z,A,B,C

Red

G1, G01

normal move

X,Y,Z,A,B,C

Green

G2, G02

clockwise arc

X,Y,Z,A,B,C,I,J,K

Green

G3, G03

counterclockwise arc

X,Y,Z,A,B,C,I,J,K

Green

G17, G18, G19

select the arc plane

(none)

not displayed

G38.2, G38.3, G38.4, G38.5

probe moves

X,Y,Z,F

Yellow

G73, G74, G81, G82, G83, G84, G85, G86, G89

canned drilling, tapping and boring cycles

X,Y,Z,R,Q,P,F

Red/Green (expanded for display)

G90

absolute coordinates

G91

relative coordinates

G90.1, G91.1

absolute or incremental arc centers

G98, G99

canned cycle retract mode

(Message)

comment

Rotary axis words A, B, C on G0/G1/G2/G3 are rendered using the full angular travel about Path.Center introduced in 26.3.

The Command object

The Command object represents a G-code command. It has four attributes: Name, Parameters, Annotations (introduced in 26.3) and Placement, and the methods toGCode(), setFromGCode(gcode), transform(placement) (returns a transformed copy) and addAnnotations(dict_or_string) (returns self for chaining). 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
{'X': 1.0, 'Y': 0.0}
>>> c.Parameters= {"X":1,"Y":0.5}
>>> c
Command G1 [ X:1 Y:0.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 second argument of the constructor may be a dictionary of parameters or an App.Placement; the placement’s Base is written into X, Y and Z. A plain App.Vector is not accepted.

Any parameter can be read or written as a lower- or upper-case single-letter attribute (c.x, c.F). Reading a letter that is not present raises AttributeError.

Annotations

introduced in 26.3

Annotations attach machine-independent metadata to a command without changing its G-code semantics. They are a dictionary of string keys to string or number values, given as the third constructor argument, via the Annotations attribute, or with addAnnotations():

>>> c = Path.Command('G0', {'Z': 10}, {'type': 'retract'})
>>> c.toGCode()
"G0Z10; type:'retract'"

toGCode() writes the annotations after the command as a comment. Post-processors and dressups can read Annotations to decide how to treat a move (for example drilling retracts or rigid tapping).

The Constants module defines the annotation keys that a machine-based Post-Processor reads during export.

Constant Key Value Effect

ANNOT_NO_ENGAGEMENT_FEED

NoEngagementFeed

The feed rate, as the string of a float

The annotation marks a G0 move that the Post-Processor can write as a G1 move. The value of the annotation becomes the F parameter of the G1 move.

ANNOT_MODAL_BARRIER

modal_barrier

True, False or absent

The annotation stops the removal of duplicates at this command. The Post-Processor does not compare later commands with this command or with the commands before it.

ANNOT_NO_COLLAPSE_G0

no_collapse_g0

True

The annotation keeps this G0 move as a separate move. The Post-Processor does not merge the move with the G0 move before it or after it.

The Post-Processor writes a NoEngagementFeed move as a G1 move only when the Machine sets the Post-Processor Property translate_no_engagement_feed. The property has the label Output non-engaging moves at the no-engagement feed rate, has Machine scope, and is off by default.

The Adaptive Operation sets NoEngagementFeed on G0 moves in X and Y when its no-engagement feed is not zero. Path.Post.DrillCycleExpander sets no_collapse_g0 on the G0 retract moves of the G73 and G83 cycles that it expands.

Writing a Machine-Based Post-Processor describes how a Post-Processor uses annotations and removes duplicates.

The Path object

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 G-code.
# The commands will be created automatically:

p = Path.Path()
p.setFromGCode(lines)

As a shortcut, a Path object can also be created directly from a full G-code sequence. It will be divided into a sequence of commands automatically. Path.Path() accepts either a list of Commands or a G-code string.

>>> p = Path.Path("G0 X2 Y2 G1 X0 Y2")
>>> p
Path [ size:2 length:2 ]

Other members: Size (number of commands), BoundBox, Center (the centre used for rotary-axis rendering, introduced in 26.3), insertCommand(cmd, pos=-1), deleteCommand(pos=-1), copy(), getCycleTime(h_feed, v_feed, h_rapid, v_rapid) returning seconds, and getClearedArea().

The Path feature

The Path feature is a FreeCAD document object, that holds a toolpath, 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 toolpath information itself won’t be modified. The transformation is purely visual. This allows you, for example, to create a toolpath around a face that has a particular orientation on your model, that is not the same orientation as your cutting material will have on the CNC machine.

However, Path Compounds can make use of the Placement of their children (see below).

Tool Bits and Tool Controllers

obsolete since 1.0 The Path.Tool and Path.Tooltable classes of version 0.18 and earlier were removed; scripts using them raise AttributeError. Tools are now Tool Bits (.fctb) referenced by Tool Controllers; see CAM Tools.

A Tool Bit is described by a .fctb JSON file and a Tool Shape (.fcstd). The ToolBit class lives in Path.Tool.toolbit (Path.Tool.Bit is a compatibility alias) and offers the factory methods from_shape_id(), from_file() and from_dict():

from Path.Tool.toolbit import ToolBit
import Path.Tool.Controller as TC

bit = ToolBit.from_shape_id('endmill.fcstd')
bit.set_property('Diameter', 6.0)
tool_obj = bit.attach_to_doc(doc=App.ActiveDocument)
tc = TC.Create('TC: 6mm Endmill', tool=tool_obj, toolNumber=3)
job.Proxy.addToolController(tc)

Path.Tool.Controller.Create(name='TC: 5mm Endmill', tool=None, toolNumber=1, assignViewProvider=True, assignTool=True) creates a Tool Controller; append it to the Job with job.Proxy.addToolController(tc).

Tool Bits, Tool Libraries (.fctl), Tool Shapes and Machines are stored in the asset directory (CamAssets) and accessed through Path.Tool.cam_assets, an AssetManager with get(uri), get_or_none(uri), list_assets(asset_type), add(asset), add_file(asset_type, path) and exists(uri). Libraries are Path.Tool.library.Library objects with add_bit(bit, bit_no), get_bits() and remove_bit(bit).

Features

The Path Compound feature

The aim of this feature is to gather one or more toolpaths. 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]

An important feature of Path Compounds is the possibility to take into account the Placement of their child paths or not, by setting their UsePlacements property to True or False. If not, the toolpath data of their children will simply be added sequentially. If True, each command of the child paths, if containing position information (G0, G1, etc..) will first be transformed by the Placement before being added.

Creating a compound with just one child path allows you therefore to turn the child path’s Placement "real" (it affects the toolpath data).

The Job

The Path Project feature of versions up to 0.18 (PathScripts.PathProject, Path::FeatureCompoundPython) no longer exists. Its replacement is the Job, which gathers the models, Stock, Tool Controllers, Operations and output settings:

import Path.Main.Job as PathJob
job = PathJob.Create('Job', [App.ActiveDocument.Body], templateFile=None)

Create(name, base, templateFile=None) accepts a list of model objects or of object names and returns a Path::FeaturePython with App::GroupExtensionPython. In the GUI use Path.Main.Gui.Job.Create(base, template=None, openTaskPanel=True), which also attaches the view provider and opens the task panel.

Job properties include: Model, Stock, Tools, Operations, SetupSheet, Fixtures, OrderOutputBy, SplitOutput, PostProcessor, PostProcessorArgs, PostProcessorOutputFile, PostProcessorPropertyOverrides, Machine (introduced in 26.3), GeometryTolerance, Description, JobType, CycleTime, LastPostProcessDate and LastPostProcessOutput.

Stock

Stock is created with Path.Main.Stock.CreateFromBase(job, neg=None, pos=None, placement=None) (bounding box of the models extended by the neg/pos vectors), CreateBox(job, extent=None, placement=None) or CreateCylinder(job, radius=None, height=None, placement=None); assign the result to job.Stock. CreateFromTemplate(job, template) rebuilds stock from a Job Template dictionary.

Creating operations

Every operation module in Path.Op exposes Create(name, obj=None, parentJob=None):

import Path.Op.Profile as PathProfile
op = PathProfile.Create('Profile')
op.Base = [(App.ActiveDocument.Body, ('Face6',))]
op.Side = 'Outside'
App.ActiveDocument.recompute()

When parentJob is omitted the operation is added to the single Job in the document (PathScripts.PathUtils.addToJob). Available modules: Profile, PocketShape, Pocket (3D), MillFacing, Drilling (Strategy includes Tapping, introduced in 26.3), Helix, Adaptive, Slot, Engrave, Deburr, Vcarve, ThreadMilling, Surface, Waterline, RotarySurface (introduced in 26.3), Custom and Probe. The GUI command objects live in Path.Op.Gui.<Name>.Command.

Custom operations subclass Path.Op.Base.ObjectOp, declare their properties in opPropertyDefinitions() (with opPropertyEnumerations() and opPropertyDefaults()), select capabilities with opFeatures() (FeatureTool, FeatureDepths, FeatureHeights, FeatureStartPoint, FeatureBaseFaces, FeatureCoolant, …​) and generate commands in opExecute(obj) by appending Path.Command objects to self.commandlist. See Path/Op/Slot.py for a compact example.

Getting Path from Shape

26.3 and later: Path.fromShape(shape) is deprecated and scheduled for removal in 27.2; use Path.fromShapes():

Path.fromShapes(shapes, start=None, return_end=False, arc_plane=1, sort_mode=1,
                abscissa=3.0, nearest_k=3, orientation=0, direction=0, threshold=0.0,
                retract_axis=2, retraction=0.0, resume_height=0.0, segmentation=0.0,
                feedrate=0.0, feedrate_v=0.0, verbose=True, abs_center=False,
                preamble=True, deflection=0.01)

shapes may be one Shape or a sequence; with return_end=True a (Path, end_vector) tuple is returned. Path.sortWires(shapes, start=None, …​) returns the sorted wires and end position without building a toolpath.

By giving a wire Part object, its toolpath 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 underlying shape itself must be moved.

>>> import Part
>>> import Path
>>> 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::Feature","myPath2")
>>> o.Path = Path.fromShapes(wire)
>>> FreeCAD.ActiveDocument.recompute()
>>> p =  o.Path
>>> print(p.toGCode())

Module-level helpers

Path.show(p, 'MyPath') creates a Path::Feature for a standalone Path object; Path.read(filename, docname) imports raw internal-format G-code into a document; Path.write(obj, filename) writes a Path feature’s G-code to disk.

Path.Area and Path.Voronoi

Path.Area wraps libarea for 2D offsetting and pocketing:

a = Path.Area()
a.add(face)
a.setParams(Offset=-3)
wire_shape = a.getShape()

Further methods: setPlane(), makeOffset(), makePocket(), makeSections(), getRestArea(), toTopoShape(), setParams(), getParams(). Parameter names are listed by Path.Area.getParamsDesc(). Path.Voronoi is used by the V-carve operation.

Python features

Python-extensible variants exist for every feature type: Path::FeaturePython (used by Jobs, operations, dressups and Tool Controllers), Path::FeatureShapePython, Path::FeatureCompoundPython, Path::FeatureAreaPython and Path::FeatureAreaViewPython. They can be used in Python code to create more advanced parametric objects derived from them.

Importing and exporting G-code

Native format

Opening or importing a G-code file (.nc, .gc, .ncc, .ngc, .cnc, .tap, *.gcode) shows a pre-processor chooser. Choosing none reads the file as internal-format G-code into a new document with Path.read(). Choosing gcode_pre imports the file into the active Job as Custom operations, one per tool change, matching Tool Controllers by tool number; a Job must exist first. Export is done by post-processing the Job (CAM Post Process) rather than by a generic export menu entry.

Toolpath information is stored into Path objects using a subset of G-code described in the "FreeCAD’s internal G-code format" section above. This subset can be imported or exported "as is", or converted to/from a particular version of G-code suited for your machine.

If you have a very simple and standard G-code program, that complies to the rules described in the "FreeCAD’s internal G-code format" section above, for example the boomerang from cnccookbook, 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 toolpath information as "agnostic" G-code, and store it manually in a file:

text = o.Path.toGCode()
print(text)
with open("/path/to/newfile.ngc", "w") as myfile:
    myfile.write(text)

If you need a different output, though, you will need to convert this agnostic G-code into a format suited for your machine. That is the job of post-processing scripts.

Using pre- and post-processing scripts

If you have a G-code file written for a particular machine, which doesn’t comply to the internal rules used by FreeCAD, described in the "FreeCAD’s internal G-code format" 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.

The shipped pre-processors live in the Path.Post.scripts package:

from Path.Post.scripts import gcode_pre
gcode_pre.insert("/path/to/myfile.ngc", App.ActiveDocument.Name)  # requires an existing Job

In the same manner, you can output a Job to G-code using a post-processor. Legacy scripts (*_legacy_post.py) expose export(objectslist, filename, argstring):

from Path.Post.scripts import linuxcnc_legacy_post
linuxcnc_legacy_post.export([job], "/tmp/out.ngc", "--no-show-editor")

New-style posts subclass Path.Post.Processor.PostProcessor and are driven through the factory:

from Path.Post.Processor import PostProcessorFactory
post = PostProcessorFactory.get_post_processor(job, 'linuxcnc')
sections = post.export()   # each item is a (name, gcode) tuple

Use post.export2() for the machine-based flow (introduced in 26.3). The GUI command (Path/Post/Command.py) selects the post-processor from the Job’s Machine when one is assigned, otherwise from the Job’s PostProcessor property.

Writing processing scripts

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). Legacy postprocessing scripts need to implement export(objectslist,filename,argstring).

Post-processors are searched in: the CAM "Default path" output folder, the user macro directory, directories registered by add-ons with Path.Preferences.addAddonPostPath(path) (introduced in 26.3), and Mod/CAM/Path/Post/scripts. File names must end in _post.py (legacy scripts ship as *_legacy_post.py) or _pre.py (make sure to use the underscore, not the hyphen, otherwise Python cannot import it). Pre-processors are only discovered from Path/Post/scripts and the macro directory. This is an example of a very, very simple preprocessor. More complex examples are found in the Mod/CAM/Path/Post/scripts 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","Import")
    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 G-code to FreeCAD’s "agnostic" G-code, while post scripts convert from FreeCAD’s "agnostic" G-code to machine-specific G-code.

A machine-based post-processor (introduced in 26.3) is a module with POST_TYPE = 'machine' defining a class derived from Path.Post.Processor.PostProcessor. Override get_common_property_schema() or get_property_schema() to declare options (file_extension, preamble, postamble, supports_tool_radius_compensation, …​); the base class handles argument parsing, header generation, canned-cycle expansion, tool changes, rotary moves and unit conversion. Defaults are merged with the Machine (.fcm) output options. See Postprocessor Customization.

Adding all faces of a ShapeString to the Base Geometry of a Profile operation

This example is based on a discussion in the german forum.

Prerequisites

  • Create a solid with ShapeString as Cutout

  • Create a Job using this solid as its model

  • Create a Profile operation named "Profile_Faces" with empty Base Geometry.

The code

The following code will then add all faces from ShapeString and create the toolpaths:

doc = App.ActiveDocument
list_of_all_element_faces = []
for i, face in enumerate(doc.ShapeString.Shape.Faces):
    list_of_all_element_faces.append('Face' + str(i + 1))

doc.Profile_Faces.Base = [(doc.ShapeString, tuple(list_of_all_element_faces))]
doc.recompute()

Legacy PathScripts module

Since 1.0 the Python layer lives in the Path package (Path.Main, Path.Op, Path.Tool, Path.Post, Path.Dressup, Path.Base). Only PathScripts.PathUtils (helpers such as findParentJob, addToJob, GetJobs, getEnvelope, depth_params, sort_locations_tsp) and PathScripts.PathPropertyBag remain for compatibility. Imports like PathScripts.PathJob, PathScripts.PathProfile or PathScripts.PathProject raise ImportError; use Path.Main.Job, Path.Op.Profile, etc.

Logging and preferences

import Path re-exports the C++ module plus Path.Log, Path.Geom and Path.Preferences. Enable debug output for a module with Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()). Path.Preferences exposes asset paths (getAssetPath(), getToolBitPath(), getTemplateDirectory()), post-processor lookup (allEnabledPostProcessors(), searchPathsPost()) and feature switches (experimentalFeaturesEnabled(), advancedOCLFeaturesEnabled()).

MachineState

introduced in 26.3

Path.Base.MachineState.MachineState tracks modal state while walking a command list:

from Path.Base.MachineState import MachineState
ms = MachineState()
ms.addCommands(path.Commands)
ms.getPosition()   # the current XYZ
ms.getState()      # the dictionary of the Tracked values
ms.Plane           # the arc plane: 'G17', 'G18' or 'G19'

Dressups and post-processors use it to expand drilling cycles accurately.

The Plane attribute of MachineState holds the arc plane. The values of Plane are "G17", "G18" and "G19". The default is "G17".

Plane is not in the MachineState.Tracked list. getState() does not return Plane, the previous dictionary does not contain Plane, and ms["Plane"] raises KeyError. Read the arc plane as ms.Plane.

These methods set Plane:

  • addCommand(command): sets Plane to the command name when the name is in Constants.GCODE_PLANE (G17, G18, G19). addCommand() returns False for such a command.

  • addGcodeText(text): sets Plane to the last G17, G18 or G19 word in a block of G-code text. addGcodeText() reads no other word and returns no value. The forms G017, G018 and G019 also match. A text with no such word, an empty text or None does not change Plane.

from Path.Base.MachineState import MachineState
ms = MachineState()
ms.Plane                         # 'G17'
ms.addGcodeText("G90 G18 G54")
ms.Plane                         # 'G18'

Postprocessor Customization describes how a machine-based Post uses the arc plane to write the center words of an arc. Writing a Machine-Based Post-Processor describes when a Post calls addGcodeText().

See also