Sample 01

This is the first of the Getting Started samples, and probably where you should start when exploring EDMsdk. In this sample, we will show you haw to create your first EDMdatabase, create a user and a repository for containing your data. You will learn how to compile an EXPRESS schema into the EDMdatabase and to import a data set in an EDMmodel. Then you will learn how you can export the data set to a Part28 XML file. This sample also shows how you may do conditional selection of subsets of the data and print it out on the screen as well as to Part28 XML.

The complete package with resource files and source code is available from Getting Started with EDMsdk, but you may also download the source code of the sample below.

Error rendering macro 'excerpt-include' : User 'null' does not have permission to view the page 'US:_icon_C(30px)'.

Error rendering macro 'excerpt-include' : User 'null' does not have permission to view the page 'US:_xc_GettingStarted_Case_01'.

In the following, we will walk you through all the statements and explain them.

When your application is a EDMstandaloneClient, you will create and operate on the EDMdatabase locally. The first you need to do is to create this database (unless you already have one on your file system). This sample assumes that there is already an EDMdatabase on your file system, created by your previous run, and that you will delete it first.

Delete EDMdatabase
if (rstat = edmiDeleteDatabase (dbPath, dbName, dbPwd)) {
  if (rstat != edmiENOSUCHDATABASE) {
    printf("\nERROR: Failed to delete database with error '%s'", edmiGetErrorText(rstat));
    goto err;
  }
}

The EDMdatabase will consist of a great number of files, so make sure you create a separate folder for it.

Create EDMdatabase
if (rstat = edmiCreateDatabase (dbPath, dbName, dbPwd)) {
  printf("\nERROR: Failed to create database with error '%s'", edmiGetErrorText(rstat));
  goto err;
}

After creating the EDMdatabase, you will have typically 50 files in your database folder. Any standalone process can open and connect to this EDMdatabase once it is created, but only one process can open it at the time. If you need multiple client applications connect to the EDMdatabase simultaneously, you need an EDMserver and write your application as an EDMthinClient.

Open EDMdatabase
if (rstat = edmiOpenDatabase(dbPath, dbName, dbPwd)) {
  printf("\nERROR: Failed to open database with error '%s'", edmiGetErrorText(rstat));
  goto err;
}

The EDMdatabase is now open and therefore uavailable to other EDMclient applications. At this stage, you would be connected to the database with a session for the default EDMuser named sdai-user. If we were to continue with API calls from here, it would be as the sdai-user. But in this sample, we will start by creating a new EDMuser and an EDMgroup. Only the superuser has permission to create new EDMusers and EDMgroups, so we now now need to connect as the superuser.

Connect as superuser
if (rstat = edmiConnect("superuser", NULL, dbPwd, &unavailMess)) {
  printf("\nERROR: Failed to connect as superuser with error '%s'", edmiGetErrorText(rstat));
  goto err;
}

The password of the superuser is by default set to the password you specified when you created the EDMdatabase. If, from some reason, the EDMdatabase should be set temporarily unavailable by the susperuser, an attempt to connect would fail with the error code edmiEUNAVAILABLE. The message provided by the superuser will be returned in the unavailMess string. Next step will be to open a session for the superuser.

Open a session
if ((suSessId = sdaiOpenSession()) == 0) {
  printf("\nERROR: Failed to open session for superuser with error '%s'", edmiGetErrorText(rstat));
  goto err;
}

As superuser, we will create a new user, MyUser and a new group, MyGroup. Then we set a password for MyUser and make it a member of MyGroup. As an application developer, you may be fine with using the default sdai-user for all your operations, but in some cases you may want to set individual Access Rights on different EDMdatabase objects. If Bob wants to import a data set that Alice shall not have write access to, it will require separate users for Bob and Alice.

Create EDMuser and EDMgroup
if (rstat = edmiCreateUser("MyUser", &myUserId)) {
  printf("\nERROR: Failed to create user with '%s'", edmiGetErrorText(rstat));
  goto err;
}
if (rstat = edmiCreateGroup("MyGroup", &myGroupId)) {
  printf("\nERROR: Failed to create group with '%s'", edmiGetErrorText(rstat));
  goto err;
}
if (rstat = edmiChangePassword(myUserId, "MyPassword")) {
  printf("\nERROR: Failed to set my password with '%s'", edmiGetErrorText(rstat));
  goto err;
}
if (rstat = edmiUserToGroup(myGroupId, myUserId)) {
  printf("\nERROR: Failed to add MyUser to MyGroup with '%s'", edmiGetErrorText(rstat));
  goto err;
}

Note that the functions edmiCreateUser() and edmiCreateGroup() return integer IDs. If you look at the declaration of the C API functions in the sdai.h header file, you will see that a lot of the functions come in pairs, e.g sdaiGetAttr() and sdaiGetAttrBN(). This is two ways of invoking an underlying function. The sdaiGetAttr() takes the instance Id of the attribute of an entity as argument. The sdaiGetAttrBN() takes the name of the attribute. Some times you know the ID but not the name. In other situations it is the other way around. In most cases it is just a matter of convenience which of the two you use, but keep in mind that there may be a performance issue. If you loop over millions of instances, it may be faster to work with IDs, since those address the data directly in the EDMdatabase.

We will now continue as MyUser/MyGroup;

Change User
if (rstat = edmiChangeMyUserBN("MyUser", "MyGroup", "MyPassword", &unavailMess, &mySessId)) {
  printf("\nERROR: Failed to connect as MyUser with '%s'", edmiGetErrorText(rstat));
  goto err;
}

This statement is for most purposes equivalent to the edmiConnect() + sdaiOpenSession() that we used for the superuser above. It performs both a connect to the EDMdatabase and opens a session for the connected EDMuser. Note that the returned instance id, mySessId, identifies the session uniquely in the EDMdatabase. That is, for any successive switch to EDMuser/EDMgroup, you may use the API sibling,  edmiChangeMyUser(), which takes only the session Id as argument. The statement above would look like this;

Change User with session Id
if (rstat = edmiChangeMyUser(mySessId)) goto err;

If Bob and Alice share a file system, they probably will make their own directories to avoid chaos. Separate directories also make it possible for Bob to set restricted access to his files for Alice. Directories may even be created for all the files that both Alice and Bob shall work on together. In an EDMdatabase, the EDMrepository represents the same concept as directories do in file systems. Again, if your application does not support individual EDMusers and EDMgroups, you may be well off with the default DataRepository that comes with the EDMdatabase. In this sample, however, we will create a repository for the data set used.

Create Repository
if (rstat = edmiCreateRepository(repositoryName, &repositoryId)) {
  printf("\nERROR: Could not create the '%s' repository with error '%s'", repositoryName, edmiGetErrorText(rstat));
  goto err;
}

By now, you probably know what the returned instance Id, repositoryId, may be used for. In the EDMdatabase, there are three types of EDMrepositories.

  • The Data Repositories are the default DataReporitory and any EDMrepository you create. 
  • Then there is the DictionaryRepository that will contain the compiled EXPRESS schemata. Every compiled EXPRESS schema will result in a so called Dictionary Model in the DictionaryRepository. The Dictionary Models contains the layout of the data sets that are imported. Dictionary Models are often denoted Meta-Data.
  • Lastly, there is the SystemRepository that specifies the layout of the Dictionary Models. This is the unique late binding feature of Express Data Manager that makes it possible to adapt to any existing or future version of EXPRESS schemata. The System Models are often denoted Meta-Meta-Data. Unless you are programming on a very advanced level, you will not need to access the System Models much.

The repository we created above is a Data Repository, named 'case_01'. So far, there will be no data sets in this repository. To import a data set into a Data Repository, we will need to compile the underlying EXPRESS schema into the Dictionary Repository. Our sample population is based on the EXPRESS schema IFC2x2_FINAL.

Compile IFC2x2_FINAL
if (rstat = edmiDefineSchema(expressFile, expressDiagFile, schemaName, 0, &nWarnings, &nErrors)) {
  printf("\nERROR: Could not compile the '%s' schema with error '%s'", schemaName, edmiGetErrorText(rstat));
  goto err;
} else if (nErrors != 0) {
  printf("\nERROR: Found %d compiler errors in the '%s' schema", schemaName, nErrors);
  goto err;
} else if (nWarnings != 0) {
  printf("\nWARNING: Found %d compiler warnings in the '%s' schema", schemaName, nWarnings);
  printf("\n         Check the file '%s' for diagnostics", expressDiagFile);
  goto err;
}

Upon successful invocation of this statement, there will be a dictionary model created in the DictionaryRepository. Note that there are several ways that this compilation may fail. Even if the IFC2x2_FINAL schema is flawless, the compilation may fail due to some problem like a typo in the file path or if the schema has already been compiled. Such problems will result in a non-zero rstat. Then there may be errors in the IFC2x2_FINAL schema that halts the compiler. These problems will result in a non-zero error counter and a problem description in the diagnostics file specified in the second input argument. Finally, the compiler may complete the compilation but still inform you about illegalities in the statements. This will result in a non-zero warning counter and a record in the diagnostics file.

Note that the edmiDefineSchema() function does not return the id of the compiled schema. If you need the Id, there are API function calls for retrieving all kind of object IDs. The following statement returns the Id of the IFC2x2_FINAL schema (even if we will not use it in the rest of this sample)

Get Schema Id
if (rstat = edmiGetSchema(schemaName, &schemaId)) {
  printf("\nERROR: Could not find the '%s' schema Id with error '%s'", schemaName, edmiGetErrorText(rstat));
  goto err;
}

Now we have the dictionary model of the underlying schema, and we will import a data set to populate it. The resulting population will be an EDMmodel in the EDMrepository we created above.

Import the Data Set
if (rstat = edmiImportStepFile(stepFile, stepDiagFile, repositoryName,
                               modelName, schemaName, NULL, 0, &modelId, &sdaiErr)) {
  printf("\nERROR: Could not import the '%s' file with error '%s'", stepFile, edmiGetErrorText(rstat));
  goto err;
} else if (sdaiErr != sdaiENOERROR) {
  printf("\nERROR: EDMstepReader returned '%s' when importing the '%s' file", edmiGetErrorText(sdaiErr), stepFile);
  goto err;
}

As input to the edmiImportStepFile(), we need to specify the name of the repository where the resulting EDMmodel will be created and the name of the underlying EXPRESS schema it will be using.  Note again that errors may occur at different levels. If the underlying schema does not exist or the name of the model is illegal, a non-zero rstat will be returned. But errors can also be caused by illegal data in the data file. In that case, the error code will be returned from the EDMstepReader in the sdaiErr variable.

We now have a population of the IFC2x2_FINAL schema, named 'Munkerud' in the EDMrepository named 'Case_01'. Lets play with it, but first, to access the data in an EDMmodel, we need to open it first.

Open the Model
if ((modelId = sdaiOpenModel(modelId, sdaiRW)) == 0) {
  printf("\nERROR: Failed to open model '%s' with error '%s'", modelName, edmiGetErrorText(rstat));
  goto err;
}

We now chose to use the sdaiOpenModel() because we got the model Id from the previous statement. We could also have used the sdaiOpenModelBN(), but then we would also have to specify both the name of the EDMmodel 'Munkerud' and the name of the EDMrepository 'case_01' because EDMmodel names meed only be unique within a repository.

Now we will export the Munkerud model to Part28 XML.

Export to XML
options = INCLUDE_HEADER | INCLUDE_CONFIGURATION | INCLUDE_SCHEMA | EDM_IDENTIFIERS;
if (rstat = edmiWriteXMLFile(repositoryName, modelName, NULL, NULL, NULL, NULL, xmlFile,
                             xmlDiagFile, options, &nWarnings, &nErrors, &sdaiErr)) {
  printf("\nERROR: EDMxmlWriter Could not export XML file with error '%s'", edmiGetErrorText(rstat));
  goto err;
} else if (sdaiErr != sdaiENOERROR) {
  printf("\nERROR: EDMxmlWriter returned '%s' when exporting the '%s' model", edmiGetErrorText(sdaiErr), modelName);
  printf("\n         Check the file '%s' for diagnostics", xmlDiagFile);
} else if (nErrors != 0) {
  printf("\nERROR: EDMxmlWriter found %d errors when exporting the '%s' model", nErrors, modelName);
  goto err;
} else if (nWarnings != 0) {
  printf("\nWARNING: EDMxmlWriter found %d warnings when exporting the '%s' model", nErrors, modelName);
}

For a detailed description of all arguments and options available, see the reference documentation of edmiWriteXMLFile(). If you need to export only parts of a data set to XML or you miss some feature in this function, there are some more flavors of it. Check these also; edmiWriteXMLDocument(), edmiWriteXMLFileEx() and edmiWriteStepFile2Ex().

All the API functions above belongs to the standalone EDMinterface API and are used to write EDMstandaloneClients. They cannot be used to write EDMthinClients for connecting to EDMservers. There is, however a more flexible part of the EDMinterface named the EDMremoteInterface API. If there is a chance that your application will be a thin client in the future you should consider using the EDMremoteInterface API. These API functions make i possible to write transparent code that can run both standalone and as clients. Also, many of the EDMremoteInterface API functions allows you to write more compact source code. You can read more about the EDMclient types here - EDMclient Applications.

The EDMremoteInterface API functions are designed to communicate stateless with an EDMserver. Therefore, all the function calls must provide a Server Context with information about the communication type, target server, identities of the caller and so forth. This sample is an EDMstandaloneClient, so what we do is to specify communication mode LOCAL_CONTEXT in stead of TCP or HTTP. This way, all the EDMremoteInterfaceAPI functions will use a local EDMdatabase and not search the internet for an EDMserver.

Create the Server Context
if (rstat = edmiDefineServerContext("MyContext", "superuser", NULL, dbPwd, "LOCAL_CONTEXT",
                                    NULL, NULL, NULL, NULL, NULL, NULL, NULL, &contextId)) {
  printf("\nERROR: Failed to create superuser context with error '%s'", edmiGetErrorText(rstat));
  goto err;
}

Note that as long as your application is an EDMstandaloneClient, most of the arguments of edmiDefineServerContext() does not apply. But to change your application to an EDMthinClient, all you need to do is address the EDMserver on the internet and change the communication type to TCP.

Now lets use an EDMremoteInterfaceAPI to select a set of instances that meet a given condition.

Query to Data Structure
numberOfHits = 100; /* Read only the 100 first hits in this invocation */
index = 0;
if (rstat = edmiRemoteSelectInstances(contextId, repositoryName, modelName, "ifcSpace", "xpxLike(Name, '1*', XPXCASE_INSENSITIVE)",
                      ASCENDING, "GlobalId, Name, OwnerHistory.OwningUser.ThePerson.FamilyName->OwnerName", NULL, "Name",
                      &index, &numberOfHits, &queryResult, NULL, &resultString, NULL, NULL)) {
  printf("\nERROR: Select IFCSPACE objects failed with error '%s'", edmiGetErrorText(rstat));
  goto err;
}

edmiRemoteSelectInstances() is a very powerful function and it is out of scope to explain it i detail here. What you should notice is how the contextId is used to address the target of the call, in this case our local EDMdatabase, and how the result from the query is returned in a queryResult data structure. The code block below shows how the results can be extracted from the returned data structure.

Parse the Query Result
if (queryResult) {
  SdaiString *gid = (SdaiString *) queryResult->columnDescr[0]->pvalue;
  SdaiString *name = (SdaiString *) queryResult->columnDescr[1]->pvalue;
  SdaiString *owner = (SdaiString *) queryResult->columnDescr[2]->pvalue;
  printf("\n%-24s%-6s%-30s", "GlobalId", "Name", "Owner Name");
  printf("\n=======================================================");
  for (i = 0; i < queryResult->rows; i++) {
    printf("\n%-24s%-6s%-30s", gid[i], name[i], owner[i]);
  }
}

The last operation in this sample will be to export a subset of the data set to Part28 XML. The exported sub set will be all instances of type IFCBUILDINGSTOREY with a name containing the string 'etg' or 'storey'.

Query to XML
  numberOfHits = 100; /* Read only the 100 first hits in this invocation */
  index = 0;
  if (rstat = edmiRemoteSelectInstances(contextId, repositoryName, modelName, "ifcBuildingStorey",
                                        "xpxLike(Name,'*etg*',XPXCASE_INSENSITIVE) or xpxLike(Name,'*storey*',XPXCASE_INSENSITIVE)",
                                        RESULT_IN_FILE | XML_FORMAT, NULL, NULL, NULL, &index, &numberOfHits, &queryResult,
                                        NULL, &resultString, queryResultFile, NULL)) {
    printf("\nERROR: Select IFCBUILDINGSTOREY objects failed with error '%s'", edmiGetErrorText(rstat));
    goto err;
  }

This statement reads only the fires 100 instances of type IFCBUILDINGSTOREY that match the condition. What if there are more than 100? That is what the arguments index and numberOfHits are for. To handle that, you simply put the code block in a loop and if numberOfHits comes out with 100 after the first iteration, you increase index with 100 and repeat until there are no more hits.

Check also the other samples in Getting Started with EDMsdk