[Microstation CE u13] ElementGraphicsProcessor example in C#

Hi all,

I am trying to extract all of the elements contained within a paramteric cell, and it was suggested that I use the ElementGraphicsProcessor abstract class to achieve this. The objective is to to return all graphical elements from the cell instance in their true location (with the same geometry), along with their associated symbology. Essentially this would have a similar outcome as dropping the cell however the typical workflow for dropping an element programmatically doesn't seem to work on parametric cells unfortunately. Does anyone have any examples or documentation that could enlighten me on how to achieve this?

Thanks!


Ed

  • Hi Edward,

    and it was suggested that I use the ElementGraphicsProcessor abstract class to achieve this

    Er ... no, in fact. I recommend to use ElementGraphicsProcessor to obtain geometry, not elements! It's important to distinguish between these two different information ;-)

    The objective is to to return all graphical elements from the cell instance in their true location (with the same geometry)

    In general, some objects allow to access internal elements, some not. There is even a method called ExposeChildren to determine whether this functionality is supported. My feeling is that general concept of new API is to do not expose internal elements (as elements), because it's implementation / persistence detail, but to prefer to use the object geometry everywhere it's possible.

    Whereas e.g. plain cell is the example of the element exposing children, parametric cell is black bock and it's not possible to access original elements (at lest in the current API version). What is not clear whether it's "by design" or it's just "not implemented yet" feature, because constraints-based modeling is still under development and new features are added.

    the typical workflow for dropping an element programmatically doesn't seem to work on parametric cells unfortunately

    I think the drop tool does not work well with parametric cells in general, not only in API.

    Does anyone have any examples or documentation that could enlighten me on how to achieve this?

    There is, the processor is used in some example delivered with SDK and when you will search this forum, you will find discussion about different aspects of this (great and clever!) object ... also because it seems in some situations the wrapper around native processor does not work as expected.

    The code can be similar to this one:

        internal class Demo
        {
            public void IterateAllElements()
            {
                ParametricCellProcessor processor = new ParametricCellProcessor();
    
                DgnModel model = Session.Instance.GetActiveDgnModel();
    
                ModelElementsCollection elements = model.GetGraphicElements();
    
                foreach (Element element in elements)
                {
                    if (element is ParametricCellElement instance)
                    {
                        MessageCenter.Instance.StatusMessage = $"Parametric cell: {instance.CellDefinition.CellName}";
                        ElementGraphicsOutput.Process(instance, processor);
                    }
                }
            }
        }
    
        internal class ParametricCellProcessor : ElementGraphicsProcessor
        {
            public override bool ProcessAsBody(bool isCurved)
            {
                return false;
            }
    
            public override bool ProcessAsFacets(bool isPolyface)
            {
                return false;
            }
    
            public override BentleyStatus ProcessCurveVector(CurveVector curves, bool isFilled)
            {
                return BentleyStatus.Error;
            }
    
            public override BentleyStatus ProcessCurvePrimitive(CurvePrimitive curve, bool isClosed, bool isFilled)
            {
                MessageCenter.Instance.StatusMessage = $"{curve.GetCurvePrimitiveType()}";
                return BentleyStatus.Success;
            }
    
            public override bool WantClipping()
            {
                return false;
            }
        }

    I recommend (in fact, I think you have to) to read ElementGraphicsProcessor description in native MicroStatioAPI doc, because it's crucial to understand what "bool returning" methods should be overridden to obtain what result.

    When you are interested in symbology (or other "not geometry info), override proper AnnounceXYZ method.

    With regards,

      Jan

  • Thanks Jan,

    use ElementGraphicsProcessor to obtain geometry, not elements!

    You're right, poor choice of words! I need to obtain the geometry of the parametric cell in order to build the (new) elements.

    In general, some objects allow to access internal elements, some not. There is even a method called ExposeChildren to determine whether this functionality is supported

    This is actually what I attempted at first, but quickly realised that there were no children that could be exposed within a Parametric Cell Element. If only it were that easy!

    I think the drop tool does not work well with parametric cells in general, not only in API

    If attempting to do this manually using the 'Drop Element' tool with just the 'Application Elements' ticked everything works as I'd hoped it would, dropping them to their original elements:

    Which makes me wonder if perhaps their is a simpler method to drop Application Elements to their primitives/breps?

    I recommend (in fact, I think you have to) to read ElementGraphicsProcessor description in native MicroStatioAPI doc, because it's crucial to understand what "bool returning" methods should be overridden to obtain what result

    Thanks for the code sample, I'll test that out. With regards to the documentation; when reading through the API docs and forum posts, I didn't quite understand how the geometry is returned to the main script. Am I right in assuming I should read the geometry output from the MessageCenter and use that info to build all the required geometry, through the use of Bentley.GeometryNET library? Looking through the GeometryNET library there are a vast array of methods for each element type; and it looks like a very arduous task if I need to write a script to use the correct methods for drawing an arc, line etc. for all the possible permutations that may occur! Hopefully this is not the case and my interpretation is wrong... What seemed like an easy task in order to finish my GC Node has turned into a full day of struggling to get nowhere lol.
    I really appreciate all the advice to help make sense of this!

    Thanks,

    Ed

  • If only it were that easy!

    Maybe easy for developers, but wrong in terms of data structures.

    It's my subjective feeling, but I see a movement from "C data structures" to "object abstraction using handlers (services)". Whereas it was normal in DGN V7 and also in V8 times to go inside structures and to change directly different parts (which often ended in strange or even corrupted elements), now it's more like "here is a handler, call services (queries) that you need, but do not care about implementation details".

    As written in C++ documentation, every handler should be very careful about providing a list of children elements, because in such case the handler has not any control over them. So for some objects it's possible, for some not.

    If attempting to do this manually using the 'Drop Element' tool with just the 'Application Elements' ticked everything works as I'd hoped it would, dropping them to their original elements:

    I guess there is no problem with 2D constraints, but I tried it with some 3D objects and the results were bad.

    Which makes me wonder if perhaps their is a simpler method to drop Application Elements to their primitives/breps?

    There is Drop structure available in C++, but I think it's not available in managed API (yet?).

    I didn't quite understand how the geometry is returned to the main script.

    I am not sure what you are asking about.

    What processor method is called and how (in what format) the geometry is provided depends on the processor configuration (what type of geometry are you interested in).

    What you will do next with received geometry is completely up to you (e.g. list of geometries added to a list injected to processor?) and does not relate to API itself.

    Looking through the GeometryNET library

    Why? At first, look at examples delivered with MicroStation SDK, at second, search in this forum, at third, read this community blogs (even when there is not many articles). In my opinion, after several years of CE API existence, all typical activities / workflows have been discussed and snippets provided.

    and use that info to build all the required geometry, through the use of Bentley.GeometryNET library?

    Not at all. GeometryNET library is mostly internal aspect of API and usually there is no reason to use it for anything (only to reference the assembly when required).

    To create elements from existing topology / geometry data, use DraftinElementSchema class.

    and it looks like a very arduous task if I need to write a script to use the correct methods for drawing an arc, line etc. for all the possible permutations that may occur!

    If your code ends with such state, something is probably wrong and it means you do not use OOP abstraction provided by API properly. For many requirements, specialized (single focused) class probably exists in API.

    What seemed like an easy task in order to finish my GC Node has turned into a full day of struggling to get nowhere lol.

    Sorry, but such comparison is complete nonsense in my opinion. There is no reason compare platform MicroStation NET API and specialized GC environment. They are different tools developed for different purposes.

    GC were implemented using platform API and it's why the platform API exists: Namely C++ API is ready to solve nearly any requirement and allows to implement nearly anything including all other discipline-specific product APIs. The low-level and flexible API typically requires to write more code ... similarly when you will try to compare C++ (powerful but seriously complicated) and Python (more user oriented and not so flexible, but more friendly).

    With regards,

      Jan

  • I need to obtain the geometry of the parametric cell in order to build the (new) elements.

    One more comment: You should be aware that using ElementGraphicsProcessor, it's not ensured you will be able to create exact copy of the source element. For 2D elements, you can expect that a curveVector equals to source element, but in the case of ParametricCell its structure is variable controlled by values.

    DropGeometry struct available in C++ API is closer to a concept of "splitting source element to individual children".

    So in summary, there are at least 3 different concepts (methods or classes) available:

    • GetChildren ... when it's supported
    • DropGeometry ... probably not available in NET now
    • ElementGraphicsProcessor

    For complete overview, see Geometry Collectors chapter in C++ API documentation.

    Regards,

      Jan

  • it was suggested that I use the ElementGraphicsProcessor

    Keep in mind the origin of the Element Graphics Processor, around the time that iModels were introduced.

    Bentley Systems had a problem: many vertical apps (e.g. OpenBuildings Designer (OBD)) create private structures and element types that could not be used in plain old MicroStation.  Other vendor's apps could not interpret their geometries, so interoperability didn't work.  iModels were invented to solve that problem: each vertical app had to be able to export its geometry in what Bentley Systems term a neutral format — the iModel. 

    The Element Graphics Processor was invented to provide a common development platform for the vertical app divisions of Bentley Systems.  Each vertical app could write a handler for its private objects that would be called by the Element Graphics Processor.  The resulting iModel contains geometry that renders in MicroStation, or another viewer, just like the original in the vertical app.  What's important is that the correct geometry is created.  A facsimile of the vertical app's object is created.

    What the Element Graphics Processor doesn't do is to extract the objects and private data required to recreate an entity that works just as it does in MicroStation.  That is, you can't use OBD to open an iModel created with OBD and expect it to manipulate OBD objects, because those objects aren't present in the iModel.

     
    Regards, Jon Summers
    LA Solutions

  • What seemed like an easy task in order to finish my GC Node has turned into a full day of struggling to get nowhere lol.

    Another reason why it seems to be so complicated is that you choose to work with the most complex element in MicroStation, that is, moreover, still under development (new features like different constrains have been discussed). And it's not very good idea to invest into API opening until everything is fixed and stable, so often it ends by a question "Why <something> is not possible?".

    Regards,

      Jan

  • If attempting to do this manually using the 'Drop Element' tool with just the 'Application Elements' ticked everything works as I'd hoped it would, dropping them to their original elements:

    Jumping in to comment on this; the "original element" is the application element, drop does not return original elements. Instead drop creates brand new elements from the application element's geometry.

    Dropping application elements is essentially an ElementGraphicsProcessor that calls the appropriate DraftingElementSchema::ToElement for each _Process callback.

    HTH

    -B



  • Thanks Jan,

    What you will do next with received geometry is completely up to you (e.g. list of geometries added to a list injected to processor?) and does not relate to API itself.

    Sorry I should have been more specific; I can see that the processor gathers all the geometry information but how do I then enumerate the results / pass this as a variable to DraftingElementSchema.ToElement/s? Please bear in mind I am very new to all of this, I've only had the SDK for a few weeks so I am finding things out the hard way ;)

    Code I have so far:

            public NodeUpdateResult DropParametricCell
            (
                NodeUpdateContext updateContext,
                [Replicatable, DgnModelProvider] object ParametricCell
            )
            {
                try
                {
                    Feature feat = ParametricCell as Feature;
                    long eleRef = feat.GetElement().MdlElementRef(); //Get Element Ref of GC Feature as long
                    Bentley.DgnPlatformNET.Elements.Element ele = Bentley.DgnPlatformNET.Elements.Element.GetFromElementRef((IntPtr)eleRef); //Retrieve Element as DgnPlatformNET Element
    
                    DgnModel model = Session.Instance.GetActiveDgnModel();
                    ParametricCellProcessor processor = new ParametricCellProcessor();
    
                    if(ele is ParametricCellElement instance)
                    {
                        Bentley.MstnPlatformNET.MessageCenter.Instance.StatusMessage = $"Parametric cell: {instance.CellDefinition.CellName}";
                        ElementGraphicsOutput.Process(instance, processor);
                        //What do I do now?!
                        //DraftingElementSchema.ToElement(model, , null);
                    }
                }
                catch (Exception ex)
                {
                    return new NodeUpdateResult.TechniqueException(ex);
                }
                return NodeUpdateResult.Success;
            }
        } // class
    
        internal class ParametricCellProcessor : ElementGraphicsProcessor
        {
            public override bool ProcessAsBody(bool isCurved)
            {
                return false;
            }
    
            public override bool ProcessAsFacets(bool isPolyface)
            {
                return false;
            }
    
            public override BentleyStatus ProcessCurveVector(CurveVector curves, bool isFilled)
            {
                return BentleyStatus.Error;
            }
    
            public override BentleyStatus ProcessCurvePrimitive(CurvePrimitive curve, bool isClosed, bool isFilled)
            {
                Bentley.MstnPlatformNET.MessageCenter.Instance.StatusMessage = $"{curve.GetCurvePrimitiveType()}";
                return BentleyStatus.Success;
            }
    
            public override bool WantClipping()
            {
                return false;
            }
        }
    } // namespace

    Thanks for all the help getting me this far!

    Edit - I think I finally understand what is required to pass the geometry to the main script. Do I need to add a List to collect the elements within the processor? The issue now is that I do not know what to pass to the DraftingElementSchema for the template element. Updated code:

            public NodeUpdateResult DropParametricCell
            (
                NodeUpdateContext updateContext,
                [Replicatable, DgnModelProvider] object ParametricCell
            )
            {
                try
                {
                    Feature feat = ParametricCell as Feature;
                    long eleRef = feat.GetElement().MdlElementRef(); //Get Element Ref of GC Feature as long
                    Bentley.DgnPlatformNET.Elements.Element ele = Bentley.DgnPlatformNET.Elements.Element.GetFromElementRef((IntPtr)eleRef); //Retrieve Element as DgnPlatformNET Element
    
                    DgnModel model = Session.Instance.GetActiveDgnModel();
                    ParametricCellProcessor processor = new ParametricCellProcessor();
    
                    if(ele is ParametricCellElement instance)
                    {
                        Bentley.MstnPlatformNET.MessageCenter.Instance.StatusMessage = $"Parametric cell: {instance.CellDefinition.CellName}";
                        
                        ElementGraphicsOutput.Process(instance, processor);
    
                        for (int i = 0; i < processor.curvePrimitives.Count; ++i)
                        {
                            Bentley.DgnPlatformNET.Elements.Element nele = DraftingElementSchema.ToElement(model, processor.curvePrimitives[i], null); //Currently throws an error...
                            nele.AddToModel();
                            Bentley.Interop.MicroStationDGN.Element fetch = MSApp.ActiveModelReference.GetLastValidGraphicalElement();
                            SetElement(fetch);
                            nele.Dispose();
                        }
                    }
                    
    
                }
                catch (Exception ex)
                {
                    return new NodeUpdateResult.TechniqueException(ex);
                }
    
                return NodeUpdateResult.Success;
            }
        } // class
    
        public class ParametricCellProcessor : ElementGraphicsProcessor
        {
            public List<CurvePrimitive> curvePrimitives = new List<CurvePrimitive>();
    
            public override bool ProcessAsBody(bool isCurved)
            {
                return false;
            }
    
            public override bool ProcessAsFacets(bool isPolyface)
            {
                return false;
            }
    
            public override BentleyStatus ProcessCurveVector(CurveVector curves, bool isFilled)
            {
                return BentleyStatus.Error;
            }
    
            public override BentleyStatus ProcessCurvePrimitive(CurvePrimitive curve, bool isClosed, bool isFilled)
            {
                Bentley.MstnPlatformNET.MessageCenter.Instance.StatusMessage = $"{curve.GetCurvePrimitiveType()}";
                curvePrimitives.Add(curve);
                
                return BentleyStatus.Success;
            }
    
            public override bool WantClipping()
            {
                return false;
            }
        }
    

    It throws an error on line 24, most likely due to null value for template. The list count = 8 though so it seems to be collecting something; when looking through debug values it is a heap of nested lists though and I couldn't see any raw geometry. Might be in there somewhere!