Another day, another bug!
One of my users has possibly lost a good amount of work from this bug unfortunately. They were working yesterday, saved and closed their file, only to open it this morning and get a Warning to stop opening and run Recover, as 300+ errors were detected.
After running Recover from the prompts, it noted that 10+ items were fixed and left alone. But 300+ items were deleted which is where the loss of work came from.
If I dont run Recover when I open the file I still see all the items deleted, but when I run Audit I get this
Command: AUDIT
Fix any errors detected? [Yes/No] <N>:
Auditing Header
Auditing Tables
Auditing Entities Pass 1
Pass 1 36300 objects auditedAcDbLine(2F283D) XData Handle Unknown Null
AcDbLine(2F283D) was not repaired.
AcDbLine(2F283E) XData Handle Unknown Null
AcDbLine(2F283E) was not repaired.
AcDbLine(2F283F) XData Handle Unknown Null
AcDbLine(2F283F) was not repaired.
AcDbLine(2F2840) XData Handle Unknown Null
AcDbLine(2F2840) was not repaired.
AcDbLine(2F2841) XData Handle Unknown Null
AcDbLine(2F2841) was not repaired.
AcDbLine(2F2842) XData Handle Unknown Null
AcDbLine(2F2842) was not repaired.
AcDbLine(2F2843) XData Handle Unknown Null
AcDbLine(2F2843) was not repaired.
AcDbLine(2F2844) XData Handle Unknown Null
AcDbLine(2F2844) was not repaired.
Pass 1 90600 objects auditedAcDbLine(37A137) XData Handle Unknown Null
AcDbLine(37A137) was not repaired.
Pass 1 90800 objects auditedAcDbArc(37A7B4) XData Handle Unknown Null
AcDbArc(37A7B4) was not repaired.
AcDbArc(37A80C) XData Handle Unknown Null
AcDbArc(37A80C) was not repaired.
AcDbArc(37A886) XData Handle Unknown Null
AcDbArc(37A886) was not repaired.
Pass 1 108700 objects audited
Auditing Entities Pass 2
Pass 2 36300 objects auditedAcDbLine(2F283D) XData Handle Unknown Null
AcDbLine(2F283D) was not repaired.
AcDbLine(2F283E) XData Handle Unknown Null
AcDbLine(2F283E) was not repaired.
AcDbLine(2F283F) XData Handle Unknown Null
AcDbLine(2F283F) was not repaired.
AcDbLine(2F2840) XData Handle Unknown Null
AcDbLine(2F2840) was not repaired.
AcDbLine(2F2841) XData Handle Unknown Null
AcDbLine(2F2841) was not repaired.
AcDbLine(2F2842) XData Handle Unknown Null
AcDbLine(2F2842) was not repaired.
AcDbLine(2F2843) XData Handle Unknown Null
AcDbLine(2F2843) was not repaired.
AcDbLine(2F2844) XData Handle Unknown Null
AcDbLine(2F2844) was not repaired.
Pass 2 90600 objects auditedAcDbLine(37A137) XData Handle Unknown Null
AcDbLine(37A137) was not repaired.
Pass 2 90800 objects auditedAcDbArc(37A7B4) XData Handle Unknown Null
AcDbArc(37A7B4) was not repaired.
AcDbArc(37A80C) XData Handle Unknown Null
AcDbArc(37A80C) was not repaired.
AcDbArc(37A886) XData Handle Unknown Null
AcDbArc(37A886) was not repaired.
Pass 2 108700 objects audited
I'm not sure where to start investigating. I use XData a fair bit in our plugin, and the items that were deleted where a bunch of line items that would have had XData assigned to them from one of the plugins process'.
I only have a few ExtensionMethods for working with XData, so I'll leave those here. This is the first time this has happened. But I have been having the Warning Errors pop up after opening, but recover usually fixes the problems without deleting anything. That problem occurs when using SaveAs and renaming the file.
I'm wondering if I should move to using ExtensionDictionaries instead of XData.
public static void EraseXData(this DBObject dbObject)
{
if (dbObject == null) return;
if (!dbObject.IsWriteEnabled)
{
dbObject.UpgradeOpen();
}
dbObject.XData = new ResultBuffer
{
new TypedValue((int)DxfCode.ExtendedDataRegAppName,
WBtoolsInitializer.WarmboardToolsRegAppName)
};
}
public static void EraseXDataValue(this DBObject dbObject, string key)
{
if (dbObject == null) return;
if (!dbObject.IsWriteEnabled)
{
dbObject.UpgradeOpen();
}
RemoveKeyValue(dbObject);
void RemoveKeyValue(DBObject dbObj)
{
ResultBuffer buffer = new ResultBuffer();
bool hasAppName = true;
if (dbObj.XData != null)
{
foreach (var data in dbObj.XData.AsArray())
{
if (data.TypeCode == (short)DxfCode.ExtendedDataRegAppName)
{
if ((string)data.Value == WBtoolsInitializer.WarmboardToolsRegAppName)
hasAppName = false;
}
if (data.TypeCode == (short)DxfCode.ExtendedDataAsciiString && data.Value is string dataString)
{
if (dataString.Contains(key)) continue;
}
buffer.Add(data);
}
}
//only adds regAppName if not added already
if (hasAppName) buffer.Add(new TypedValue((int)DxfCode.ExtendedDataRegAppName, WBtoolsInitializer.WarmboardToolsRegAppName));
dbObj.XData = buffer;
}
}
public static string FindStringXDataValue(this DBObject dbObject, string searchTerm)
{
try
{
if (dbObject == null || dbObject.XData == null) return string.Empty;
foreach (TypedValue entry in dbObject.XData.AsArray())
{
if (!entry.TypeCode.Equals((int)DxfCode.ExtendedDataAsciiString)) continue;
if (!(entry.Value is string entryValue)) continue;
string[] splitValue = entryValue.Split(',');
if (splitValue.Length < 2) continue;
if (!splitValue[0].Equals(searchTerm)) continue;
return splitValue[1];
}
return string.Empty;
}
catch
{
return string.Empty;
}
}
public static void SetStringXDataValue(this DBObject dbObject, string key, string value)
{
if (dbObject == null || value == null || value == string.Empty) return;
if (!dbObject.IsWriteEnabled)
{
dbObject.UpgradeOpen();
}
AddValueToEnd(dbObject);
void AddValueToEnd(DBObject dbObj)
{
ResultBuffer buffer = new ResultBuffer();
bool hasAppName = true;
if (dbObj.XData != null)
{
foreach (var data in dbObj.XData.AsArray())
{
if (data.TypeCode == (short)DxfCode.ExtendedDataRegAppName)
{
if ((string)data.Value == WBtoolsInitializer.WarmboardToolsRegAppName)
hasAppName = false;
}
if (data.TypeCode == (short)DxfCode.ExtendedDataAsciiString && data.Value is string dataString)
{
if(dataString.Contains(key)) continue;
}
buffer.Add(data);
}
}
//only adds regAppName if not added already
if (hasAppName) buffer.Add(new TypedValue((int)DxfCode.ExtendedDataRegAppName, WBtoolsInitializer.WarmboardToolsRegAppName));
buffer.Add(new TypedValue((int)DxfCode.ExtendedDataAsciiString, $"{key},{value}"));
dbObj.XData = buffer;
}
}
/// <summary>
/// This method will try to find a long value at the key given. This long will try to convert to a handle.
/// This handle will try to convert to an ObjectId.
/// If successful, will return true and assigned id.
/// </summary>
/// <param name="dbObject">Object who's XData will be looked at</param>
/// <param name="key">the key the long was stored at</param>
/// <param name="id">if not found will be ObjectId.Null</param>
/// <returns></returns>
public static bool TryGetLinkedObjectId(this DBObject dbObject, string key, out ObjectId id)
{
string handleVal = dbObject.FindStringXDataValue(key);
if (!long.TryParse(handleVal, out long handleLong))
{
id = ObjectId.Null;
return false;
}
Handle handle = new Handle(handleLong);
ObjectId objectId = new ObjectId();
if (!Active.Database.TryGetObjectId(handle, out objectId))
{
id = ObjectId.Null;
return false;
}
id = objectId;
return true;
}
public static void TrySetObjectIdLink(this DBObject dbObject, string key, ObjectId other)
{
Handle otherHandle = other.Handle;
dbObject.SetStringXDataValue(key, otherHandle.Value.ToString());
}
It means that the database object referenced by the handle stored in xdata no longer exists.
Using xdata to maintain inter-object references is unreliable because AutoCAD doesn't perform translation of them during operations like deep-clone. You should use XRecords to store ObjectId references, and set the XRecord's XlateReferences property to true.
Wow, this is all new to me. Thank you so much.
So even though I was storing the handle of another entity in the Xdata of another as a string, AutoCAD was trying to translate that and got an error?
In my code that I posted, TrySetObjectIdLink() would take the ObjectId of another entity, and turn the handle into a string that would then get stored as ExtendedDataASCIIString.
Then in TryGetLinkedObjectId() I would take the stored string, try to convert it to a long, to then make a new handle and use Database.TryGetObjectId() to find the Id.
Where in that process did something go wrong? Mostly out of curiosity.
I am writing what is essentially the methods I post already, but operating with ExtensionDictionary on the DbObject's and using Xrecords.
What DxfCode should I use to store the link? And setting XlateReferences does what exactly?
Sorry for all the questions, but thank you in advance for all the help.
The messages from AUDIT are about handles that are stored in XData as Handles, not as strings. It doesn't recognize strings as handles. There may be some cases where handles get translated, but AFAIK they are limited.
Can't find what you're looking for? Ask the community or share your knowledge.