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.
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. . . .