Doc with changed xrefs not saved

Doc with changed xrefs not saved

m.de.vriesTH5VM
Enthusiast Enthusiast
519 Views
4 Replies
Message 1 of 5

Doc with changed xrefs not saved

m.de.vriesTH5VM
Enthusiast
Enthusiast

I am coding an addin that scans a folder of dwg files recursively and checks if the xrefs need to be set to a new location or have to be removed entirely. The end result should be that each dwg file is ready for importing into a Vault (which will not process dwg files if they have unresolved references).

The code I am using here is based on example code from this forum as well as the example projects in the SDK, where they appear to be working as intended.

 

I am using Application.DocumentManager.Open() to open each of the dwg files in turn (in an autocad session with an empty drawing so I can netload the command and run it. This works as expected, and the first dwg file is opened in the Autocad editor.

 

I then use this function to get all xrefs in the document that are marked as xref and as unresolved

private List<BlockTableRecord> GetAllXRefs(Document doc, Transaction trans)
{
	List<BlockTableRecord> res = new List<BlockTableRecord>();
	if (null == doc || null == trans) return res;
 
	try
	{
		doc.Database.ResolveXrefs(truefalse); // try to resolve any unresolved xrefs in the database
		XrefGraph graph = doc.Database.GetHostDwgXrefGraph(true);
		List<BlockTableRecord> xrefs = FindUnresolvedXrefs(graph.RootNode, trans);
		if (null != xrefs && xrefs.Count > 0) res.AddRange(xrefs);
	}
	catch (Exception ex)
	{
		MessageBox.Show($"Problem in GetAllXRefs ({ex.Message})");
	}
 
	return res;
}

This function works as well. In my test dwg I have 3 xrefs, one of which is resolved, one of which has been moved to a different folder and one which has been deleted in the folder.
I then go through these blocktablerecords one at a time and use this function to try and find the unresolved xref in any of a given number of paths, and if that fails remove it from the dwg

private bool ProcessUnresolved(BlockTableRecord btr, List<ObjectId> toPurge)
{
	if (btr.XrefStatus != XrefStatus.Unresolved) return false;
 
	Messages.Add($"XRef {btr.Name} was unresolved or not found. It was unloaded from the document");
 
	// Unresolved should not be possible, we force a resolve of the xrefs earlier. In theory the state should either be Resolved or FileNotFound
	string xpath = Cmd.General.FindFilesInFolders(Path.GetFileName(btr.PathName));
	if (string.IsNullOrEmpty(xpath) || !File.Exists(xpath))
	{
		// xref could not be resolved
		Messages.Add($"XRef {btr.Name} could not be found in any of the search locations. It will be removed from the document (including the instances of this xref!)");
		toPurge.Add(btr.Id);
		return true;
	}
 
	// the path was changed
	btr.UpgradeOpen();
	btr.PathName = xpath;
	btr.DowngradeOpen();
	return true// and this document was modified
 
	// there should be a valid path now, but the xref is still unloaded. Assume that Vault will resolve this correctly.
	// if we need to move the xref location we do that here (and mark the document as modified again)
}

xrefs that can be found in a new location have their Pathname changed, the ones that cannot be found are added to the toPurge list.

After processing all unresolved xrefs this way the function is called to handle the xrefs that have to be removed entirely

private bool ProcessPurges(List<ObjectId> toPurge, Document doc, Transaction trans)
{
	if (null == toPurge || toPurge.Count <= 0) return false;
	if (null == doc || null == trans) return false;
 
	Messages.Add($"Purge {toPurge.Count} missing/unresolved xrefs from the database");
	// these have to be removed more thoroughly. not just the btr, but also the references to it
	ObjectIdCollection instances = new ObjectIdCollection();
	foreach (ObjectId id in toPurge.Distinct())
	{
		BlockTableRecord btr = trans.GetObject(id, OpenMode.ForWrite) as BlockTableRecord;
		if (null == btr) continue;
 
		instances.Add(id); // we will want to eventually purge the master btr
		ObjectIdCollection refs = btr.GetBlockReferenceIds(truetrue);
		foreach (ObjectId objId in refs)
		{
			DBObject inst = trans.GetObject(objId, OpenMode.ForWrite);
			inst.Erase();
			instances.Add(objId);
		}
 
		// we removed the instances of this record, we can now also remove the reference itself
		DB.DetachXref(id);
		btr.Erase();
	}
 
	doc.Database.Purge(instances);
	return true// mark the database as modified
}

 The code finds any references to the unresolvable xrefs and removes them, then detaches the xref and marks the btr as erased.

 

At this point when I call GetAllXRefs() again I get only 2 childnodes, both of which are marked as resolved. This suggests that the functions that fixed/removed the xrefs have worked correctly.

 

However, when I then try to save the modified document with Document.CloseAndSave() it is not saved. if I save it under a different name it is saved, but the unresolved xrefs are still in that copy. If I use Database.SaveAs() instead it again is saved but with the unresolved xrefs still present.

And when I do none of this and leave the document open the GUI xref manager will also show both the original unresolved xrefs as still present in the drawing and both still unresolved.

 

I must be missing something fairly simple, but it has been a while since I last programmed in the AutoCAD API and that was with C++ so there may be something that does not translate directly to the C# version of the API. Or I just overlooked a detail here.

 

0 Likes
Accepted solutions (2)
520 Views
4 Replies
Replies (4)
Message 2 of 5

_gile
Consultant
Consultant
Accepted solution

Hi,

 

Do you Commit the transaction before saving the Database?

 

Another thing:

doc.Database.Purge(instances)

does not 'purge' anything. The Database.Purge method only filters the objectIdCollection to return the 'purgeable' ObjectIds.



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 3 of 5

m.de.vriesTH5VM
Enthusiast
Enthusiast

Thank you for your answer.

I am closing both the document lock and the transaction before attempting to save

 

This is the code with error handling and such removed.

using (DocumentLock dlock = doc.LockDocument())
{
List<BlockTableRecord> xrefs = new List<BlockTableRecord>();

using (Transaction trans = DB.TransactionManager.StartTransaction())
{
xrefs = GetAllXRefs(doc, trans);
List<ObjectId> toPurge = new List<ObjectId>();
foreach (BlockTableRecord btr in xrefs)
{
if (ProcessUnresolved(btr, toPurge)) mod = true;
}

if (ProcessPurges(toPurge, doc, trans)) mod = true;
}
}

// if anything was changed, save the document (at this point the transaction and lock are already removed)
if (mod)
{
doc.CloseAndSave(path);
}
else
{
doc.CloseAndDiscard();
}

 

I will remove the calls to Purge() from my code to improve its runtime (when it has to process 35,000+ files)

 

0 Likes
Message 4 of 5

norman.yuan
Mentor
Mentor
Accepted solution

<QUOTE>

I am closing both the document lock and the transaction before attempting to save

</QUOTE>

 

Your code indeed closes the Transaction by the "using (Transaction tran=....){.....}. However, closing DOES NOT MEAN the transaction is committed. if you do not call Transaction.Commit() EXPLICITLY, it is aborted at the end of "using..." block. That is why @_gile asked you if you commit the transaction or not. It seems you did not.

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 5 of 5

m.de.vriesTH5VM
Enthusiast
Enthusiast

I added the trans.Commit() but still needed to make one more additional change to the code.

Rather than using DocumentManager.Open(path) to load a dwg file in the editor I had to create an empty database and read the dwg file into that.  Database.SaveAs(path, version) correctly overwrote the file where Document.Save() did not.

 

So

using(Database db = new Database(false, true))

{
   db.ReadDwgFile(path, mode, false, null);

   ...

   if (modified) db.SaveAs(path, DwgVersion.CurrentVersion);

}

 

worked for opening, modifying and saving an existing dwg file.

0 Likes