[CONNECT Update 15 C++] - migration from v8i: Creating text in work DGN files

Hi all,

Ok, I've made a lot of progress in the last few months.  My current snag point is converting from using mdlText_create functionality to using TextBlocks.

Summary:

  • Old v8 Application creates complex elements out of lines, line strings, ellipses, shapes, and text
  • App writes the elements to work DGN files, creating between 30 and 100 individual part files that are referenced into an assembly.
  • CE uses textBlocks, can't reuse the old code.
  • Trying to make as few changes as possible, just a straight port over from MDL to C++ native code.

I've found many examples of how to add text to the active design file.  This program creates several files from a common seed.  Line geometry is being created (hooray) and updated properly in the files.  When they are referenced manually into an Assembly file (the active design file), everything is working as it should.

For each file, I create a chart that identifies the model, who created it (ran the program), date/time stamp, and creates a generic revision block in case of any manual revisions that need to be added.  This is populated using mdlText_create and sets level, color, line style, &c properties, which is then added to an Element Descriptor, which is appended to the workDGN after all of the bits are made for the rev block.  The parent routine calls ldWriteLine(...) and ldWriteText(...) to create the rev block.  Below is the base v8 routine that uses mdlText_create.

Public MSElementDescr *LDWriteText
(
	WChar           *sss,             /* String to print */
	double          sTS,              /* Text size */
	int             sF,               /* Font number */
	int             sJx,              /* Horizontal Justification (0-4) */
	int             sJy,              /* Vertical Justification (0-2) */
	Dpoint3d        *pt,              /* Origin */
	double          xrot,             /* Rotation about X axis */
	double          yrot,             /* Rotation about Y axis */
	double          zrot,             /* Rotation about Z axis */
	UInt32          *color,           /* Text Color */
	UInt32          *weight,          /* Text Weight */
	MSElementDescr  *addDes
)

{
	MSElement           partSegment;
	MSElementDescr      *partDescr;
	TextSizeParam       txSize;
	TextParam           txParm;
	RotMatrix           iMatrix, rMatrix;

	txSize.mode = TXT_BY_TILE_SIZE;
	txSize.size.width = sTS;
	txSize.size.height = sTS;
	txSize.aspectRatio = fc_1;
	txParm.font = sF;
	txParm.just = (sJx > -1 ? sJx * 3 + sJy : TXTJUST_CC);
	txParm.style = 0;
	txParm.viewIndependent = 0;

	mdlRMatrix_getIdentity(&iMatrix);
	mdlRMatrix_rotate(&rMatrix, &iMatrix, xrot, yrot, zrot);

	if (mdlText_create(&partSegment, NULL, sss, pt, &txSize, &rMatrix, &txParm, NULL) == SUCCESS)
	{
		mdlElement_setSymbology(&partSegment, color, weight, NULL);
		if (addDes == NULL)
		{
			mdlElmdscr_new(&partDescr, NULL, &partSegment);
			return partDescr;
		}
		else
		{
			partDescr = mdlElmdscr_appendElement(addDes, &partSegment);
			return partDescr;
		}
	}
}

This code is what's used to add the text to the complex element.

I'm attempting to update this code, limiting the changes to the function itself.  As mentioned, I have good examples of creating TextBlock elements.  I think I've got the element creation bits down, but I can't seem to find sample code that takes the EditElement (or EditElementHandle) and appends it to an Element Descriptor to make it a complex element.

I'm trying to use the API documentation and searching examples, but that's slow going (as manual searches are), so I thought I'd drop something here to see if anyone has a "Oh yeah, it's dead easy, look at this..." response. 

I know I probably don't have enough information here, but I don't know what else to add.  If I knew how to convert the EEH to a Part Descriptor, or append it to one, I think that would push me over the hump.

Thanks,

Gary

  • Hi Gary,

    at first, be aware you asked in general Developer and Programming forum. I recommend to move your question to MicroStation Programming forum. To move existing discussion to another forum, use More > Move tool, available under your original post.

    Also, please follow the best practices and always specify used product and its exact version. Although probably not crucial for this specific discussion, it's generally recommended to share the information always.

    Old v8 Application creates complex elements out of lines, line strings, ellipses, shapes, and text

    What "complex elements"? Please be more specific, there are many "complex elements" in MicroStation (see ComplexHeaderDisplayHandler struct inheritance graph). Are final elements (unnamed) cells?

    CE uses textBlocks, can't reuse the old code.

    Not only text related code can't be reused ;-) And in many other examples (nearly always), to stick with old C-style code makes things much more complicated, when new OOP C++ classes can handle the same tasks easily.

    but I can't seem to find sample code that takes the EditElement (or EditElementHandle) and appends it to an Element Descriptor to make it a complex element.

    I do not recall such example as well, probably because it does not make any sense: When you have (Edit)ElementHandle and related Handler, you have everything you need, packed into well described classes. To convert it to element descriptor only makes situation probably more complicated.

    limiting the changes to the function itself.

    In my opinion (but I know nothing about the application and its context) it is generally wrong idea, requiring more effort than necessary.

    What about to do it in opposite way? Because it is simple to create EditElementHandle from element descriptor, what about to always work with Handlers. Functional and working old code can be used to obtain descriptors (and to create Handlers from them). New / rewritten functionality returns handles automatically, which should make the whole process clear and simple. At the end, it can be packed e.g. using NormalCellHeaderHandler.

    With regards,

      Jan

  • Jan - 

    Thanks for the reminder/suggestions.  I think I moved/fixed it ok.

    My last foray into MDL development was some minor enhancements around 2001 for Microstation 7.

    What "complex elements"? Please be more specific, there are many "complex elements" in MicroStation (see ComplexHeaderDisplayHandler struct inheritance graph). Are final elements (unnamed) cells?

    What I mean by "Complex Elements" is that it aggregates a bunch of bits (lines and text) into one element descriptor for the title block and writes that as a single MSElementDescriptor.  While I can't be sure (it's been 20+ years since I developed it), I wrote it to write things in chunks and output to the file to minimize I/O, which was a consideration at the time.  But I've slept a lot since then, so I could be making the reason up at this point.   Are they unnamed cells?  I don't think so - they're just a chain of elements, if that's a thing any more.  It seems to be working.

    Undoubtedly the element handle is the better option.  You can then write the element using a function similar to mdlWorkDgn_write(...)?  

    There's many things that would have to change to use handlers that I could avoid if we can append using the element descriptors.  If there is no other way, then maybe what I need to do is assemble the text bits into handlers and add the current MSElementDescr items as is, then the text as a separate element using the handler(s).

    I'm assuming that there's a way to add the EEH to a work file? How does one write text to a work file?

  • What I mean by "Complex Elements" is that it aggregates a bunch of bits (lines and text) into one element descriptor for the title block and writes that as a single MSElementDescriptor

    Don't let  read that!

    I have no idea what happens when you write an element descriptor chain.  I suggest that you create a normal cell header and append your elements to that.

    Undoubtedly the element handle is the better option.  You can then write the element

    ElementHandle is a C++ smart pointer that wraps MSElementDescrEditElementHandle.AddToModel() does what you want.

     
    Regards, Jon Summers
    LA Solutions

  • Hi Gary,

    My last foray into MDL development was some minor enhancements around 2001 for Microstation 7.

    ok, so my advice is: Do not even try to remember what you knew at this time, because nearly everything has changed. And even when some old approach still works and can be used, it is probably wrong. 

    CE API is about object abstraction: Do not care about data persistence, use Handles (data representation) and Handlers + Queries (tools to access data). Every time you need element descriptor, something is wrong in used design or implementation.

    Another typical problem of developers is that DGN element type != API class. Again, it's about abstraction, and proper handler takes care to persist (save) data, represented by a handle, in the right way. To demonstrate it, think about cell: it can be used to represent graphic cell, grouped elements (unnamed cell), grouped holes, SmartSolid, annotation (label with annotation dimension) etc. In API, when proper handlers are used, they ensure data is persisted right.

    What I mean by "Complex Elements" is that it aggregates a bunch of bits (lines and text) into one element descriptor for the title block and writes that as a single MSElementDescriptor. 

    I agree with Jon, and I am sure a ghost of data and code quality comes to haunt you.

    I think it was not valid approach even in V7 era, but DGN format and MicroStation itself has always be tolerant to different types of developers' sins.

    I don't think so - they're just a chain of elements, if that's a thing any more.

    It's not possible. Even in DGN V7, complex element must start with complex header, which can be ComplexString (chain), ComplexShape or CellHeader. Today, complex elements are represented by more classes (see the graph I mentioned earlier), but the concept is still the same in DGN V8 as was in DGN V7.

    It seems to be working.

    It would be interesting to have the original DGN V7 file and to use "element analyze" tool to check how the descriptor is persisted.

    You can then write the element using a function similar to mdlWorkDgn_write(...)?  

    Forget "work DGN", there is DgnFile object available that can be used to access (load, create...) models, and consequently to use EditElementHandle to add element to the model.

    I recommend to read DgnFile Struct Reference and related chapters (Working With Models, EditElementHandle Struct Reference etc.) There are plenty of useful information available.

    There's many things that would have to change to use handlers that I could avoid if we can append using the element descriptors.

    As described in EditElementHandle Struct Reference chapter, when you have valid Element Descriptor, it's simple to create EditElementHandle (and e.g. to compose more ElementHandles to more complex element and to add it to a model).

    If there is no other way, then maybe what I need to do is assemble the text bits into handlers and add the current MSElementDescr items as is, then the text as a separate element using the handler(s).

    What works now (although I assume it's dirty code probably) and product ElementDescriptor, use to create EditElementHandles. What must be converted to CE API, create as EditElementHandles. Compose all of them to required object.

    I'm assuming that there's a way to add the EEH to a work file?

    There is no "work file". When you do not want to use active model, use DgnFile object to open a file, load (or create) model and add elements to it. No difference between (active) model object and model object, opened directly at background.

    I did not try it in C++ yet, but I did couple of tests in NET and it worked fine.

    With regards,

      Jan

  • Ok, I get it now.  I had a disconnect.  Let me see if I have the right idea / process.  Details aren't that important at this point - just trying to make sure I have the big picture.  I realize that there are some holes here.

    Here's the current process (written for old MDL version)

    • Copy the seed file to a new target file name
    • Open the workDgn file
    • Call "TitleBlock" routine
      • Create the first thing - in this case a text element via mdlText_create.
        • Is there an existing ElementDescriptor? 
          • Yes: Append that element to the ElmDescr chain.
          • No:  Create a new Element Descriptor to hold all elements in this chain
      • Create subsequent lines and text all associated with the returned element descriptor.
    • When done, write the element descriptor using the mdlWorkDgn_write(...) function.
    • Process active geometry
      • create lines, circles, etc., appending them to the MSElementDescr.  Create it if NULL, append if not.
    • When done, write the element descriptor using the mdlWorkDgn_write(...) function.
    • Close the workDgn file
    • Rinse and repeat for all the other parts.

    The new method to do the same is similar but using the DgnFile object.

    • Create a DgnFile object, load it with the appropriate seed file, and save it to ensure the file is created.
    • Call "TitleBlock" routine.
      • Create a model to be associated with the DgnFile object.  (I'm not sure if it should be a separate model for the title block and the active geometry, but I'll figure that out as I go).
      • Use GetDictionaryModel to be able to use the linestyles, levels, etc.  loaded in the seed / from libraries.  This was going to be a later question; glad I see it here now.
      • Create the lines and text blocks to add to the DgnFile object.
        • Either separate EEH's or chained EEH's, I don't know yet.  I'm assuming the element creation works the same, but the text creation is different.
      • These are EditElementHandles that then use the eeh.AddToModel().  ** my confusion here was because in my old MDL, anything that didn't use mdlWorkDgn_... got written to the active design file.  And "AddToModel()" doesn't specify which model because I was missing part of the process in my head, so I thought it was adding to the current open file.  I think I get it better now.
    • Add the TITLEBLOCK to the model.  I'm still unsure if all the bits are part of one 'complex' element chain or if I add each bit to the model as it's created.  
    • Process active geometry
      • Same general process as above, but using the DgnFile object.
    • Write the active geometry (non-titleblock data) using AddToModel().
    • Finalize, save, and close the DgnFile.

    There's plenty I have to work out here - probably quite a bit with the element attributes (old MDL language) in the new world, like can I string together all the bits that use the same color, weight, level, etc. and add them at once or can I add them back and forth without incurring processing overhead?

    It seems like the general logic is the same, but the old one (apparently?) used some sketchy bits that maybe weren't a good idea then and shouldn't be used anymore.  I just wasn't seeing it.  It's fine - this stuff was built on code written ca. 1993 for Microstation J that has continued to work until CE.  Maybe not fine, maybe on the edge of collapse, but still worked for v8i SS10 and didn't seem to cause any issues that were reported.

  • So why does mdlWorkDgn_XXXXX still exist?

    Because of API compatibility. Although CE API is new and C++ based (in contrast with V8i API, that is primarily C), many C functions were migrated to C++ environment. It allows to migrate old code simpler.

    Create a model to be associated with the DgnFile object

    When you create a new DGN file from seed, there is always default model available. When you need anything else, you must create the model and (probably) ensure it is loaded.

    Use GetDictionaryModel to be able to use the linestyles, levels, etc.  loaded in the seed / from libraries.

    No. Of course you must ensure all models you use are loaded. This process is described in detail in C++ API documentation. But to access objects like level, linestyles etc, specialized methods or individual classes are available (e.g. DgnFile::GetLevelCache).

    How to access these settings should be discussed separately, but you can also find relevant code in SDK examples and in this forum.

    I'm assuming the element creation works the same, but the text creation is different.

    Text API is a bit specific, because one from the most versatile, but also the most complex, in MicroStation API. But generally it is the same as all other elements: Compose necessary data, required to create element (typically calling proper ElementHandler), which created EditElementHandle.

    Either separate EEH's or chained EEH's

    Handler are never "chained". You must decide, what object you want to create (e.g. normal cell) and to check what input data is required. For example, to create a cell, ElementAgenda (which is element handles container) is required.

    I'm still unsure if all the bits are part of one 'complex' element chain or if I add each bit to the model as it's created.  

    I am not sure what you mean by "all the bits", but you always must start with the decision what element (not DGN element, but "user element") you want to create.

    It seems like the general logic is the same

    Yes, because what changed are tools to create elements, not logic how MicroStation works.

    but the old one (apparently?) used some sketchy bits that maybe weren't a good idea then and shouldn't be used anymore.

    They are plenty of "new limitations" in CE: MicroStation/J (and still V8 too) allows to access DGN data directly, and to create "dirty elements". MicroStation CE is now more strict (like all elements in complex shape or chain must have the same symbology), plus some other changes were implemented at background (complex header, like cell header, has no level). The target is to produce cleaner data with more correct internal structure.

    With regards,

      Jan

  • One more comment: I recommend to start with EditElementHandle Struct Reference chapter in MicroStationAPI documentation. It explains an element lifecycle and provide plenty of useful information how handle and element descriptor relationship including code snippets.

    Regards,

      Jan