Overview

The Generate class can create codes from a STEP-NC process for a particular machine tool. It has many built-in formats: G-Code for Fanuc, Haas, Heidenhain, Okuma, Siemens style controls, five axis via TCP with IJK, AC, BC, or AB moves, Renishaw and native probing cycles, as well as other languages like APT, DMIS, or CRCL.

The class has callback functions that return a string description for various process events. By replacing some of these with your own python functions, you can modify the output for your own local machine variations or operators preferences.

Basic Example

The simple example below reads a STEP-NC file and calls export_cncfile() to print Fanuc-style codes for the entire process to the program.nc file.

from steptools import step

# Read a STEP-NC file
DESIGN = step.open_project(data/tp2d.stpnc);

GEN = step.Generate()
GEN.set_style(fanuc)   # use Fanuc style
GEN.export_cncfile(DESIGN,program.nc)   # export codes

This is the simplest way to export to a file, but you can get the codes iteratively and do other things with them. The example below replaces export_cncfile() with a few more lines of code to set up the process cursor and state object, then print the codes to the terminal.

from steptools import step

# Read a STEP-NC file
DESIGN = step.open_project(data/tp2d.stpnc);

GEN = step.Generate()
GEN.set_style(fanuc)   # use Fanuc style

# expand export_cncfile() and print to terminal instead of file
CUR = step.Adaptive()      # walks over the process
CUR.start_project(DESIGN)
CUR.set_wanted_all()
GS = step.GenerateState()  # keeps current state of codes

GEN.set_unit_system(CUR);  # output in same units as stepnc program

# Format calls may returns 'None', so print an empty string if that happens. 
# Also, make print() omit ending newline since the strings returned by the
# format calls already have them where needed.
#
while CUR.next():
    print(GEN.format_event(GS,CUR) or , end=)

The fanuc-style codes produced look something like the following. Workingsteps and tool changes are usually identified with comments. The more complex example below discusses how to customize tool changes and initial code setup for your own local machine variations or operator preferences.

%
O100 (STEP-NC AP-238 PROGRAM)
(STEP-NC FILE: TP2D.STPNC)
(GENERATED: 2021-12-29T17:52:52-05:00)
G20
(WORKINGSTEP: 2.5D LINES WITH RAPIDS WS 1)
(TOOL CHANGE: TOOL 1)
( DIAMETER: 0.25IN)
( LENGTH: 2IN)
( CORNER RADIUS: 0IN)
( TAPER: 0DEG)
( TIP ANGLE: 0DEG)
G49
T1M6
G90
G43.5H1I0J0K1
G0X0.Y0.Z3.
Z1.
M4S1000
M08
G1Z0.F20.
Y3.
X1.
Y0.
. . . 

UUID Example

The Fishhead sample data on ap238.org has UUIDs on every workingstep. We can customize the start workingstep event to add these as a G-code comment if they are found. The handling of many different aspects of a process can be customized with set_event_fn(), set_type_fn(), and set_other_fn(). In our example below, we also look for UUIDs on toolpaths.

Fishhead With 5-Axis Fixture

The workingstep and toolpath UUID functions have three arguments, the Generate object, a GenerateState object with the currently commanded state of the machine tool, and an Adaptive process cursor with the current position in the process and all associated context. The function returns None or a string containing one or more lines of code. All user-supplied functions follow this pattern.

Using set_event_fn(), we register these two functions to be called whenever we start a new workingstep or toolpath.

import sys
from steptools import step

def workingstep_uuid(gen: step.Generate, gs: step.GenerateState, cur: step.Adaptive):
    uuid = step.AptAPI.get_uuid(cur.get_active_workingstep())
    if uuid is None:
        return step.Generate.builtin_workingstep_start_default(gen,gs,cur)
    
    # add comment with our workingstep uuid, then call the normal workingstep 
    # start function in case it adds anything else.   The or '' is because the
    # builtin function might return None
    RET =  gen.format_comment(gs, WS UUID, uuid)
    RET += step.Generate.builtin_workingstep_start_default(gen,gs,cur) or ''
    return RET

def toolpath_uuid(gen: step.Generate, gs: step.GenerateState, cur: step.Adaptive):
    uuid = step.AptAPI.get_uuid(cur.get_active_toolpath())
    if uuid is None:
        return step.Generate.builtin_toolpath_start_default(gen,gs,cur)
    
    # add comment with our toolpath uuid, as discussed above
    RET =  gen.format_comment(gs, TP UUID, uuid)
    RET += step.Generate.builtin_toolpath_start_default(gen,gs,cur) or ''
    return RET


D = step.open_project(Fishhead_DMG_with_hardness.stpnc)

# Customize output with UUIDs at start of workingstep and toolpath if present
GEN = step.Generate()
GEN.set_style('fanuc')
GEN.set_event_fn(step.CtlEvent.EXEC_WORKSTEP_START, workingstep_uuid)
GEN.set_event_fn(step.CtlEvent.TOOLPATH_START, toolpath_uuid)

if not GEN.export_cncfile(D, my_codes.nc):
    print(problems creating code file, file = sys.stderr )

Going throught the generated codes, you will find the following pattern at the start of workingsteps. The comment with the workingstep name comes through the workingstep_start_default function that we call after adding the UUID comment.

. . .
(WS UUID: 19CC3DB8-BAD4-4788-9020-9BC3FA4A1084)
(WORKINGSTEP: R_LEVEL_1)
X192.6811Y437.5023Z237.4774
Y423.0249Z233.5982
G1X164.7411F9223.92
X164.7408Y381.0916Z390.0954
G02X162.4264Y380.4926Z392.331R2.3145
G1X-161.6164
G02X-163.9308Y381.0916Z390.0954R2.3146
. . .

Full Example

The example below is a real-world customization that was done for machining demonstrations using a particular Haas machine at RPI. The preferences of the particular operator there are reflected in the way the program setup and tool changes are done. For example, rather than use set_workoffset_frame() to specify a particular work offset when making codes, they wanted to fix everything to G54. They also always wanted an optional stop M01 after every tool change.

In this case, we replace the program start and tool change actions with our own functions, and replace the functions for probing operations with builtin ones that use the Renishaw probing macros.

The tool change function calls support functions to make sure the spindle and coolant are off. The GenerateState object tracks the last commanded values for certain things to avoid duplicate commands. We clear the stored state for position and feedrate so that they will all be commanded on the next move. At the end of the function we queue a G43 to be issued with the next move because some machines will not accept that as a separate block.

The program start function uses some support functions to add some useful comments, then another function to set G20/G21 depending on the units we are using, and finally resets several modal states.

import sys
from steptools import step

def tool_change(gen: step.Generate, gs: step.GenerateState, cur: step.Adaptive):
    toolnum = gen.get_tool_number(cur.get_active_tool())
    if toolnum is None:
        return None   # can't figure out tool number

    # we add the or '' in case the format function returns None
    RET =  gen.format_other(gs, cur,spindle-off) or ''
    RET += gen.format_other(gs, cur,coolant-off) or ''
    gs.clear_feed()
    gs.clear_position()
  
    # make the toolchange more visible with a comment and also
    # display some of the information in the stepnc data.
    RET += gen.format_other(gs, cur,tool-comment) or ''
    RET += gen.format_block(gs,T%d M6 % toolnum)

    # requested after change
    RET += gen.format_other(gs, cur,ncfun-optional-stop) or ''
    
    # reset absolute and work offset    
    RET += gen.format_block(gs,G0 G90 G54)

    # Queue the G43 as a prefix for that next move, which will include 
    # a Z component because of the earlier clear_position()
    gs.add_move_prefix (G43 H%d  % toolnum)
    return RET


def program_start(gen: step.Generate, gs: step.GenerateState, cur: step.Adaptive):
    RET = gen.format_block_nonum(gs, O%d (STEP-NC AP-238 PROGRAM) % gen.get_program_number())
    RET += gen.format_other(gs, cur,filename) or ''
    RET += gen.format_other(gs, cur,timestamp) or ''
    RET += gen.format_comment(gs,CUSTOM HAAS OUTPUT STYLE)
    RET += gen.format_comment(gs,WORK OFFSET ALWAYS GIVEN BY G54)

    RET += gen.format_other(gs, cur,units) or ''
    RET += gen.format_block(gs,G0 G17 G40 G49 G80 G90  (CANCEL ALL MODES))
    return RET


# read data, recognize arm objects before use
D = step.find_design(data/tp2d.stpnc)
step.arm_recognize(D)

# Customize output for a particular Haas machine, with the preferences
# of the local operator.
GEN = step.Generate()
GEN.set_style('haas')
GEN.set_use_whitespace(True) 
GEN.set_event_fn(step.CtlEvent.TOOL_CHANGE, tool_change)
GEN.set_event_fn(step.CtlEvent.PROJECT_START, program_start)

# use renishaw style probing
GEN.set_type_fn(step.CtlType.OP_PROBE, step.Generate.builtin_probe_haas_renishaw)
GEN.set_other_fn(workplan-probe-start, step.Generate.builtin_workplan_probe_start_haas_renishaw)
GEN.set_other_fn(workplan-probe-end, step.Generate.builtin_workplan_probe_end_haas_renishaw)

# Convenience function to make a file, this creates a adaptive and genstate objects 
# and loops over the process.   Replace with a two line while loop if you want to the 
# codes to go somewhere else.
if not GEN.export_cncfile(D, my_codes.nc):
    print(problems creating code file, file = sys.stderr )

The output using these settings is shown below. You can compare it with the default settings in the first example.

O100 (STEP-NC AP-238 PROGRAM)
(STEP-NC FILE: TP2D.STPNC)
(GENERATED: 2023-05-02T22:07:19-04:00)
(CUSTOM HAAS OUTPUT STYLE)
(WORK OFFSET ALWAYS GIVEN BY G54)
G20
G0 G17 G40 G49 G80 G90  (CANCEL ALL MODES)
(WORKINGSTEP: 2.5D LINES WITH RAPIDS WS 1)
(TOOL CHANGE: TOOL 1)
( DIAMETER: 0.25IN)
( LENGTH: 2IN)
( CORNER RADIUS: 0IN)
( TAPER: 0DEG)
( TIP ANGLE: 0DEG)
T1 M6
M01
G0 G90 G54
G43 H1 G0 X0. Y0. Z3.
Z1.
M4 S1000
M08
G1 Z0. F20.
Y3.
X1.
. . .