[CONNECT U16 C#] How to copy element from one model into a new DGN? (Add-in crashes on dgnFile.ProcessChanges)

Hi,

as a follow-up to this thread, I'm trying to copy an element from an currently opened DGN file to a new temporary DGN file in order to export the single element (normally a cell) as an FBX file.

According to suggestions that I've read in this forum, I'm trying to attach the respective DGN model to the temporary file and then use ElementCopyContext to copy the element to the new model. 

Here's my method that accepts a file path for the new DGN file, the source model and the element to copy:

private void CopyElementToNewFile(string newDgnFilePath, DgnModel sourceModel, Element sourceElement)
        {
            DgnModelStatus error;
            DgnFileStatus status = 0;

            // create new DgnDocument (still in-memory)
            using (DgnDocument targetDgnDocument = DgnDocument.CreateForNewFile(out status, newDgnFilePath, null,0, null ,DgnDocument.OverwriteMode.Always, DgnDocument.CreateOptions.Default))
            {
                if (status != DgnFileStatus.Success)
                {
                    ShowMessage(msg: $"Error creating new file: {status}");
                    return;
                }

                using (DgnFileOwner dgnFileOwner = DgnFile.Create(document: targetDgnDocument, openMode: DgnFileOpenMode.ReadWrite))
                {
                    DgnFile dgnFile = dgnFileOwner.DgnFile;

                    // create a new model based on the source model
                    DgnModel targetDgnModel = dgnFile.CreateNewModel(error: out error, name: "Default",type: DgnModelType.Normal,is3D: true, seedModel: sourceModel);

                    if (error != DgnModelStatus.Success)
                    {
                        ShowMessage(msg: $"Error creating new model: {error}");
                        return;
                    }
                   
                    var sourceFileName = sourceModel.GetDgnFile().GetFileName();
                    DgnDocumentMoniker sourceDgnDocumentMoniker = DgnDocumentMoniker.CreateFromFileName(fileName: sourceFileName, searchPath: null);

                    // attach the source model to the new model 
                    DgnAttachment attachment = targetDgnModel.CreateDgnAttachment(moniker: sourceDgnDocumentMoniker, modelName: sourceModel.ModelName);
                    var attachedModel = attachment.GetDgnModel();

                    // find the element to copy in the attached model (maybe just use sourceElement directly?)
                    var elementInAttachedModel = attachedModel.FindElementById(sourceElement.ElementId); 
                    if (elementInAttachedModel == null)
                    {
                        ShowMessage(msg: $"Error. Could not find element by ID.");
                    }

                    // copy the element to the new model
                    using (ElementCopyContext cc = new ElementCopyContext(dgnModelRef: targetDgnModel))
                    {
                        cc.DoCopy(elementInAttachedModel);
                    }

                    // process the changes, method should write the file to disc
                    var result = dgnFile.ProcessChanges(reason: DgnSaveReason.UserInitiated); // add-in crashes
                    //  AccessViolation in Bentley::DgnPlatform::DgnExclusiveLock::DgnExclusiveLock+0xa 
                }
            }
        }

Sadly, the add-in crashes on dgnFile.ProcessChanges(), instantly followed by an error report dialog in Microstation. Try/catch yielded nothing.
Microstation's exception log shows an Access violation at Bentley::DgnPlatform::DgnExclusiveLock::DgnExclusiveLock

Any hints about what I'm doing wrong are appreciated. If another approach or another set of API methods is more suitable, please advise.

Thanks, Regards,


Lars

Parents
  • Hi Lars,

    I'm trying to attach the respective DGN model to the temporary file

    Why to do that? I think no such thing is required.

    You have (active?) mode (source) and at background created new file. Just copy the element, using proper ElementCopyContext, from source to target.

    Here's my method that accepts a file path for the new DGN file, the source model and the element to copy:

    Sorry, but it looks confusing to me, because there is source, target, but source is attached as reference to target, and at the end you copy element from source to target.  Why to do it so complicated?

    Any hints about what I'm doing wrong are appreciated.

    My first thought is about new file content loading. I do not see any code, that ensures "everything mandatory" to create new file is done: To load it, to create a new model and to load (when necessary) both dictionary and new model.

    I recommend to check C++ API documentation, specifically DgnFile Struct Reference, where these steps are explained.

    Regards,

      Jan

  • Thanks, Jan!


    I'm currently fiddling around with the new info I found in the API docs to get the simpler version running with no attachment.

    From managed API help file (carelessly copied from the C++ one):


    Creating a new file

    You must start with an existing file, which serves as a "seed". To create a DGN file:
    1. Call #Create to create a DgnFile object.
    2. Call #LoadDgnFile to attempt to open the seed file.
    3. Call #DoSaveAs to save the file to a new name.

       DgnFilePtr file = DgnFile::Create (seedFileDocument, DgnFileOpenMode::ReadOnly);
       if (file->LoadDgnFile () != DGNFILE_STATUS_Success)
           return;
       if (file->DoSaveAs (newFileDocument) != SUCCESS)
           return;

    (From Bentley.DgnPlatformNET.DgnFile help)


    That looks easy, but it gets ugly when you try it with managed code.

    • DgnFile.Create() method takes a DgnDocument as parameter. Is that the seed document? Documentation unclear
    • DgnFile.CreateNew() accepts SeedData, you need a bunch of lines to get the SeedData

    There's an example for DgnFile.Create() in the API help file:

    DgnDocument     targetDgnDocument = DgnDocument.CreateForLocalFile  (dgnFilePath);
    DgnFileOwner    dgnFileOwner = DgnFile.Create  (dgnDocument, DgnFileOpenMode.ReadWrite);
    DgnFile         dgnFile = dgnFileOwner.DgnFile;
    DgnModel        dgnModel = dgnFile.LoadRootModelById (out loadDetails, dgnFile.DefaultModelId);
    
    DoSomeWork  (dgnModel);
    
    dgnFile.ProcessChanges  (DgnSaveReason.ApplicationInitiated);
    dgnFileOwner.Dispose ();
    
    

    targetDgnDocument is never used, or is that dgnDocument of line 2? If yes, even that code crashes at ProcessChanges().

    Regards,

    Lars

  • Hi Lars,

    Any hints about what I'm doing wrong are appreciated.

    I returned to this discussion (found some time to think about it ;-) and I am still thinking your code is over-complicated.

    That looks easy, but it gets ugly when you try it with managed code.

    I do not think it is ugly, but it is true that API is low level, so simple task "create and use file" must be implemented as several steps.

    I think there are more ways how to create new file from seed, one solution is this (simplified):

    // seedFile: Full path to seed file (c:\files\myseed.dgn)
    // newFilePath: Full path to new file (c:\files\newfile.dgn)
    
    
    StatusInt status = StatusInt.Error;
    
    using (DgnDocument document = DgnDocument.CreateForLocalFile(seedFile))
    using (DgnFileOwner owner = DgnFile.Create(document, DgnFileOpenMode.ReadOnly))
    {
        DgnFile dgnFile = owner.DgnFile;
        StatusInt openForWriteStatus;
        DgnFileStatus fileStatus = dgnFile.LoadDgnFile(out openForWriteStatus);
    
        if (fileStatus == DgnFileStatus.Success)
        {
            using (DgnDocument newDocument = DgnDocument.CreateForLocalFile(newFilePath))
            {
                status = dgnFile.DoSaveAs(newDocument, DgnFileFormatType.V8, false, false);
            }
        }
    
        return status;
    }

    If another approach or another set of API methods is more suitable, please advise.

    As I wrote in my previous comment, the copying is simple process, because when set right, API (copy context) does everything for you.

    Code to copy all cells from active model to (existing) external file, opened at background (no attaching or anything else is required):

    // targetLocation: Full path to existing target file (c:\files\newfile.dgn)
    
    using (DgnDocument document = DgnDocument.CreateForLocalFile(targetLocation))
    using (DgnFileOwner owner = DgnFile.Create(document, DgnFileOpenMode.ReadWrite))
    {
        DgnFile targetDgn = owner.DgnFile;
    
        StatusInt openForWriteStatus;
        DgnFileStatus loadDgnStatus = targetDgn.LoadDgnFile(out openForWriteStatus);
    
        ModelIndexCollection indexes = targetDgn.GetModelIndexCollection();
    
        StatusInt errorDetails;
        DgnModel targetModel = targetDgn.LoadRootModelById(out errorDetails, 0);
    
        DgnModel activeModel = Session.Instance.GetActiveDgnModel();
        ModelElementsCollection elements = activeModel.GetGraphicElements();
    
        using (ElementCopyContext context = new ElementCopyContext(targetModel))
        {
            foreach (var element in elements)
            {
                if (element is CellHeaderElement cell)
                {
                    context.DoCopy(cell);
                }
            }
        }
    
        targetDgn.ProcessChanges(DgnSaveReason.FileClose);
    }

    Of course, real code should be implemented in more robust way to ensure every step is checked for its result / status.

    With regards,

      Jan

    Answer Verified By: Lars Kriev 

  • Hi Jan,

    thank you very much. The code you've provided helps a lot. 
     

    I returned to this discussion (found some time to think about it ;-) and I am still thinking your code is over-complicated.

    Yes, I agree. Discontinued.

    I've tried to change your code to use an in-memory target file, but then ProcessChanges() fails again. Maybe this method really requires a physical file in the file system. But whatever, I can live with temporary files...

    Thanks again, Best Regards,


    Lars

  • Hi Lars, 

    I've tried to change your code to use an in-memory target file,

    What is in-memory file in the context of DgnFile object?

    It is mentioned quite often in documentation, that classes like DgnFile, DGN Document, moniker  etc. are implemented around a concept of file systém (and some existing file).

    I can live with temporary files...

    I think MicroStation, what ever it does, use at least temp files in temporary folder, too. 

    Regards,

      Jan

Reply Children
No Data