Reference Models and Model References: two terms commonly found in the Be Communities MicroStation Programming Forums. What do those names mean for an MDL programmer? Questions similar to this appear on the Bentley Discussion Groups. These questions, or something like them, appeared in the MDL discussion group.

Q Questions about DGN Models and Reference Models pop up occasionally …

A A MicroStation V8 DGN file is a container for one or more models. Each model is a container for a 2D or 3D drawing. A file can contain a mixture of 2D and 3D models. In other words, there is no such thing as a 2D or 3D DGN file, because the file has no dimensionality: it's just a container for some models.

A DGN model is represented in MDL by a pointer to an anonymous struct: DgnModelRefP. A special model, the active model, contains the data that the MicroStation user is currently using. You can always get a DgnModelRefP to the active model using macro ACTIVEMODEL, which is nothing more than a shortcut to MDL function mdlModelRef_getActive.

A common MicroStation idiom is the use of reference attachments. A reference attachment is a DGN model that is read-only and visible in the same coordinate system as the active model. A DGN model may have zero or many attachments. Each attachment is a DgnModelRefP. However, the DgnModelRefP of a reference is similar but different to the DgnModelRefP of the active model and other models in the active DGN file.

Model References and Referenced Attachments

The DgnModelRefP representing a model in the active DGN file and the DgnModelRefP representing a reference attachment are similar. Unsurpringly, this similarity causes confusion at times. A common task for MDL developers is to iterate reference attachments. A less common task is to iterate DGN models. This article attempts to clarify both procedures.

A portion of clarity is provided by careful nomenclature. In this article …

Iterating DGN Models

DGN Models

It you want to iterate the models contained in a DGN file, you have several ways …

  1. the mdlModelIterator_api
  2. mdlDgnFileObj_traverseRootModelRefs
  3. the mdlDgnFileObj_api

Yongan Fu, a regular contributor to the Be Communities, made these comments about the three APIs: mdlDgnFileObj_traverseRootModelRefs() can only traverse loaded models but the other two can traverse all models include loaded and unloaded. mdlDgnFileObj_api needs a callback function but mdlModelIterator_api doesn't. The above is just my point of view.

In this example we use the mdlModelIterator_api …

DgnIndexIteratorP iterator = mdlModelIterator_create (mdlModelRef_getDgnFile (ACTIVEMODEL));
DgnIndexItemP item = mdlModelIterator_getFirst (iterator);
while (item)
{
  //  Do something with item
  item = mdlModelIterator_getNext (iterator);
}
mdlModelIterator_free (iterator);

Activate a Model

What kind of action can you take on each model? You may want to get data about each model, in which case mdlModelItem_getData is useful. However, if you want to make a model the active model so that a user can work with it, you have to do a little more …

DgnIndexIteratorP   iterator = mdlModelIterator_create (mdlModelRef_getDgnFile (ACTIVEMODEL));
DgnIndexItemP       item     = mdlModelIterator_getFirst (iterator);
MSWChar             modelName [MAX_MODEL_NAME_LENGTH];
while (item)
{
  mdlModelItem_getName (item, modelName, MAX_MODEL_NAME_LENGTH);
  models_activate (modelName);
  item = mdlModelIterator_getNext (iterator);
}
mdlModelIterator_free (iterator);
//  Active a model to enable a user to work with it
BoolInt             models_activate
(
MSWChar const*      modelName      //   =>	Activate this model
)
{
    BoolInt         activated   = FALSE;
    MSWChar         activeName	[MAX_MODEL_NAME_LENGTH];
    mdlModelRef_getModelName  (ACTIVEMODEL, activeName);
    DgnFileObjP     dgnFileObj  = mdlModelRef_getDgnFile (ACTIVEMODEL);
    DgnModelRefP    modelRef    = NULL;
    if (SUCCESS == mdlModelRef_createWorkingByName (&modelRef, dgnFileObj, modelName, TRUE, FALSE)
        &&
        SUCCESS == mdlModelRef_activateAndDisplay (modelRef))
    {
        activated = models_areSame (modelRef, ACTIVEMODEL);
    }
    mdlModelRef_freeWorking (modelRef);
    return activated;
}

Comparing Models for Equality

Comparing two DgnModelRefP pointers doesn't work, because although those pointers might be looking at the same model, they could have been created in different ways and comparison would fail.

The correct way to find if two models are the same is to determine what model cache they derive from. If the cache is the same then the models are the same. This example uses MDL function mdlModelRef_getCache to test whether two models are the same …

//  Implements alternative to unpublished function mdlModelRef_modelsAreSame
BoolInt             models_areSame
(
DgnModelRefP        modelRef1,
DgnModelRefP        modelRef2
)
{
   return mdlModelRef_getCache (modelRef1) == mdlModelRef_getCache (modelRef2);
}

C++ Model Iterator Wrapper class

If you're writing C++ then use a class to wrap the mdlModelIterator_api functions. Use the RAII idiom to allocate memory in the class constructor and deallocate in its destructor. Something like this …

// Header file
class CModelIterator
{
    DgnIndexIteratorP    iterator_;
public:
    DgnIndexItemP   GetNext () const;
    CModelIterator  (DgnModelRefP rootModelRef);
    ~CModelIterator ();
};
// Implementation file
// Constructor
CModelIterator::CModelIterator     (DgnModelRefP rootModelRef)
:   iterator_  (0)
{
    DgnFileObjP oDgnFile = mdlModelRef_getDgnFile (rootModelRef);
    iterator_            = mdlModelIterator_create (oDgnFile);
}
// Destructor
CModelIterator::~CModelIterator    ()
{
    mdlModelIterator_free (iterator_);
}
// Iteration
DgnIndexItemP  CModelIterator::GetNext  ()	const
{
    return mdlModelIterator_getNext (iterator_);
}

Example usage …

// Constructor allocates memory automatically
CModelIterator iterator (ACTIVEMODEL);
DgnIndexItemP item = iterator.GetNext ();
while (item)
{
   // Do something with item
   item = iterator.GetNext ();
}
// Destructor frees memory automatically

Reference Attachments

A DGN model, whether the active model or not, may have reference attachments. From the MDL programmer's viewpoint, each attachment is a DgnModelRefP. However, those model references are created in a different way to an active or working model reference and cannot be treated similarly.

Model Attachments

You can iterate the reference attachment collection and gather data about each attachment. You can update attachment data and make your changes persistent. Your configuration of MicroStation may allow nested references. If you want to iterate nested attachments, then you must observe the MRITERATE_Xxx policies and iteration depth when iterating reference attachments.

Iterating Reference Attachments

A reference attachment is a model. Here's how to iterate the attachments of a given model. What you receive is a DgnModelRefP pointer to each attachment …

#include <msdgnmodelref.fdf>
ModelRefIteratorP iterator = NULL;
mdlModelRefIterator_create (&iterator, ACTIVEMODEL, MRITERATE_PrimaryChildRefs, 0);
DgnModelRefP      refModel = mdlModelRefIterator_getFirst (iterator);
while (refModel)
{
  // Do something with refModel

  refModel = mdlModelRefIterator_getNext (iterator);
}
mdlModelRefIterator_free (&iterator);

Reference Attachment Data

If you want to examine the attachment, then the mdlRefFile_api is useful. In particular, mdlRefFile_getParameters can a get about thirty-five pieces of information about a reference attachment. For example, here's how to determine whether a given attachment is displayed or not …

#include <msreffil.fdf>
int displayed = 0;
DgnModelRefP refModel = //  Get reference model from somewhere
mdlRefFile_getParameters (&displayed, REFERENCE_DISPLAY, refModel);
if (displayed)
{
}

If you need even more detailed information, then mdlRefFile_getInfo gives you a non-const ReferenceFile* pointer. The pointer provides read/write access to the contents of the ReferenceFile struct for a given attachment.

// #include <refernce.h>
struct  referenceFile
    {
    File_id_desc            file_id;                    /* file identification */
    Ref_display             display;                    /* display information */
    Ref_attach_desc         attach;                     /* attachment description */
    Clip_desc               clip;                       /* clipping descriptor(points separate) */
    UInt32                  *colorMap_deprecated[2];    /* DEPRECATED: element to screen color mappings */
    RefLevelMasks           levelMask;                  /* Level mask */
    MSElementDescrP         readOnlyRefAttachEdP;       /* Attachment descriptor for reference attached during readonly session */
    MiscellaneousRefData    *extraDataP;                /* variable sized part of reference structure. More can be added to it. */
    void                    *additionalDataP[2];        /* Future use */
    };
#include <msreffil.fdf>
//  Get the attachments origin in its parent model
ReferenceFile* info = mdlRefFile_getInfo (refModel);
DPoint3d origin = info->display.mast_org;

You can modify an attachment using mdlRefFile_setParameters, which covers most common uses. You can also modify the data pointed at by the ReferenceFile* pointer. In either case, you must call mdlRefFile_writeAttachment to persist the change. When you modify an attachment, it is the parent model reference that is changed, not the attachment. You must have write access to the parent model in order to make a change permanent.

C++ Reference Iterator Wrapper class

If you're writing C++ then use a class to wrap the mdlModelRefIterator_api functions. Use the RAII idiom to allocate memory in the class constructor and deallocate in its destructor. Something like this …

// Header file
class CModelRefIterator
{
    ModelRefIteratorP       iterator_;

public:
    UInt32                  ElementCount    ()  const;
    bool                    Valid           ()  const   {  return NULL != iterator_;  }
    DgnModelRefP            GetFirst        ()  const;
    DgnModelRefP            GetNext         ()  const;
    explicit CModelRefIterator              (bool        refs);
    explicit CModelRefIterator              (int         scanPolicy);
    ~CModelRefIterator                      ();

private:
    //	Private copy constructor and assignment operator prevent copying
    //	(declared but not implemented)
    CModelRefIterator (const CModelRefIterator& other);
    CModelRefIterator&	operator=(const CModelRefIterator& other);
};
// Implementation file

// Constructor
CModelRefIterator::CModelRefIterator        (bool        refs)
:   iterator_   (NULL)
{
    const int	policy    (refs? MRITERATE_Root | MRITERATE_PrimaryChildRefs: MRITERATE_Root);
    mdlModelRefIterator_create (&iterator_, ACTIVEMODEL, policy, 0);
}
// Destructor
CModelRefIterator::~CModelRefIterator  ()
{
    mdlModelRefIterator_free (&iterator_);
}
// GetFirst
DgnModelRefP	CModelRefIterator::GetFirst  ()	const
{
    return mdlModelRefIterator_getFirst (iterator_);
}
// GetNext
DgnModelRefP	CModelRefIterator::GetNext   () const
{
    return mdlModelRefIterator_getNext (iterator_);
}
//  Element Count
UInt32          CModelRefIterator::ElementCount () const
{
    UInt32          nElements   = 0;
    DgnModelRefP    modelRef    = GetFirst ();
    nElements = dgnCache_getElementCount (mdlModelRef_getCache (modelRef), DGNCACHE_SECTION_GRAPHIC_ELMS);
    while (NULL != (modelRef = GetNext ()))
    {
        nElements  += dgnCache_getElementCount (mdlModelRef_getCache (modelRef), DGNCACHE_SECTION_GRAPHIC_ELMS);
    }
    return nElements;
}

Here's how to use that class …

const int               policy      = MRITERATE_PrimaryChildRefs;
CModelRefIterator       iterator    (policy);
if (iterator.Valid ())
{
    DgnModelRefP        modelRef    = iterator.GetFirst ();
    for (; NULL != modelRef; modelRef = iterator.GetNext ())
    {
        //	Check if file not found
        int             fnf         = 0;
        mdlRefFile_getParameters (&fnf, REFERENCE_FILENOTFOUND, modelRef);
        //	Attachment data
        ReferenceFile*  refInfo     = mdlRefFile_getInfo (modelRef);
    }
}

Return to MDL articles index.