3.1 Overview
ROSE applications operate on EXPRESS-defined data sets within a memory workspace. This chapter describes how data sets are organized in memory, how to read and write data sets into and out of memory, as well as other operations on the memory workspace.
The EXPRESS-defined objects manipulated by an application are grouped into clusters called RoseDesign objects. Designs are the basic unit of file I/O and can be written to secondary storage in a number of formats, primarily STEP Part 21.
ROSE applications can read and write EXPRESS-defined data in STEP Part 21 text, STEP Part 28 XML, or ROSE working format. Section 3.3, Section 3.4, and Section 3.5 describe these formats in more detail.
3.2 Data Sets In Memory
ROSE applications read EXPRESS-defined objects into memory and write them to storage in clusters known as a "design" objects. These RoseDesign objects hold the data objects and define operations that work on multiple objects.
The RoseInterface class defines operations on the entire memory workspace. There is a single instance of this class called the ROSE interface object. You can use its functions for creating, reading and writing designs, as well as for managing the designs in memory.
Figure 3.1 illustrates a design object. Objects are considered "persistent" if they are owned by a RoseDesign , because they can be written out to secondary storage. Objects that are not owned by a RoseDesign are sometimes referred to as "non-persistent". An object may belong to only one RoseDesign at any given time.
The RoseDesign class defines functions that operate on the objects owned by a design. Many of these are described in the following sections, as well as Working with STEP Data Objects and Object Search and Traversal.
The ROSE Interface Object
There is one global instance of the ROSE interface object, called ROSE . The functions are invoked as ROSE.function() . The interface object keeps track of the memory workspace, such as the designs in memory, database search paths, EXPRESS data dictionary, and C++ type information.
A listing of designs in memory can be accessed with the RoseInterface::workspaceDesigns() and workspaceDesignNames() functions. The RoseInterface::isInWorkspace() function indicates whether or not a design object with a given name is resident in memory. If the design is in memory, isInWorkspace() returns true rather than a pointer to the design object; use the findDesign() function described below to retrieve a pointer to the design.
The RoseInterface::clearWorkspace() function deletes all normal design objects from memory. It will not remove schema designs. Once a schema has been loaded into memory, it will remain in memory for the remainder of the program run. Schema designs are described in more detail in Section 3.2.3.
Current Design
An application can use as many designs as desired. One of these designs can be designated as the default target for operations. This default is called the "current" design. RoseInterface design functions have three versions. One takes a design name, another a design pointer, and a third uses the current design. The RoseInterface::useDesign() function sets the current design while RoseInterface::design() returns the current design.
RoseDesign * design;
design = ROSE.design(); /* get current design */
ROSE.useDesign (design); /* set current design */
ROSE.useDesign ("gizmo"); /* set current design by name */
An application that use a single design might take advantage of this default to simplify code, whereas applications that use multiple designs will probably pass design pointers to every function.
Creating and Deleting Design Objects
Design objects can be created using either the new operator or the RoseInterface::newDesign() function. When creating a design object, you must specify a filename or complete path that will be used when the design is saved.
The new design will not contain any objects. The example below shows two different ways of creating a new design. The newDesign() function makes the new design the current design, while the new operator does not.
RoseDesign * design;
design = ROSE.newDesign ("/usr/local/gizmo.stp");
design = new RoseDesign ("gizmo.stp");
Each design is has one or more schemas. If you build your application using C++ classes generated from EXPRESS, your design will be associated with that schema by default. You can also specify a schema using the EXPRESS name, or a pointer to a schema design. Schemas are represented in memory as special design objects as described in ROSE Compiled Schemas.
// config_control_design is the name of the AP203 schema
RoseDesign * design;
design = ROSE.newDesign ("gizmo","config_control_design");
design = new RoseDesign ("gizmo","config_control_design");
When not needed, a design and its contents can be scheduled for deletion using rose_move_to_trash() and then deleted when the trash is "emptied" using rose_empty_trash(). Alternatively, they can be deleted immediately using the C++ delete operator.
Deleting through the trashcan cleans up "dangling references" to deleted objects. When the trashcan is emptied, all persistent objects in memory are examined and references to non-persistent or trashed objects are set to null. The C++ delete opeator does not check other objects that hold pointers to freed memory, which could cause core dumps or other problems, particularly when writing designs to secondary storage.
The RoseDesign::emptyYourself() function objects within in a design. As with the delete operator, use of this function may result in dangling references to the deleted data objects.
RoseDesign * design;
/* preferred way to remove */
rose_move_to_trash (design); /* schedule for deletion */
rose_empty_trash(); /* safe deletion */
design-> emptyYourself(); /* delete all data objects */
delete design; /* delete entire design */
Reading and Writing Design Objects
The RoseInterface::findDesign() function reads files. The function first searches memory for a matching design. If no such design is in memory, the function will search secondary storage, read the file into memory and return a RoseDesign pointer. If no match is found, the function will return null.
The useDesign() function will call findDesign() and make the result the current design. Only useDesign() and newDesign() will change the current design.
RoseDesign * current, other;
current = ROSE.useDesign ("/usr/local/samples/tutorial.stp");
other = ROSE.findDesign ("/home/examples.stp");
If you call findDesign() without a full path or filename extension, the search procedure described in Section 3.2.6 will be used to try to match your input, such as "tutorial" with an actual file like "/foo/bar/tutorial.rose".
RoseDesign * current, other;
current = ROSE.useDesign ("tutorial"); /* finds tutorial design */
/* now the current design */
other = ROSE.findDesign ("examples"); /* finds example design */
/* current design is unchanged */
To just search for a design in memory only, without going to secondary storage, use RoseInterface::findDesignInWorkspace(). This function returns null if a design is not already present in memory.
RoseDesign * design;
design = ROSE.findDesignInWorkspace ("gizmo");
Save a design by calling RoseDesign::save() or RoseDesign::saveAs(). By default, a design will be written to the same directory that it originally came from or the current directory if it is new.
RoseDesign * design;
design-> save();
design-> saveAs ("backup.stp");
ST-Developer applications read and write files in STEP Part 21 format. Other formats are available and can be specified with RoseDesign::format(). The formats understood by the ROSE library are:
p21- The STEP Part 21 ASCII exchange file format defined in ISO 10303-21. This is the default format. Files are written as <design-name>.stp . Refer to Section 3.3 for more on the features of STEP Part 21 files.
p28- The STEP Part 28 XML file format defined in ISO 10303-28. Files are written as <design-name>.p28 . The files are zip-compressed to reduce size. Your application must link in the Part 28 library and call a function to enable support for this. See Section 3.4 for details and more on Part 28 files.
p28-raw- The STEP Part 28 XML file format as above, but written without any compression. Files are written as <design-name>.xml .
step- An alias for the "p21" format described above.
binary- The Binary ROSE Working Form is a machine-independent binary format. Files are written as <design-name>.rose . Refer to Section 3.5 for more on the features of ROSE Working Form files.
rose- The ASCII ROSE Working Form is a verbose text format used by the RoseDesign::display() function to write a design to the terminal. Files are written as <design-name>.rose .
Reading and Writing to Explicit Paths
The ST-Developer libraries can read and write STEP files with any extension, files with periods in the name, and Windows directory paths with forward slashes, backslahes, and drive specifications.
The findDesign() , newDesign() , and saveAs() functions handle filenames in a simple, consistent fashion. You can pass a simple name or full path to any of them. If the name has no extension, we will add ".stp", but if you provide one, we will use it.
Creating a design and then doing d->save() gives:
d = new RoseDesign ("foo"); => ./foo.stp
d = new RoseDesign ("foo.bar"); => ./foo.bar
d = new RoseDesign ("/baz/foo"); => /baz/foo.stp
d = new RoseDesign ("/baz/foo.bar"); => /baz/foo.bar
d = new RoseDesign ("/baz/foo.bar.stp"); => /baz/foo.bar.stp
You can also call saveAs() at any point to save the file with a specific name:
d-> saveAs ("foo"); => ./foo.stp
d-> saveAs ("foo.bar"); => ./foo.bar
d-> saveAs ("/baz/foo"); => /baz/foo.stp
d-> saveAs ("/baz/foo.bar"); => /baz/foo.bar
d-> saveAs ("/baz/foo.bar.stp"); => /baz/foo.bar.stp
Searching for designs finds the following. Searching for names without an extension will still match all of the common Part21 extensions.
d = ROSE.findDesign ("foo"); <= ./foo.{stp,step,p21,etc}
d = ROSE.findDesign ("foo.bar"); <= ./foo.bar
d = ROSE.findDesign ("/baz/foo"); <= /baz/foo.{stp,step,p21,etc}
d = ROSE.findDesign ("/baz/foo.bar"); <= /baz/foo.bar
d = ROSE.findDesign ("/baz/foo.bar.stp"); <= /baz/foo.bar.stp
The RoseDesign class has a path() function to get and set the full pathname of a file. The name(), fileDirectory() , and fileExtension() functions get/set components of the pathname.
RoseDesign * design;
char * dir = design-> path(); /* get full path */
design-> path ("/home/datadir/gizmo.stp"); /* set full path */
char * nm = design-> name(); /* retrieve design name */
design-> name ("gizmo"); /* change a design name to gizmo */
/* names must be unique in memory */
char * dir = design-> fileDirectory(); /* get save directory */
design-> fileDirectory ("/home/datadir"); /* set save directory */
char * ext = design-> fileExtension(); /* get file extension */
design-> fileExtension ("foo"); /* set file extension */
char * p = design-> path(); /* get full filename */
design-> path ("/home/datadir/gizmo.foo"); /* set full filename */
Database Search Paths
If you do not specify a complete filename and directory to findDesign() or useDesign() , the ROSE library will search for the design by name using a search path of directories. Applications normally read files with an explicit path and this search path logic is only used to find ROSE data dictionaries for schemas.
ROSE.useDesign ("/usr/data/tutorial.step"); /* reads directly */
ROSE.useDesign ("tutorial"); /* searches for step or rose file*/
ROSE.useDesign ("tutorial.step"); /* searches for tutorial.step */
The ROSE library searches for compiled EXPRESS schemas using this search path. By default, the path contains the current directory (" . "), a local schemas directory (" ./schemas "), and the ST-Runtime schemas directory.
If you can set the $ROSE_DB environment variable to customize this path. This should contain a list of directories separated by a colon (" : ") on UNIX, a semicolon (" ; ") on Windows and a comma on VMS (" , "). The following example show a path on a UNIX machine:
% setenv ROSE_DB .:/usr/local/designs:/usr/local/schemas
The first directory in the search path should be the current directory (" . "). When the ROSE library saves a new design to secondary storage, it uses the first directory in the search path. The library always appends the ST-Runtime schemas directory to the path. The schemas directory be changed using rose_get/setenv system_schema_path().
An easy way to see the contents of your search path from the command line is to use the rose ls utility. This will display all directories in the search path, and all the design files within those directories:
% rose ls
Total number of designs:: 26
In /usr/local/steptools/system_db/schemas
config_control_design.rose
iges_annotation_schema.rose
iges_boundary_representation_schema.rose
iges_constructive_solid_geometry_schema.rose
iges_curve_and_surface_geometry_schema.rose
iges_structure_schema.rose
iges_types_schema.rose
[ ... etc ... ]
When searching directories, the library uses filename extensions to distinguish design files from other files. When a filename ends in .step , .stp , .p21 , or .pdes , it is considered to be a STEP Part 21 file. If a filename ends with .rose or .ros , it is a ROSE Working Form file.
Note
Designs written in ROSE Working Form take precedence over an equivalent STEP Part 21 file. This is because the ROSE files contain additional information used by the compiled schema files, such as object identifiers and names. If two files, data.rose and data.stp , are in the same directory, the data.rose file will be chosen. The data.stp file will only be chosen if it is ahead of the data.rose file in the search path.
The RoseInterface::pathDesignNames() function returns the names of all designs in the search path, or in a particular directory if you specify one. The RoseInterface::isInPath() function determines whether a design with a particular name is in the search path.
ListOfString * names;
names = ROSE.pathDesignNames(); /* all designs in path */
names = ROSE.pathDesignNames("/usr/data"); /* only one directory */
if (ROSE.isInPath ("gizmo")) { /* do something */ }
The RoseInterface::path() function returns the directories making up the search path. The RoseInterface::usePath() function sets the search path from within an application. This function takes a string containing the individual directory names.
ListOfString * dirs = ROSE.path(); /* get path */
unsigned i, sz;
for (i=0, sz=dirs->size(); i<sz; i++)
printf ("path [%d] %s\n", i, dirs->get(i));
ROSE.usePath (".:./schemas:/usr/myapp/schemas"; /* set path */
Memory Cleanup Functions
At startup, the ROSE library creates internal indicies and other information necessary to associate EXPRESS definitions and C++ classes. Some information is set up when static constructors are called and some on first access to some STEP data. The library also readsn compiled data-dictionary information.
This information is used throughout the life of the application. Normally, releasing these indicies is unnecessary because memory is released on application exit.
However, checkers such as Purify may report this information as "memory leaks" so RoseInterface has two methods for releasing system memory at the end of your application:
ROSE.shutdown();
ROSE.shutdown_everything(); /* permanent */
The RoseInterface::shutdown() function releases all persistent data in memory, and restores the ROSE library to pre-startup condition. If you want to, you can read designs back into memory again and continue processing.
The RoseInterface::shutdown_everything() function releases all persistent data in memory, as well as all runtime C++ type information created by static constructors. Once this is done, you should not make any more calls to the ROSE library.
3.3 STEP Part 21 Exchange Files
All ST-Developer applications can read and write STEP Part 21 ASCII exchange files (ISO 10303-21). The library can read and write files using either the original 1994 specification, the 1996 update, or the 2002 second edition. See Section 3.3.6 for more information on the different versions.
Files can also be read and written as STEP Part 28 Edition 2 XML exchange files (ISO 10303-28:2007), but the application must be link in a separate p28e2 library. Consult Section 3.4 for more information.
Header Information
A STEP Part 21 file has a header section with identifying information, and a data section with the information to be transferred. The outline of a STEP file is shown below:
ISO-10303-21; /* opening keyword */
HEADER; /* header section */
[ ... header infomation ... ]
ENDSEC;
DATA; /* data section */
[ ... entity instances ... ]
ENDSEC;
END-ISO-10303-21; /* closing keyword */
A STEP file becomes a design object in memory. The header section information is available through the functions described in Section 3.3.2. Each entity instance in the data section becomes a C++ object owned by the design. The entity identifier information for these objects is available through functions discussed in Section 3.3.4.
Header Section Information
Header section information from STEP Part 21 exchange files is available to applications. The STEP specification says that the header of each exchange file should contain one each of the following objects:
ENTITY file_description;
description : LIST [1:?] OF STRING (256);
implementation_level : STRING (256);
END_ENTITY;
ENTITY file_name;
name : STRING (256);
time_stamp : STRING (256);
author : LIST [1:?] OF STRING (256);
organization : LIST [1:?] OF STRING (256);
preprocessor_version : STRING (256);
originating_system : STRING (256);
authorization : STRING (256);
END_ENTITY;
ENTITY file_schema;
schema_identifiers : LIST [1:?] OF STRING(256);
END_ENTITY;
The header section for a sample STEP file is shown below. The fields marked with "sys" are automatically set by the system:
HEADER;
FILE_DESCRIPTION (
('Sample NURBS geometry for a Boeing 707', /* description */
'for the Common STEP Tasks tutorial'),
'2;1'); /* impl level (sys) */
FILE_NAME (
'ap203_database', /* name */
'1996-05-18T14:18:59-04:00', /* timestamp (sys) */
('Blair Downie'), /* author */
('STEP Tools Inc.', /* organization */
'Rensselaer Technology Park',
'Troy, New York 12180',
'info@steptools.com'),
'ST-DEVELOPER v7.0', /* preprocessor (sys) */
'', /* originating system */
''); /* authorization */
FILE_SCHEMA (('CONFIG_CONTROL_DESIGN')); /* schema (sys) */
ENDSEC;
The FILE_SCHEMA information represents schemas given to the design by the useSchema() and addSchema() functions. The FILE_DESCRIPTION and FILE_NAME information can set directly by an application.
The RoseDesign::header_description() function returns a file description object. This object indicates the contents of the exchange file, along with the version of ISO 10303 that was used to create it. As shown above, the library will automatically fill in the implementation_level field.
The RoseDesign::header_name() function returns the file name object. This provides information about the person and systems that created the exchange data. The library handles the time_stamp and preprocessor_version fields, and will initialize the name field with the design name.
When you create a new RoseDesign object, the header information is not automatically created. The RoseDesign::initialize_header() function creates header objects and initializes the fields as needed. Below is an example of creating a design and filling in the header information:
/* create the design */
design = new RoseDesign ("camshaft", "config_control_design");
/* use Part 21 when writing it out */
design-> format ("p21");
/* fill in the header information */
design-> initialize_header();
/* implementation_level field is taken care of by the
* tool kit.
*/
design-> header_description()-> description ()->
add ("This file contains the design for widgets");
/* name, time_stamp, and preprocessor_version fields
* are taken care of by the tool kit.
*/
design-> header_name()-> originating_system ("MonkeyCad 2.3");
design-> header_name()-> author ()->
add ("Sam Simian");
design-> header_name()-> organization()->
add ("Monkey International");
design-> header_name()-> authorisation ("Sam's Boss");
Short Names
The STEP information models each come with a file containing abbreviated entity names. These short names can be used in a STEP physical file to reduce the amount of space used. Below are some of the short names for AP-203. The aliases are given as a comma-separated pair of names, long name first:
AXIS1_PLACEMENT,AX1PLC
AXIS2_PLACEMENT_2D,A2PL2D
AXIS2_PLACEMENT_3D,A2PL3D
BEZIER_CURVE,BZRCRV
BEZIER_SURFACE,BZRSRF
BOUNDARY_CURVE,BNDCR
BOUNDED_CURVE,BNDCRV
BOUNDED_SURFACE,BNDSRF
BREP_WITH_VOIDS,BRWTVD
B_SPLINE_CURVE,BSPCR
B_SPLINE_CURVE_WITH_KNOTS,BSCWK
The STEP file processor looks for abbreviation files of the form <schema>.nam in the database file search path. These are normally installed as part of ST-Runtime.
The following is a portion of an AP-203 file that uses short names:
#9100 = CRTPNT((0.0,0.0,0.0));
#9101 = DRCTN((1.0,0.0,0.0));
#9102 = DRCTN((0.0,1.0,0.0));
#9103 = DRCTN((0.0,0.0,1.0));
#9120 = A2PL3D(#9100,#9103,#9101);
#9121 = PLANE(#9120);
#9122 = CIRCLE(#9120,30.0);
#9123 = CRTPNT((30.0,0.0,0.0));
#9124 = VRTPNT(#9123);
#9125 = EDGCRV(#9124,#9124,#9122,.T.);
Entity Identifiers
Each entity instance in the data section of a STEP file has an integer identifier. These are the #nnn numbers that reference objects in the file. When a file is read, entity instances are converted to C++ objects and references are turned to C++ pointers. Nevertheless, the original entity id number can be useful for printing debugging information.
The RoseObject::entity_id() function returns the original entity id number for an object. Ids are only available for entities that are read from STEP physical files. This function returns zero for aggregates, selects, and any object read in from a ROSE working form files.
RoseObject * obj;
printf ("This is entity #%d\n", obj-> entity_id());
The entity id is not a permanent identifier. The number is unique within a file, but not between files. Entities are automatically renumbered by default when written to disk. An application can control this renumbering and preserve the ids if necessary. Consult Customized File Handling for details.
External Mappings
EXPRESS AND/OR complex entities are written to a STEP file in a special way. Normal entities are written using an "internal mapping" where the name of the entity type is followed by a list of attributes in superclass-to-subclass order. The following are some entities written using the "internal mapping."
#57=DATE_AND_TIME(#58,#59);
#58=CALENDAR_DATE(1993,17,7);
#59=LOCAL_TIME(13,47,28.0,#29);
Rather than a single type, an AND/OR complex object has a set of types, so the STEP Part 21 specification uses a different encoding, called an "external mapping." Such objects are written as a parenthesized set of individual types. Each type lists just the attributes defined by that type, and the types are sorted into alphabetical order. The example below shows the STEP file entry for an object that is both a B-Spline Surface with Knots and a Rational B-Spline Surface.
#10 = (
BOUNDED_SURFACE ()
B_SPLINE_SURFACE (3, 3, ((#20, #60, #100, #140),
(#30, #70, #110, #150), (#40, #80, #120, #160),
(#50, #90, #130, #170)), $, .F., .F., .F.)
B_SPLINE_SURFACE_WITH_KNOTS ($, $, (0., 0., 0., 0., 1., 1., 1., 1.),
(0., 0., 0., 0., 1., 1., 1., 1.), $)
GEOMETRIC_REPRESENTATION_ITEM ()
RATIONAL_B_SPLINE_SURFACE (((1., 1., 1., 1.), (1., 1., 1., 1.),
(1., 1., 1.,1.), (1., 1., 1., 1.)))
REPRESENTATION_ITEM ()
SURFACE () );
Note that all supertypes are listed, even ones that have no attributes. If you are reading in a STEP physical file and see the message:
"No AND/OR domain with this combination of types, entity ignored."
Then there is an external mapping in the file that uses a combination of types that has not been added to the schema working set. The error message will tell you what combination of types are needed. You can also determine this by examining the instance for "leaf" entity types in the hierarchy. In the example above, B_SPLINE_SURFACE_WITH_KNOTS and RATIONAL_B_SPLINE_SURFACE would be leaf types. Everything else is a supertype of one of these. You should add an ANDOR() entry to your working set for this new type and re-compile your schema.
ANDOR (B_SPLINE_SURFACE_WITH_KNOTS RATIONAL_B_SPLINE_SURFACE)
For more information on working sets and AND/OR types, consult Section 2.5.
Customized File Handling
Applications that need detailed control over Part 21 file handling may take advantage of the features described in the following sections. The classes that handle Part 21 reading, ( RoseP21Lex and RoseP21Parser ) and the class that handles writing ( RoseP21Writer ), have a number of hooks and variables applications can use to provide their own functions.
These hooks are available to select different versions of the Part 21 standard, alter the writing of schema identifiers, change the numbering of entities, scan the comments in a file, or put up a status bar.
Alternate Versions and Conformance Classes
The original Part 21 specification was issued in 1994, and shortly thereafter a technical corrigendum was published to correct some errors in the way that selects were handled. This technical corrigendum or TC version is what is in common use today.
A second edition of the Part 21 was published in 2002. This is upwards compatible, so existing files remain valid. Under the new edition, files may contain extra context information in the header, and more than one data section.
In the header section of a file, the file_description has an implementation level field which identifies the version of the Part 21 specification. In an original 1994 file, this field contains "1", in a TC file and second edition file without extra features, it contains "2;1", and in second edition file with new features it contains "3;1".
ISO-10303-21;
HEADER;
FILE_DESCRIPTION ( ('descriptive text'), '2;1' );
...
By default, files are written using the TC specification for compatibility with older applications. However, if the data set uses second edition features such as the following, it will be automatically written out as a second edition file:
- multiple data sections (as described in the following section)
- only one data section, but the data section has a name.
- extra header section information using the section_context , section_language , or file_population entities.
An application can change the preferred Part 21 specification version by calling the use_spec_version() static function on the RoseP21Writer class. As noted above, data sets containing second edition features must be written as second edition files, but in all other cases the version indicated by use_spec_version() will be used.
The following symbols are available to indicate the various Part 21 specification versions. The symbol PART21_IS indicates the original 1994 version, PART21_TC indicates the revised, technical corrigendum version, and PART21_ED2 indicates the second edition of Part 21.
static void RoseP21Writer::use_spec_version (unsigned);
/* write files using ISO 10303-21:1994 */
RoseP21Writer::use_spec_version (PART21_IS);
/* write files using ISO 10303-21:1994, Corr 1:1996 */
RoseP21Writer::use_spec_version (PART21_TC);
/* write files using ISO 10303-21:2002 (Edition 2) */
RoseP21Writer::use_spec_version (PART21_ED2);
The Part 21 specification supports two different conformance classes. These conformance classes determine how entity instances are written to a file.
Under conformance class one, only complex entity instances are written using an external mapping, while all other entities are written using the internal mapping. This is the default behavior for ST-Developer and other STEP processors. Under conformance class two, which is rarely used, all entities with supertypes are written using the external mapping.
You can control this behavior using the RoseP21Writer::use_conformance_class() static function. The following symbols are available to indicate the various Part 21 conformance classes. The symbol PART21_INTERNAL_CC indicates conformance class one while PART21_EXTERNAL_CC indicates conformance class two.
static void RoseP21Writer::use_spec_version (unsigned);
/* default conformance class */
RoseP21Writer::use_conformance_class (PART21_INTERNAL_CC);
/* write everything as an external mapping */
RoseP21Writer::use_conformance_class (PART21_EXTERNAL_CC);
Customized Schema Handling
An application can customize the handling of schemas in a file by supplying hook functions. When reading the header section of a Part 21 file, a hook can be called for each schema name in the FILE_SCHEMA() object. This function takes a design pointer and a schema name, and should find the appropriate compiled schema and add it to the design. A return value of ROSE_OK indicates successful completion while a non-zero value indicates an error.
The variable RoseP21Parser::add_schema_fn holds a pointer to the function. If the add_schema_fn pointer is null, the rose_p21_dflt_add_schema() function is used.
typedef RoseErrorCode (*RoseP21AddSchemaFn) (
RoseDesign * design,
const char * name
);
static RoseP21AddSchemaFn RoseP21Parser::add_schema_fn;
RoseErrorCode a_simple_add_schema_function (
RoseDesign * design,
const char * sname
)
{
/* like findDesign(), but ignores case and ASN/1 idents */
RoseDesign * schema = rose_p21_find_schema (sname);
if (schema) {
design-> addSchema (schema);
return ROSE_OK;
}
return ROSE_IO_BAD_SCHEMA;
}
When writing a design, a hook function can provided to supply the names of each schema in a design. The function will be called on each element of the schemas() list of the design. If the function returns null for a particular schema, no entry will be written for it in the Part 21 FILE_SCHEMA list.
The variable RoseP21Writer::schema_name_fn holds a pointer to the function. The function will be passed two arguments, a pointer to the design being written, and a pointer to the schema (which is also a design). If the schema_name_fn pointer is null, the default behavior is used.
typedef char * (*RoseP21SchemaNameFn) (
RoseDesign * design,
RoseDesign * schema,
);
static RoseP21SchemaNameFn RoseP21Writer::schema_name_fn;
char * a_simple_write_schema_function (
RoseDesign * /* design, unused */,
RoseDesign * schema
)
{
return schema-> name();
}
Behavior on Missing Schemas
If the Part 21 reader can not find the compiled schema definitions for a file, it will probably not be able to process many of the instances in the file. An application can control the default behavior if this happens.
static RoseBoolean RoseP21Parser::allow_bad_schemas;
/* try to keep reading, even with missing schema */
RoseP21Parser::allow_bad_schemas = ROSE_TRUE;
/* (default) generate an error, do not continue reading */
RoseP21Parser::allow_bad_schemas = ROSE_FALSE;
The RoseP21Parser::allow_bad_schemas variable controls the behavior of the file reader if a schema is not found. By default, this variable is set to false, and if schema cannot by found, the filer generates a single error for the schema itself, and the RoseDesign that is returned from ROSE.findDesign() is NULL.
If this variable is set to true, the filer attempts to read as much as it can. If schemas are missing, it unlikely to find the compiled EXPRESS definition for many entities. When it can not fine a definition, it will issue a warning and skip the instance. This may result in many messages and a RoseDesign containing no instances.
Entity ID Numbering
By default, the Part 21 file writer numbers all entity instances in increments of one (#10, #11, #12, etc.). It assigns new numbers each time the file is written. Your application can control how these numbers are assigned and the order in which the instances appear in the file using the facilities described below.
Entity ID Preservation
By default, entities are renumbered when they are written, but the library can also preserve the entity ids across reads and writes. The options below control the default entity numbering provided by the rose_p21_dflt_renumber() function. If you have provided a custom renumber function described below, these options have no effect. This functionality is controlled by the following static members of RoseP21Writer .
static RoseBoolean RoseP21Writer::preserve_eids;
/* save eids from input file */
RoseP21Writer::preserve_eids = ROSE_TRUE;
/* (default) renumber all entities, most effiecient */
RoseP21Writer::preserve_eids = ROSE_FALSE;
The RoseP21Writer::preserve_eids variable controls how entities are assigned IDs (the #numbers) in a file. If true, any entity instance that was read in from a file will keep the same IDs that it had in that file. An application can also assign its own IDs to newly created instances when they are created. Newly created instances that do not have an ID will be assigned one that does not conflict with any existing IDs.
If false, all entity instances are given a new ID. Preserving IDs can slow down the creation of Part 21 files, so this variable is set to false by default.
static RoseBoolean RoseP21Writer::sort_eids;
/* only write instances sorted by ID */
RoseP21Writer::sort_eids = ROSE_TRUE;
/* (default) write in the most efficient way */
RoseP21Writer::sort_eids = ROSE_FALSE;
The RoseP21Writer::sort_eids variable controls the order in which entity instances are written to a Part 21 file. By default, the instances are written in the most efficient manner, which may or may not be sorted. In general, the entities will be sorted, since the writer assigns IDs to the instances and then to write them in the same order. If you are using the preserve_eids described above to save IDs from an existing file, then writing using the most efficient traversal may result in instances appearing in a random order. An unsorted Part21 file may look strange to a human reader, but it is perfectly legal according to the Part 21 specification.
If you are using preserve_eids and want to force the instances to be written in numeric order, set the sort_eids variable to true. This forces the library to build an index of the IDs before writing the file, which is somewhat expensive.
static unsigned int RoseP21Writer::renumber_density;
/* Ratio of entities/max eid */
RoseP21Writer::renumber_density = 5;
The RoseP21Writer::renumber_density variable determines how new object are numbered. For any instance that does not have an EID, there are two possibilities: the greatest EID in the file can be found, and all new EIDs are chosen as increments from that, or gaps in the numbering sequence can be located.
The first of these options is fast, but may lead to the EIDs eventually overflowing, the second option is safe, but requires the creation of an index. The algorithm to use is determined by the value of the renumber_density variable. If the greatest EID in the model divided by the number if entity instances in the model is greater than renumber_density , then the gaps are filled, otherwise the values are appended to the file. The default value is 5.
Batch Renumbering
An application can provide a function that will be called before writing begins and should traverse the entire design, assigning new ids with the RoseObject::entity_id() function. The variable RoseP21Writer::renumber_fn holds a pointer to an application-provided function. If the pointer is null, the default behavior is used. The function should return ROSE_OK to indicate successful completion.
typedef RoseErrorCode (*RoseP21RenumberFn) (RoseDesign * design);
static RoseP21RenumberFn RoseP21Writer::renumber_fn;
The following hook function demonstrates how to renumber the objects by increments of five. This function uses a RoseCursor to visit every entity in the data sections of a design.
RoseErrorCode renumber_by_five (RoseDesign * design)
{
RoseCursor objects;
register RoseStructure * object;
register unsigned long eid = 5;
/* Traverse entities in all data sections. Seting the
* cursor section type is not really needed since data sections
* are the default */
objects.traverse (design);
objects.section_type (ROSE_SECTION_DATA); /* the default */
objects.domain (ROSE_DOMAIN(RoseStructure));
/* number the entity ids by 5s */
while (object = (RoseStructure *) objects.next()) {
object-> entity_id (eid);
eid += 5;
}
return ROSE_OK;
}
/* to use this function, be sure to set renumber_fn */
RoseP21Writer::renumber_fn = renumber_by_five;
/* reset to default behavior */
RoseP21Writer::renumber_fn = 0;
Reading and Writing Comments
An application can add comments to the header section of a Part 21 file and to individual entity instances. Comments on individual entity instances are not useful for production files, but could be very helpful when creating sample datasets for training or documentation purposes.
To add comments to instances in a file, just call the RoseObject::entity_comment() function with the contents of the comment. Given the following fragment:
calendar_date * cd;
local_time * lt;
cd-> entity_comment ("This is my date instance");
lt-> entity_comment (" This is\n...my time instance\n ");
When the instance is written out to a STEP Part 21 file, the comment would appear as follows:
/*This is my date instance*/
#58=CALENDAR_DATE(1993,17,7);
/* This is
...my time instance
*/
#59=LOCAL_TIME(13,47,28.0,#29);
When reading, comments are normally discarded and entity_comment() will just return null, but you can force the file processor to save them by adding the following lines to your application program:
/* somewhere in your main, before you start reading files */
RoseP21Lex::comment_fn = rose_p21_read_and_preserve_comment;
The variable RoseP21Lex::comment_fn holds a pointer to function to scan the comments in a Part 21 file. If the pointer is null, comments are processed and ignored. Two built-in functions are provided
typedef void (* RoseP21CommentFn) (RoseP21Lex *);
static RoseP21CommentFn RoseP21Lex::comment_fn;
extern void rose_p21_read_and_discard_comment (RoseP21Lex * lex);
extern void rose_p21_read_and_preserve_comment (RoseP21Lex * lex);
The rose_p21_read_and_discard_comment() function is the default, while rose_p21_read_and_preserve_comment() will capture the comment and associates it with the nearest object so that it is available via RoseObject::entity_comment().
The following sample code scans and discards comments. The function is called when the Part 21 lexer encounters the beginning of a comment. The function is responsible for reading through to the closing "*/" and incrementing the lexer line count where appropriate. Use this as the starting point for your own function.
/* EXAMPLE */
void process_comment (RoseP21Lex * lex)
{
register int c;
register FILE * f = lex->file();
for (c=getc(f); c != EOF; c=getc(f))
{
if (c == '*') {
/* Lookahead to check for the closing slash. */
int c2 = getc(f);
if (c2 == EOF) break;
if (c2 == '/') return;
ungetc (c2, f); /* pushback and continue */
}
else if (c == '\n') {
lex-> trace()-> increment_line();
}
}
rose_io_ec()-> warning ("End of file in comment.");
}
Writing Header Section Comments
An application may want to add comments to the header section of a file, to identify the product in more detail than allowed by the FILE_NAME() entry, or for documenting internal settings for debugging purposes.
The variable RoseP21Writer::comment_fn holds a pointer to a function that will be called while writing the beginning of the header section. The function is called with a pointer to the file being used.
typedef RoseErrorCode (*RoseP21WriteCommentFn) (FILE * file);
static RoseP21WriteCommentFn RoseP21Writer::comment_fn;
/* EXAMPLE */
RoseErrorCode write_custom_comment (FILE * file) {
fprintf (file, "/* Produced by MonkeyCAD\n");
fprintf (file, " * Debugging Information:\n");
. . .
fprintf (file, " */");
return ROSE_OK;
}
/* somewhere in your main() */
RoseP21Writer::comment_fn = write_custom_comment;
Periodic Status Updates
The Part 21 reader and writer functions can periodically call a hook function to implement a status bar. Calling frequency is specified as a number of objects. A frequency of 100 means the status function will be called after every 100 objects are read or written. A value of 1 means the function will be called after every object.
The variables RoseP21Parser::status_fn and RoseP21Writer::status_fn hold pointers to application-provided status functions. The variables RoseP21Parser::status_freq and RoseP21Writer::status_freq hold the calling frequency. If the calling frequency is zero or the function pointer is null, the status function will not be called.
typedef RoseErrorCode (*RoseP21ReadStatusFn) (
RoseP21Parser * parser_obj,
unsigned long current_entity_count
);
typedef RoseErrorCode (*RoseP21WriteStatusFn) (
RoseP21Writer * writer_obj,
unsigned long current_entity_count
);
static RoseP21ReadStatusFn RoseP21Parser::status_fn;
static unsigned RoseP21Parser::status_freq;
static RoseP21WriteStatusFn RoseP21Writer::status_fn;
static unsigned RoseP21Writer::status_freq;
The status functions return a RoseErrorCode . Successful completion is indicated by the value ROSE_OK . A non-zero value indicates failure and further file processing is halted. Thus, the status function could put up a status bar with a cancel button. Normally the function would return ROSE_OK , but when the button is pressed, the function could return a non-zero value, halting the read or write.
International Character Support
Strings in Part 21 exchange files can include any character in the ISO 10646 character set. This is done with escape sequences that cause the data in the strings to be interpreted as hexadecimal values for the character codes rather than literal ASCII characters. These escape codes can specify 8-, 16- or 32-bit character values.
Since ROSE represents all characters internally as 8 bit values, the filer normally decodes the 8-bit characters, and ignores the 16- and 32- values. If your application must process wide characters, you can force the STEP filer to operate in a raw mode, where 16 and 32 bit characters are not decoded (8-bit escapes are still handled by the reader).
void rose_enable_wchar (RoseBoolean stat);
Use rose_enable_wchar(ROSE_TRUE) to turn on raw mode. With raw mode on, you can safely read and write files containing \X2 and \X4 escapes without losing any information. (The default behavior is to delete the extended characters.) These string will be returned with the \X2\ and \X0\ escapes embedded. in the string.
The following Part 21 instance definition includes some Greek characters:
#10=DIRECTION('Greek Data: \X2\039803C8\X0\',(0.,1.,0.));
By default, Part 21 reader reads the string as " Greek Data: " (note that the actual Greek characters are lost. On the other hand, when an application enables wide character support, the string is read in as " Greek Data: \X2\039803C8\X0\ ". In this case, the data is preserved as an 8-bit encoding, but there is no information loss.
After the reading the data, you will still need to decode it. ST-Developer includes a library which converts 16-bit characters ( \X2 ) to Unicode. To convert the string to Unicode, ST-Developer includes the following two functions:
wchar_t * rose_cvt_p21_to_wchar (const char *);
char * rose_cvt_wchar_to_p21 (const wchar_t * str);
These functions convert between the 8-bit Part 21 representation and 16-bit Unicode characters. Other character sets are rarely used. If you need to use one of them, it is necessary to write your own converter from the Part 21 encoding to the actual character set.
The following is a complete program which traverses a design and decodes and displays all the strings as Unicode.
#include <rose.h>
#include <stepi18n.h>
void DisplayUnicode(wchar_t* wszArg)
{
/* Display the actual values here. For example, on Windows
* you could do something like:
* MessageBoxW(NULL, wszArg, L"Unicode display", MB_OK);
*/
}
int main (int argc, char **argv)
{
rose_enable_wchar(ROSE_TRUE);
char * filename = argv[1];
if (!filename) {
printf ("Usage: test [filename]\n");
exit (1);
}
RoseDesign * des = ROSE.findDesign(filename);
RoseDesignCursor cur;
cur.traverse(des);
cur.domain(ROSE_DOMAIN(RoseStructure));
RoseObject * obj;
while (obj = cur.next()) {
List(RoseAttribute) * atts = obj->attributes();
for (unsigned i=0; i<atts->size(); i++) {
RoseAttribute * att = atts->get(i);
if (att->isString()) {
/* Get the string */
wchar_t * wstr = rose_cvt_p21_to_wchar(obj->getString());
DisplayUnicode (wstr);
}
}
}
return 0;
}
3.4 STEP Part 28 XML Files
ST-Developer applications can read and write STEP Part 28 Edition 2 XML exchange files (ISO 10303-28:2007). To enable this functionality, simply link against the p28e2 library, include rose_p28.h , then call rose_p28_init() at the start of your program to register the XML reader and writer.
From then on, your application will automatically recognize STEP Part 28 XML files when reading. By default, new files are written as Part 21 but you can switch to Part 28 XML by calling RoseDesign::format() with " p28 " or p28-raw " strings.
RoseDesign * d;
d-> format ("p21");
d-> save() // write as Part 21 text
d-> format ("p28");
d-> save() // write compressed Part 28 XML
d-> format ("p28-raw");
d-> save() // write raw Part 28 XML
When the " p28 " format is specified, the XML will be zip compressed to reduce size, and the file will be given the " .p28 " extension if none is specified. If you run unzip on the result, you will see a single file called iso_10303_28.xml , which contains the raw XML.
With the " p28-raw " format is specified, the XML will be written without any compression, and the file will be given the " .xml " extension if none is specified.
The small Part 28 XML file below shows some AP238 instance data with an axis placement, cartesian point, and two directions. The specifics of the Part 28 encoding are discussed in Section 3.4.3 below.
<?xml version="1.0"?>
<iso_10303_28_terse
xmlns="urn:oid:1.0.10303.238.1.0.1"
xmlns:exp="urn:oid:1.0.10303.28.2.1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
schema="integrated_cnc_schema">
<exp:header>
<exp:name>dataset</exp:name>
<exp:preprocessor_version>stepnc writer</exp:preprocessor_version>
</exp:header>
<!-- instance data -->
<Axis2_placement_3d id="id1" Name="" Location="id2" Axis="id3"
Ref_direction="id4"/>
<Cartesian_point id="id2" Name="loc" Coordinates="3.5 -3.5 -4.16875"/>
<Direction id="id3" Name="Z direction" Direction_ratios="0 0 1"/>
<Direction id="id4" Name="X direction" Direction_ratios="1 0 0"/>
</iso_10303_28_terse>
Sample Part 28 Program
The program below reads a STEP file (Part 21 or Part 28) and writes it back out to a different name and format. Including rose_p28.h and calling rose_p28_init() forces the program to recognize Part 28 as well as Part 21.
#include <rose.h>
#include <rose_p28.h> // bring in Part 28 support
void main(int argc, char ** argv)
{
rose_p28_init(); // call at start of program
if (argc != 4) {
printf ("usage: format <p21|p28|p28-raw> <in-name> <out-name>\n");
return;
}
const char * fmt = argv[1];
const char * in_name = argv[2];
const char * out_name = argv[3];
RoseDesign * d = ROSE.findDesign(in_name);
if (d) {
d->path (out_name);
d->format(fmt);
d->save();
}
else printf ("ERROR: Could not read design '%s'\n", in_name);
}
To build this sample program, compile normally, but add the p28e2 library to the link line as shown in the following commands:
On UNIX and MacOS
% CC -I$ROSE_INCLUDE format.cxx -L$ROSE_LIB -lrose -lp28e2
On Windows
> cl /I"%ROSE_INCLUDE%" format.cxx /LIBPATH:"%ROSE_LIB%" rose.lib p28e2.lib
Browsing Part 28 XML
ST-Developer makes it simple to examine STEP XML files in any web browser with the Part 28 to HTML conversion tool. On Windows, this tool integrates into the Explorer so you can open a file in a web browser by simply right clicking on it and selecting "Browse P28 XML" as shown below.
On other platforms just run the p28html command-line tool to prepare HTML for your browser as shown below
% p28html datafile.p28 ==> writes datafile.html in the current dir
% firefox datafile.html
All of the entity instances are linked, so you can move through the file simply by clicking on references. As shown in the figure above, clicking on any instance in the file pops up a window showing all attributes, their types and their values, even unset attributes. You will also see a complete USEDIN() listing of objects that refer to the instance, and can quickly jump to any of them.
XML Configuration
The ISO 10303-28 file format uses the "terse" configuration shown below.
<?xml version="1.0"?>
<configuration
xmlns="urn:oid:1.0.10303.28.2.1.2"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<schema targetNamespace="urn:oid:1.0.10303.238.1.0.1" />
<option exp-attribute="attribute-content" tagless="true" />
<uosElement name="iso_10303_28_terse" >
<add_attribute name="schema" type="xs:NMTOKEN" usage="required" />
</uosElement>
</configuration>
The configuration uses the Part 28 attribute-content and tagless encoding options and defines the iso_10303_28_terse unit of serialization (UOS) element to enclose the instance data. The "targetNamespace" configuration for the element is based on the ASN/1 identifer for the appropriate STEP Application Protocol (AP238 in the example above). The element also has a "schema" attribute with the schema name as in the header of a Part 21 file, to simplify processing by EXPRESS-based tools.
The Part 28 namespace for an AP can be specified in the compiled EXPRESS data dictionary by using the -p28ns flag within a workingset file provided to the expfront compiler. 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
Alternatively, an application can specify a hook function to provide a namespace when writing a design. The function is called with a pointer to the design and first schema. The string return value will be the namespace. If the function returns null, no default namespace will be used.
typedef char * (*RoseP28NamespaceFn) (
RoseDesign * design,
RoseDesign * schema
);
extern RoseP28NamespaceFn rose_p28_get_namespace_fn();
extern void rose_p28_put_namespace_fn (RoseP28NamespaceFn fn);
The functions above get and put the namespace hook function. Putting a null function pointer will reset the behavior to call the rose_p28_dflt_namespace() function.
For handling the schema names in the "schemas" attribute, the Part 28 reader and writer use the Part 21 schema hook functions described in Section 3.3.8.
3.5 ROSE Working Form Files
ROSE working form files can be read by any ST-Developer applications. These files contain the same information as a STEP Part 21 file, plus a dictionary of object names, and a "root" object. These features are discussed in Object Search and Traversal. The format also stores a globally unique object identifiers and supports inter-file references. Section 3.5.1 discusses this in more detail.
ROSE working form files can be written in binary or ASCII form. The binary form is the fastest and most commonly used. The ASCII form is used by the display() function when printing an object to stdout . Both file formats are written with the .rose or .ros filename extension. Files may be converted between Part 21 and ROSE with the rose format utility:
% rose format <fntname> <filename>
% rose format step foo.rose - from ROSE working form to part 21
% rose format binary foo.step - from part 21 to binary working form
% rose format rose foo.step - from part 21 to ascii working form
If you convert from ROSE working form to Part 21, you lose object identifier information, so you should never do this for the compiled EXPRESS schemas (the *.rose files produced by the EXPRESS compiler).
Object Identifiers and References Between Designs
Every object has a unique object identifier (OID). These 160 bit identifiers are unique across time and space, and incorporate things like time of creation, creating process and machine identifier. An identifier will stay with an object as long as it is stored as a ROSE file. The STEP Part 21 format does not preserve these identifiers. When a design is written to a Part 21 file and read back in, new OIDs are generated.
Since OIDs do not change, they can be used for references between designs. This allows you to split application data between several designs. Consider a database of machine parts. This data could be stored in a "parts" design object, but part objects might have attributes such as "part_material" and "part_drawing." These attributes could hold pointers to objects in other design objects, such as "materials" and "drawings". Figure 3.3 illustrates this example.
Saving "parts" writes the part objects to the parts.rose file, but data in the other designs remain untouched. References between them remain valid as long as the designs are stored in ROSE working form files. If any were converted to Part 21 files, the extra OID information would be lost and the references invalidated.
References to other designs are resolved on demand. The first time an application follows a reference from a part object to a materials or drawings attribute, the materials or drawings design will be read, and the matching object will be found.
Schema Migration
Each file formats handles changes to EXPRESS definitions in a different way. The ROSE working form is more tolerant of schema changes than the STEP Part 21 format. When an EXPRESS schema is recompiled, the compiler compares previously compiled definitions with the new EXPRESS model, and generates new definitions for any types that have changed. The newest types are accessible by name or OID, while the older types are still accessible by OID.
The STEP Part 21 format references EXPRESS definitions by name, so old instances will be read using the newest definitions. Attribute values are stored in the order in which they appear in the EXPRESS schema, so when the ordering of attributes or names of entities in a schema changes, entities in a Part 21 file may become unreadable.
The ASCII ROSE working form also references EXPRESS definitions by name, so it will also use the newest definitions. Attribute values are stored with the names of the attribute, so changes in ordering will not affect the files, although name changes still will.
The binary ROSE working form references the compiled EXPRESS definitions by object identifier. Instances will be read using the same definitions they were written with, so an application might need to update instances. Another way to move a binary file to the latest definitions is to use the rose format tool to convert it to ROSE ASCII form and back again:
% rose format rose datafile.rose
% rose format standard datafile.rose
Since the binary format uses OIDs to find definitions, you should protect the compiled EXPRESS schema. If the compiled schema is removed and regenerated, the new schema will have different OIDs, and any binary file that used the original will be unreadable. The same problem will occur if the compiled schema file is converted to Part 21 format. The OIDs would be lost and binary files that depended on that schema would be rendered unreadable.