diff --git a/src/ACadSharp.Tests/IO/AecProxyFallbackTests.cs b/src/ACadSharp.Tests/IO/AecProxyFallbackTests.cs
new file mode 100644
index 000000000..ceedff0f7
--- /dev/null
+++ b/src/ACadSharp.Tests/IO/AecProxyFallbackTests.cs
@@ -0,0 +1,54 @@
+using ACadSharp.Classes;
+using ACadSharp.IO.DWG;
+using Xunit;
+
+namespace ACadSharp.Tests.IO
+{
+ public class AecProxyFallbackTests
+ {
+ [Theory]
+ [InlineData("AEC_MODI", "TDbSymbModi", true)]
+ [InlineData("AEC_SYMB_SECTION", "TDbSymbSection", true)]
+ [InlineData("AEC_DBCONFIG", "TDbConfig", true)]
+ [InlineData("AEC_DOOR", "AecDbDoor", true)]
+ [InlineData("AECS_MEMBER", "AecsDbMember", true)]
+ [InlineData("AECB_PIPE", "AecbDbPipe", true)]
+ [InlineData("AEC_INTERFACESRECO", "AecInterfaceRecord", true)]
+ [InlineData("SOME_CUSTOM_ENTITY", "AecDbCustom", true)]
+ [InlineData("SOME_TIANZHENG_ENTITY", "TDbCustom", true)]
+ [InlineData("aec_wall", "AecDbWall", true)]
+ [InlineData("ACDBDICTIONARYWDFLT", "AcDbDictionaryWithDefault", false)]
+ [InlineData("ACDBDETAILVIEWSTYLE", "AcDbDetailViewStyle", false)]
+ [InlineData("CELLSTYLEMAP", "AcDbCellStyleMap", false)]
+ [InlineData("LINE", "AcDbLine", false)]
+ public void IsAecClass_DetectsAecFamilies(string dxfName, string cppClassName, bool expected)
+ {
+ DxfClass dxfClass = new DxfClass
+ {
+ DxfName = dxfName,
+ CppClassName = cppClassName,
+ };
+
+ Assert.Equal(expected, DwgObjectReader.IsAecClass(dxfClass));
+ }
+
+ [Fact]
+ public void IsAecClass_DetectsAecApplicationName()
+ {
+ DxfClass dxfClass = new DxfClass
+ {
+ DxfName = "SOME_CUSTOM_ENTITY",
+ CppClassName = "SomeCustomObject",
+ ApplicationName = "AEC_KERNAL|missing interpreter",
+ };
+
+ Assert.True(DwgObjectReader.IsAecClass(dxfClass));
+ }
+
+ [Fact]
+ public void IsAecClass_NullClass_ReturnsFalse()
+ {
+ Assert.False(DwgObjectReader.IsAecClass(null));
+ }
+ }
+}
diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs
index d2a91569e..de62ea2e0 100644
--- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs
+++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs
@@ -5787,6 +5787,11 @@ private CadTemplate readUnlistedType(short classNumber)
break;
}
+ if (template == null && IsAecClass(c))
+ {
+ template = this.readAecProxy(c);
+ }
+
if (template == null && c.IsAnEntity)
{
template = this.readUnknownEntity(c);
@@ -5806,6 +5811,94 @@ private CadTemplate readUnlistedType(short classNumber)
return template;
}
+ ///
+ /// Reads an unlisted AEC object as a proxy object keeping the proxy graphics and the class definition.
+ ///
+ ///
+ /// AEC objects saved without their application are stored in proxy format and can be parsed by the
+ /// proxy readers. If the object is stored in its native format the proxy readers fail, in that case
+ /// the object is read by the unknown object readers instead.
+ ///
+ private CadTemplate readAecProxy(DxfClass c)
+ {
+ //Read the object on a copy of the readers, if the object is stored in its native format the
+ //proxy readers fail and the original readers remain untouched for the unknown object readers
+ IDwgStreamReader objectReader = this._objectReader;
+ IDwgStreamReader textReader = this._textReader;
+ IDwgStreamReader handlesReader = this._handlesReader;
+ IDwgStreamReader mergedReaders = this._mergedReaders;
+
+ this._objectReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding);
+ this._objectReader.SetPositionInBits(objectReader.PositionInBits());
+ this._textReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding);
+ this._textReader.SetPositionInBits(textReader.PositionInBits());
+ this._handlesReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding);
+ this._handlesReader.SetPositionInBits(handlesReader.PositionInBits());
+ this._mergedReaders = new DwgMergedReader(this._objectReader, this._textReader, this._handlesReader);
+
+ try
+ {
+ if (c.IsAnEntity)
+ {
+ CadTemplate template = this.readProxyEntity();
+ ((ProxyEntity)template.CadObject).DxfClass = c;
+ this._builder.Notify($"Unlisted AEC entity with DXF name {c.DxfName} has been read as a ProxyEntity", NotificationType.Warning);
+ return template;
+ }
+ else
+ {
+ CadTemplate template = this.readProxyObject();
+ ((ProxyObject)template.CadObject).DxfClass = c;
+ this._builder.Notify($"Unlisted AEC object with DXF name {c.DxfName} has been read as a ProxyObject", NotificationType.Warning);
+ return template;
+ }
+ }
+ catch (Exception)
+ {
+ //The object is stored in its native format, restore the original readers so the
+ //unknown object readers can read its common data
+ this._objectReader = objectReader;
+ this._textReader = textReader;
+ this._handlesReader = handlesReader;
+ this._mergedReaders = mergedReaders;
+
+ if (!this._builder.Configuration.Failsafe)
+ throw;
+
+ return null;
+ }
+ }
+
+ ///
+ /// Checks if a dxf class belongs to an AEC (Architecture, Engineering and Construction) application
+ /// based on the naming of its dxf, application and cpp class names.
+ ///
+ ///
+ /// AEC class names are not limited to the AEC_* dxf name, structural members use AECS_*
+ /// and the MEP family uses AECB_*. The application name uses the Aec* prefix and the
+ /// cpp class name is used as a secondary signal for the Autodesk AecDb*/AecsDb* families
+ /// and for the Tianzheng/Revit TDb* family.
+ ///
+ internal static bool IsAecClass(DxfClass c)
+ {
+ if (c == null)
+ return false;
+
+ if (c.DxfName?.StartsWith("AEC", StringComparison.OrdinalIgnoreCase) == true)
+ return true;
+
+ if (c.ApplicationName?.StartsWith("AEC", StringComparison.OrdinalIgnoreCase) == true)
+ return true;
+
+ if (c.CppClassName?.StartsWith("Aec", StringComparison.OrdinalIgnoreCase) == true)
+ return true;
+
+ if (c.CppClassName?.StartsWith("TDb", StringComparison.OrdinalIgnoreCase) == true)
+ return true;
+
+ return false;
+ }
+
#region Evaluation Graph, Enhanced Block etc.
private Matrix4 read4x3Matrix()
diff --git a/src/ACadSharp/IO/Templates/CadTemplate.cs b/src/ACadSharp/IO/Templates/CadTemplate.cs
index 4f6b9209e..8a8c17c1c 100644
--- a/src/ACadSharp/IO/Templates/CadTemplate.cs
+++ b/src/ACadSharp/IO/Templates/CadTemplate.cs
@@ -49,6 +49,7 @@ public void Build(CadDocumentBuilder builder)
switch (this.CadObject)
{
case ProxyEntity:
+ case ProxyObject:
case UnknownEntity:
case UnknownNonGraphicalObject:
break;