Overview

Most applications use the STEP libraries or IFC libraries. These contain C++ classes generated from the EXPRESS schemas, plus meshing and higher-level functions for working with assemblies, geometry, units, and measures. Refer to the project setup instructions for more details.

For other EXPRESS schemas, you may need to generate C++ classes and an EXPRESS data dictionary using the EXPRESS compiler as described below.

Generating C++ Classes

ROSE applications can use C++ classes created from EXPRESS for strong type-checking and simple access/update functions. The STEP Merged AP and IFC BIM libraries contain ready-to-use classes for the most widely used schemas. You can use the ST-Developer EXPRESS compiler to generate C++ classes from your own schemas, as described below.

Call the EXPRESS compiler with the -classes option to create a subdirectory called classes with generated C++ code. The class for each ENTITY, SELECT, and ENUMERATION goes into a separate .h and .cxx file. The structure and member functions of each class are described in to EXPRESS-defined C++ Objects

% expfront -classes custom_schema.exp 

The classes for AGGREGATES are defined in the same file as their base type. For example, the EXPRESS LIST OF LIST OF LIST OF XYZ will produce the following classes along with XYZ in XYZ.h/cxx:

ListOfListOfListOfXYZ
ListOfListOfXYZ
ListOfXYZ

The compiler also generates the <schema>.h header file that includes the definitions of all classes. A working set file can be used to fine tune the classes that are generated.

Once you have the classes, you can import them into a Visual Studio project or other build environment. The mkmakefile tool can also generate a makefile that compiles and assembles a library from all class files in a directory.

Class Names

The C++ language is is case sensitive but the EXPRESS language is not. For example, "SAMPLE" and "sample" are the same in EXPRESS but different in C++. By default, the generated C++ classes use the name and case that appears in the EXPRESS. Two other conventions exist for SDAI C++ compatibility, but are not widely used.

ROSE Convention

This is the default naming convention, and used by all libraries included with ST-Developer. The STEP libraries are also given the stp_ prefix as described in Adding a Prefix to Class Names so definitions like product and cartesian_point become classes named stp_product and stp_cartesian_point.

Classes and attributes have the same name and case as the EXPRESS. If a symbol conflicts with a C++ reserved word or known function, the compiler will issue a warning and eliminate the conflict by capitalizing the first letter of the word.

-- Sample EXPRESS Definitions
SCHEMA example_schema;
    ENTITY representation_item 
	name : STRING; 
    END_ENTITY;

    TYPE colorType = ENUMERATION OF (red, green, blue);
    END_TYPE;
END_SCHEMA;


//
// Resulting C++ definitions
//
class representation_item {
    char * name(); 
    void name (char * str);
};

enum colorType {
	colorType_NULL = NULL_ENUM,
	colorType_red = 0,
	colorType_green,
	colorType_blue
};

Full SDAI C++ Convention

Call the compiler with the -namestyle fullsdai flag. The SDAI C++ states that classes are to be put in a namespace or given a name consisting of the schema name prepended to the entity name. The concatenated name is used for portability. All names are lowercase, with the first letter capitalized. For enumerations, the SDAI styles separate the type name and value by two underscores, with a NULL value <typename>_unset.

class Example_schema_Representation_item {
    char * Name(); 
    void Name (char * str);
};

enum Example_schema_Colortype {
	Example_schema_Colortype_unset = NULL_ENUM,
	Example_schema_Colortype__red = 0,
	Example_schema_Colortype__green,
	Example_schema_Colortype__blue
};

Terse SDAI C++ Convention

Call the compiler with the -namestyle sdai flag. The terse version omits the initial schema name, but is otherwise the same as the Full SDAI C++ style. Since most applications only use one schema, terse names are a more practical alternative. The entity definition above becomes:

class Representation_item {
    char * Name(); 
    void Name (char * str);
};

enum Colortype {
	Colortype_unset = NULL_ENUM,
	Colortype__red = 0,
	Colortype__green,
	Colortype__blue
};

Class Customizing

The ROSE library can attach extra data to any object using a general mechanism called managers. Managers are the preferred way to keep extra data since they can be used on any class.

If you generate your own C++ classes, the extclass tool can insert #include statements at key places in the generated classes so that you can add member data and functions using separate files that will not be lost if the classes are regenerated. There is also an extall tool that scans a directory for extension files and calls extclass as needed.

extclass files
Extclass Files

The declarations for your new functions and extra instance data go into the .hi and .hx files. The body of the functions go into the .cx file as described below:

<class>.hi
Class extensions, new include statements and ordinary function declarations. The contents of this file are placed before and outside of the scope of the class definition.
<class>.hx
Class extensions, new member declarations. The contents of this file are placed inside the scope of the class definition after all other members have been declared.
<class>.cx
Class extensions, new member functions bodies and constructor extensions.

For example, extending a point class with some extra graphics data might look something like the following. The Point.hi file brings in the include files that we need for our graphics code.

/* File: Point.hi
 * New include-files and plain (non-member)
 * function declarations for the Point class.
 */

#include <graphics.h>        /* graphics package decls */

The Point.hx file declares the new member functions and instance variables for our extended class. In this example, we add a couple of new fields and a new member function.

/* File: Point.hx
 *  New member functions and data for the Point class.
 */
int      NP_redraw_state;      /* non-persistent data */
Window   NP_drawing_window;

void draw();                   /* new member function */

Our Point.cx contains the following member function definitions and constructor extensions. We define the macro ROSE_CTOR_EXTENSIONS with initialization code to extend the default constructor to initialize the new member data.

/* File: Point.cx
 *  Member function definitions and constructor extensions
 */

#define ROSE_CTOR_EXTENSIONS \
	NP_redraw_state = FALSE; \
	NP_drawing_window = NULL;

void Point::draw()
{
    /* C++ code to draw the point on the screen */
}

Generating Data Dictionary

The libraries need a dictionary of EXPRESS schema definitions to read and write exchange files. These definitions are built into the STEP and IFC libraries, but an application that uses a custom schema will need to create a compiled data dictionary and ship it with their application.

Calling the EXPRESS compiler with the -rose option will create a compiled dictionary file named after the original EXPRESS schemas (<schema>.rose).

% expfront -rose custom_schema.exp 

STEP Part 21 files refer to EXPRESS definitions by name, so removing and regenerating compiled schemas is safe. However, files stored in the older binary ROSE file format use UUID references which change if the schema is regenerated.

Files with the _EXPX suffix contain parse information used by the EXPRESS interpreter to evaluate WHERE clauses and derived attributes. These files may be safely removed and recreated at any time.

Packaging Applications

Applications that use the STEP and IFC libraries are completely self-contained. Applications built with a custom schema must find their compiled dictionary file at runtime.

We have an collection of older dictionary files, called ST-Runtime, that you can extend with your own compiled schemas. We provide ST-Runtime as a Windows MSI, a merge module as well as a zip file in the redist directory under the ST-Developer installation.

You can configure where your application looks for the compiled schemas. files. The libraries look at environment variables, and the Windows registry, in the following search order:

You can override these with the rose_get/setenv system_schema_path() function.

Working Sets and Best-Fit Classes

A working set file controls the EXPRESS compiler C++ class generation. This small text file can force extra AND/OR class combinations or extra aggregates, generate a subset of definitions, and change the naming of classes or files.

Working set files normally contain a list of entity or type names. Each is listed on a separate line, with possible modifiers after them. Use backslashes (\) to break an entry over several lines. Use the hash character (#) to mark comment lines.

# This is a comment line
# The entry for type2 is split across multiple lines
#
type1
type2  -option1 foo \
       -option2 bar

A summary of the known entries and modifiers are summarized below and discussed in the following sections.

SCHEMA schema_name \
	[-all | -none] \
	-prefix pfx_name \
	-p28ns xml_namespace_name

entity_name \
 	-name class_and_file_name \
	-file file_name \
 	-aggs L;S;B;A;LL \
	-group grp_name \
 	-fitpriority

ANDOR(ent1_name ent2_name ...) \
 	-name class_and_file_name \
	-file file_name \
	-schema schema_name

# for identifying and generating subsets of definitions
GROUP grp_name \
	-prefix pfx_name \
	-dllspec dll_symbol \
	-schema schema_name \
	-include extra_incfile

EXPRESS AND/OR Entities

EXPRESS allows a form of multiple inheritance on a per instance level, which is called an AND/OR or complex entity. A particular data instance has a set of types rather than one EXPRESS type for the combination. These appear as a complex instance in a Part 21 file, with each entity type enclosed in parenthesis. They are commonly used by STEP for "mix-in" combinations of types for units and contexts.

/* A complex instance from a Part21 file.  This is a combination of
 * length_unit and si_unit.   Both are subtypes of named_unit.
 */
#16=(
   LENGTH_UNIT()
   NAMED_UNIT(*)
   SI_UNIT(.MILLI.,.METRE.)
   );

When generating C++, we must add extra classes for these complex instance combinations. A limited number of combinations are generally used, so we list them in the working set description and the EXPRESS compiler generates a C++ class for each. We specify a combination with the ANDOR keyword, then a list of type names. e.g.:

ANDOR (type1 type2 type3 ...)

This creates dictionary data and a C++ class. The ROSE library automatically creates dictionary information for new combinations as it finds them in a data file, but the workingset file is the only way to get new classes. The name is built from the type concatenated together with _and_. The constructed name uses the types in alphabetical order.

# A complex class length_unit_and_si_unit is used for the Part21
# sample data shown above.

ANDOR( si_unit  length_unit )

If the working set contains only AND/OR entries, it will generate all classes. Below is a sample working set file with some common AND/OR combinations for STEP schemas. The ST-Developer documentation has complete workingset files for each pre-installed application protocol or other model.

# The AP units for area, length, mass, plane_angle, solid_angle, and
# volume. Create SI and conversion unit combinations for most.  Just
# SI for solid angle, because only steradians are ever used.

ANDOR( si_unit  length_unit )
ANDOR( si_unit  mass_unit )
ANDOR( si_unit  plane_angle_unit )
ANDOR( si_unit  solid_angle_unit )

ANDOR( conversion_based_unit    mass_unit )
ANDOR( conversion_based_unit    length_unit )
ANDOR( conversion_based_unit    plane_angle_unit )

#  
# Spline curves and surfaces (nurbs)
#
ANDOR(  rational_b_spline_curve         b_spline_curve_with_knots )
ANDOR(  rational_b_spline_curve         bezier_curve )
ANDOR(  rational_b_spline_curve         quasi_uniform_curve )
ANDOR(  rational_b_spline_curve         uniform_curve )

ANDOR(  rational_b_spline_surface       b_spline_surface_with_knots )
ANDOR(  rational_b_spline_surface       bezier_surface )
ANDOR(  rational_b_spline_surface       quasi_uniform_surface )
ANDOR(  rational_b_spline_surface       uniform_surface )

When the compiler generates a C++ class for and AND/OR entry, it concatenates the entity names to produce a name for the class. If you use the default rose name style, the names are concatenated with _and_ between them. If you use the sdai or fullsdai style, the names are capitalized before concatenating them.

ANDOR ( si_unit plane_angle_unit  )

plane_angle_unit_and_si_unit                    -- ROSE
Config_control_design_Plane_angle_unitSi_unit   -- full SDAI
Plane_angle_unitSi_unit                         -- terse SDAI

Some of these names can get absurdly long, so you may want to specify your own using the -name option. If you are using the -prefix option, note that the prefix will only be applied to the final concatenated name, not to each element of the name.

ANDOR( measure_representation_item length_measure_with_unit ) \
       -name stp_length_measure_representation_item

Best-Fit Classes

The ROSE library keeps runtime C++ type information and matches each EXPRESS definition to the most appropriate C++ class. When an application is built with a subset of classes using a working set, or when a data file contains new AND/OR combinations, there may be instances for which there is no exact C++ type.

Missing Polygon Class
Missing Polygon Class

The figure above shows a Polygon instance with no matching C++ type. This is represented in memory as an instance of a more general C++ type, such as RoseObject. The RoseObject class has no special access or update functions for Polygon attributes, but the late-bound RoseObject::get<name>()/put<name>() functions can be used to manipulate the values.

The ROSE library can do sophisticated class matching. When reading an instance, the library will search the C++ type information for a best-fit class. The best-fit class is the closest match to the EXPRESS definition. For example, consider the figure below. Our application program contains only the Point class, but we are reading instances of both Point and Extended_Point types.

Best Fit Classes
Best Fit Classes

The Point instance has an exact match to the Point class. The Extended_Point instance has no exact match, but the Point class is the closest fit. All attributes inherited from Point have generated access and update functions, while any extra attributes defined by Extended_Point can be accessed through late-bound functions. It is an instance of the Point C++ class, but the object is still an EXPRESS Extended_Point and will be written as such if the file is saved.

If a type inherits from several supertypes (multiple inheritance) there might be more than one potential "best fit" class. The library will choose the class that covers the most attributes or subtypes.

Best-fit matching is also used when reading a complex entity instance, that does not contain a matching ANDOR class. For complex instances, the library will look for a class that has a a complex instance combination for a subset of types if the full combination is not available. It finds a combination with a supertype if one with a subtype is not available.

Best Fit AND/OR
Best Fit AND/OR Classes

At the other extreme, the figure below shows an application that contains no generated classes. In this situation, every instance will be matched with the built-in RoseObject types. In fact, entity instances will be matched to RoseStructure, selects to RoseUnion, and aggregates to one of the built-in RoseAggregate subtypes such as RosePtrList, RosePtrSet, RosePtrBag, or RosePtrArray.

Completely Late-Bound
Completely Late-Bound

Generating a Subset of Definitions

STEP schemas can be large, but some have conformance classes with well-defined subsets of definitions. If you list the subset in a working set file, the EXPRESS compiler can generate classes for just these entities. References to anything outside of the working set appear as the generic type RoseObject.

When you read a data set into memory, objects in your working set are instantiated as the appropriate C++ class, while others are instantiated as RoseObjects. No data is lost. Attributes of the RoseObject instances are accessed through late-bound functions as in Data-Dictionary Access and Update.

The following example compiles the EXPRESS file schema.exp and generates C++ only for entities listed in the file myents:

% expfront -classes -ws myents schema.exp 

where the myents file contains:

% cat myents
ent_one
ent_two
ent_three
ent_four
%

In this case, the compiler generates C++ classes for ent_one through ent_four, but not for any other entities. If ent_one contained a attribute called some_att of some type ent_five, the compiler would generate an access and update function for it, but it would be of type RoseObject.

Changing Class and File Names

A workingset file can also control the file or class name used for a definition. The C++ class name or source file name can be changed as needed, but will still correspond to the original EXPRESS type.

As we have seen, the simplest workingset entry is just the name of a definition. The following will generate a C++ class Automobile for the EXPRESS type of the same name. The class definitions are put in the files Automobile.h/cxx

Automobile

The -file option changes the file name, but not the class name. The example below will generates a C++ class Automobile in the files auto.h/cxx. Use this option to make file names conform to special naming conventions such as 8.3 filesystems.

Automobile -file car

The -name option changes the file name and the class name. The example below generates a C++ class auto for the EXPRESS type named Automobile, and puts the class definition into auto.h/cxx.

Automobile -name auto

Adding a Prefix to Class Names

STEP application protocols define entites with common names (point, line, address, vertex). If you are use other packages, you may see name clashes because some other package contains a point or line class. (For example, one geometry kernel has conflicts with bounded_curve, curve, ellipse, pcurve, plane, surface, and vector)

The -name option will rename individual classes, but to rename them all, you can specify a prefix for all generated type names (entities, selects, and enumerations). You can give the prefix either on the command line or in a workingset file.

On the command line, just call the EXPRESS compiler with the -prefix option. In a workingset file, add -prefix to the SCHEMA entry. The following examples will prepend stp_ to all definitions in the AP203 schema.

On the command line:
> expfront -classes -prefix stp_ ap203.exp

In a workingset file:
SCHEMA config_control_design -prefix stp_

Instead of C++ classes like product and cartesian_point, you will get classes named stp_product and stp_cartesian_point. If a workingset file has both a schema prefix and -name for a particular type, the -name will be used for that type.

Other Control Options

Entire schemas can be called out in the working set file using the special SCHEMA keyword, then the name of the schema. You can use the -all and -none directives to control what definitions are generated.

By default, only definitions listed in the control file are generated. If the control file contains only AND/OR entries, all definitions are generated. Putting in a schema entry with the -all directive forces generation of all definitions for the schema. The -none directive stops generation of any definitions for the schema.

SCHEMA config_control_design -all
SCHEMA config_control_design -none

There is also an ENTITY keyword to identify entity entries. It is not required, and rarely used, but it might make your working set clearer at times.

Part 28 Namespace

The Part 28 namespace for an AP can be added to the compiled EXPRESS data dictionary by using the -p28ns flag on a SCHEMA entry. If none is provided, XML will be written in the default namespace.

# Sample workingset file entry
SCHEMA config_control_design \
    -p28ns urn:oid:1.0.10303.203.1.0.1

Extra Aggregates

The EXPRESS compiler generates classes and data-dictionary entries for all aggregates used by the schema. You can use the -aggs directive to force extra aggregate definitions for a base type.

The -aggs argument indicates the aggregate types using the first letter of the aggregate style. For example L indicates a list, B indicates a bag, LL is a nested list of list of the base type, and so on. The argument is not case sensitive.

For example, suppose a schema had a cartesian_point definition, but never used any aggregates. My application might use a list of cartesian points for some temporary processing. Since the schema doesn't use a list, one would not normally be generated, but I could add the following line to my working set file:

cartesian_point -aggs l

This would forces the compiler to generate a ListOfcartesian_point class. By default the compiler generates only those classes listed in the working set file. The schema -all option will force the compiler to generate all of the other classes in addition to cartesian point:

SCHEMA my_schema_name -all     <-- generate all classes in schema
cartesian_point -aggs l        <-- also generate the extra list

If you need to list several aggregates, separate them with semicolons. The examples below force the generation of list, set, bag, array, and list of lists of cartesian point. All three entries are equivalent.

cartesian_point -aggs l;s;b;a;ll
cartesian_point -aggs L;S;B;A;LL
ENTITY cartesian_point -aggs L;S;B;A;LL