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;