Overview

A step.Object is one STEP data instance, while a step.Design is the collection of all instances in a file.

An EXPRESS schema describes the type of instances, which can be an ENTITY with attributes, or an AGGREGATE such as a list, set, or bag. An ENTITY instance has a file identifier (#123) while AGGREGATEs have no identifier because they only appear as values of attributes.

STEP instances often describe low-level atomic values, like a direction vector, or a property name, which are then combined in well-defined patterns to describe higher-level concepts like a workpiece, part feature, or machining workplan. The patterns are described in the "Application Reference Model (ARM)" section of the STEP standard.

The STEP Python interface recognizes these patterns when reading data and associates the graph of objects surrounding a root STEP instance as ARM attributes that are accessed exactly like the basic EXPRESS attributes. This greatly simplifies understanding and operations on STEP data sets.

An STEP file example is shown below. Each of the instances #50 to #62 below is a step.Object with attribute values shown in parenthesis. Instance #50 is the root of the higher level MILLING_MACHINE_FUNCTIONS ARM concept, so it will also have a through spindle coolant attribute with a value of 'through spindle coolant off', built from instances #51-#54, and similarly for chip removal and coolant.

/************************************************
 * Application object: MILLING_MACHINE_FUNCTIONS (#50)
 * THROUGH_SPINDLE_COOLANT: #50, #51, #52, #53, #54: [through spindle coolant off]
 * CHIP_REMOVAL: #50, #55, #56, #57, #58: [chip removal off]
 * COOLANT: #50, #59, #60, #61, #62: [coolant on]
 */
#50=MACHINING_FUNCTIONS('','milling',' ',' ');
 
#51=ACTION_PROPERTY('through spindle coolant','',#50);
#52=ACTION_PROPERTY_REPRESENTATION('','',#51,#53);
#53=REPRESENTATION('',(#54),#31);
#54=DESCRIPTIVE_REPRESENTATION_ITEM('constant','through spindle coolant off');
 
#55=ACTION_PROPERTY('chip removal','',#50);
#56=ACTION_PROPERTY_REPRESENTATION('','',#55,#57);
#57=REPRESENTATION('',(#58),#31);
#58=DESCRIPTIVE_REPRESENTATION_ITEM('constant','chip removal off');
 
#59=ACTION_PROPERTY('coolant','',#50);
#60=ACTION_PROPERTY_REPRESENTATION('','',#59,#61);
#61=REPRESENTATION('',(#62),#31);
#62=DESCRIPTIVE_REPRESENTATION_ITEM('constant','coolant on');

Basic Operations

The Python repr() gives basic information about the step.Object. An ENTITY has the file identifier (#123) and the EXPRESS type. If it is the root of an ARM concept, that appears first. Some common entities (measures, units, points) include a summary of the underlying data rather than the EXPRESS type. AGGREGATEs include EXPRESS type and size. They have no file identifier because they only appear as values of attributes.

print (val)

# ENTITY instances
<step.Object #200 polyline> 
<step.Object #20 product_definition_shape>

# ENTITY instances, root of an ARM concept
<step.Object ARM WORKPLAN #1035 machining_workplan> 
<step.Object ARM WORKPIECE #19 product_definition>

# ENTITY instances for points, units, measures
<step.Object #897 MEASURE 25.4 mm>
<step.Object #683 UNIT deg>
<step.Object #198 POINT -0.109698, -0.0599282, 1>

# AGGREGATE lists, bags, sets.  Similar sequence for ARM data
<step.Object SetOfrepresentation_item size 1>
<step.ArmCollection size 10>

Attributes of an ENTITY are available as a dictionary or by the usual dot attribute syntax. The entity_id() function returns the file identifier. The step.type() and step.isinstance() functions follow the similarly-named Python builtins, but operate on the EXPRESS type of an object. The step.arm_type() function returns the ARM type for objects that are the root of an ARM concept or None otherwise.

Unset attribute values are None. Integer, floating point, and string values are all handled as their normal Python values. The EXPRESS Logical type can have the usual Python True or False values, as well as a step.Logical.UNKNOWN value.

print (LINE)
pprint (dict(LINE))  # pprint sorts keys

<step.Object #200 polyline>
{'name': 'starting line',
 'points': <step.Object ListOfcartesian_point size 3>}

print (exp type:, step.type(LINE))
print (is rep item:, step.isinstance(LINE, representation_item))
print (file id: #%d % LINE.entity_id())
print (name as att:, LINE.name)
print (name as sub:, LINE['name'])

exp type: polyline
is rep item: True
file id: #200
name as att: starting line
name as sub: starting line

Aggregates are Python sequences, and accept the len() and usual iterator and index operators. In the example below, the polyline has a list of cartesian point objects, and each point has a list of floating point coordinate values.

ARM attributes with multiple values use a ArmCollection sequence object that accepts the len() and usual iterator and index operators.

PT = LINE.points[0]
pprint (dict(PT)) 

{'coordinates': <step.Object ListOfdouble size 3>,
 'name': 'w'}

print (length:, len(PT.coordinates))
print (vals as tuple:, *PT.coordinates)
print (vals by index:, PT.coordinates[0], PT.coordinates[1])

length: 3
vals as tuple: 13412.0 -1486.0 0.0
vals by index: 13412.0 -1486.0

The EXPRESS SELECT union type is not used. Only the underlying value of the union appears. When the union holds a string or number, it appears as a tuple with a type name, like (2.0, 'length_measure').

STEP Files

A step.Design object is a containier for all instances in a STEP file. The step.open_project() function reads a file, recognizes all ARM concepts, prepares indexes for the high-level API, and returns a design object that has been set as the current project. The step.save_project() function writes the current project out to a STEP file.

You can find object with the high level functions in AptAPI, ToleranceAPI or FinderAPI. You can also iterate over objects by EXPRESS type with a DesignCursor. The design can also be used as a dictionary with with the names (usually UUIDs) given to objects by the ANCHOR section in newer STEP files.

D = step.open_project(part123.stp)

# iterate over all entities
for obj in step.DesignCursor(D):
    print (entity #%d % obj.entity_id())

# iterate over just the representation items
for obj in step.DesignCursor(D, representation_item):
    print (rep item #%d, name %s % (obj.entity_id(), obj.name))


# get object by ANCHOR value from STEP file (P21e3)
obj = D['6db46031-4fab-4838-824b-91cea43922e4']

# print all of the UUID ANCHORs from STEP file
PP.pprint (dict(D))

{ '0098e447-15ab-44ac-b360-3d5fd4f7dcd3': <step.Object #7978 dimensional_location>,
  '04d1431a-9535-4ef6-a615-f00315a87548': <step.Object #8003 dimensional_location>,
  '09a905e5-da9c-44ab-aa9b-48689aafcd59': <step.Object #8018 dimensional_location>,
  '09c07753-38cf-4928-928f-4acd0e636247': <step.Object #7928 datum>,
  ...

Making STEP Data

When creating STEP data, you first need a Design to hold the objects. You can read an existing file and add to it, or create a new Design. The new_project() function creates a new STEP-NC design with a project and empty main workplan. You can also use the normal Python constructor to create a completely empty Design with a name that will be the default value for the filename when it is saved.

D = step.new_project(step_proj)
print(D)
==> <step.Design 'step_proj' containing 24 entities>

# create some data

step.save_project(step_proj.stpnc)

You can also use the normal Python constructor to create a completely empty Design.

D = step.Design(step_new)
print(D)
==> <step.Design 'step_new' containing 0 entities>

Next, you can create STEP data objects, by passing in the Design that will own the object plus the EXPRESS ENTITY type name. EXPRESS selects and aggregates are generally second-class objects that are created automatically by assigning to an entity attribute.

The example below creates a cartesian point object and populates its name and coordinates list. Newly created objects have a zero entity_id() value until they are saved to a file.

OBJ = step.Object(D, cartesian_point)
OBJ.name = my point
OBJ.coordinates = [ 1, 2, 3 ]

print(dict(OBJ))

==>{ 'coordinates': <step.Object ListOfdouble size 3>,
     'name': 'my point' }

You can create STEP AND/OR complex instances by passing a sequence of ENTITY names instead of a single one. This is occasionally used in STEP data sets for things like NURBS curves/surfaces.

NURBS = step.Object(D, (rational_b_spline_curve, b_spline_curve_with_knots))
print (nurb curve:, NURBS)
print (dict(NURBS))

==> nurb curve: <step.Object #0 b_spline_curve_with_knots_and_rational_b_spline_curve>
{'closed_curve': False,
 'control_points_list': None,
 'curve_form': None,
 'degree': 0,
 'knot_multiplicities': None,
 'knot_spec': None,
 'knots': None,
 'name': None,
 'self_intersect': False,
 'weights_data': None}

Aggregate attributes support direct assignment and will automatically append when assigning one beyond the size. They also support the standard Python append(), extend(), insert(), pop(), and clear() functions.

OBJ = step.Object(D, cartesian_point)
OBJ.coordinates = [ ]
OBJ.coordinates[0] = 1
OBJ.coordinates[1] = 2
OBJ.coordinates[2] = 3
print (*OBJ.coordinates)
==> 1.0 2.0 3.0

OBJ.coordinates.append(4)
print (*OBJ.coordinates)
==> 1.0 2.0 3.0 4.0

OBJ.coordinates.extend([5,6])
print (*OBJ.coordinates)
==> 1.0 2.0 3.0 4.0 5.0 6.0

OBJ.coordinates.insert(3, 7.0)
print (*OBJ.coordinates)
==> 1.0 2.0 3.0 7.0 4.0 5.0 6.0

print(OBJ.coordinates.pop(3))
print (*OBJ.coordinates)
==> 7.0
==> 1.0 2.0 3.0 4.0 5.0 6.0

print(OBJ.coordinates.pop())
print (*OBJ.coordinates)
==> 6.0
==> 1.0 2.0 3.0 4.0 5.0

OBJ.coordinates.clear()
print (*OBJ.coordinates)
==> 

You can assign an ENTITY object directly to an EXPRESS SELECT attribute, but a primitive or aggregate value must be wrapped in a tuple with the EXPRESS type name in the same way that it is returned by the get behavior.

OBJ = step.Object(D, measure_with_unit)
OBJ.value_component = ( 5, positive_length_measure )
print(dict(OBJ))

==> {'value_component': (5.0, 'positive_length_measure'),
     'unit_component': None}

Making STEP ARM Concepts

Newly made step.Object are only treated as low-level STEP AIM instances. To work with the higher-level ARM concepts, you can create instances of step.ArmObject. This will create the root AIM instance, any other supporting objects, and make sure that it is recognized as the appropriate ARM concept so that the ARM properties are available. After creating the ArmObject, you can get its root() and use it as normal.

The code below just creates a product definition object. The result has no other ARM properties:

# low level AIM create
OBJ = step.Object(D, product_definition)
print(basic instance, OBJ)
pprint(dict(OBJ))

==> basic instance <step.Object #0 product_definition>
{'description': None,
 'formation': None,
 'frame_of_reference': None,
 'id': None}

The code below creates a complete Workpiece ARM concept. The root object is still a product definition, but it is recognized as the root of a Workpiece and has many additional ARM properties available:

# high level ARM create
OBJ = step.ArmObject(D, WORKPIECE).root()
print(part of ARM concept, OBJ)
pprint(dict(OBJ))

==> part of ARM concept <step.Object ARM WORKPIECE #0 product_definition>
{'clamping_positions': <step.ArmCollection size 0>,
 'description': '',
 'formation': <step.Object #0 product_definition_formation>,
 'frame_of_reference': <step.Object #0 product_definition_context>,
 'global_tolerance': None,
 'id': '',
 'its_approvals': <step.ArmCollection size 0>,
 'its_bounding_geometry': None,
 'its_categories': <step.ArmCollection size 0>,
 'its_components': <step.ArmCollection size 0>,
 'its_constructive_models': <step.ArmCollection size 0>,
 'its_datestamps': <step.ArmCollection size 0>,
 'its_geometry': None,
 'its_id': None,
 'its_material': None,
 'its_orgs': <step.ArmCollection size 0>,
 'its_people': <step.ArmCollection size 0>,
 'its_rawpiece': None,
 'its_related_geometry': <step.ArmCollection size 0>,
 'its_security_classification': <step.ArmCollection size 0>,
 'its_styled_models': <step.ArmCollection size 0>,
 'its_timestamps': <step.ArmCollection size 0>,
 'product_approvals': <step.ArmCollection size 0>,
 'product_datestamps': <step.ArmCollection size 0>,
 'product_orgs': <step.ArmCollection size 0>,
 'product_people': <step.ArmCollection size 0>,
 'product_timestamps': <step.ArmCollection size 0>,
 'revision_approvals': <step.ArmCollection size 0>,
 'revision_datestamps': <step.ArmCollection size 0>,
 'revision_id': '',
 'revision_orgs': <step.ArmCollection size 0>,
 'revision_people': <step.ArmCollection size 0>,
 'revision_security_classification': <step.ArmCollection size 0>,
 'revision_timestamps': <step.ArmCollection size 0>,
 'security_classification': <step.ArmCollection size 0>,
 'shape_definition': None}

ARM properties can be assigned in the same way as AIM attributes, but behind the scenes a chain of AIM objects may be created and linked together to represent the property.

As with AIM aggregates, ARM properties that are collections can be assigned by a Python sequence and will automatically append when assigning one beyond the size. They also support the standard Python append(), extend(), and clear() functions.

# Set ARM property
OBJ.its_geometry = step.Object(D, shape_representation)
print (OBJ.its_geometry)
==> <step.Object #0 shape_representation>

# Set ARM collection property
OBJ.its_categories = [ foo, bar ]
OBJ.its_categories += [ baz ]
print (*OBJ.its_categories)
==> foo bar baz

Common STEP Objects

The following EXPRESS types and ARM concepts are a good starting place for understanding the contents of a STEP file. The sections below show an example of the repr() you might see from printing an object with print(OBJ), plus the contents of the dict(OBJ) of the object.

Find ARM Concept
Find EXPRESS Entity

ARM Workpiece

The root of an ARM Workpiece is a product_definition instance. This is the backbone of a STEP file and describes the individual products in an assembly.

Shape is largest and most complex property of a workpiece and is typically found through the its_geometry and its_related_geometry ARM attributes. Other ARM properties give information about material and administrative information like approvals and signoffs.

<step.Object ARM WORKPIECE #21 product_definition>
{'clamping_positions': <step.ArmCollection size 0>,
 'description': ' ',
 'formation': <step.Object #20 product_definition_formation_with_specified_source>,
 'frame_of_reference': <step.Object #3 product_definition_context>,
 'global_tolerance': None,
 'id': '7AJS9999-0001A_FOR_NC',
 'its_approvals': <step.ArmCollection size 0>,
 'its_bounding_geometry': None,
 'its_categories': <step.ArmCollection size 1>,
 'its_components': <step.ArmCollection size 0>,
 'its_constructive_models': <step.ArmCollection size 1>,
 'its_datestamps': <step.ArmCollection size 0>,
 'its_geometry': <step.Object #16 shape_representation>,
 'its_id': '7AJS9999-0001A_FOR_NC',
 'its_material': None,
 'its_orgs': <step.ArmCollection size 0>,
 'its_people': <step.ArmCollection size 0>,
 'its_rawpiece': None,
 'its_related_geometry': <step.ArmCollection size 0>,
 'its_security_classification': <step.ArmCollection size 0>,
 'its_styled_models': <step.ArmCollection size 0>,
 'its_timestamps': <step.ArmCollection size 0>,
 'product_approvals': <step.ArmCollection size 0>,
 'product_datestamps': <step.ArmCollection size 0>,
 'product_orgs': <step.ArmCollection size 0>,
 'product_people': <step.ArmCollection size 0>,
 'product_timestamps': <step.ArmCollection size 0>,
 'revision_approvals': <step.ArmCollection size 0>,
 'revision_datestamps': <step.ArmCollection size 0>,
 'revision_id': '',
 'revision_orgs': <step.ArmCollection size 0>,
 'revision_people': <step.ArmCollection size 0>,
 'revision_security_classification': <step.ArmCollection size 0>,
 'revision_timestamps': <step.ArmCollection size 0>,
 'security_classification': <step.ArmCollection size 0>,
 'shape_definition': <step.Object #22 product_definition_shape>}
 

ARM Geometric_tolerance

The root of an ARM Geometric_tolerance is a geometric_tolerance instance. There are subtypes in both ARM and EXPRESS for position, flatness, surface profile, and many other kinds of tolerance. The actual EXPRESS instance can get rather complex as seen in the example below. The ToleranceAPI has a variety of high-level functions for working with tolerances.

<step.Object ARM SURFACE_PROFILE_TOLERANCE_WITH_DATUM #5449 geometric_tolerance_with_datum_reference_and_surface_profile_tolerance>
{'applied_to': <step.Object ARM COMPOSITE_CALLOUT #5417 all_around_shape_aspect>,
 'associated_draughting': <step.ArmCollection size 1>,
 'datum_system': <step.Object SetOfdatum_system_or_reference size 1>,
 'description': '',
 'id': None,
 'magnitude': <step.Object #5447 MEASURE 0.5 mm>,
 'name': 'Position surface profile.2',
 'qualifying_note': '',
 'reference_datum': <step.ArmCollection size 0>,
 'related_tolerances': <step.ArmCollection size 0>,
 'significant_digits': None,
 'system_datum': <step.Object ARM DATUM_SYSTEM #5448 datum_system>,
 'tolerance_value': <step.Object #5447 MEASURE 0.5 mm>,
 'toleranced_shape_aspect': <step.Object ARM COMPOSITE_CALLOUT #5417 all_around_shape_aspect>}


<step.Object ARM POSITION_TOLERANCE_WITH_DATUM #1554 geometric_tolerance_with_datum_reference_and_geometric_tolerance_with_modifiers_and_position_tolerance>
{'affected_plane': None,
 'applied_to': <step.Object ARM CENTER_OF_SYMMETRY_CALLOUT #1533 centre_of_symmetry>,
 'associated_draughting': <step.ArmCollection size 1>,
 'datum_system': <step.Object SetOfdatum_system_or_reference size 1>,
 'description': '',
 'id': None,
 'magnitude': <step.Object ARM QUALIFIED_PLUS_MINUS_VALUE #1552 length_measure_with_unit_and_measure_representation_item_and_qualified_representation_item>,
 'modifiers': <step.Object SetOfgeometric_tolerance_modifier size 1>,
 'name': 'Position.1',
 'qualifying_note': '',
 'reference_datum': <step.ArmCollection size 0>,
 'related_tolerances': <step.ArmCollection size 0>,
 'significant_digits': None,
 'system_datum': <step.Object ARM DATUM_SYSTEM #1438 datum_system>,
 'tolerance_value': <step.Object ARM QUALIFIED_PLUS_MINUS_VALUE #1552 length_measure_with_unit_and_measure_representation_item_and_qualified_representation_item>,
 'toleranced_shape_aspect': <step.Object ARM CENTER_OF_SYMMETRY_CALLOUT #1533 centre_of_symmetry>}

ARM Material

The root of an ARM material is a material_designation instance. It generally appears as the its_material property of a workpiece.

<step.Object ARM MATERIAL #17348 material_designation>
{'definitions': <step.Object SetOfcharacterized_definition size 1>,
 'material_identifier': 'EN AW-7075 (3.4365 or AlZn5,5MgCu)',
 'material_property': <step.ArmCollection size 0>,
 'name': 'EN AW-7075 (3.4365 or AlZn5,5MgCu)',
 'standard_identifier': 'ASM'}

ARM Project

The root of an ARM Project is a product_definition instance. Every STEP-NC program contains one project, and the "main_workplan" gives the starting point for the manufacturing process. The AptAPI.get_current_project() function is an easy way to find this.

<step.Object ARM PROJECT #10 product_definition>
{'description': '',
 'formation': <step.Object #12 product_definition_formation>,
 'frame_of_reference': <step.Object #16 product_definition_context>,
 'id': '',
 'its_id': 'imts_ashtray_v1',
 'its_manufacturer': None,
 'its_manufacturer_organization': None,
 'its_owner': None,
 'its_owner_organization': None,
 'its_release': None,
 'its_security_classification': <step.ArmCollection size 0>,
 'its_status': None,
 'its_workpieces': <step.ArmCollection size 1>,
 'main_workplan': <step.Object ARM WORKPLAN #19 machining_workplan>}

ARM Workplan and Workingstep

The ARM Workplan and Workingstep concepts are the two most commonly used subtypes of the ARM Executable concept, which describes the control flow in an STEP-NC process. The root is a machining_process_executable instance, or subtypes machining_workplan and machining_workingstep.

The Adaptive class is the easiest way to walk through a STEP-NC process. The as_is_geometry, to_be_geometry, and removal_geometry properties describe the shape at that location in the process.

<step.Object ARM WORKPLAN #19 machining_workplan>
{'as_is_geometry': <step.Object ARM WORKPIECE #2054 product_definition>,
 'consequence': '',
 'description': '',
 'enabled': None,
 'fixture_geometry': None,
 'its_channel': None,
 'its_elements': <step.ArmCollection size 1>,
 'its_id': 'Aerospace',
 'its_minimum_machine_params': None,
 'its_security_classification': <step.ArmCollection size 0>,
 'its_setup': <step.Object ARM SETUP #6267 product_definition_formation>,
 'machine_used': None,
 'name': 'Aerospace',
 'planning_operation': None,
 'process_properties': <step.ArmCollection size 0>,
 'purpose': '',
 'removal_geometry': None,
 'to_be_geometry': <step.Object ARM WORKPIECE #1648 product_definition>,
 'toolpath_orientation': None,
 'twin_end': None,
 'twin_exception': None,
 'twin_plan': None,
 'twin_source': None,
 'twin_start': None,
 'twin_worktime': None}


<step.Object ARM MACHINING_WORKINGSTEP #905 machining_workingstep>
{'as_is_geometry': None,
 'consequence': '',
 'description': 'machining',
 'enabled': None,
 'final_features': <step.ArmCollection size 0>,
 'fixture_geometry': None,
 'its_feature': <step.Object ARM TOOLPATH_FEATURE #994 instanced_feature>,
 'its_id': '2D 90deg arcs and lines WS 2',
 'its_operation': <step.Object ARM FREEFORM_OPERATION #863 freeform_milling_operation>,
 'its_secplane': None,
 'its_secplane_rep': None,
 'its_security_classification': <step.ArmCollection size 0>,
 'machine_used': None,
 'name': '2D 90deg arcs and lines WS 2',
 'process_properties': <step.ArmCollection size 0>,
 'purpose': '',
 'removal_geometry': None,
 'to_be_geometry': None,
 'toolpath_orientation': None,
 'twin_end': None,
 'twin_exception': None,
 'twin_plan': None,
 'twin_source': None,
 'twin_start': None,
 'twin_worktime': None}