Overview

This page is outdated. The recommended way to support multiple APs is to use the STEP Merged AP Library to build your application.

This chapter discusses some of the issues that arise when working with C++ classes, multiple schemas, and features of the ROSE library that were used to build the Merged AP Library.

How ROSE Handles Schemas

To understand the impact of multiple APs on applications, you must first be familiar with the way the ROSE library uses these schemas. We use the ROSE library and other ST-Developer tools to build C++ applications using classes generated from an EXPRESS schema. The most common case has been to generate classes based on an AP long form schema and build an application around that.

When the application is run, either new objects are created and written out, or an external file is read and used to populate objects. In either case, application must know the structure of the data. The program actually requires two different structure descriptions. One is the "physical schema," which describes the C++ classes linked into the program. The other is the "database schema," the EXPRESS definitions that describe the external, logical structure of the data.

The physical schema is built at application start-up by generated code in each class. The description objects are sewn together so each knows about supertypes and subtypes, members, member types, offsets of member data and base classes for casting, alignment and more. The result of this is a table of all generated C++ classes that have been compiled into the program.

The database (EXPRESS) schema is normally read in from secondary storage. These descriptions are read in from a file generated by the EXPRESS compiler. Once in memory, we "activate" the schema by matching (by name) each database type to a physical type. Database types that do not have an exact match to a physical type must still be "best-fit" to the class of a supertype and provisions made for storing the database fields for which there exist no physical counterpart.

When we write an application against a single AP, the physical and database schemas are normally the same (or the physical can be a subset of the database schema if we use a workingset), and the matching process is handled automatically by the ROSE library. As we move to multiple APs, we must become more aware of this matching process, and may place some of it under application control. The STEP Merged AP Library avoids these issues by using one combined schema so that classes are only matched once.

Creating C++ Classes for Use With Several APs

The first step in developing an application that works with several APs is to construct a physical schema (C++ classes) that covers all of the different entity types that we must work with.

When working with a single AP, we normally generate classes from the long form EXPRESS schema. If we want to process a common subset of several APs, such as an AIC, we could still generate classes from one AP or AIC schema. If an AIC were not available, we could also define a working set file to generate a subset of classes from an AP. Working sets are discussed in greater detail in Working Sets and Best-Fit Classes.

If we intend to create fully conforming datasets for several APs, we need more than a common subset of definitions. The conformance classes for each AP will probably mandate a number of AP-specific relationship objects like the cc_design_* entities of AP203 or the auto_design_* entities of AP214.

In the worst case, we may need AP-specific classes for each AP that we must support. The physical schema of our application would be a union of all of the AP definitions. There are two ways to produce the classes. We could manually merge the AP long form or short form schemas into one, resolve any conflicts, and then generate classes from the merged schema.

An expedient alternative is to generate C++ classes from each individual schema into the same directory. If there are duplicate entity definitions, the latter source files will simply overwrite earlier ones. You should use caution with this technique. The APs should not have many structure conflicts, since the APs are all based on the same integrated resources, but there may be a few.

In particular, select types are prone to variability between the APs. When an AP is converted from short form to long form, selects are trimmed to eliminate types outside the AP scope. In extreme cases, the select contains just one type. Consequently, if you compare several long form schemas, the same select type definition may vary widely between them. For example, consider some definitions from AP203 and AP214.

    TYPE person_organization_select = SELECT:   
    AP203 only has person_and_organization   
    AP214 CC2 has person,organization,person_and_organization
    
    TYPE unit = SELECT:   
    AP203 only has named_unit
    AP214 has named_unit,derived_unit

An application will have problems if C++ classes are generated from a definition that does not have all of the required types. To avoid this, generate the classes for these selects from the most inclusive EXPRESS schema available, like a merged schema, an integrated resource (IR), an AIC, or the most inclusive long form AP schema.

Switching Between with Several Long Form APs

Suppose we have written an AP203 translator or analysis tool. Now we want to extend it to support some other APs. The tool works with entities that are common to all of the APs, so we would not expect the bulk of our code to change. We would like to add a switch to our translator that says "use AP203" or "use AP214."

The first step is to make sure that our application has C++ classes for each AP that it will support, as described in the previous section. Next we must consider how the compiled EXPRESS definitions are matched to these classes.

When the application reads a Part 21 file, it finds the compiled EXPRESS schema from the P21 header. When reading an entity instance, the ROSE library finds the EXPRESS definition, and can navigate from that to the C++ class that it was "best-fit" to use when creating an instance in memory. This is how a cartesian point entity instance in a file becomes an instance of the cartesian_point class in memory.

The situation is reversed when our application uses pnew to create an instance of the cartesian_point class. Now we have the C++ class and must find an EXPRESS definition to associate with the instance. The generated C++ class code includes the name of the originating EXPRESS schema. The ROSE library uses this to find the EXPRESS definition to be used when creating objects.

This works fine if we are using a single AP. When we have several APs, there will be a compiled EXPRESS schema for each. Since each compiled schema will have a complete copy of the definitions, any overlapping types will appear in each compiled schema. These overlapping definitions have the same name and use the same C++ class, but are distinct database types (RoseDomain objects).

ROSE library functions, such as traversals with RoseCursor and findObjects(), depend on type comparisons using the RoseDomain objects. Comparing the RoseDomain object for AP203 cartesian_point with the one from the AP214 schema will return false, since they are different RoseDomain objects. Mixing domain objects can cause misleading behavior. One common symptom is a traversal which does not find any objects of a particular type because the objects and search criteria use domains with the same name, but in different schemas.

The pnew operator and ROSE_DOMAIN() macro operate on C++ classes, but also must find the RoseDomain object that matches the class. These operations look for the domain in the schema that the class was originally generated from. This works well for a single AP, but when multiple APs are present, we need a way to control which schema will be used.

The following functions change the EXPRESS schema used by the C++ classes. For example, consider an application built for AP203 and AP214. When working on an AP214 file, we would force the classes to use AP214 domains. This makes pnew create an AP214 instance and ROSE_DOMAIN() return the domain object from the AP214 schema. If we were working on an AP203 file, we would force the classes to use AP203 domains.

/* Change domains that the built in types correspond to. */

void rose_set_cxx_schema (
	RoseDesign * schema
	);

void rose_set_cxx_schema_from_design(
	RoseDesign * design
	);

Both functions change the schema used when we create new objects through pnew or find domains using ROSE_DOMAIN(). Both functions look at all classes compiled into the application. If the schema contains a definition for the class, that definition will be used, otherwise the class will remain unchanged. The first function accepts the schema as a parameter, while the second function finds the schema from a given design object.

Consider a program that uses the following classes. For this example, assume that we generated the AP214 classes first, and then generated all AP203 classes. Some of the generated classes:

    C++ class                          Generated from schema
    ---------                          ---------------------
    auto_design_approval_assignment    --> AP214
    cc_design_approval                 --> AP203
    cartesian_point                    --> AP203
    product                            --> AP203
    product_definition                 --> AP203

The cartesian_point, product, and product_definition definitions are common to both APs, but the classes were generated from AP203. If we are working with an AP214 file, we want to make sure that these classes are viewed as AP214. When reading a file, we call rose_set_cxx_schema_from_design() to change the classes to use the same schema as the design.

    RoseDesign * design;
    design = ROSE.findDesign ("AP214_datafile.stp");
    rose_set_cxx_schema_from_design (design);
    
    /* the C++ classes will now use domains from:
    auto_design_approval_assignment    --> AP214
    cc_design_approval                 --> AP203 (no such thing in AP214)
    cartesian_point                    --> AP214
    product                            --> AP214
    product_definition                 --> AP214
    */

If we are creating a new design, we can set the schema manually using the rose_set_cxx_schema() function or we can set it from the design as before.

    RoseDesign * design;
    
    /* create AP203 data set */
    design = new RoseDesign ("AP203_datafile.stp", "config_control_design");
    rose_set_cxx_schema_from_design (design);
    
    /* the C++ classes will now use domains from:
    auto_design_approval_assignment    --> AP214 (no such thing in AP203)
    cc_design_approval                 --> AP203
    cartesian_point                    --> AP203
    product                            --> AP203
    product_definition                 --> AP203
    */

This approach works well for import/export tools and other applications that work with one AP at a time. The application can be written as if it were several single AP translators that happen to share source code. The "set cxx schema" functions are the switch that changes the entire application over from one AP to another.

If the application must have data defined by more than one AP in memory at a given time, we must use the approach described in the next section.

Databases that Integrate Multiple APs

The "set schema" functions let us quickly adapt a single AP translator to read or generate other APs. However, each AP is still a distinct construct, with separate database definitions for overlapping entities.

Suppose we want to build an application that converts AP203 files to AP214, or one that creates data before it knows which AP to use. An example of this would be a CAD translator that must create geometry data before it is told whether to output an AP203 or AP214 file.

In situations like this, the application potentially has data from many APs in memory at the same time. If we used the techniques described in the previous section, we would have multiple database definitions (RoseDomains) for the overlapping EXPRESS types. The result would be datasets that use a mix of overlapping definitions, such as cartesian point objects, some defined by the AP203 version of the domain and some defined by the AP214 version. The previous section described some of the algorithmic problems this can cause, so clearly, we would prefer all of the cartesian points to use the same domain. But if we used a unified schema, we would also need some way of indicating the AP identity of an object.

Integrated Schemas and AP Contexts

We avoid this problem by separating the database definition of an object from the AP that it is assigned to. Using these new features, we can now define our objects with a single integrated database schema (like our C++ classes), and use an "AP context" to control the assignment of objects to an AP.

These extensions change some aspects of the behavior of the ROSE library, so they are not enabled by default. To enable them, you must call the following function at the start of your application. This turns on the integrated schema features and forces the library to look for the configuration file described below. If this fails, the function will return an error code, otherwise it will return zero.

    #include <rose.h>
    
     . . . 
    
    if (rose_use_ap_interoperability()) {
        printf ("ERROR: could not use interoperability features\n");
    }

When we generated C++ classes in an earlier section, we merged overlapping definitions from many APs to create a single class library. The AP interoperability extensions to the ROSE library let us do a similar thing with the database schemas. For example, when you read an AP203 data set using these extensions, the cartesian point domain can come from an integrated schema, but the instance will still also know that it came in as an AP203 object.

The integrated model and AP recognition is done from a central configuration file called iso_ap_list.rose. At the moment, this file contains two named list of strings. The first is a list of recognized AP names and the second is a list of integrated schemas to use for these APs.

    named list "__ISO_AP_LIST"
    (<0-0> ListOfSTR
            "CONFIG_CONTROL_DESIGN"
            "ASSOCIATIVE_DRAUGHTING")
    
    named list "__ISO_IR_LIST"
    (<0-1> ListOfSTR
            "unified_ap_schema")

The environment variable $ROSE_AP_PATH should be set to the directory containing the integrated schema files as well as iso_ap_list.rose. The IR list can contain any number of short form schemas or one merged long form schema. Applications can also control the location with rose_get/setenv integrated_schema_path(), but the path must be set before rose_use_ap_interoperability() is called.

    setenv ROSE_AP_PATH  /usr/local/step/ap_schemas

When a Part 21 file is read in, the FILE_SCHEMA is matched against the AP list in the configuration file. If it matches, the integrated schemas are given to the design.

When the integrated schemas are used, each design section is given an AP context. At the moment, the only attribute of the RoseAPContext is a name. There is also a static function on the class to find a context of a given name. There is one context for each AP listed in the control file.

    RoseAPContext * apc;
    apc = RoseAPContext::find ("CONFIG_CONTROL_DESIGN");
    
    printf ("using apc %s\n", apc-> name());

AP contexts lets us keep track of an AP schema, apart from the compiled schemas used for the domains. The context is used to write the schema information at the start of extended data section Part 21 files.

    RoseObject * obj;
    RoseAPContext * apc;
    
    apc = obj-> design_section()-> ap_context();
    if (apc)
            printf ("object using apc %s\n", apc-> name());
    else    printf ("object not assigned to an AP\n");

With the AP contexts in place, we can now use an integrated set of short-form schemas to represent one or more Application Protocols.

Traversing Object From Multiple APs

Since we are using an integrated set of schemas, there is a single domain for each type. This means that isa() and other domain-based functions work properly even though objects may have come from different APs. Traversals by domain will return all instances of the type, even if some originated in different APs.

The design cursors can traverse individual RoseDesignSections, or all sections in a design that have a particular AP context. You can traverse by both domain and context for all objects of a particular type and AP, or just by context for all objects from the AP.

    RoseCursor objects;
    RoseObject * obj;
    RoseAPContext * apc;
    
    apc = RoseAPContext::find ("CONFIG_CONTROL_DESIGN");
    
    /* traverse all objects */
    objects.traverse (design);
    while (obj = objects.next()) do_something (obj);
    
    /* traverse all AP203 objects */
    objects.traverse (design);
    objects.ap_context (apc);
    while (obj = objects.next()) do_something (obj);
    
    /* traverse all AP203 cartesian points */
    objects.traverse (design);
    objects.ap_context (apc);
    objects.domain (ROSE_DOMAIN(cartesian_point));
    while (obj = objects.next()) do_something (obj);

Creating Objects and Setting The AP Context

When you call rose_use_ap_interoperability(), we load the integrated schemas and update the C++ classes so that they use the integrated domains when you do a pnew. This also instructs the Part 21 file handler to look for AP contexts. If you create a new design it has no AP context. You can assign a default context for the design using the RoseDesign::dflt_context() function. This will be used by any data section that has not been explicitly assigned a context. You can assign a context to a design section using the RoseDesignSection::ap_context() function.

    RoseAPContext * apc_203;
    RoseDesign * design;
    RoseDesignSection * section;
    
    apc_203 = RoseAPContext::find ("CONFIG_CONTROL_DESIGN");
    
    design-> dflt_context (apc_203);
    section-> ap_context (apc_203);

Integrated Databases Using Multiple AP Part 21 Files

One of the long-term goals of STEP is to build integrated databases that cover the entire life cycle of a product. The scope of these databases are larger than any single AP. They must integrate information that spans many APs. In support of this goal, the ISO community is examining AP integration issues and has added requirements for multiple AP data sets to the next version of Part 21. Extensions have been proposed to enable the use of multiple APs in the same Part 21 file. ST-Developer v7 supports these extensions.

Here is an example of an extended Part 21 file. The FILE_SCHEMA header section entry contains a list of all APs used in the file. The file also has multiple data sections. Each data section has an attribute list. The first attribute is the name of the section. The second attribute is the schema name.

    ISO-10303-21;
    HEADER;
    FILE_DESCRIPTION(
    /* description */ (''), /* implementation_level */ '2;1');
    
    FILE_NAME(
    /* name */ 'merged',
    /* time_stamp */ '1997-05-01T12:24:32-04:00',
    /* author */ (''), /* organization */ (''),
    /* preprocessor_version */ 'ST-DEVELOPER v7',
    /* originating_system */ '', /* authorisation */ '');
    
    FILE_SCHEMA (('CONFIG_CONTROL_DESIGN','ASSOCIATIVE_DRAUGHTING'));
    ENDSEC;
    
    DATA('unused',('CONFIG_CONTROL_DESIGN'));
    #25=CARTESIAN_POINT('',(13412.,-1486.,0.));
    #26=CARTESIAN_POINT('',(10642.,-1267.,0.));
    ENDSEC;
    DATA('unused',('ASSOCIATIVE_DRAUGHTING'));
    #50=CARTESIAN_POINT('',(10642.,-1267.,0.));
    #51=CARTESIAN_POINT('',(12349.,-1348.,0.));
    ENDSEC;
    END-ISO-10303-21;

When we read an extended Part 21 file into memory, the design object will contain a RoseDesignSection object for each data section in the file. The section objects will be given an AP context matching the file section it appears in. When we write a Part 21 file, the library will use the extended format if the design contains multiple data sections, or if it contains one data section that has a name. This latter case preserves the name associated with the design.

The extended Part 21 files work both with and without the AP interoperability extensions described in the previous section. If you read an extended Part 21 file without calling rose_use_ap_interoperability(), the parser will look for compiled long-form schema definitions rather than integrated definitions. This may be sufficient when working with schemas that have little or no overlap (such as an AP and a schema with local extensions), but will cause problems when working with schemas that have much overlap (such as several APs).

The behavior of AP contexts also depends on the use of integrated schemas. When the interoperability features are enabled, a context is assigned to every data section that uses one of the integrated schemas. This is true regardless of whether the exchange file has one data section or many.

Without the interoperability features, AP contexts are used for multiple data section Part 21 files, but not for normal, single-section Part 21 files. When AP contexts are not present, the Part 21 file writer will compute the schemas for the file by looking at the contents of the design. If an AP context is present, it will be used instead.

At the present time, only Part 21 files can preserve multiple data sections and AP context information. If you write a data set as a ROSE binary or text working form file, you will get back objects in one data section. If you are using the integrated schemas, the objects will lose any information about their original AP.

Here is a simple little program that merges two files. If you merge two AP203 files you get one big AP203 file. If you merge files with different APs, you get an extended Part 21 file with two data sections.

    #include <rose.h>
    
    RoseDesignSection * get_section_for (
        RoseDesign * design,
        RoseAPContext * context
        )
    {
        RoseDesignSection * section;
    
        /* look for the first data section to match our ap context */
        for (section=design->sections(); section; section=section->next()) {
            if (section-> section_type() != ROSE_SECTION_DATA) continue;
            if (section-> ap_context() == context) return section;
        }
    
        /* not found, look for an empty section with no ap context and
         * give it ours.  This avoids creating multiple section part 21
         * files if we don't need to 
         */
        for (section=design->sections(); section; section=section->next()) {
            if (section-> section_type() != ROSE_SECTION_DATA) continue;
            if (!section-> ap_context() && !section-> size()) {
                section-> ap_context (context);
                return section;
            }
        }
    
        /* not found, just create a new data section for the ap data */
        section = new RoseDesignSection (design);
        section-> ap_context (context);
        return section;
    }
    
    
    void merge_all_data_objects (
        RoseDesign * merged, 
        RoseDesign * orig)
    {
        RoseObject * obj;
        RoseDesignSection * merged_section;
        RoseDesignSection * orig_section;
        RoseDesignCursor objects;
    
        /* Go through the sections of all designs and move the objects to
         * the new design.  We could just move the sections, but we would
         * prefer to merge the contents of all data sections based on the
         * same AP.
         */
        for (orig_section=orig->sections(); orig_section; 
             orig_section=orig_section->next()) 
        {
            /* only merge data, ignore header and system data */
            if (orig_section-> section_type() != ROSE_SECTION_DATA) continue;
    
            /* Get data section for the merged objects */
            merged_section = get_section_for
                 (merged, orig_section-> ap_context());
    
            /* move the data objects over */
            objects.traverse (orig_section);
            while (obj = objects.next()) {
                rose_move_to_section (obj, merged_section);
            }
        }
    }
    
    
    main(int argc, char *argv[])
    {
        RoseDesign *d1, *d2;
        RoseDesign *merged;
    
        rose_use_ap_interoperability();
    
        if ( argc != 3 ) {
            printf("Usage: %s file1 file2\n", name);
            exit(0);
        }
    
        d1 = ROSE.findDesign (argv[1]);
        if (!d1) {
            printf ("design %s not found\n", argv[1]);
            exit (1);
        }
    
        d2 = ROSE.findDesign (argv[2]);
        if (!d2) {
            printf ("design %s not found\n", argv[2]);
            exit (1);
        }
    
        merged = new RoseDesign ("merged"); 
        merged-> format ("step");
    
        merge_all_data_objects (merged, d1);
        merge_all_data_objects (merged, d2);
    
        merged-> save();
        return 0;
    }