MicroStation and Application Development

MicroStation® is a computer-aided-design (CAD) tool produced by Bentley Systems. It is used by engineers, architects, and draughting technicians to create 3D models and 2D drawings.

MicroStation can be customised. Bentley Systems themselves have many applications for specialised markets that use MicroStation as a platform. Third-parties likewise may use MicroStation as a platform on which to develop their application software. Bentley Systems provide the MicroStation CONNECT Software Development Kit (SDK) that software developers use to create their applications. The SDK has three libraries …

The MicroStationAPI is the sole API for C++ programmers. Earlier versions of MicroStation provided the MicroStation Development Library (MDL). MDL is not available as a separate API with MicroStation CONNECT, but is subsumed into the MicroStationAPI. This article attempts to clarify and explain the MicroStationAPI from a developer's viewpoint.

Dependency API

The Dependency API  is a subset of the MicroStationAPI C++ interface. It lets a developer write code that makes one graphic element depend in some way on one or more root elements. The nature of the dependency is up to the programmer.

For example, when a user creates a tag element it is dependent on its host element. That dependency is an offset relationship. The dependent element (the tag) tries to main a constant geometric offset from its host. When a user moves the host, using MicroStation's Move tool, the tags follow the host.

You can download the source code of the BoxDependency C++ project described here.

What's Included?

The box dependency provides two example application for MicroStation CONNECT …

Box Dependency Example

Boxes with Dependent Shape

The original BoxDependency was created by Mark Anderson (alas no longer with Bentley Systems), not long after MicroStation V8 was introduced. He wrote it using almost entirely MDL (C-style) programming, because the C++ MicroStationAPI was not available to him at that time.

BoxDependency demonstrates a use of the dependency API. The user selects a number of elements and the application draws a rectangle (a shape element) around them. In dependency API terms, those elements are the root elements of the dependency. BoxDependency adds a dependency linkage to that rectangle: the linkage includes the Element ID of each element in the selection set (the root IDs). The application also starts an asyncronous dependency monitor, in the form of a callback function. If any of the root elements is modified (e.g. moved), the dependency callback is invoked by MicroStation. The callback, in the BoxDependency example, computes a new rectangle that encompasses all the root elements. It then modifies and redraws the rectangle.

You can download the source code of the BoxDependency C++ project described here.

User Experience

Start by using BoxDependency to witness its functions. Here are some boxes (shape elements) ready to demonstrate BoxDependency …

Boxes

Select those elements and key-in BoxDependency command
BOX DEPEND CREATE

That command performs the following actions …

The IDs of the boxes are known as the root IDs of the dependency.

Boxes with Dependent Shape

The BoxDependency application sets a system callback in its MdlMain(). The effect of that callback is that when MicroStation observes a change in a dependency, it invokes the registered callback. In this example the callback performs the following …

The consequence of those calculations is a new boundary …

Dependent Shape recalculated

Offset Dependency using root Element ID

This example works when user identifies a root element. The root this case is the anchor on which the dependency lies. User places a cell and we attach a dependency linkage to that cell. The dependency linkage includes …

Offset Dependency using Association Point

This example works when user identifies a root element. The root this case is the anchor on which the dependency lies. User places a cell and we attach a dependency linkage to that cell. The dependency linkage includes …

Interpreting DependencyManager and DependencyManagerLinkage

The Dependency API provided in the MicroStation CONNECT SDK is a C++ wrapper around the mdlDependency_api.

The DependencyManagerLinkage is a data struct. See header file DgnPlatform/DependencyManagerLinkage.h.

Its heritage is a C-style union of data that represent different kinds of dependency …

union
{
	UChar                         data        [DEPENDENCY_SOME_DATA];
	ElementId                     elemid      [DEPENDENCY_SOME_ELEMIDS];
	DependencyRootFarElementID    far_elemid  [DEPENDENCY_SOME_FARELEMIDS];
	DependencyRootElementID_V     e_v         [DEPENDENCY_SOME_ELEMIDVS];
	DependencyRootFarElementID_V  far_e_v     [DEPENDENCY_SOME_FARELEMIDVS];
	AssocPoint                    assoc       [DEPENDENCY_SOME_ASSOCPOINTS];
	DependencyRootPath_V          path_v;
}       root;

Which member of that union is specified in member variable rootDataType. For historical reasons that variable is a four-bit integer (i.e. stores values in range 0..15), and is buried in yet another union u.f.rootDataType.

The number of roots is specified in member variable UInt16 nRoots. nRoots is the maximum value of the index into those root arrays. For most purposes those arrays seem enormous: certainly for our purposes, where our example application uses a single root.

Extra Dependency Data

Some applications may want to store extra data in the dependency linkage. In this example …

  1. BoxDependency relies solely on the root Element ID array elemid — it doesn't need any extra data
  2. OffsetAssocDependency uses the AssocPoint array assoc, and stores the offset vector as extra data
  3. OffsetIdDependency uses the Element ID array elemid, and stores the offset vector as extra data

The mechanism for storing extra data deviates badly from C++ guidelines. Suppose, as in the BoxDependency example, we use a single root. That is, nRoots is one. We poke extra data in the chosen union array just after that location. In other words …

  //  OffsetIdDependency
  DependencyLinkage.root.elemid [0];  //  Root ID goes here; nRoots == 1
  DependencyLinkage.root.elemid [1];  //  Extra data goes here
  //  OffsetAssocDependency
  DependencyLinkage.root.assoc [0];  //  AssocPoint goes here; nRoots == 1
  DependencyLinkage.root.assoc [1];  //  Extra data goes here

In this example, we poke a DVec3d into that location. Because it's a different data type, we have to perform some casting hacks to persuade the C++ compiler to accept it.

IRootsChangedCallback class

We create a class that inherits from IRootsChangedCallback and register it with MicroStation, usually in MdlMain(). That class must implement virtual method OnRootsChanged, which MicroStation calls when it observes a dependency change. Here's the signature of OnRootsChanged …

virtual StatusInt        OnRootsChanged
(
ElementHandleCR          dependentElement,
DependencyLinkage const& dependencyData,
UInt8*                   pRootStatus,
UInt8                    selfStatus
) override;

That callback gives you information about your dependent element. You will probably receive multiple callbacks in no predetermined order. Briefly …

Our callback function must observe and react to the rootStatus array and to the selfStatus code. A couple of switch statements handles those.


SelectionSetManager

The BoxDependency application requires a user-defined selection set when establishing its criteria. I started to write a SelectionSetManager class, but the compiler told me that one already existed! Sure enough, I looked in header file DgnView/SelectionSetManager.h and found a perfectly-formed class already in place.

Class Bentley::DgnPlatform::SelectionSetManager is straightforward to use, and as you would expect eliminates the memory-management issues that were your responsibility with the MDL mdlSelect_api. You can see how it's used if you download the source code of the BoxDependency example.

Download

Download the BoxDependency Project

You can download the BoxDependency. The project is built using Bentley Make (BMake). It uses the C++ compiler and linker installed with Visual Studio 2013.