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::DgnExclusiveLockAny 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
Hi Lars,
Lars Kriev said: 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.
Lars Kriev said: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?
Lars Kriev said: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
Bentley Accredited Developer: iTwin Platform - AssociateLabyrinth Technology | dev.notes() | cad.point
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):
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.
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().
I returned to this discussion (found some time to think about it ;-) and I am still thinking your code is over-complicated.
Lars Kriev said: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; }
Lars Kriev said: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,
Answer Verified By: Lars Kriev
Hi Jan,thank you very much. The code you've provided helps a lot.
Jan Šlegr said: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 Kriev said: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).
Lars Kriev said:I can live with temporary files...
I think MicroStation, what ever it does, use at least temp files in temporary folder, too.