Page Contents
Questions similar to this appear on the Bentley Discussion Groups. These questions, or something like them asked by MDL and VBA developers, appeared in the MDL discussion group.
Q Questions about element location with MDL pop up occasionally …
A MicroStation uses a state machine when a user creates or locates an element. The state machine provides a way to apply different logic at the various stages of element creation or location. In the case of location logic, which is what we're interested in here, the available states are …
CMD_XXX function)
The best way to start a locate command is by calling mdlState_startModifyCommand() in your command function.
Yes, I know that it doesn't sound right, because you want to locate, not modify, an element; but don't let a function
name stop you from using it: "What's in a name? That which we call a rose
by any other word would smell as sweet".
MDL's state commands initialise MicroStation's location logic. If you want to specify the location criteria,
you should call mdlLocate_xxx functions after any mdlState_xxx function, including mdlState_startModifyCommand().
It looks neater if you place location initialisation in its own function: that way, you can share the same locate
criteria between several commands.
For hints on using MDL to extract metrics from elements, see this article.
MDL's state functions let you specify the ID of a string resource for command and prompt messages. This enables you to provide your own prompt (e.g. Identify Object instead of MicroStation's Locate Element). However, for this to work you must perform four distinct tasks …
mdlState_registerStringIds()CMDSTR() macro mdlState_startXxx function
First, define your message list IDs in your header (xxx.h) file.
The header file is #included by your source (xxx.mc) and command (xxxcmd.r) files.
With MicroStation V8, you can use an enumeration for resource IDs, because the enum
keyword is recognised by both mcomp.exe, which compiles your source (.mc) code,
and rcomp.exe, which compiles your resource (.r) data …
enum MessageListIDs
{
MESSAGELISTID_Commands = 10,
MESSAGELISTID_Prompts,
MESSAGELISTID_Messages,
MESSAGELISTID_Errors,
};
enum MESSAGELISTID_commands
{
CMDID_LocateSomething = 1,
};
enum MESSAGELISTID_prompts
{
PROMPTID_LocateSomething = 1,
};
enum MESSAGELISTID_messages
{
MSGID_Measure = 1,
};
enum MESSAGELISTID_errors
{
ERRID_CommandTable = 1,
ERRID_Measure,
};
The header file is #included by your source (xxx.mc) and command (xxxcmd.r) files.
MicroStation lets your application respond to an arbitrary number of command keyins. You determine the command
syntax, which is specified formally in a command table resource. A command table is an MDL resource (.r) file that
you compile and merge with your application.
One result of the resource compilation is a header file containing a list of macro definitions, one for each of your commands.
Each macro associates a token (e.g. CMD_MYAPP_DO_SOMETHING) with a unique numeric ID.
You associate the token with a command function (in your .mc file) through a command table array.
The array lists each function in your source code with a token. For example …
/////////////////////////////////////////////////////////////////////////////// Application command tableMdlCommandNumber commandNumbers [] = { { cmd_locateText, CMD_LOCATE_TEXT }, { cmd_locateExit, CMD_LOCATE_EXIT },// This table must be NULL-terminated0 };
When a user wants to use your application, she uses a set of command key-words that you have published in your documentation.
When you register the command number array, MicroStation knows how to associate the keyin with the function that you want it to execute in response.
Register the command array with MicroStation like this, usually in your main() entry point …
status = mdlSystem_registerCommandNumbers (commandNumbers);
There is an example of an MdlCommandNumber table in the LocateExample you can find here.
Second, add one line of code to main() to register your message list IDs.
You do this with a call to mdlState_registerStringIds() …
// main()int main (int argc, char* argv []) { RscFileHandle rscFileH; // a resource file handle// Open the resource file that we came out ofmdlResource_openFile (&rscFileH, NULL, 0);// Load the command tableif (NULL == mdlParse_loadCommandTable (NULL)) { printf ("%s: unable to load command table\n", mdlSystem_getCurrTaskID ()); exit (EXIT_FAILURE); }// Tell MicroStation to use our messages in commandsmdlState_registerStringIds (MESSAGELISTID_Commands, MESSAGELISTID_Prompts); return SUCCESS; }
Third, include your command message ID in a CMDSTR() macro in your commad table resource (.r) file …
/* No. Subtable CommandClass Options CommandWord */
{ 1, CT_NONE, LOCATE, CMDSTR (CMDID_LocateSomething), "CELL" },
Fourth, include your command and prompt message IDs in your command function's
call to an mdlState_startXxx function …
mdlState_startModifyCommand (...
CMDID_LocateSomething, // Command ID in your message resource
PROMPTID_LocateSomething, // Prompt ID in your message resource
...
);
Here's an example locate command. It specifies a datapoint callback function example_locateDatapoint
and message list IDs. The mdlState_startXxxx function initialises MicroStation's locate logic, so
after that call we specify our own locate criteria function example_setLocateCriteria. It's a handy
container for the locate logic that can be shared by several state functions as required …
// Example locate commandPublic void cmdExample_locateSomething (char* unused) cmdNumber CMD_EXAMPLE_LOCATE_SOMETHING// From your command table in mycommand.r{ mdlState_startModifyCommand (cmdExample_locateSomething,// Reset start this command againexample_locateDatapoint,// Datapoint calls this functionNULL,// Dynamic function not used hereNULL,// Show function not used hereNULL,// Clean function not used hereCMDID_LocateSomething,// Command ID in your message resourcePROMPTID_LocateSomething,// Prompt ID in your message resourceFALSE,// Don't use Selection Set0);// No additional datapoints requiredexample_setLocateCriteria (TRUE); }// Search locate criteria may be common to several commandsvoid example_setLocateCriteria (BoolInt allowRefs/* Permit user to locate elements in referenced models */) { static int searchMask [] = { CELL_HEADER_ELM, LINE_ELM, LINE_STRING_ELM, CURVE_ELM, CMPLX_STRING_ELM, ARC_ELM, }; mdlLocate_init (); if (allowRefs) mdlLocate_noElemAllowLocked (); else mdlLocate_noElemNoLocked (); mdlLocate_setElemSearchMask (sizeof (searchMask) / sizeof (searchMask [0]), searchMask); }
The datapoint callback function is called once a user has identified an element and that element has passed your locate criteria. This function is a convenient place to obtain element information. For instance, you may want to measure a linear element or take the area of a shape.
The datapoint function has a standard signature …
void example_locateDatapoint (DPoint3d* pt, int view)
In the datapoint callback function, you often want to operate on the element that was located,
in which case it's usual to find the element's model reference and file position
calling mdlElement_getFilePos() like this …
DgnModelRefP modelRef = NULL; ULong filePos = mdlElement_getFilePos (FILEPOS_CURRENT, &modelRef);
If you're planning to let the user locate a complex or cell element, then use FILEPOS_COMPONENT in place of FILEPOS_CURRENT.
Once you have the located element's file position and model reference, you can read the element data
from file into an MSElementUnion or MSElementDescr* …
// Example datapoint callback functionvoid example_locateDatapoint (char* unused) { DgnModelRefP modelRef = NULL; ULong filePos = mdlElement_getFilePos (FILEPOS_COMPONENT, &modelRef); MSElementDescr* pComponent = NULL; if (0 < mdlElmdscr_readToMaster (&pComponent, filePos, modelRef, FALSE, NULL)) {// Do something with pComponent}// Always free memoryif (NULL != pComponent) mdlElmdscr_freeAll (&pComponent);// Usually, you want to start this command againmdlState_restartCurrentCommand (); }
There are some example MDL projects …
Return to MDL articles index.