From 0c6026a4c828f925eaa7821e95b37029bb1a0661 Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Mon, 20 Jul 2026 12:04:39 +0200 Subject: [PATCH 01/81] Read the surface entity family as modeler geometry --- src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs | 128 ++++++++++++++++++ src/ACadSharp/DxfFileToken.cs | 12 ++ src/ACadSharp/DxfSubclassMarker.cs | 14 ++ src/ACadSharp/Entities/ExtrudedSurface.cs | 22 +++ src/ACadSharp/Entities/LoftedSurface.cs | 22 +++ src/ACadSharp/Entities/NurbSurface.cs | 22 +++ src/ACadSharp/Entities/PlaneSurface.cs | 22 +++ src/ACadSharp/Entities/RevolvedSurface.cs | 22 +++ src/ACadSharp/Entities/Surface.cs | 37 +++++ src/ACadSharp/Entities/SweptSurface.cs | 22 +++ .../DWG/DwgStreamReaders/DwgObjectReader.cs | 34 +++++ .../DxfStreamReader/DxfSectionReaderBase.cs | 37 +++++ 12 files changed, 394 insertions(+) create mode 100644 src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs create mode 100644 src/ACadSharp/Entities/ExtrudedSurface.cs create mode 100644 src/ACadSharp/Entities/LoftedSurface.cs create mode 100644 src/ACadSharp/Entities/NurbSurface.cs create mode 100644 src/ACadSharp/Entities/PlaneSurface.cs create mode 100644 src/ACadSharp/Entities/RevolvedSurface.cs create mode 100644 src/ACadSharp/Entities/Surface.cs create mode 100644 src/ACadSharp/Entities/SweptSurface.cs diff --git a/src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs new file mode 100644 index 000000000..a8b8f2686 --- /dev/null +++ b/src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs @@ -0,0 +1,128 @@ +using ACadSharp.Entities; +using ACadSharp.IO; +using System.IO; +using System.Linq; +using System.Text; +using Xunit; + +namespace ACadSharp.Tests.IO.DXF; + +public class DxfSurfaceTests +{ + [Fact] + public void ReadGenericSurface() + { + CadDocument doc = readDxf(surfaceDxf("SURFACE", "AcDbSurface")); + + Surface surface = Assert.IsType(doc.Entities.Single()); + Assert.Equal(6, surface.UIsolines); + Assert.Equal(7, surface.VIsolines); + Assert.Equal(SatHeader + "\n" + SatBody, surface.GetAcisText()); + } + + [Fact] + public void ReadPlaneSurface() + { + CadDocument doc = readDxf(surfaceDxf("PLANESURFACE", "AcDbSurface", "AcDbPlaneSurface")); + + PlaneSurface surface = Assert.IsType(doc.Entities.Single()); + Assert.Equal(6, surface.UIsolines); + Assert.Equal(7, surface.VIsolines); + Assert.Equal(SatHeader + "\n" + SatBody, surface.GetAcisText()); + } + + [Fact] + public void ReadExtrudedSurface() + { + CadDocument doc = readDxf(surfaceDxf("EXTRUDEDSURFACE", "AcDbSurface", "AcDbExtrudedSurface")); + + ExtrudedSurface surface = Assert.IsType(doc.Entities.Single()); + Assert.Equal(6, surface.UIsolines); + Assert.Equal(7, surface.VIsolines); + Assert.Equal(SatHeader + "\n" + SatBody, surface.GetAcisText()); + } + + [Fact] + public void ReadLoftedSurface() + { + CadDocument doc = readDxf(surfaceDxf("LOFTEDSURFACE", "AcDbSurface", "AcDbLoftedSurface")); + + Assert.IsType(doc.Entities.Single()); + } + + [Fact] + public void ReadRevolvedSurface() + { + CadDocument doc = readDxf(surfaceDxf("REVOLVEDSURFACE", "AcDbSurface", "AcDbRevolvedSurface")); + + Assert.IsType(doc.Entities.Single()); + } + + [Fact] + public void ReadSweptSurface() + { + CadDocument doc = readDxf(surfaceDxf("SWEPTSURFACE", "AcDbSurface", "AcDbSweptSurface")); + + Assert.IsType(doc.Entities.Single()); + } + + [Fact] + public void ReadNurbSurface() + { + CadDocument doc = readDxf(surfaceDxf("NURBSURFACE", "AcDbSurface", "AcDbNurbSurface")); + + Assert.IsType(doc.Entities.Single()); + } + + private const string SatHeader = "400 0 1 0"; + + private const string SatBody = "body $-1 $1 $-1 $-1 #"; + + //A minimal entities section with one surface entity: the modeler geometry + //codes (70, 1, 3) carry the SAT text, the AcDbSurface codes 71 and 72 the + //isoline counts, followed by the empty subclass of the derived surface. + private static string surfaceDxf(string name, params string[] subclasses) + { + StringBuilder dxf = new StringBuilder(); + void add(string code, string value) + { + dxf.Append(code).Append('\n').Append(value).Append('\n'); + } + + add("0", "SECTION"); + add("2", "ENTITIES"); + add("0", name); + add("5", "A1"); + add("100", "AcDbEntity"); + add("8", "0"); + add("100", "AcDbModelerGeometry"); + add("70", "1"); + add("1", SatHeader); + add("1", SatBody); + + foreach (string subclass in subclasses) + { + add("100", subclass); + + if (subclass == "AcDbSurface") + { + add("71", "6"); + add("72", "7"); + } + } + + add("0", "ENDSEC"); + add("0", "EOF"); + + return dxf.ToString(); + } + + private static CadDocument readDxf(string dxf) + { + using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(dxf))) + using (DxfReader reader = new DxfReader(stream)) + { + return reader.Read(); + } + } +} diff --git a/src/ACadSharp/DxfFileToken.cs b/src/ACadSharp/DxfFileToken.cs index 950098c27..c43321492 100644 --- a/src/ACadSharp/DxfFileToken.cs +++ b/src/ACadSharp/DxfFileToken.cs @@ -68,6 +68,8 @@ public static class DxfFileToken public const string EntityEllipse = "ELLIPSE"; + public const string EntityExtrudedSurface = "EXTRUDEDSURFACE"; + public const string EntityHatch = "HATCH"; public const string EntityHelix = "HELIX"; @@ -82,6 +84,8 @@ public static class DxfFileToken public const string EntityLine = "LINE"; + public const string EntityLoftedSurface = "LOFTEDSURFACE"; + public const string EntityLwPolyline = "LWPOLYLINE"; public const string EntityMesh = "MESH"; @@ -92,12 +96,16 @@ public static class DxfFileToken public const string EntityMultiLeader = "MULTILEADER"; + public const string EntityNurbSurface = "NURBSURFACE"; + public const string EntityOle2Frame = "OLE2FRAME"; public const string EntityOleFrame = "OLEFRAME"; public const string EntityPdfUnderlay = "PDFUNDERLAY"; + public const string EntityPlaneSurface = "PLANESURFACE"; + public const string EntityPoint = "POINT"; public const string EntityPolyFaceMesh = "PFACE"; @@ -110,6 +118,8 @@ public static class DxfFileToken public const string EntityRegion = "REGION"; + public const string EntityRevolvedSurface = "REVOLVEDSURFACE"; + public const string EntitySection = "SECTION"; public const string EntitySeqend = "SEQEND"; @@ -124,6 +134,8 @@ public static class DxfFileToken public const string EntitySurface = "SURFACE"; + public const string EntitySweptSurface = "SWEPTSURFACE"; + public const string EntityTable = "ACAD_TABLE"; public const string EntityText = "TEXT"; diff --git a/src/ACadSharp/DxfSubclassMarker.cs b/src/ACadSharp/DxfSubclassMarker.cs index 8c340cb0d..406a61ad7 100644 --- a/src/ACadSharp/DxfSubclassMarker.cs +++ b/src/ACadSharp/DxfSubclassMarker.cs @@ -144,6 +144,8 @@ public static class DxfSubclassMarker public const string EvalGraphExpr = "AcDbEvalExpr"; + public const string ExtrudedSurface = "AcDbExtrudedSurface"; + public const string Face3d = "AcDbFace"; public const string Field = "AcDbField"; @@ -182,6 +184,8 @@ public static class DxfSubclassMarker public const string LinkedTableData = "AcDbLinkedTableData"; + public const string LoftedSurface = "AcDbLoftedSurface"; + public const string LwPolyline = "AcDbPolyline"; public const string Material = "AcDbMaterial"; @@ -206,6 +210,8 @@ public static class DxfSubclassMarker public const string MultiLeaderObjectContextData = "AcDbMLeaderObjectContextData"; + public const string NurbSurface = "AcDbNurbSurface"; + public const string ObjectContextData = "AcDbObjectContextData"; public const string Ole2Frame = "AcDbOle2Frame"; @@ -220,6 +226,8 @@ public static class DxfSubclassMarker public const string PdfReference = "AcDbPdfReference"; + public const string PlaneSurface = "AcDbPlaneSurface"; + public const string PlotSettings = "AcDbPlotSettings"; public const string Point = "AcDbPoint"; @@ -258,6 +266,8 @@ public static class DxfSubclassMarker public const string Ray = "AcDbRay"; + public const string RevolvedSurface = "AcDbRevolvedSurface"; + public const string Scale = "AcDbScale"; public const string Shape = "AcDbShape"; @@ -272,6 +282,10 @@ public static class DxfSubclassMarker public const string Spline = "AcDbSpline"; + public const string Surface = "AcDbSurface"; + + public const string SweptSurface = "AcDbSweptSurface"; + public const string Table = "AcDbSymbolTable"; public const string TableContent = "AcDbTableContent"; diff --git a/src/ACadSharp/Entities/ExtrudedSurface.cs b/src/ACadSharp/Entities/ExtrudedSurface.cs new file mode 100644 index 000000000..162e444ea --- /dev/null +++ b/src/ACadSharp/Entities/ExtrudedSurface.cs @@ -0,0 +1,22 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Entities +{ + /// + /// Represents an entity. + /// + /// + /// Object name
+ /// Dxf class name + ///
+ [DxfName(DxfFileToken.EntityExtrudedSurface)] + [DxfSubClass(DxfSubclassMarker.ExtrudedSurface)] + public class ExtrudedSurface : Surface + { + /// + public override string ObjectName => DxfFileToken.EntityExtrudedSurface; + + /// + public override string SubclassMarker => DxfSubclassMarker.ExtrudedSurface; + } +} diff --git a/src/ACadSharp/Entities/LoftedSurface.cs b/src/ACadSharp/Entities/LoftedSurface.cs new file mode 100644 index 000000000..7b72606b4 --- /dev/null +++ b/src/ACadSharp/Entities/LoftedSurface.cs @@ -0,0 +1,22 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Entities +{ + /// + /// Represents a entity. + /// + /// + /// Object name
+ /// Dxf class name + ///
+ [DxfName(DxfFileToken.EntityLoftedSurface)] + [DxfSubClass(DxfSubclassMarker.LoftedSurface)] + public class LoftedSurface : Surface + { + /// + public override string ObjectName => DxfFileToken.EntityLoftedSurface; + + /// + public override string SubclassMarker => DxfSubclassMarker.LoftedSurface; + } +} diff --git a/src/ACadSharp/Entities/NurbSurface.cs b/src/ACadSharp/Entities/NurbSurface.cs new file mode 100644 index 000000000..d229285ec --- /dev/null +++ b/src/ACadSharp/Entities/NurbSurface.cs @@ -0,0 +1,22 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Entities +{ + /// + /// Represents a entity. + /// + /// + /// Object name
+ /// Dxf class name + ///
+ [DxfName(DxfFileToken.EntityNurbSurface)] + [DxfSubClass(DxfSubclassMarker.NurbSurface)] + public class NurbSurface : Surface + { + /// + public override string ObjectName => DxfFileToken.EntityNurbSurface; + + /// + public override string SubclassMarker => DxfSubclassMarker.NurbSurface; + } +} diff --git a/src/ACadSharp/Entities/PlaneSurface.cs b/src/ACadSharp/Entities/PlaneSurface.cs new file mode 100644 index 000000000..2e49e5a9b --- /dev/null +++ b/src/ACadSharp/Entities/PlaneSurface.cs @@ -0,0 +1,22 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Entities +{ + /// + /// Represents a entity. + /// + /// + /// Object name
+ /// Dxf class name + ///
+ [DxfName(DxfFileToken.EntityPlaneSurface)] + [DxfSubClass(DxfSubclassMarker.PlaneSurface)] + public class PlaneSurface : Surface + { + /// + public override string ObjectName => DxfFileToken.EntityPlaneSurface; + + /// + public override string SubclassMarker => DxfSubclassMarker.PlaneSurface; + } +} diff --git a/src/ACadSharp/Entities/RevolvedSurface.cs b/src/ACadSharp/Entities/RevolvedSurface.cs new file mode 100644 index 000000000..0c73cf94a --- /dev/null +++ b/src/ACadSharp/Entities/RevolvedSurface.cs @@ -0,0 +1,22 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Entities +{ + /// + /// Represents a entity. + /// + /// + /// Object name
+ /// Dxf class name + ///
+ [DxfName(DxfFileToken.EntityRevolvedSurface)] + [DxfSubClass(DxfSubclassMarker.RevolvedSurface)] + public class RevolvedSurface : Surface + { + /// + public override string ObjectName => DxfFileToken.EntityRevolvedSurface; + + /// + public override string SubclassMarker => DxfSubclassMarker.RevolvedSurface; + } +} diff --git a/src/ACadSharp/Entities/Surface.cs b/src/ACadSharp/Entities/Surface.cs new file mode 100644 index 000000000..d58bf7351 --- /dev/null +++ b/src/ACadSharp/Entities/Surface.cs @@ -0,0 +1,37 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Entities +{ + /// + /// Represents a entity. + /// + /// + /// Object name
+ /// Dxf class name + ///
+ [DxfName(DxfFileToken.EntitySurface)] + [DxfSubClass(DxfSubclassMarker.Surface)] + public class Surface : ModelerGeometry + { + /// + public override ObjectType ObjectType => ObjectType.UNLISTED; + + /// + public override string ObjectName => DxfFileToken.EntitySurface; + + /// + public override string SubclassMarker => DxfSubclassMarker.Surface; + + /// + /// Number of U isolines. + /// + [DxfCodeValue(71)] + public short UIsolines { get; set; } + + /// + /// Number of V isolines. + /// + [DxfCodeValue(72)] + public short VIsolines { get; set; } + } +} diff --git a/src/ACadSharp/Entities/SweptSurface.cs b/src/ACadSharp/Entities/SweptSurface.cs new file mode 100644 index 000000000..758f75915 --- /dev/null +++ b/src/ACadSharp/Entities/SweptSurface.cs @@ -0,0 +1,22 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Entities +{ + /// + /// Represents a entity. + /// + /// + /// Object name
+ /// Dxf class name + ///
+ [DxfName(DxfFileToken.EntitySweptSurface)] + [DxfSubClass(DxfSubclassMarker.SweptSurface)] + public class SweptSurface : Surface + { + /// + public override string ObjectName => DxfFileToken.EntitySweptSurface; + + /// + public override string SubclassMarker => DxfSubclassMarker.SweptSurface; + } +} diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 51e783410..ab9f72ea6 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -5590,6 +5590,19 @@ private CadTemplate readSolid3D() return template; } + private CadTemplate readSurface(CadEntityTemplate template) + where T : Surface, new() + { + //AcDbModelerGeometry: the common modeler data carries the ACIS payload + //(embedded in the entity before R2013, AcDs data section from R2013 on) + this.readModelerGeometry(template); + + //The AcDbSurface data (isoline counts) and the surface subclass data + //(sweep, loft and revolve parameters) that follow are not decoded yet; + //the object data is length delimited, so the unread tail is skipped + return template; + } + private CadTemplate readSortentsTable() { SortEntitiesTable sortTable = new SortEntitiesTable(); @@ -6060,6 +6073,27 @@ private CadTemplate readUnlistedType(short classNumber) case "SORTENTSTABLE": template = this.readSortentsTable(); break; + case DxfFileToken.EntitySurface: + template = this.readSurface(new CadEntityTemplate()); + break; + case DxfFileToken.EntityExtrudedSurface: + template = this.readSurface(new CadEntityTemplate()); + break; + case DxfFileToken.EntityLoftedSurface: + template = this.readSurface(new CadEntityTemplate()); + break; + case DxfFileToken.EntityNurbSurface: + template = this.readSurface(new CadEntityTemplate()); + break; + case DxfFileToken.EntityPlaneSurface: + template = this.readSurface(new CadEntityTemplate()); + break; + case DxfFileToken.EntityRevolvedSurface: + template = this.readSurface(new CadEntityTemplate()); + break; + case DxfFileToken.EntitySweptSurface: + template = this.readSurface(new CadEntityTemplate()); + break; case "RASTERVARIABLES": template = this.readRasterVariables(); break; diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs index 5f4a2c09e..4cdada779 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs @@ -229,6 +229,20 @@ protected CadEntityTemplate readEntity() return this.readEntityCodes(new CadSolid3DTemplate(), this.readSolid3d); case DxfFileToken.EntityRegion: return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readModelerGeometry); + case DxfFileToken.EntitySurface: + return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readSurfaceEntity); + case DxfFileToken.EntityExtrudedSurface: + return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readSurfaceEntity); + case DxfFileToken.EntityLoftedSurface: + return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readSurfaceEntity); + case DxfFileToken.EntityNurbSurface: + return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readSurfaceEntity); + case DxfFileToken.EntityPlaneSurface: + return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readSurfaceEntity); + case DxfFileToken.EntityRevolvedSurface: + return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readSurfaceEntity); + case DxfFileToken.EntitySweptSurface: + return this.readEntityCodes(new CadModelerGeometryTemplate(), this.readSurfaceEntity); case DxfFileToken.EntityImage: return this.readEntityCodes(new CadWipeoutBaseTemplate(new RasterImage()), this.readWipeoutBase); case DxfFileToken.EntityWipeout: @@ -1612,6 +1626,29 @@ private bool readSolid3d(CadEntityTemplate template, DxfMap map, string subclass } } + private bool readSurfaceEntity(CadEntityTemplate template, DxfMap map, string subclass = null) + { + switch (this._reader.Code) + { + //SAT text and modeler geometry codes: same handling as the other + //modeler geometry entities (the template accumulates the payload) + case 1: + case 2: + case 3: + case 290: + return this.readModelerGeometry(template, map, DxfSubclassMarker.ModelerGeometry); + default: + //surface codes (the isoline counts) resolve through the map of + //the subclass being read + if (this.tryAssignCurrentValue(template.CadObject, map)) + { + return true; + } + + return this.readModelerGeometry(template, map, DxfSubclassMarker.ModelerGeometry); + } + } + private bool readSpline(CadEntityTemplate template, DxfMap map, string subclass = null) { CadSplineTemplate tmp = template as CadSplineTemplate; From f6d283303d188c0e516d471d2bac1544ac498af0 Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Wed, 30 Sep 2026 09:30:52 +0200 Subject: [PATCH 02/81] Skip the surface entities in the DWG and DXF writers --- .../IO/WriterSingleObjectTests.cs | 22 +++++++++++++++++++ .../DWG/DwgStreamWriters/DwgObjectWriter.cs | 1 + .../DxfSectionWriterBase.Entities.cs | 1 + 3 files changed, 24 insertions(+) diff --git a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs index bd3e8fcb8..2ba720bf2 100644 --- a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs @@ -93,6 +93,7 @@ static WriterSingleObjectTests() Data.Add(new(nameof(SingleCaseGenerator.XData))); Data.Add(new(nameof(SingleCaseGenerator.XRef))); Data.Add(new(nameof(SingleCaseGenerator.SPlineCreation))); + Data.Add(new(nameof(SingleCaseGenerator.SurfaceEntities))); Data.Add(new(nameof(SingleCaseGenerator.TextAlignment))); Data.Add(new(nameof(SingleCaseGenerator.InvalidEntities))); Data.Add(new(nameof(SingleCaseGenerator.CreateXRecords))); @@ -2676,6 +2677,27 @@ public void SPlineCreation() }; } + public void SurfaceEntities() + { + Line line = new Line(XYZ.Zero, new XYZ(10, 10, 0)); + this.Document.Entities.Add(line); + + //Surfaces are not written yet, the writers skip them + this.Document.Entities.Add(new Surface()); + this.Document.Entities.Add(new ExtrudedSurface()); + this.Document.Entities.Add(new LoftedSurface()); + this.Document.Entities.Add(new NurbSurface()); + this.Document.Entities.Add(new PlaneSurface()); + this.Document.Entities.Add(new RevolvedSurface()); + this.Document.Entities.Add(new SweptSurface()); + + this.AssertRoundtrip = (doc) => + { + Assert.NotNull(doc.GetCadObject(line.Handle)); + Assert.Empty(doc.Entities.OfType()); + }; + } + public void TextAlignment() { XYZ insert = new XYZ(0, 0, 0); diff --git a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.cs b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.cs index b0b0b9381..d607032e5 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.cs @@ -154,6 +154,7 @@ private bool isEntitySupported(Entity entity) case Solid3D: case CadBody: case Region: + case Surface: this.notify($"Entity type not implemented {entity.GetType().FullName}", NotificationType.NotImplemented); return false; default: diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs index eecf48192..4155b5998 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs @@ -159,6 +159,7 @@ private bool isEntitySupported(Entity entity) case Solid3D: case CadBody: case Region: + case Surface: this.notify($"Entity type not implemented {entity.GetType().FullName}", NotificationType.NotImplemented); return false; default: From 7a6958e8add3b922950367132f760e67e2a84e2f Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Wed, 30 Sep 2026 09:31:03 +0200 Subject: [PATCH 03/81] Use file-scoped namespaces in the surface entity files --- src/ACadSharp/Entities/ExtrudedSurface.cs | 33 +++++++------ src/ACadSharp/Entities/LoftedSurface.cs | 33 +++++++------ src/ACadSharp/Entities/NurbSurface.cs | 33 +++++++------ src/ACadSharp/Entities/PlaneSurface.cs | 33 +++++++------ src/ACadSharp/Entities/RevolvedSurface.cs | 33 +++++++------ src/ACadSharp/Entities/Surface.cs | 57 +++++++++++------------ src/ACadSharp/Entities/SweptSurface.cs | 33 +++++++------ 7 files changed, 124 insertions(+), 131 deletions(-) diff --git a/src/ACadSharp/Entities/ExtrudedSurface.cs b/src/ACadSharp/Entities/ExtrudedSurface.cs index 162e444ea..99b0d0ac4 100644 --- a/src/ACadSharp/Entities/ExtrudedSurface.cs +++ b/src/ACadSharp/Entities/ExtrudedSurface.cs @@ -1,22 +1,21 @@ using ACadSharp.Attributes; -namespace ACadSharp.Entities +namespace ACadSharp.Entities; + +/// +/// Represents an entity. +/// +/// +/// Object name
+/// Dxf class name +///
+[DxfName(DxfFileToken.EntityExtrudedSurface)] +[DxfSubClass(DxfSubclassMarker.ExtrudedSurface)] +public class ExtrudedSurface : Surface { - /// - /// Represents an entity. - /// - /// - /// Object name
- /// Dxf class name - ///
- [DxfName(DxfFileToken.EntityExtrudedSurface)] - [DxfSubClass(DxfSubclassMarker.ExtrudedSurface)] - public class ExtrudedSurface : Surface - { - /// - public override string ObjectName => DxfFileToken.EntityExtrudedSurface; + /// + public override string ObjectName => DxfFileToken.EntityExtrudedSurface; - /// - public override string SubclassMarker => DxfSubclassMarker.ExtrudedSurface; - } + /// + public override string SubclassMarker => DxfSubclassMarker.ExtrudedSurface; } diff --git a/src/ACadSharp/Entities/LoftedSurface.cs b/src/ACadSharp/Entities/LoftedSurface.cs index 7b72606b4..34cedb838 100644 --- a/src/ACadSharp/Entities/LoftedSurface.cs +++ b/src/ACadSharp/Entities/LoftedSurface.cs @@ -1,22 +1,21 @@ using ACadSharp.Attributes; -namespace ACadSharp.Entities +namespace ACadSharp.Entities; + +/// +/// Represents a entity. +/// +/// +/// Object name
+/// Dxf class name +///
+[DxfName(DxfFileToken.EntityLoftedSurface)] +[DxfSubClass(DxfSubclassMarker.LoftedSurface)] +public class LoftedSurface : Surface { - /// - /// Represents a entity. - /// - /// - /// Object name
- /// Dxf class name - ///
- [DxfName(DxfFileToken.EntityLoftedSurface)] - [DxfSubClass(DxfSubclassMarker.LoftedSurface)] - public class LoftedSurface : Surface - { - /// - public override string ObjectName => DxfFileToken.EntityLoftedSurface; + /// + public override string ObjectName => DxfFileToken.EntityLoftedSurface; - /// - public override string SubclassMarker => DxfSubclassMarker.LoftedSurface; - } + /// + public override string SubclassMarker => DxfSubclassMarker.LoftedSurface; } diff --git a/src/ACadSharp/Entities/NurbSurface.cs b/src/ACadSharp/Entities/NurbSurface.cs index d229285ec..3a7a9099d 100644 --- a/src/ACadSharp/Entities/NurbSurface.cs +++ b/src/ACadSharp/Entities/NurbSurface.cs @@ -1,22 +1,21 @@ using ACadSharp.Attributes; -namespace ACadSharp.Entities +namespace ACadSharp.Entities; + +/// +/// Represents a entity. +/// +/// +/// Object name
+/// Dxf class name +///
+[DxfName(DxfFileToken.EntityNurbSurface)] +[DxfSubClass(DxfSubclassMarker.NurbSurface)] +public class NurbSurface : Surface { - /// - /// Represents a entity. - /// - /// - /// Object name
- /// Dxf class name - ///
- [DxfName(DxfFileToken.EntityNurbSurface)] - [DxfSubClass(DxfSubclassMarker.NurbSurface)] - public class NurbSurface : Surface - { - /// - public override string ObjectName => DxfFileToken.EntityNurbSurface; + /// + public override string ObjectName => DxfFileToken.EntityNurbSurface; - /// - public override string SubclassMarker => DxfSubclassMarker.NurbSurface; - } + /// + public override string SubclassMarker => DxfSubclassMarker.NurbSurface; } diff --git a/src/ACadSharp/Entities/PlaneSurface.cs b/src/ACadSharp/Entities/PlaneSurface.cs index 2e49e5a9b..bb5b4e173 100644 --- a/src/ACadSharp/Entities/PlaneSurface.cs +++ b/src/ACadSharp/Entities/PlaneSurface.cs @@ -1,22 +1,21 @@ using ACadSharp.Attributes; -namespace ACadSharp.Entities +namespace ACadSharp.Entities; + +/// +/// Represents a entity. +/// +/// +/// Object name
+/// Dxf class name +///
+[DxfName(DxfFileToken.EntityPlaneSurface)] +[DxfSubClass(DxfSubclassMarker.PlaneSurface)] +public class PlaneSurface : Surface { - /// - /// Represents a entity. - /// - /// - /// Object name
- /// Dxf class name - ///
- [DxfName(DxfFileToken.EntityPlaneSurface)] - [DxfSubClass(DxfSubclassMarker.PlaneSurface)] - public class PlaneSurface : Surface - { - /// - public override string ObjectName => DxfFileToken.EntityPlaneSurface; + /// + public override string ObjectName => DxfFileToken.EntityPlaneSurface; - /// - public override string SubclassMarker => DxfSubclassMarker.PlaneSurface; - } + /// + public override string SubclassMarker => DxfSubclassMarker.PlaneSurface; } diff --git a/src/ACadSharp/Entities/RevolvedSurface.cs b/src/ACadSharp/Entities/RevolvedSurface.cs index 0c73cf94a..01957253e 100644 --- a/src/ACadSharp/Entities/RevolvedSurface.cs +++ b/src/ACadSharp/Entities/RevolvedSurface.cs @@ -1,22 +1,21 @@ using ACadSharp.Attributes; -namespace ACadSharp.Entities +namespace ACadSharp.Entities; + +/// +/// Represents a entity. +/// +/// +/// Object name
+/// Dxf class name +///
+[DxfName(DxfFileToken.EntityRevolvedSurface)] +[DxfSubClass(DxfSubclassMarker.RevolvedSurface)] +public class RevolvedSurface : Surface { - /// - /// Represents a entity. - /// - /// - /// Object name
- /// Dxf class name - ///
- [DxfName(DxfFileToken.EntityRevolvedSurface)] - [DxfSubClass(DxfSubclassMarker.RevolvedSurface)] - public class RevolvedSurface : Surface - { - /// - public override string ObjectName => DxfFileToken.EntityRevolvedSurface; + /// + public override string ObjectName => DxfFileToken.EntityRevolvedSurface; - /// - public override string SubclassMarker => DxfSubclassMarker.RevolvedSurface; - } + /// + public override string SubclassMarker => DxfSubclassMarker.RevolvedSurface; } diff --git a/src/ACadSharp/Entities/Surface.cs b/src/ACadSharp/Entities/Surface.cs index d58bf7351..d8eda6c75 100644 --- a/src/ACadSharp/Entities/Surface.cs +++ b/src/ACadSharp/Entities/Surface.cs @@ -1,37 +1,36 @@ using ACadSharp.Attributes; -namespace ACadSharp.Entities +namespace ACadSharp.Entities; + +/// +/// Represents a entity. +/// +/// +/// Object name
+/// Dxf class name +///
+[DxfName(DxfFileToken.EntitySurface)] +[DxfSubClass(DxfSubclassMarker.Surface)] +public class Surface : ModelerGeometry { - /// - /// Represents a entity. - /// - /// - /// Object name
- /// Dxf class name - ///
- [DxfName(DxfFileToken.EntitySurface)] - [DxfSubClass(DxfSubclassMarker.Surface)] - public class Surface : ModelerGeometry - { - /// - public override ObjectType ObjectType => ObjectType.UNLISTED; + /// + public override ObjectType ObjectType => ObjectType.UNLISTED; - /// - public override string ObjectName => DxfFileToken.EntitySurface; + /// + public override string ObjectName => DxfFileToken.EntitySurface; - /// - public override string SubclassMarker => DxfSubclassMarker.Surface; + /// + public override string SubclassMarker => DxfSubclassMarker.Surface; - /// - /// Number of U isolines. - /// - [DxfCodeValue(71)] - public short UIsolines { get; set; } + /// + /// Number of U isolines. + /// + [DxfCodeValue(71)] + public short UIsolines { get; set; } - /// - /// Number of V isolines. - /// - [DxfCodeValue(72)] - public short VIsolines { get; set; } - } + /// + /// Number of V isolines. + /// + [DxfCodeValue(72)] + public short VIsolines { get; set; } } diff --git a/src/ACadSharp/Entities/SweptSurface.cs b/src/ACadSharp/Entities/SweptSurface.cs index 758f75915..cfec7a085 100644 --- a/src/ACadSharp/Entities/SweptSurface.cs +++ b/src/ACadSharp/Entities/SweptSurface.cs @@ -1,22 +1,21 @@ using ACadSharp.Attributes; -namespace ACadSharp.Entities +namespace ACadSharp.Entities; + +/// +/// Represents a entity. +/// +/// +/// Object name
+/// Dxf class name +///
+[DxfName(DxfFileToken.EntitySweptSurface)] +[DxfSubClass(DxfSubclassMarker.SweptSurface)] +public class SweptSurface : Surface { - /// - /// Represents a entity. - /// - /// - /// Object name
- /// Dxf class name - ///
- [DxfName(DxfFileToken.EntitySweptSurface)] - [DxfSubClass(DxfSubclassMarker.SweptSurface)] - public class SweptSurface : Surface - { - /// - public override string ObjectName => DxfFileToken.EntitySweptSurface; + /// + public override string ObjectName => DxfFileToken.EntitySweptSurface; - /// - public override string SubclassMarker => DxfSubclassMarker.SweptSurface; - } + /// + public override string SubclassMarker => DxfSubclassMarker.SweptSurface; } From 8beb6600e42d0f7b526df4c020b55268f3e79e69 Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Wed, 30 Sep 2026 09:31:03 +0200 Subject: [PATCH 04/81] Define the DXF class of the surface entities --- src/ACadSharp/Entities/ExtrudedSurface.cs | 16 ++++++++++++++++ src/ACadSharp/Entities/LoftedSurface.cs | 16 ++++++++++++++++ src/ACadSharp/Entities/NurbSurface.cs | 16 ++++++++++++++++ src/ACadSharp/Entities/PlaneSurface.cs | 16 ++++++++++++++++ src/ACadSharp/Entities/RevolvedSurface.cs | 16 ++++++++++++++++ src/ACadSharp/Entities/Surface.cs | 18 +++++++++++++++++- src/ACadSharp/Entities/SweptSurface.cs | 16 ++++++++++++++++ 7 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/Entities/ExtrudedSurface.cs b/src/ACadSharp/Entities/ExtrudedSurface.cs index 99b0d0ac4..01f4c1095 100644 --- a/src/ACadSharp/Entities/ExtrudedSurface.cs +++ b/src/ACadSharp/Entities/ExtrudedSurface.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using ACadSharp.Classes; namespace ACadSharp.Entities; @@ -18,4 +19,19 @@ public class ExtrudedSurface : Surface /// public override string SubclassMarker => DxfSubclassMarker.ExtrudedSurface; + + /// + public override DxfClass GetDxfClass() + { + return new DxfClass + { + CppClassName = DxfSubclassMarker.ExtrudedSurface, + DwgVersion = (ACadVersion)26, + DxfName = DxfFileToken.EntityExtrudedSurface, + ItemClassId = 498, + MaintenanceVersion = 0, + ProxyFlags = ProxyFlags.None, + WasZombie = false, + }; + } } diff --git a/src/ACadSharp/Entities/LoftedSurface.cs b/src/ACadSharp/Entities/LoftedSurface.cs index 34cedb838..b179f884e 100644 --- a/src/ACadSharp/Entities/LoftedSurface.cs +++ b/src/ACadSharp/Entities/LoftedSurface.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using ACadSharp.Classes; namespace ACadSharp.Entities; @@ -18,4 +19,19 @@ public class LoftedSurface : Surface /// public override string SubclassMarker => DxfSubclassMarker.LoftedSurface; + + /// + public override DxfClass GetDxfClass() + { + return new DxfClass + { + CppClassName = DxfSubclassMarker.LoftedSurface, + DwgVersion = (ACadVersion)26, + DxfName = DxfFileToken.EntityLoftedSurface, + ItemClassId = 498, + MaintenanceVersion = 0, + ProxyFlags = ProxyFlags.None, + WasZombie = false, + }; + } } diff --git a/src/ACadSharp/Entities/NurbSurface.cs b/src/ACadSharp/Entities/NurbSurface.cs index 3a7a9099d..282ac6ca1 100644 --- a/src/ACadSharp/Entities/NurbSurface.cs +++ b/src/ACadSharp/Entities/NurbSurface.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using ACadSharp.Classes; namespace ACadSharp.Entities; @@ -18,4 +19,19 @@ public class NurbSurface : Surface /// public override string SubclassMarker => DxfSubclassMarker.NurbSurface; + + /// + public override DxfClass GetDxfClass() + { + return new DxfClass + { + CppClassName = DxfSubclassMarker.NurbSurface, + DwgVersion = (ACadVersion)26, + DxfName = DxfFileToken.EntityNurbSurface, + ItemClassId = 498, + MaintenanceVersion = 0, + ProxyFlags = (ProxyFlags)4095, + WasZombie = false, + }; + } } diff --git a/src/ACadSharp/Entities/PlaneSurface.cs b/src/ACadSharp/Entities/PlaneSurface.cs index bb5b4e173..89166b973 100644 --- a/src/ACadSharp/Entities/PlaneSurface.cs +++ b/src/ACadSharp/Entities/PlaneSurface.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using ACadSharp.Classes; namespace ACadSharp.Entities; @@ -18,4 +19,19 @@ public class PlaneSurface : Surface /// public override string SubclassMarker => DxfSubclassMarker.PlaneSurface; + + /// + public override DxfClass GetDxfClass() + { + return new DxfClass + { + CppClassName = DxfSubclassMarker.PlaneSurface, + DwgVersion = (ACadVersion)26, + DxfName = DxfFileToken.EntityPlaneSurface, + ItemClassId = 498, + MaintenanceVersion = 0, + ProxyFlags = (ProxyFlags)4095, + WasZombie = false, + }; + } } diff --git a/src/ACadSharp/Entities/RevolvedSurface.cs b/src/ACadSharp/Entities/RevolvedSurface.cs index 01957253e..a5767c5ad 100644 --- a/src/ACadSharp/Entities/RevolvedSurface.cs +++ b/src/ACadSharp/Entities/RevolvedSurface.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using ACadSharp.Classes; namespace ACadSharp.Entities; @@ -18,4 +19,19 @@ public class RevolvedSurface : Surface /// public override string SubclassMarker => DxfSubclassMarker.RevolvedSurface; + + /// + public override DxfClass GetDxfClass() + { + return new DxfClass + { + CppClassName = DxfSubclassMarker.RevolvedSurface, + DwgVersion = (ACadVersion)26, + DxfName = DxfFileToken.EntityRevolvedSurface, + ItemClassId = 498, + MaintenanceVersion = 0, + ProxyFlags = ProxyFlags.None, + WasZombie = false, + }; + } } diff --git a/src/ACadSharp/Entities/Surface.cs b/src/ACadSharp/Entities/Surface.cs index d8eda6c75..ab810989d 100644 --- a/src/ACadSharp/Entities/Surface.cs +++ b/src/ACadSharp/Entities/Surface.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using ACadSharp.Classes; namespace ACadSharp.Entities; @@ -11,7 +12,7 @@ namespace ACadSharp.Entities; /// [DxfName(DxfFileToken.EntitySurface)] [DxfSubClass(DxfSubclassMarker.Surface)] -public class Surface : ModelerGeometry +public class Surface : ModelerGeometry, IDxfClassDefined { /// public override ObjectType ObjectType => ObjectType.UNLISTED; @@ -33,4 +34,19 @@ public class Surface : ModelerGeometry /// [DxfCodeValue(72)] public short VIsolines { get; set; } + + /// + public virtual DxfClass GetDxfClass() + { + return new DxfClass + { + CppClassName = DxfSubclassMarker.Surface, + DwgVersion = (ACadVersion)26, + DxfName = DxfFileToken.EntitySurface, + ItemClassId = 498, + MaintenanceVersion = 0, + ProxyFlags = (ProxyFlags)4095, + WasZombie = false, + }; + } } diff --git a/src/ACadSharp/Entities/SweptSurface.cs b/src/ACadSharp/Entities/SweptSurface.cs index cfec7a085..329240388 100644 --- a/src/ACadSharp/Entities/SweptSurface.cs +++ b/src/ACadSharp/Entities/SweptSurface.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using ACadSharp.Classes; namespace ACadSharp.Entities; @@ -18,4 +19,19 @@ public class SweptSurface : Surface /// public override string SubclassMarker => DxfSubclassMarker.SweptSurface; + + /// + public override DxfClass GetDxfClass() + { + return new DxfClass + { + CppClassName = DxfSubclassMarker.SweptSurface, + DwgVersion = (ACadVersion)26, + DxfName = DxfFileToken.EntitySweptSurface, + ItemClassId = 498, + MaintenanceVersion = 0, + ProxyFlags = ProxyFlags.None, + WasZombie = false, + }; + } } From be6d5692509dc1646f14a573948692c3e8152e06 Mon Sep 17 00:00:00 2001 From: Petr Sommer Date: Wed, 30 Sep 2026 18:36:12 +0000 Subject: [PATCH 05/81] Skip used handles when assigning one from the handle seed --- .../IO/DXF/DxfHandleSeedTests.cs | 80 +++++++++++++++++++ src/ACadSharp/CadDocument.cs | 6 ++ 2 files changed, 86 insertions(+) create mode 100644 src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs diff --git a/src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs new file mode 100644 index 000000000..3e236ffd6 --- /dev/null +++ b/src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs @@ -0,0 +1,80 @@ +using ACadSharp.Entities; +using ACadSharp.IO; +using System.IO; +using System.Linq; +using System.Text; +using Xunit; + +namespace ACadSharp.Tests.IO.DXF; + +public class DxfHandleSeedTests +{ + [Fact] + public void ReadR12WithZeroHandleSeed() + { + // R12 file without handles ($HANDLING 0) that still declares $HANDSEED 0, + // as some CAM exporters write it. Handle 0 is taken by the document itself. + string dxf = string.Join("\n", + "0", "SECTION", + "2", "HEADER", + "9", "$ACADVER", + "1", "AC1009", + "9", "$HANDLING", + "70", "0", + "9", "$HANDSEED", + "5", "0", + "0", "ENDSEC", + "0", "SECTION", + "2", "TABLES", + "0", "TABLE", + "2", "LTYPE", + "70", "1", + "0", "LTYPE", + "2", "CONTINUOUS", + "70", "0", + "3", "Solid line", + "72", "65", + "73", "0", + "40", "0.0", + "0", "ENDTAB", + "0", "TABLE", + "2", "LAYER", + "70", "2", + "0", "LAYER", + "2", "0", + "70", "0", + "62", "7", + "6", "CONTINUOUS", + "0", "LAYER", + "2", "Cutting", + "70", "0", + "62", "1", + "6", "CONTINUOUS", + "0", "ENDTAB", + "0", "ENDSEC", + "0", "SECTION", + "2", "ENTITIES", + "0", "LINE", + "8", "Cutting", + "10", "0.0", + "20", "0.0", + "30", "0.0", + "11", "100.0", + "21", "50.0", + "31", "0.0", + "0", "ENDSEC", + "0", "EOF"); + + CadDocument doc; + using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(dxf))) + using (DxfReader reader = new DxfReader(stream)) + { + doc = reader.Read(); + } + + Line line = Assert.IsType(doc.Entities.Single()); + Assert.Equal("Cutting", line.Layer.Name); + Assert.NotEqual(0UL, line.Handle); + Assert.True(doc.Header.HandleSeed > line.Handle); + } +} diff --git a/src/ACadSharp/CadDocument.cs b/src/ACadSharp/CadDocument.cs index 10fd3e442..9632b61ac 100644 --- a/src/ACadSharp/CadDocument.cs +++ b/src/ACadSharp/CadDocument.cs @@ -641,6 +641,12 @@ internal void AddCadObject(CadObject cadObject) { var nextHandle = this.Header.HandleSeed; + //The seed read from a file may point to a used handle, e.g. $HANDSEED 0 collides with the document itself + while (this._cadObjects.ContainsKey(nextHandle)) + { + nextHandle++; + } + cadObject.Handle = nextHandle; this.Header.HandleSeed = nextHandle + 1; } From 6ba46a3efa102ee4b057168b20d8f8fab6353fe9 Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Thu, 1 Oct 2026 09:35:49 +0200 Subject: [PATCH 06/81] Join every binary chunk of an ACDSDATA record column A column payload longer than one 310 line kept only the last chunk, so ModelerGeometry.AcisData was truncated for real DXF 2013+ files. --- src/ACadSharp.Tests/IO/DXF/DxfAcdsDataSectionTests.cs | 11 +++++++---- .../DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs | 11 +++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/ACadSharp.Tests/IO/DXF/DxfAcdsDataSectionTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfAcdsDataSectionTests.cs index 9314edd0a..6b22deedc 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfAcdsDataSectionTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfAcdsDataSectionTests.cs @@ -9,13 +9,16 @@ namespace ACadSharp.Tests.IO.DXF; public class DxfAcdsDataSectionTests { - [Fact] - public void ReadAcdsDataAttachesAcisPayloadToOwnerEntity() + [Theory] + [InlineData("414349532042696E61727946696C65")] + [InlineData("41434953204269", "6E61727946696C65")] + public void ReadAcdsDataAttachesAcisPayloadToOwnerEntity(params string[] chunks) { // Minimal R2013+ style layout: the REGION entity carries no geometry codes, // the SAB payload lives in the ACDSDATA section and points back to the // entity through the AcDbDs::ID handle at group code 320. - // The payload here is just the "ACIS BinaryFile" signature (15 bytes). + // The payload here is just the "ACIS BinaryFile" signature (15 bytes), + // split over one 310 line per chunk. string dxf = string.Join("\n", "0", "SECTION", "2", "ENTITIES", @@ -47,7 +50,7 @@ public void ReadAcdsDataAttachesAcisPayloadToOwnerEntity() "2", "ASM_Data", "280", "15", "94", "15", - "310", "414349532042696E61727946696C65", + string.Join("\n", chunks.Select(c => $"310\n{c}")), "0", "ENDSEC", "0", "EOF"); diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs index 78060d426..67dfb383a 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs @@ -1,6 +1,7 @@ using ACadSharp.DataStorage; using System; using System.Collections.Generic; +using System.IO; namespace ACadSharp.IO.DXF.DxfStreamReader; @@ -90,6 +91,7 @@ private AcdsRecordColumn readAcdsRecordColumn() { AcdsRecordColumn column = new(); column.Name = this._reader.ValueAsString; + MemoryStream binaryData = null; this._reader.ReadNext(); @@ -102,6 +104,10 @@ private AcdsRecordColumn readAcdsRecordColumn() case 280: column.DataType = this._reader.ValueAsShort; break; + case 310: + binaryData ??= new(); + binaryData.Write(this._reader.ValueAsBinaryChunk, 0, this._reader.ValueAsBinaryChunk.Length); + break; default: column.CodeValuePair = new KeyValuePair(this._reader.Code, this._reader.Value); break; @@ -110,6 +116,11 @@ private AcdsRecordColumn readAcdsRecordColumn() this._reader.ReadNext(); } + if (binaryData != null) + { + column.CodeValuePair = new KeyValuePair(310, binaryData.ToArray()); + } + return column; } From 369f9088869b7b65f49052ebd57e158bb0242531 Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Thu, 1 Oct 2026 11:07:21 +0200 Subject: [PATCH 07/81] Remove the DXF surface reading tests --- src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs | 128 ------------------ 1 file changed, 128 deletions(-) delete mode 100644 src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs diff --git a/src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs deleted file mode 100644 index a8b8f2686..000000000 --- a/src/ACadSharp.Tests/IO/DXF/DxfSurfaceTests.cs +++ /dev/null @@ -1,128 +0,0 @@ -using ACadSharp.Entities; -using ACadSharp.IO; -using System.IO; -using System.Linq; -using System.Text; -using Xunit; - -namespace ACadSharp.Tests.IO.DXF; - -public class DxfSurfaceTests -{ - [Fact] - public void ReadGenericSurface() - { - CadDocument doc = readDxf(surfaceDxf("SURFACE", "AcDbSurface")); - - Surface surface = Assert.IsType(doc.Entities.Single()); - Assert.Equal(6, surface.UIsolines); - Assert.Equal(7, surface.VIsolines); - Assert.Equal(SatHeader + "\n" + SatBody, surface.GetAcisText()); - } - - [Fact] - public void ReadPlaneSurface() - { - CadDocument doc = readDxf(surfaceDxf("PLANESURFACE", "AcDbSurface", "AcDbPlaneSurface")); - - PlaneSurface surface = Assert.IsType(doc.Entities.Single()); - Assert.Equal(6, surface.UIsolines); - Assert.Equal(7, surface.VIsolines); - Assert.Equal(SatHeader + "\n" + SatBody, surface.GetAcisText()); - } - - [Fact] - public void ReadExtrudedSurface() - { - CadDocument doc = readDxf(surfaceDxf("EXTRUDEDSURFACE", "AcDbSurface", "AcDbExtrudedSurface")); - - ExtrudedSurface surface = Assert.IsType(doc.Entities.Single()); - Assert.Equal(6, surface.UIsolines); - Assert.Equal(7, surface.VIsolines); - Assert.Equal(SatHeader + "\n" + SatBody, surface.GetAcisText()); - } - - [Fact] - public void ReadLoftedSurface() - { - CadDocument doc = readDxf(surfaceDxf("LOFTEDSURFACE", "AcDbSurface", "AcDbLoftedSurface")); - - Assert.IsType(doc.Entities.Single()); - } - - [Fact] - public void ReadRevolvedSurface() - { - CadDocument doc = readDxf(surfaceDxf("REVOLVEDSURFACE", "AcDbSurface", "AcDbRevolvedSurface")); - - Assert.IsType(doc.Entities.Single()); - } - - [Fact] - public void ReadSweptSurface() - { - CadDocument doc = readDxf(surfaceDxf("SWEPTSURFACE", "AcDbSurface", "AcDbSweptSurface")); - - Assert.IsType(doc.Entities.Single()); - } - - [Fact] - public void ReadNurbSurface() - { - CadDocument doc = readDxf(surfaceDxf("NURBSURFACE", "AcDbSurface", "AcDbNurbSurface")); - - Assert.IsType(doc.Entities.Single()); - } - - private const string SatHeader = "400 0 1 0"; - - private const string SatBody = "body $-1 $1 $-1 $-1 #"; - - //A minimal entities section with one surface entity: the modeler geometry - //codes (70, 1, 3) carry the SAT text, the AcDbSurface codes 71 and 72 the - //isoline counts, followed by the empty subclass of the derived surface. - private static string surfaceDxf(string name, params string[] subclasses) - { - StringBuilder dxf = new StringBuilder(); - void add(string code, string value) - { - dxf.Append(code).Append('\n').Append(value).Append('\n'); - } - - add("0", "SECTION"); - add("2", "ENTITIES"); - add("0", name); - add("5", "A1"); - add("100", "AcDbEntity"); - add("8", "0"); - add("100", "AcDbModelerGeometry"); - add("70", "1"); - add("1", SatHeader); - add("1", SatBody); - - foreach (string subclass in subclasses) - { - add("100", subclass); - - if (subclass == "AcDbSurface") - { - add("71", "6"); - add("72", "7"); - } - } - - add("0", "ENDSEC"); - add("0", "EOF"); - - return dxf.ToString(); - } - - private static CadDocument readDxf(string dxf) - { - using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(dxf))) - using (DxfReader reader = new DxfReader(stream)) - { - return reader.Read(); - } - } -} From d05ea12ccac19d2a1735ef81648b7f07495cad3c Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Thu, 1 Oct 2026 12:02:44 +0200 Subject: [PATCH 08/81] Remove the modeler geometry comment from readSurface --- src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index ab9f72ea6..9c3a3eef3 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -5593,8 +5593,6 @@ private CadTemplate readSolid3D() private CadTemplate readSurface(CadEntityTemplate template) where T : Surface, new() { - //AcDbModelerGeometry: the common modeler data carries the ACIS payload - //(embedded in the entity before R2013, AcDs data section from R2013 on) this.readModelerGeometry(template); //The AcDbSurface data (isoline counts) and the surface subclass data From f68a81e0a6b50893845e5f79fa4ea2c0672a1f23 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 1 Oct 2026 12:24:10 +0200 Subject: [PATCH 09/81] Refactor Prototype1b DWG parsing Add `DwgPrototype1bReaderV2` and `FileSegmentHeader`, switch `DwgReader` to the new reader, expand `CadFileDataStorage` for file-segment storage, and refactor `DwgPrototype1bReader` to use specialized helpers, safer segment loading, and repeated-segment collections. --- .../DataStorage/CadFileDataStorage.cs | 53 + .../DataStorage/FileSegmentHeader.cs | 35 + .../DwgStreamReaders/DwgPrototype1bReader.cs | 1188 +++++++++-------- .../DwgPrototype1bReaderV2.cs | 140 ++ src/ACadSharp/IO/DwgReader.cs | 7 +- 5 files changed, 826 insertions(+), 597 deletions(-) create mode 100644 src/ACadSharp/DataStorage/FileSegmentHeader.cs create mode 100644 src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs diff --git a/src/ACadSharp/DataStorage/CadFileDataStorage.cs b/src/ACadSharp/DataStorage/CadFileDataStorage.cs index 09ab0738f..bd78a214e 100644 --- a/src/ACadSharp/DataStorage/CadFileDataStorage.cs +++ b/src/ACadSharp/DataStorage/CadFileDataStorage.cs @@ -11,4 +11,57 @@ internal class CadFileDataStorage public const string AsmData = "ASM_Data"; public const string Id = "AcDbDs::ID"; +} + +internal class FileSegment +{ + public FileSegmentHeader Header { get; set; } +} + +internal class FileSegmentIndex : FileSegment +{ + public List Entries { get; } = new(); + + public class Entry + { + public ulong Offset { get; set; } + + public uint Size { get; set; } + } + + public void AddEntry(ulong offset, uint size) + { + this.Entries.Add(new Entry { Offset = offset, Size = size }); + } +} + +internal class FileHeader +{ + public int DataIndexSegmentIndex { get; set; } + + public int DataStorageRevision { get; set; } + + public int FileHeaderSize { get; set; } + + public uint FileSignature { get; set; } + + public int FileSize { get; set; } + + public int PreviousSaveIndex { get; set; } + + public int SchemaIndexSegmentIndex { get; set; } + + public int SearchSegmentIndex { get; set; } + + public int SegmentIndexEntryCount { get; set; } + + public int SegmentIndexOffset { get; set; } + + public int SegmentIndexUnknown { get; set; } + + public int Unknown1 { get; set; } = 2; + + public int Unknown2 { get; set; } = 0; + + public int Version { get; set; } = 2; } \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/FileSegmentHeader.cs b/src/ACadSharp/DataStorage/FileSegmentHeader.cs new file mode 100644 index 000000000..f01c3accc --- /dev/null +++ b/src/ACadSharp/DataStorage/FileSegmentHeader.cs @@ -0,0 +1,35 @@ +namespace ACadSharp.DataStorage; + +internal class FileSegmentHeader +{ + public int DataStorageRevision { get; set; } + + public int IsBlob { get; set; } + + public string Name { get; set; } + + public int ObjectDataAlignmentOffset { get; set; } + + public uint SegmentIndex { get; set; } + + public uint SegmentSize { get; set; } + + public short Signature { get; set; } + + public int SystemDataAlignmentOffset { get; set; } + + public int Unknown2 { get; set; } + + public int Unknown3 { get; set; } + + /// + /// The size of the segment header. The is always 6 characters + /// long and the AlignmentBytes is always 8 bytes long. + /// + public const uint SIZE = 48; + + public override string ToString() + { + return Name; + } +} diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs index 6a5933269..79bbfbfe3 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs @@ -7,737 +7,739 @@ using System.Linq; using System.Text; -namespace ACadSharp.IO.DWG.DwgStreamReaders +namespace ACadSharp.IO.DWG.DwgStreamReaders; + +internal class DwgPrototype1bReader : DwgSectionIO { - internal class DwgPrototype1bReader : DwgSectionIO - { - public static readonly uint[] TYPE_SIZES = new uint[] { 0, 0, 2, 1, 2, 4, 8, 1, 2, 4, 8, 4, 8, 0, 0, 0 }; - private readonly IDwgStreamReader _reader; - private readonly Prototype1b.DataStorage _storage = new Prototype1b.DataStorage(); - private readonly DwgDocumentBuilder _builder; + public override string SectionName => DwgSectionDefinition.AcDsPrototype; - public override string SectionName => DwgSectionDefinition.AcDsPrototype; + public static readonly uint[] TYPE_SIZES = new uint[] { 0, 0, 2, 1, 2, 4, 8, 1, 2, 4, 8, 4, 8, 0, 0, 0 }; - public DwgPrototype1bReader(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) - { - this._reader = reader; - this._builder = builder; - } + private readonly DwgDocumentBuilder _builder; - public Prototype1b.DataStorage Read() - { - try - { - this._storage.FileHeader = this.readFileHeader(); + private readonly IDwgStreamReader _reader; - // Indices for reading values - this._storage.IndexPointers = new DataStoragePointers(); - this._storage.IndexPointers.SegmentIndex = this.readSegmentIndex(); - this._storage.IndexPointers.DataIndex = this.readDataIndex(); - this._storage.IndexPointers.SchemaIndex = this.readSchemaIndex(); - this._storage.IndexPointers.FreeSpace = this.readFreeSpace(); // Index to empty spaces (padding or so) within the file + private readonly Prototype1b.DataStorage _storage = new Prototype1b.DataStorage(); - // Probably the file state before the last save - this._storage.PreviousSave = this.readPreviousSave(); + public DwgPrototype1bReader(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) + { + this._reader = reader; + this._builder = builder; + } - // Schema data lookup - this._storage.SchemaSearch = this.readSchemaSearch(); - foreach (SchemaSearchEntry search in this._storage.SchemaSearch.Entries) - { - search.SchemaName = this._storage.IndexPointers.SchemaIndex.SchemaNames[search.SchemaNameIndex]; - } + public Prototype1b.DataStorage Read() + { + try + { + this._storage.FileHeader = this.readFileHeader(); - this._storage.SchemaFields = []; - this._storage.DataFields = []; - this._storage.Blobs = []; + // Indices for reading values + this._storage.IndexPointers = new DataStoragePointers(); + this._storage.IndexPointers.SegmentIndex = this.readSegmentIndex(); + this._storage.IndexPointers.DataIndex = this.readDataIndex(); + this._storage.IndexPointers.SchemaIndex = this.readSchemaIndex(); + this._storage.IndexPointers.FreeSpace = this.readFreeSpace(); // Index to empty spaces (padding or so) within the file - // From this point on it should be possible to read storage entries sequentially - // The issue is that sometimes there seemingly are empty padding sections (which should be - // referenced by the FreeSpace entry) that are missing from the FreeSpace definition or are larger - // than specified in the FreeSpace definition. + // Probably the file state before the last save + this._storage.PreviousSave = this.readPreviousSave(); - this.ReadSegments(); - } - catch (Exception ex) + // Schema data lookup + this._storage.SchemaSearch = this.readSchemaSearch(); + foreach (SchemaSearchEntry search in this._storage.SchemaSearch.Entries) { - if (!this._builder.Configuration.Failsafe) - throw; - - this.notify("An error occurred while reading the Prototype1b", NotificationType.Error, ex); + search.SchemaName = this._storage.IndexPointers.SchemaIndex.SchemaNames[search.SchemaNameIndex]; } - return this._storage; - } + this._storage.SchemaFields = []; + this._storage.DataFields = []; + this._storage.Blobs = []; - internal IPrototype1bSegment ReadSegmentAt(ulong offset) - { - long pos = this._reader.Position; - this._reader.Position = (long)offset; - IPrototype1bSegment segment = this.ReadSegment(); - this._reader.Position = pos; - return segment; - } + // From this point on it should be possible to read storage entries sequentially + // The issue is that sometimes there seemingly are empty padding sections (which should be + // referenced by the FreeSpace entry) that are missing from the FreeSpace definition or are larger + // than specified in the FreeSpace definition. - private SegmentIndex readSegmentIndex() - { - return (SegmentIndex)this.ReadSegmentAt((ulong)this._storage.FileHeader.SegmentIndexOffset); + this.ReadSegments(); } - - private DataIndex readDataIndex() + catch (Exception ex) { - return (DataIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.DataIndexSegmentIndex].Offset); + if (!this._builder.Configuration.Failsafe) + throw; + + this.notify("An error occurred while reading the Prototype1b", NotificationType.Error, ex); } - private SchemaSearch readSchemaSearch() + return this._storage; + } + + internal IPrototype1bSegment ReadSegment() + { + IPrototype1bSegment segment; + long segmentStartPosition = this._reader.Position; + SegmentHeader header = this.readSubItemHeader(); + segment = this.ReadSegmentData(header); + + // Blob01 entries do not specify how large they are (The headers SegmentSize only has the size of the header without any blob data, blobs are aligned to the next 128 byte boundary) + if (header.IsBlob != 1) { - return (SchemaSearch)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SearchSegmentIndex].Offset); + // Skip to the end of this sub item + long readSize = this._reader.Position - segmentStartPosition; + long paddingDataSize = header.SegmentSize - readSize; + byte[] _padding = this._reader.ReadBytes((int)paddingDataSize); // Should be filled with only 0x70 values (TODO: Sometimes there are some other strange padding bytes) } - private FreeSpace readFreeSpace() + return segment; + } + + internal IPrototype1bSegment ReadSegmentAt(ulong offset) + { + long pos = this._reader.Position; + this._reader.Position = (long)offset; + IPrototype1bSegment segment = this.ReadSegment(); + this._reader.Position = pos; + return segment; + } + + internal IPrototype1bSegment ReadSegmentData(SegmentHeader header) => header.Name switch + { + "segidx" => this.readSegmentIndexValue(header), + "datidx" => this.readDataIndexValue(header), + "schidx" => this.readSchemaIndexValue(header), + "search" => this.readSchemaSearchValue(header), + "freesp" => this.readFreeSpaceValue(header), + "prvsav" => this.readPreviousSaveValue(header), + "schdat" => this.readSchemaDataValue(header), + "_data_" => this.readDataValue(header), + "blob01" => this.readBlob01(header), + _ => throw new InvalidDataException($"Unknown segment type \"{header.Name}\""), + }; + + internal void ReadSegments() + { + foreach (KeyValuePair entry in this._storage.IndexPointers.SegmentIndex.Pointers) { - if (this._storage.FileHeader.FreeSpaceSegmentIndex == 0) return null; - return (FreeSpace)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.FreeSpaceSegmentIndex].Offset); + if (entry.Value.Size == 0) continue; + this._reader.Position = (long)entry.Value.Offset; + + IPrototype1bSegment segment = this.ReadSegment(); + switch (segment) + { + // Those entries have already been parsed (as they were directly referenced in the header + // and there should probably not be any additional entries of the same type) + case SegmentIndex segidx: + case DataIndex datidx: + case SchemaIndex schidx: + case SchemaSearch search: + case FreeSpace freesp: + case PreviousSave prvsav: + break; + + // Add entries which are types that can appear multiple times to a collection of them + case SchemaData schdat: + this._storage.SchemaFields.Add(schdat); + break; + case DataField data: + this._storage.DataFields.Add(data); + break; + case Blob01 blob: + this._storage.Blobs.Add(blob); + break; + default: + break; + } } + } - private PreviousSave readPreviousSave() + private Blob01 readBlob01(SegmentHeader header) + { + Blob01 blob = new() { - if (this._storage.FileHeader.PreviousSaveIndex == 0) return null; - return (PreviousSave)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.PreviousSaveIndex].Offset); - } + Header = header, + TotalDataSize = this._reader.ReadRawULong(), + PageStartOffset = this._reader.ReadRawULong(), + PageIndex = this._reader.ReadUInt(), + PageCount = this._reader.ReadUInt(), + PageDataSize = this._reader.ReadRawULong() + }; + blob.Data = this._reader.ReadBytes((int)blob.PageDataSize); - private SchemaIndex readSchemaIndex() + // Align to the next 128 byte boundary + long boundary = this._reader.Position % 128; + if (boundary != 0) { - return (SchemaIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SchemaIndexSegmentIndex].Offset); + byte[] _ = this._reader.ReadBytes((int)(128 - boundary)); } - internal void ReadSegments() - { - foreach (KeyValuePair entry in this._storage.IndexPointers.SegmentIndex.Pointers) - { - if (entry.Value.Size == 0) continue; - this._reader.Position = (long)entry.Value.Offset; + return blob; + } - IPrototype1bSegment segment = this.ReadSegment(); - switch (segment) - { - // Those entries have already been parsed (as they were directly referenced in the header - // and there should probably not be any additional entries of the same type) - case SegmentIndex segidx: - case DataIndex datidx: - case SchemaIndex schidx: - case SchemaSearch search: - case FreeSpace freesp: - case PreviousSave prvsav: - break; - - // Add entries which are types that can appear multiple times to a collection of them - case SchemaData schdat: - this._storage.SchemaFields.Add(schdat); - break; - case DataField data: - this._storage.DataFields.Add(data); - break; - case Blob01 blob: - this._storage.Blobs.Add(blob); - break; - default: - break; - } - } - } + private DataIndex readDataIndex() + { + return (DataIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.DataIndexSegmentIndex].Offset); + } + + private DataIndex readDataIndexValue(SegmentHeader header) + { + DataIndex index = new() + { + Header = header + }; + int entryCount = this._reader.ReadInt(); + index.Unknown1 = this._reader.ReadInt(); - internal IPrototype1bSegment ReadSegment() + index.Entries = []; + for (int j = 0; j < entryCount; j++) { - IPrototype1bSegment segment; - long segmentStartPosition = this._reader.Position; - SegmentHeader header = this.readSubItemHeader(); - segment = this.ReadSegmentData(header); + uint dataSegmentIndex = this._reader.ReadUInt(); + uint dataSegmentOffset = this._reader.ReadUInt(); + uint schemaIndex = this._reader.ReadUInt(); + + // Segment index values of 0 can be ignored (stub entries) + if (dataSegmentIndex == 0) continue; - // Blob01 entries do not specify how large they are (The headers SegmentSize only has the size of the header without any blob data, blobs are aligned to the next 128 byte boundary) - if (header.IsBlob != 1) + // Get or create a data index entry + if (!index.Entries.TryGetValue(dataSegmentIndex, out DataIndexEntry entry)) { - // Skip to the end of this sub item - long readSize = this._reader.Position - segmentStartPosition; - long paddingDataSize = header.SegmentSize - readSize; - byte[] _padding = this._reader.ReadBytes((int)paddingDataSize); // Should be filled with only 0x70 values (TODO: Sometimes there are some other strange padding bytes) + entry = new DataIndexEntry + { + DataSegmentIndex = dataSegmentIndex, + Pointers = [] + }; } - return segment; - } - - private SegmentHeader readSubItemHeader() - { - return new() + // Add the current pointer to the list of pointers + entry.Pointers.Add(new DataIndexEntryPointer { - Signature = this._reader.ReadShort(), - Name = Encoding.ASCII.GetString(this._reader.ReadBytes(6)), - SegmentIndex = this._reader.ReadUInt(), - IsBlob = this._reader.ReadInt(), - SegmentSize = this._reader.ReadUInt(), // Multiple of 0x40 bytes (AutoCAD uses 0x80), padded wth 0x70 values - Unknown1 = this._reader.ReadInt(), - DataStorageRevision = this._reader.ReadInt(), - Unknown2 = this._reader.ReadInt(), - SystemDataAlignmentOffset = this._reader.ReadInt(), - ObjectDataAlignmentOffset = this._reader.ReadInt(), - AlignmentBytes = this._reader.ReadBytes(8) // Align to next 16 byte boundary - }; + Offset = dataSegmentOffset, + SchemaIndex = schemaIndex + }); + index.Entries[dataSegmentIndex] = entry; } - internal IPrototype1bSegment ReadSegmentData(SegmentHeader header) => header.Name switch + return index; + } + + private DataField readDataValue(SegmentHeader header) + { + DataField dataField = new() { - "segidx" => this.readSegmentIndexValue(header), - "datidx" => this.readDataIndexValue(header), - "schidx" => this.readSchemaIndexValue(header), - "search" => this.readSchemaSearchValue(header), - "freesp" => this.readFreeSpaceValue(header), - "prvsav" => this.readPreviousSaveValue(header), - "schdat" => this.readSchemaDataValue(header), - "_data_" => this.readDataValue(header), - "blob01" => this.readBlob01(header), - _ => throw new InvalidDataException($"Unknown segment type \"{header.Name}\""), + Header = header, + Entries = [] }; - private FileHeader readFileHeader() + // Read headers + List headers = []; + uint headerEndPosition = (uint)this._reader.Position; + if (!this._storage.IndexPointers.DataIndex.Entries.TryGetValue(header.SegmentIndex, out DataIndexEntry entry)) { - uint fileSignature = this._reader.ReadUInt(); - short fileHeaderSize = this._reader.ReadShort(); - return new() - { - FileSignature = fileSignature, - FileHeaderSize = fileHeaderSize, - UnknownFlag = this._reader.ReadShort(), - Unknown1 = this._reader.ReadInt(), - Version = this._reader.ReadInt(), - Unknown2 = this._reader.ReadInt(), - DataStorageRevision = this._reader.ReadInt(), - SegmentIndexOffset = this._reader.ReadInt(), - SegmentIndexUnknown = this._reader.ReadInt(), - SegmentIndexEntryCount = this._reader.ReadInt(), - SchemaIndexSegmentIndex = this._reader.ReadInt(), - DataIndexSegmentIndex = this._reader.ReadInt(), - SearchSegmentIndex = this._reader.ReadInt(), - PreviousSaveIndex = this._reader.ReadInt(), - FileSize = this._reader.ReadInt(), - Unknown3 = this._reader.ReadInt(), - FreeSpaceSegmentIndex = this._reader.ReadInt(), - FreeSpaceEntryCount = this._reader.ReadInt(), - Unknown5 = this._reader.ReadInt(), - - UnknownRemaining = this._reader.ReadBytes(fileHeaderSize - 72) - }; + return dataField; } - private Schema readSchema() + // As entries can be out of order, keep updating the max position to resume correctly after reading all entries + long maxPos = 0; + foreach (DataIndexEntryPointer pointer in entry.Pointers) { - Schema schema = new(); - - ushort indexCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - schema.Indices = new ulong[indexCount]; - for (int i = 0; i < indexCount; i++) - { - schema.Indices[i] = this._reader.ReadRawULong(); - } + this._reader.Position = headerEndPosition + pointer.Offset; + headers.Add(new DataHeader + { + EntrySize = this._reader.ReadUInt(), + Unknown1 = this._reader.ReadUInt(), + Handle = this._reader.ReadRawULong(), + DataOffset = this._reader.ReadUInt(), + SchemaIndex = pointer.SchemaIndex + }); + maxPos = Math.Max(maxPos, this._reader.Position); + } + this._reader.Position = maxPos; - ushort propCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - schema.Properties = new SchemaProperty[propCount]; - for (int i = 0; i < propCount; i++) - { - schema.Properties[i] = this.readSchemaProperty(); - } + // Sort headers to get ascending offset values to prevent wrong offset calculations (TODO: Is this ok or should the order maybe be preserved?) + headers = headers.OrderBy(x => x.DataOffset).ToList(); - return schema; + // Get the position to the beginning of the data content section and skip padding and unreferenced DataHeader entries + uint headerStartPosition = headerEndPosition - SegmentHeader.SIZE; + uint dataRefPosition = headerStartPosition + (uint)(header.ObjectDataAlignmentOffset << 4); + if (dataRefPosition != this._reader.Position) + { + byte[] data = this._reader.ReadBytes((int)(dataRefPosition - this._reader.Position)); + // Many unreferenced / seemingly dangling DataHeader entries might be defined here, they would + // also have valid file data in the next step, where file contents are being read. This can be a lot of entries, e.g. + // 10 - 20 valid DataHeader entries have been seen } - private SchemaProperty readSchemaProperty() + // Read data associated with headers + for (int i = 0; i < headers.Count; i++) { - SchemaProperty prop = new() - { - PropertyFlags = this._reader.ReadUInt(), // 1 = Unknown / 2 = NoType / 8 = Unknown - NameIndex = this._reader.ReadUInt() - }; - - // Get type size - if (!((prop.PropertyFlags & (1 << 1)) != 0)) - { - prop.Type = this._reader.ReadUInt(); // 0 - 15 - if (prop.Type == 0xe) + DataValue value = new(); + DataHeader dataHeader = headers[i]; + uint recordStreamOffset = dataRefPosition + dataHeader.DataOffset; + uint maxRecordSize = i + 1 < headers.Count ? (headers[i + 1].DataOffset - dataHeader.DataOffset) : (header.SegmentSize - (uint)(header.ObjectDataAlignmentOffset << 4) - dataHeader.DataOffset); + + this._reader.Position = recordStreamOffset; + value.DataSize = this._reader.ReadUInt(); + if ((value.DataSize + 4) <= maxRecordSize) + { + value.Data = this._reader.ReadBytes((int)value.DataSize); + + //// Example how to detect the actual file type based on the file byte signature + //byte[] FILE_MAGIC_PNG = [137, 80, 78, 71, 13, 10, 26, 10]; + //byte[] FILE_MAGIC_ACIS_BINARY = Encoding.ASCII.GetBytes("ACIS BinaryFile"); + //byte[] FILE_MAGIC_ASM_BINARY = Encoding.ASCII.GetBytes("ASM BinaryFile"); + + //if (value.Data.Length >= FILE_MAGIC_PNG.Length && value.Data.AsSpan(0, FILE_MAGIC_PNG.Length).SequenceEqual(FILE_MAGIC_PNG)) { + // // The bytes represent a PNG file + //} + //else if (value.Data.Length >= FILE_MAGIC_ACIS_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ACIS_BINARY.Length).SequenceEqual(FILE_MAGIC_ACIS_BINARY)) { + // // The bytes represent an ACIS binary file + //} + //else if (value.Data.Length >= FILE_MAGIC_ASM_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ASM_BINARY.Length).SequenceEqual(FILE_MAGIC_ASM_BINARY)) { + // // The bytes represent an ACIS / ASM binary file + //} + //else { + // // The bytes represent a file other than a PNG and ACIS file + //} + } + else if (value.DataSize == 0xbb106bb1) + { + value.BlobReference = new DataBlobReference() { - prop.TypeSize = this._reader.ReadUInt(); - } - else + TotalDataSize = this._reader.ReadRawULong(), + PageCount = this._reader.ReadUInt(), + RecordSize = this._reader.ReadUInt(), + PageSize = this._reader.ReadUInt(), + LastPageSize = this._reader.ReadUInt(), + Unknown1 = this._reader.ReadUInt(), + Unknown2 = this._reader.ReadUInt(), + SegmentPointers = [] + }; + for (int j = 0; j < value.BlobReference.PageCount; j++) { - prop.TypeSize = TYPE_SIZES[prop.Type.Value]; + value.BlobReference.SegmentPointers.Add(( + this._reader.ReadUInt(), // segment index + this._reader.ReadUInt() // size + )); } } - - // Read unknown fields - if (prop.PropertyFlags == 1) + else { - prop.Unknown1 = this._reader.ReadUInt(); - } - else if (prop.PropertyFlags == 8) - { - prop.Unknown2 = this._reader.ReadUInt(); + Debugger.Break(); // This should not be possible + value.DataSize = 0; } - // Read values - prop.PropertyValueCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - prop.Values = new byte[prop.PropertyValueCount, prop.TypeSize]; - if (prop.TypeSize != 0) + dataField.Entries.Add(new DataEntry { - for (int i = 0; i < prop.PropertyValueCount; i++) - { - byte[] propertyValue = this._reader.ReadBytes((int)prop.TypeSize); - for (int j = 0; j < propertyValue.Length; j++) - { - prop.Values[0, j] = propertyValue[j]; - } - } - } - - return prop; + Header = dataHeader, + Value = value + }); } - private SegmentIndex readSegmentIndexValue(SegmentHeader header) - { - SegmentIndex index = new() - { - Header = header, - Pointers = [] - }; - for (int j = 0; j < this._storage.FileHeader.SegmentIndexEntryCount; j++) - { - index.Pointers[j] = new SegmentIndexEntry - { - Offset = this._reader.ReadRawULong(), - Size = this._reader.ReadUInt() - }; - } + return dataField; + } - // TODO: Sometimes additional bytes (that are not padding bytes) follow here until the segment size - // They actually seem to be garbage data and can be ignored. Often times there are seemingly - // random bytes without any pattern and sometimes there are bytes representing 2-Byte strings. - // Garbage string examples are e.g.: - // --> "nslation\0" followed by the bytes [0, 0, 7, 0, 176, 4] - // --> "k\\auto" - // --> "op> "utocad" - // --> "atio" + private FileHeader readFileHeader() + { + uint fileSignature = this._reader.ReadUInt(); + short fileHeaderSize = this._reader.ReadShort(); + return new() + { + FileSignature = fileSignature, + FileHeaderSize = fileHeaderSize, + UnknownFlag = this._reader.ReadShort(), + Unknown1 = this._reader.ReadInt(), + Version = this._reader.ReadInt(), + Unknown2 = this._reader.ReadInt(), + DataStorageRevision = this._reader.ReadInt(), + SegmentIndexOffset = this._reader.ReadInt(), + SegmentIndexUnknown = this._reader.ReadInt(), + SegmentIndexEntryCount = this._reader.ReadInt(), + SchemaIndexSegmentIndex = this._reader.ReadInt(), + DataIndexSegmentIndex = this._reader.ReadInt(), + SearchSegmentIndex = this._reader.ReadInt(), + PreviousSaveIndex = this._reader.ReadInt(), + FileSize = this._reader.ReadInt(), + Unknown3 = this._reader.ReadInt(), + FreeSpaceSegmentIndex = this._reader.ReadInt(), + FreeSpaceEntryCount = this._reader.ReadInt(), + Unknown5 = this._reader.ReadInt(), + + UnknownRemaining = this._reader.ReadBytes(fileHeaderSize - 72) + }; + } - return index; - } + private FreeSpace readFreeSpace() + { + if (this._storage.FileHeader.FreeSpaceSegmentIndex == 0) return null; + return (FreeSpace)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.FreeSpaceSegmentIndex].Offset); + } - private DataIndex readDataIndexValue(SegmentHeader header) + private FreeSpace readFreeSpaceValue(SegmentHeader header) + { + FreeSpace space = new() { - DataIndex index = new() - { - Header = header + Header = header, + Unknown = this._reader.ReadRawULong(), + FreeSpaces = new FreeSpaceArea[this._storage.FileHeader.FreeSpaceEntryCount] + }; + for (int i = 0; i < space.FreeSpaces.Length; i++) + { // TODO: Sometimes when there is an additional freespace definition, it might not match the `FreeSpaceEntryCount` count + ulong position = this._reader.ReadRawULong(); + uint size = this._reader.ReadUInt(); + space.FreeSpaces[i] = new FreeSpaceArea + { + Position = position, + Size = size }; - int entryCount = this._reader.ReadInt(); - index.Unknown1 = this._reader.ReadInt(); + } + return space; + } - index.Entries = []; - for (int j = 0; j < entryCount; j++) - { - uint dataSegmentIndex = this._reader.ReadUInt(); - uint dataSegmentOffset = this._reader.ReadUInt(); - uint schemaIndex = this._reader.ReadUInt(); + private string readNullTerminatedString() + { + byte b; + List bytes = []; + while ((b = this._reader.ReadByte()) != 0) + { + bytes.Add(b); + } + return Encoding.UTF8.GetString(bytes.ToArray()); // TODO: Is this ASCII only or UTF-8 ? + } - // Segment index values of 0 can be ignored (stub entries) - if (dataSegmentIndex == 0) continue; + private PreviousSave readPreviousSave() + { + if (this._storage.FileHeader.PreviousSaveIndex == 0) return null; + return (PreviousSave)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.PreviousSaveIndex].Offset); + } - // Get or create a data index entry - if (!index.Entries.TryGetValue(dataSegmentIndex, out DataIndexEntry entry)) - { - entry = new DataIndexEntry - { - DataSegmentIndex = dataSegmentIndex, - Pointers = [] - }; - } + private PreviousSave readPreviousSaveValue(SegmentHeader header) => new() + { + Header = header, + FileHeader = this.readFileHeader() + }; - // Add the current pointer to the list of pointers - entry.Pointers.Add(new DataIndexEntryPointer - { - Offset = dataSegmentOffset, - SchemaIndex = schemaIndex - }); - index.Entries[dataSegmentIndex] = entry; - } + private Schema readSchema() + { + Schema schema = new(); - return index; + ushort indexCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + schema.Indices = new ulong[indexCount]; + for (int i = 0; i < indexCount; i++) + { + schema.Indices[i] = this._reader.ReadRawULong(); } - private SchemaIndex readSchemaIndexValue(SegmentHeader header) + ushort propCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + schema.Properties = new SchemaProperty[propCount]; + for (int i = 0; i < propCount; i++) { - SchemaIndex schemaIndex = new() - { - Header = header - }; - uint unknownPropertyCount = this._reader.ReadUInt(); - schemaIndex.SchemaPointer = new SchemaPropertyPointer[unknownPropertyCount]; - schemaIndex.Unknown1 = this._reader.ReadUInt(); - - for (int i = 0; i < unknownPropertyCount; i++) - { - schemaIndex.SchemaPointer[i] = new SchemaPropertyPointer - { - Index = this._reader.ReadUInt(), - SchemaIndex = this._reader.ReadUInt(), - Offset = this._reader.ReadUInt(), - }; - } - - schemaIndex.UnknownMagic = this._reader.ReadRawLong(); - uint propertyEntryCount = this._reader.ReadUInt(); - uint unknownCount = this._reader.ReadUInt(); // 0; 8 - - schemaIndex.UnknownPropertyEntryPointer = new SchemaPropertyPointer[propertyEntryCount]; - for (int i = 0; i < propertyEntryCount; i++) - { - schemaIndex.UnknownPropertyEntryPointer[i] = new SchemaPropertyPointer - { - SchemaIndex = this._reader.ReadUInt(), - Offset = this._reader.ReadUInt(), - Index = this._reader.ReadUInt(), - }; - } - - schemaIndex.SchemaUnknownPropertyPointer = new SchemaPropertyPointer[unknownCount]; - for (int i = 0; i < unknownCount; i++) - { - schemaIndex.SchemaUnknownPropertyPointer[i] = new SchemaPropertyPointer - { - Index = this._reader.ReadUInt(), - SchemaIndex = this._reader.ReadUInt(), - Offset = this._reader.ReadUInt(), - }; - } + schema.Properties[i] = this.readSchemaProperty(); + } - schemaIndex.UnknownIndex1 = this._reader.ReadUInt(); + return schema; + } - // Align to the next 16 byte boundary - long boundary = this._reader.Position % 16; - if (boundary != 0) + private SchemaData readSchemaDataValue(SegmentHeader header) + { + // Read unknown schema properties + List unknownProps = []; + foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaUnknownPropertyPointer) + { + if (pointer.SchemaIndex != header.SegmentIndex) continue; + unknownProps.Add(new SchemaUnknownProperty { - byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); - } + DataSize = this._reader.ReadUInt(), + UnknownFlags = this._reader.ReadUInt() + }); + } - // Read labels (NULL terminated strings) - uint stringCount = this._reader.ReadUInt(); - schemaIndex.SchemaNames = new string[stringCount]; - for (int i = 0; i < stringCount; i++) - { - schemaIndex.SchemaNames[i] = this.readNullTerminatedString(); - } + // Read schemas + List schemaValues = []; + foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaPointer) + { + if (pointer.SchemaIndex != header.SegmentIndex) continue; + Schema schema = this.readSchema(); + schema.Index = pointer.Index; + schema.Name = this._storage.IndexPointers.SchemaIndex.SchemaNames[pointer.Index]; + schemaValues.Add(schema); + } - return schemaIndex; + // Align to the next 16 byte boundary + long boundary = this._reader.Position % 16; + if (boundary != 0) + { + byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); } - private DataField readDataValue(SegmentHeader header) + // Read schema property names + uint propertyNameCount = this._reader.ReadUInt(); + string[] propertyNames = new string[propertyNameCount]; + for (int i = 0; i < propertyNameCount; i++) { - DataField dataField = new() - { - Header = header, - Entries = [] - }; + propertyNames[i] = this.readNullTerminatedString(); + } - // Read headers - List headers = []; - uint headerEndPosition = (uint)this._reader.Position; - if (!this._storage.IndexPointers.DataIndex.Entries.TryGetValue(header.SegmentIndex, out DataIndexEntry entry)) + // Assign schema Property names + foreach (Schema schema in schemaValues) + { + foreach (SchemaProperty property in schema.Properties) { - return dataField; + property.Name = propertyNames[property.NameIndex]; } + } - // As entries can be out of order, keep updating the max position to resume correctly after reading all entries - long maxPos = 0; - foreach (DataIndexEntryPointer pointer in entry.Pointers) - { - this._reader.Position = headerEndPosition + pointer.Offset; - headers.Add(new DataHeader - { - EntrySize = this._reader.ReadUInt(), - Unknown1 = this._reader.ReadUInt(), - Handle = this._reader.ReadRawULong(), - DataOffset = this._reader.ReadUInt(), - SchemaIndex = pointer.SchemaIndex - }); - maxPos = Math.Max(maxPos, this._reader.Position); - } - this._reader.Position = maxPos; + return new SchemaData + { + Header = header, + SchemaUnknownProperties = unknownProps, + Values = schemaValues, + }; + } - // Sort headers to get ascending offset values to prevent wrong offset calculations (TODO: Is this ok or should the order maybe be preserved?) - headers = headers.OrderBy(x => x.DataOffset).ToList(); + private SchemaIndex readSchemaIndex() + { + return (SchemaIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SchemaIndexSegmentIndex].Offset); + } - // Get the position to the beginning of the data content section and skip padding and unreferenced DataHeader entries - uint headerStartPosition = headerEndPosition - SegmentHeader.SIZE; - uint dataRefPosition = headerStartPosition + (uint)(header.ObjectDataAlignmentOffset << 4); - if (dataRefPosition != this._reader.Position) - { - byte[] data = this._reader.ReadBytes((int)(dataRefPosition - this._reader.Position)); - // Many unreferenced / seemingly dangling DataHeader entries might be defined here, they would - // also have valid file data in the next step, where file contents are being read. This can be a lot of entries, e.g. - // 10 - 20 valid DataHeader entries have been seen - } + private SchemaIndex readSchemaIndexValue(SegmentHeader header) + { + SchemaIndex schemaIndex = new() + { + Header = header + }; + uint unknownPropertyCount = this._reader.ReadUInt(); + schemaIndex.SchemaPointer = new SchemaPropertyPointer[unknownPropertyCount]; + schemaIndex.Unknown1 = this._reader.ReadUInt(); - // Read data associated with headers - for (int i = 0; i < headers.Count; i++) + for (int i = 0; i < unknownPropertyCount; i++) + { + schemaIndex.SchemaPointer[i] = new SchemaPropertyPointer { - DataValue value = new(); - DataHeader dataHeader = headers[i]; - uint recordStreamOffset = dataRefPosition + dataHeader.DataOffset; - uint maxRecordSize = i + 1 < headers.Count ? (headers[i + 1].DataOffset - dataHeader.DataOffset) : (header.SegmentSize - (uint)(header.ObjectDataAlignmentOffset << 4) - dataHeader.DataOffset); - - this._reader.Position = recordStreamOffset; - value.DataSize = this._reader.ReadUInt(); - if ((value.DataSize + 4) <= maxRecordSize) - { - value.Data = this._reader.ReadBytes((int)value.DataSize); - - //// Example how to detect the actual file type based on the file byte signature - //byte[] FILE_MAGIC_PNG = [137, 80, 78, 71, 13, 10, 26, 10]; - //byte[] FILE_MAGIC_ACIS_BINARY = Encoding.ASCII.GetBytes("ACIS BinaryFile"); - //byte[] FILE_MAGIC_ASM_BINARY = Encoding.ASCII.GetBytes("ASM BinaryFile"); - - //if (value.Data.Length >= FILE_MAGIC_PNG.Length && value.Data.AsSpan(0, FILE_MAGIC_PNG.Length).SequenceEqual(FILE_MAGIC_PNG)) { - // // The bytes represent a PNG file - //} - //else if (value.Data.Length >= FILE_MAGIC_ACIS_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ACIS_BINARY.Length).SequenceEqual(FILE_MAGIC_ACIS_BINARY)) { - // // The bytes represent an ACIS binary file - //} - //else if (value.Data.Length >= FILE_MAGIC_ASM_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ASM_BINARY.Length).SequenceEqual(FILE_MAGIC_ASM_BINARY)) { - // // The bytes represent an ACIS / ASM binary file - //} - //else { - // // The bytes represent a file other than a PNG and ACIS file - //} - } - else if (value.DataSize == 0xbb106bb1) - { - value.BlobReference = new DataBlobReference() - { - TotalDataSize = this._reader.ReadRawULong(), - PageCount = this._reader.ReadUInt(), - RecordSize = this._reader.ReadUInt(), - PageSize = this._reader.ReadUInt(), - LastPageSize = this._reader.ReadUInt(), - Unknown1 = this._reader.ReadUInt(), - Unknown2 = this._reader.ReadUInt(), - SegmentPointers = [] - }; - for (int j = 0; j < value.BlobReference.PageCount; j++) - { - value.BlobReference.SegmentPointers.Add(( - this._reader.ReadUInt(), // segment index - this._reader.ReadUInt() // size - )); - } - } - else - { - Debugger.Break(); // This should not be possible - value.DataSize = 0; - } - - dataField.Entries.Add(new DataEntry - { - Header = dataHeader, - Value = value - }); - } - - return dataField; + Index = this._reader.ReadUInt(), + SchemaIndex = this._reader.ReadUInt(), + Offset = this._reader.ReadUInt(), + }; } - private string readNullTerminatedString() + schemaIndex.UnknownMagic = this._reader.ReadRawLong(); + uint propertyEntryCount = this._reader.ReadUInt(); + uint unknownCount = this._reader.ReadUInt(); // 0; 8 + + schemaIndex.UnknownPropertyEntryPointer = new SchemaPropertyPointer[propertyEntryCount]; + for (int i = 0; i < propertyEntryCount; i++) { - byte b; - List bytes = []; - while ((b = this._reader.ReadByte()) != 0) + schemaIndex.UnknownPropertyEntryPointer[i] = new SchemaPropertyPointer { - bytes.Add(b); - } - return Encoding.UTF8.GetString(bytes.ToArray()); // TODO: Is this ASCII only or UTF-8 ? + SchemaIndex = this._reader.ReadUInt(), + Offset = this._reader.ReadUInt(), + Index = this._reader.ReadUInt(), + }; } - private Blob01 readBlob01(SegmentHeader header) + schemaIndex.SchemaUnknownPropertyPointer = new SchemaPropertyPointer[unknownCount]; + for (int i = 0; i < unknownCount; i++) { - Blob01 blob = new() + schemaIndex.SchemaUnknownPropertyPointer[i] = new SchemaPropertyPointer { - Header = header, - TotalDataSize = this._reader.ReadRawULong(), - PageStartOffset = this._reader.ReadRawULong(), - PageIndex = this._reader.ReadUInt(), - PageCount = this._reader.ReadUInt(), - PageDataSize = this._reader.ReadRawULong() + Index = this._reader.ReadUInt(), + SchemaIndex = this._reader.ReadUInt(), + Offset = this._reader.ReadUInt(), }; - blob.Data = this._reader.ReadBytes((int)blob.PageDataSize); + } - // Align to the next 128 byte boundary - long boundary = this._reader.Position % 128; - if (boundary != 0) - { - byte[] _ = this._reader.ReadBytes((int)(128 - boundary)); - } + schemaIndex.UnknownIndex1 = this._reader.ReadUInt(); - return blob; + // Align to the next 16 byte boundary + long boundary = this._reader.Position % 16; + if (boundary != 0) + { + byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); } - private SchemaData readSchemaDataValue(SegmentHeader header) + // Read labels (NULL terminated strings) + uint stringCount = this._reader.ReadUInt(); + schemaIndex.SchemaNames = new string[stringCount]; + for (int i = 0; i < stringCount; i++) { - // Read unknown schema properties - List unknownProps = []; - foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaUnknownPropertyPointer) - { - if (pointer.SchemaIndex != header.SegmentIndex) continue; - unknownProps.Add(new SchemaUnknownProperty - { - DataSize = this._reader.ReadUInt(), - UnknownFlags = this._reader.ReadUInt() - }); - } + schemaIndex.SchemaNames[i] = this.readNullTerminatedString(); + } - // Read schemas - List schemaValues = []; - foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaPointer) - { - if (pointer.SchemaIndex != header.SegmentIndex) continue; - Schema schema = this.readSchema(); - schema.Index = pointer.Index; - schema.Name = this._storage.IndexPointers.SchemaIndex.SchemaNames[pointer.Index]; - schemaValues.Add(schema); - } + return schemaIndex; + } - // Align to the next 16 byte boundary - long boundary = this._reader.Position % 16; - if (boundary != 0) - { - byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); - } + private SchemaProperty readSchemaProperty() + { + SchemaProperty prop = new() + { + PropertyFlags = this._reader.ReadUInt(), // 1 = Unknown / 2 = NoType / 8 = Unknown + NameIndex = this._reader.ReadUInt() + }; - // Read schema property names - uint propertyNameCount = this._reader.ReadUInt(); - string[] propertyNames = new string[propertyNameCount]; - for (int i = 0; i < propertyNameCount; i++) + // Get type size + if (!((prop.PropertyFlags & (1 << 1)) != 0)) + { + prop.Type = this._reader.ReadUInt(); // 0 - 15 + if (prop.Type == 0xe) { - propertyNames[i] = this.readNullTerminatedString(); + prop.TypeSize = this._reader.ReadUInt(); } - - // Assign schema Property names - foreach (Schema schema in schemaValues) + else { - foreach (SchemaProperty property in schema.Properties) - { - property.Name = propertyNames[property.NameIndex]; - } + prop.TypeSize = TYPE_SIZES[prop.Type.Value]; } + } - return new SchemaData - { - Header = header, - SchemaUnknownProperties = unknownProps, - Values = schemaValues, - }; + // Read unknown fields + if (prop.PropertyFlags == 1) + { + prop.Unknown1 = this._reader.ReadUInt(); + } + else if (prop.PropertyFlags == 8) + { + prop.Unknown2 = this._reader.ReadUInt(); } - private FreeSpace readFreeSpaceValue(SegmentHeader header) + // Read values + prop.PropertyValueCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + prop.Values = new byte[prop.PropertyValueCount, prop.TypeSize]; + if (prop.TypeSize != 0) { - FreeSpace space = new() + for (int i = 0; i < prop.PropertyValueCount; i++) { - Header = header, - Unknown = this._reader.ReadRawULong(), - FreeSpaces = new FreeSpaceArea[this._storage.FileHeader.FreeSpaceEntryCount] - }; - for (int i = 0; i < space.FreeSpaces.Length; i++) - { // TODO: Sometimes when there is an additional freespace definition, it might not match the `FreeSpaceEntryCount` count - ulong position = this._reader.ReadRawULong(); - uint size = this._reader.ReadUInt(); - space.FreeSpaces[i] = new FreeSpaceArea + byte[] propertyValue = this._reader.ReadBytes((int)prop.TypeSize); + for (int j = 0; j < propertyValue.Length; j++) { - Position = position, - Size = size - }; + prop.Values[0, j] = propertyValue[j]; + } } - return space; } - private PreviousSave readPreviousSaveValue(SegmentHeader header) => new() + return prop; + } + + private SchemaSearch readSchemaSearch() + { + return (SchemaSearch)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SearchSegmentIndex].Offset); + } + + private SchemaSearchEntry readSchemaSearchEntry() + { + SchemaSearchEntry search = new() { - Header = header, - FileHeader = this.readFileHeader() + SchemaNameIndex = this._reader.ReadUInt() }; - private SchemaSearch readSchemaSearchValue(SegmentHeader header) + ulong sortedIndexCount = this._reader.ReadRawULong(); + search.SortedIndices = new ulong[sortedIndexCount]; + for (ulong i = 0; i < sortedIndexCount; i++) { - SchemaSearch schemaSearch = new() - { - Header = header - }; - int schemaCount = this._reader.ReadInt(); - schemaSearch.Entries = []; - for (int j = 0; j < schemaCount; j++) - { - schemaSearch.Entries.Add(this.readSchemaSearchEntry()); - } - return schemaSearch; + search.SortedIndices[i] = this._reader.ReadRawULong(); } - private SchemaSearchEntry readSchemaSearchEntry() - { - SchemaSearchEntry search = new() - { - SchemaNameIndex = this._reader.ReadUInt() - }; - - ulong sortedIndexCount = this._reader.ReadRawULong(); - search.SortedIndices = new ulong[sortedIndexCount]; - for (ulong i = 0; i < sortedIndexCount; i++) - { - search.SortedIndices[i] = this._reader.ReadRawULong(); - } + uint idIndexesCount = this._reader.ReadUInt(); + SearchEntryObject[][] idEntryObjects = new SearchEntryObject[idIndexesCount][]; - uint idIndexesCount = this._reader.ReadUInt(); - SearchEntryObject[][] idEntryObjects = new SearchEntryObject[idIndexesCount][]; + // TODO: Find out what it would mean if there were multiple index lists. Never happened in test files so far + if (idIndexesCount > 1) + { + Debugger.Break(); + } - // TODO: Find out what it would mean if there were multiple index lists. Never happened in test files so far - if (idIndexesCount > 1) - { - Debugger.Break(); - } + if (idIndexesCount > 0) + { + search.Unknown1 = this._reader.ReadUInt(); - if (idIndexesCount > 0) + for (uint i = 0; i < idIndexesCount; i++) { - search.Unknown1 = this._reader.ReadUInt(); + uint idIndexCount = this._reader.ReadUInt(); - for (uint i = 0; i < idIndexesCount; i++) + SearchEntryObject[] entryObjects = new SearchEntryObject[idIndexCount]; + for (uint j = 0; j < idIndexCount; j++) { - uint idIndexCount = this._reader.ReadUInt(); + ulong handle = this._reader.ReadRawULong(); - SearchEntryObject[] entryObjects = new SearchEntryObject[idIndexCount]; - for (uint j = 0; j < idIndexCount; j++) + ulong indexCount = this._reader.ReadRawULong(); + ulong[] indices = new ulong[indexCount]; + for (ulong k = 0; k < indexCount; k++) { - ulong handle = this._reader.ReadRawULong(); - - ulong indexCount = this._reader.ReadRawULong(); - ulong[] indices = new ulong[indexCount]; - for (ulong k = 0; k < indexCount; k++) - { - indices[k] = this._reader.ReadRawULong(); - } - - entryObjects[j] = new SearchEntryObject - { - Handle = handle, - Indices = indices - }; + indices[k] = this._reader.ReadRawULong(); } - idEntryObjects[i] = entryObjects; + + entryObjects[j] = new SearchEntryObject + { + Handle = handle, + Indices = indices + }; } + idEntryObjects[i] = entryObjects; } - search.IdEntryObjects = idEntryObjects; + } + search.IdEntryObjects = idEntryObjects; + + return search; + } + + private SchemaSearch readSchemaSearchValue(SegmentHeader header) + { + SchemaSearch schemaSearch = new() + { + Header = header + }; + int schemaCount = this._reader.ReadInt(); + schemaSearch.Entries = []; + for (int j = 0; j < schemaCount; j++) + { + schemaSearch.Entries.Add(this.readSchemaSearchEntry()); + } + return schemaSearch; + } - return search; + private SegmentIndex readSegmentIndex() + { + return (SegmentIndex)this.ReadSegmentAt((ulong)this._storage.FileHeader.SegmentIndexOffset); + } + + private SegmentIndex readSegmentIndexValue(SegmentHeader header) + { + SegmentIndex index = new() + { + Header = header, + Pointers = [] + }; + for (int j = 0; j < this._storage.FileHeader.SegmentIndexEntryCount; j++) + { + index.Pointers[j] = new SegmentIndexEntry + { + Offset = this._reader.ReadRawULong(), + Size = this._reader.ReadUInt() + }; } + + // TODO: Sometimes additional bytes (that are not padding bytes) follow here until the segment size + // They actually seem to be garbage data and can be ignored. Often times there are seemingly + // random bytes without any pattern and sometimes there are bytes representing 2-Byte strings. + // Garbage string examples are e.g.: + // --> "nslation\0" followed by the bytes [0, 0, 7, 0, 176, 4] + // --> "k\\auto" + // --> "op> "utocad" + // --> "atio" + + return index; + } + + private SegmentHeader readSubItemHeader() + { + return new() + { + Signature = this._reader.ReadShort(), + Name = Encoding.ASCII.GetString(this._reader.ReadBytes(6)), + SegmentIndex = this._reader.ReadUInt(), + IsBlob = this._reader.ReadInt(), + SegmentSize = this._reader.ReadUInt(), // Multiple of 0x40 bytes (AutoCAD uses 0x80), padded wth 0x70 values + Unknown1 = this._reader.ReadInt(), + DataStorageRevision = this._reader.ReadInt(), + Unknown2 = this._reader.ReadInt(), + SystemDataAlignmentOffset = this._reader.ReadInt(), + ObjectDataAlignmentOffset = this._reader.ReadInt(), + AlignmentBytes = this._reader.ReadBytes(8) // Align to next 16 byte boundary + }; } -} +} \ No newline at end of file diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs new file mode 100644 index 000000000..02cc80fc2 --- /dev/null +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs @@ -0,0 +1,140 @@ +using ACadSharp.DataStorage; +using System; +using System.Text; + +namespace ACadSharp.IO.DWG.DwgStreamReaders; + +internal class DwgPrototype1bReaderV2 : DwgSectionIO +{ + public override string SectionName => DwgSectionDefinition.AcDsPrototype; + + private readonly DwgDocumentBuilder _builder; + + private readonly IDwgStreamReader _reader; + + private FileHeader _fileHeader; + + public DwgPrototype1bReaderV2(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) + { + this._reader = reader; + this._builder = builder; + } + + public void Read() + { + CadFileDataStorage storage = new(); + + try + { + this._fileHeader = this.readFileHeader(); + this.readFileSegmentIndex(); + + this._builder.DataStorage = storage; + } + catch (Exception ex) + { + if (!this._builder.Configuration.Failsafe) + throw; + + this.notify("An error occurred while reading the Prototype1b", NotificationType.Error, ex); + } + } + + private FileSegmentIndex readFileSegmentIndex() + { + this._reader.Position = this._fileHeader.SegmentIndexOffset; + + FileSegmentIndex segmentIndex = new(); + + this.readDataStorageFileSegment(segmentIndex); + + for (int i = 0; i < this._fileHeader.SegmentIndexEntryCount; i++) + { + var offset = this._reader.ReadRawULong(); + var size = this._reader.ReadUInt(); + segmentIndex.AddEntry(offset, size); + } + + return segmentIndex; + } + + private void readDataStorageFileSegment(FileSegment segment) + { + var pos = this._reader.Position; + segment.Header = this.readSubItemHeader(); + } + + private FileSegmentHeader readSubItemHeader() + { + var header = new FileSegmentHeader(); + + //Int16 Signature (always 0xd5ac?) + header.Signature = this._reader.ReadShort(); + //byte[6] Name (6 bytes). Names for the several file segments are: + header.Name = Encoding.ASCII.GetString(this._reader.ReadBytes(6)); + //Int32 Segment index + header.SegmentIndex = this._reader.ReadUInt(); + //Int32 Unknown 1 (0 or 1? 1 in blob01 segment). + header.IsBlob = this._reader.ReadInt(); + //Int32 Segment size (multiple of 0x40 bytes (AutoCAD uses 0x80), padded with 0x70 values). + header.SegmentSize = this._reader.ReadUInt(); + //Int32 Unknown 2 (always 0?) + header.Unknown2 = this._reader.ReadInt(); + //Int32 Data storage revision + header.DataStorageRevision = this._reader.ReadInt(); + //Int32 Unknown 3 (always 0?) + header.Unknown3 = this._reader.ReadInt(); + //Int32 System data alignment offset (calculate the stream position by shifting left 4 + //bits and adding to the file segment’s stream start position). This offset is used + //for schema index and schema data segments.So the name “system data” seems + //to refer to schema index/schema data. + header.SystemDataAlignmentOffset = this._reader.ReadInt(); + //Int32 Object data alignment offset (calculate the stream position by shifting left 4 + //bits and adding to the file segment’s stream start position). This offset is used + //for the data segment. + header.ObjectDataAlignmentOffset = this._reader.ReadInt(); + //byte[8] 8 alignment bytes (always 8 x 0x55?). + var alignmentBytes = this._reader.ReadBytes(8); + + return header; + } + + private FileHeader readFileHeader() + { + FileHeader fileHeader = new(); + + //UInt32 File signature + fileHeader.FileSignature = this._reader.ReadUInt(); + //Int32 File header size + fileHeader.FileHeaderSize = this._reader.ReadInt(); + //Int32 Unknown 1 (always 2?) + fileHeader.Unknown1 = this._reader.ReadInt(); + //Int32 Version(always 2 ?) + fileHeader.Version = this._reader.ReadInt(); + //Int32 Unknown 2 (always 0?) + fileHeader.Unknown2 = this._reader.ReadInt(); + //Int32 Data storage revision + fileHeader.DataStorageRevision = this._reader.ReadInt(); + //Int32 Segment index offset (the stream off set from the data store’s stream start + //position). See paragraph 24.2.2.1 for the segment index file segment. + fileHeader.SegmentIndexOffset = this._reader.ReadInt(); + //Int32 Segment index unknown + fileHeader.SegmentIndexUnknown = this._reader.ReadInt(); + //Int32 Segment index entry count + fileHeader.SegmentIndexEntryCount = this._reader.ReadInt(); + //Int32 Schema index segment index. This is the index into the segment index entry + //array(see paragraph 24.2.2.1) for the schema index file segment(see paragraph 24.2.2.4). + fileHeader.SchemaIndexSegmentIndex = this._reader.ReadInt(); + //Int32 Data index segment index. This is the index into the segment index entry array + //(see paragraph 24.2.2.1) for the data index file segment(see paragraph 24.2.2.2). + fileHeader.DataIndexSegmentIndex = this._reader.ReadInt(); + //Int32 Search segment index + fileHeader.SearchSegmentIndex = this._reader.ReadInt(); + //Int32 Previous save index + fileHeader.PreviousSaveIndex = this._reader.ReadInt(); + //Int32 File size + fileHeader.FileSize = this._reader.ReadInt(); + + return fileHeader; + } +} \ No newline at end of file diff --git a/src/ACadSharp/IO/DwgReader.cs b/src/ACadSharp/IO/DwgReader.cs index 1022722fe..c9a6cfe21 100644 --- a/src/ACadSharp/IO/DwgReader.cs +++ b/src/ACadSharp/IO/DwgReader.cs @@ -593,10 +593,9 @@ private void readDsPrototype_1b() return; } - var reader = new DwgPrototype1bReader(this._fileHeader.AcadVersion, this._builder, sreader); - reader.OnNotification += onNotificationEvent; - - this._document.DataStorage = reader.Read(); + var reader = new DwgPrototype1bReaderV2(this._fileHeader.AcadVersion, this._builder, sreader); + reader.OnNotification += this.onNotificationEvent; + reader.Read(); } /// From 2870b90a21581e021f9f233f64fc49a0382631fe Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 1 Oct 2026 15:47:38 +0200 Subject: [PATCH 10/81] Parse DWG schema index and improve models Parse and store the file segment index, add schema index parsing after it, and move file-header reading into a dedicated method. Add richer `ToString()` output and helpers for file segments, segment entries, and schema pointer metadata, including null-terminated schema names. --- .../DataStorage/CadFileDataStorage.cs | 43 +++- .../DataStorage/FileSegmentHeader.cs | 2 +- .../DwgPrototype1bReaderV2.cs | 186 ++++++++++++++---- 3 files changed, 185 insertions(+), 46 deletions(-) diff --git a/src/ACadSharp/DataStorage/CadFileDataStorage.cs b/src/ACadSharp/DataStorage/CadFileDataStorage.cs index bd78a214e..ad260654c 100644 --- a/src/ACadSharp/DataStorage/CadFileDataStorage.cs +++ b/src/ACadSharp/DataStorage/CadFileDataStorage.cs @@ -16,22 +16,61 @@ internal class CadFileDataStorage internal class FileSegment { public FileSegmentHeader Header { get; set; } + + public override string ToString() + { + return $"{this.Header.Name}"; + } } internal class FileSegmentIndex : FileSegment { public List Entries { get; } = new(); + public void AddEntry(ulong offset, uint size) + { + this.Entries.Add(new Entry { Offset = offset, Size = size }); + } + public class Entry { public ulong Offset { get; set; } public uint Size { get; set; } + + public override string ToString() + { + return $"Offset: {this.Offset} | Size: {this.Size}"; + } } +} - public void AddEntry(ulong offset, uint size) +internal class SchemaIndex : FileSegment +{ + public List PropertyPointers { get; set; } = new(); + + public List SchemaNames { get; set; } = new(); + + public List SchemaUnknownPropertyPointer { get; set; } = new(); + + public uint UnknownIndex1 { get; set; } + + public long UnknownMagic { get; set; } + + public List UnknownPropertyPointers { get; set; } = new(); + + public class Pointer { - this.Entries.Add(new Entry { Offset = offset, Size = size }); + public uint Index { get; set; } + + public uint Offset { get; set; } + + public uint SchemaIndex { get; set; } + + public override string ToString() + { + return $"Index: {this.Index} | SchemaIndex: {this.SchemaIndex} | Offset: {this.Offset}"; + } } } diff --git a/src/ACadSharp/DataStorage/FileSegmentHeader.cs b/src/ACadSharp/DataStorage/FileSegmentHeader.cs index f01c3accc..c8bcb094f 100644 --- a/src/ACadSharp/DataStorage/FileSegmentHeader.cs +++ b/src/ACadSharp/DataStorage/FileSegmentHeader.cs @@ -30,6 +30,6 @@ internal class FileSegmentHeader public override string ToString() { - return Name; + return this.Name; } } diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs index 02cc80fc2..05efbfae4 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs @@ -1,5 +1,7 @@ using ACadSharp.DataStorage; +using CSUtilities.Text; using System; +using System.Collections.Generic; using System.Text; namespace ACadSharp.IO.DWG.DwgStreamReaders; @@ -14,6 +16,8 @@ internal class DwgPrototype1bReaderV2 : DwgSectionIO private FileHeader _fileHeader; + private FileSegmentIndex _fileSegmentIndex; + public DwgPrototype1bReaderV2(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) { this._reader = reader; @@ -27,7 +31,8 @@ public void Read() try { this._fileHeader = this.readFileHeader(); - this.readFileSegmentIndex(); + this._fileSegmentIndex = this.readFileSegmentIndex(); + this.readSchemaIndex(); this._builder.DataStorage = storage; } @@ -40,6 +45,51 @@ public void Read() } } + private void readDataStorageFileSegment(FileSegment segment) + { + var pos = this._reader.Position; + segment.Header = this.readSubItemHeader(); + } + + private FileHeader readFileHeader() + { + FileHeader fileHeader = new(); + + //UInt32 File signature + fileHeader.FileSignature = this._reader.ReadUInt(); + //Int32 File header size + fileHeader.FileHeaderSize = this._reader.ReadInt(); + //Int32 Unknown 1 (always 2?) + fileHeader.Unknown1 = this._reader.ReadInt(); + //Int32 Version(always 2 ?) + fileHeader.Version = this._reader.ReadInt(); + //Int32 Unknown 2 (always 0?) + fileHeader.Unknown2 = this._reader.ReadInt(); + //Int32 Data storage revision + fileHeader.DataStorageRevision = this._reader.ReadInt(); + //Int32 Segment index offset (the stream off set from the data store’s stream start + //position). See paragraph 24.2.2.1 for the segment index file segment. + fileHeader.SegmentIndexOffset = this._reader.ReadInt(); + //Int32 Segment index unknown + fileHeader.SegmentIndexUnknown = this._reader.ReadInt(); + //Int32 Segment index entry count + fileHeader.SegmentIndexEntryCount = this._reader.ReadInt(); + //Int32 Schema index segment index. This is the index into the segment index entry + //array(see paragraph 24.2.2.1) for the schema index file segment(see paragraph 24.2.2.4). + fileHeader.SchemaIndexSegmentIndex = this._reader.ReadInt(); + //Int32 Data index segment index. This is the index into the segment index entry array + //(see paragraph 24.2.2.1) for the data index file segment(see paragraph 24.2.2.2). + fileHeader.DataIndexSegmentIndex = this._reader.ReadInt(); + //Int32 Search segment index + fileHeader.SearchSegmentIndex = this._reader.ReadInt(); + //Int32 Previous save index + fileHeader.PreviousSaveIndex = this._reader.ReadInt(); + //Int32 File size + fileHeader.FileSize = this._reader.ReadInt(); + + return fileHeader; + } + private FileSegmentIndex readFileSegmentIndex() { this._reader.Position = this._fileHeader.SegmentIndexOffset; @@ -58,10 +108,99 @@ private FileSegmentIndex readFileSegmentIndex() return segmentIndex; } - private void readDataStorageFileSegment(FileSegment segment) + private string readNullTerminatedString() { - var pos = this._reader.Position; - segment.Header = this.readSubItemHeader(); + byte b; + List bytes = []; + while ((b = this._reader.ReadByte()) != 0) + { + bytes.Add(b); + } + return this._reader.Encoding.GetString(bytes.ToArray()); + } + + private SchemaIndex readSchemaIndex() + { + var startOffset = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.SchemaIndexSegmentIndex].Offset; + this._reader.Position = startOffset; + + var schema = new SchemaIndex(); + + this.readDataStorageFileSegment(schema); + + //UInt32 Unknown property count + uint propertyCount = this._reader.ReadUInt(); + //UInt32 Unknown (0) + var unknown0 = this._reader.ReadUInt(); + for (int i = 0; i < propertyCount; i++) + { + //UInt32 Index (starting at 0) + var index = this._reader.ReadUInt(); + //UInt32 Segment index into the segment index file segment entry table (paragraph + //24.2.2.1) of the schema data file segment (paragraph 24.2.2.6) + var schemaIndex = this._reader.ReadUInt(); + //UInt32 Local offset of the unknown schema property. This is a local offset in the + //stream, relative to the schema data file segment’s stream start position. + var offset = this._reader.ReadUInt(); + schema.UnknownPropertyPointers.Add(new SchemaIndex.Pointer + { + Index = index, + SchemaIndex = schemaIndex, + Offset = offset, + }); + } + + //Int64 Unknown (0x0af10c) + var unknown1 = this._reader.ReadRawULong(); + //UInt32 Property entry count + uint propertyEntryCount = this._reader.ReadUInt(); + //UInt32 Unknown (0) + uint unknownCount = this._reader.ReadUInt(); + for (int i = 0; i < propertyEntryCount; i++) + { + //UInt32 Segment index into the segment index file segment entry table (paragraph + //24.2.2.1) of the schema data file segment(paragraph 24.2.2.6). + var schemaIndex = this._reader.ReadUInt(); + //UInt32 Local offset of the schema property. This is a local offset in the stream, relative + //to the schema data file segment’s stream start position. + var offset = this._reader.ReadUInt(); + //UInt32 Index + var index = this._reader.ReadUInt(); + schema.PropertyPointers.Add(new SchemaIndex.Pointer + { + SchemaIndex = schemaIndex, + Offset = offset, + Index = index, + }); + } + + //Begin repeat schema unknown properties in the associated schema index file + //segment(paragraph 24.2.2.4), where the property’s segment index is equal to + //this file segment’s segment index(found in the header). + + //Begin repeat schema entries in the associated schema index file segment + //(paragraph 24.2.2.4), where the property’s segment index is equal to this file + //segment’s segment index(found in the header). + + //A schema, see paragraph 24.2.2.6.1. The stream position is the file segment’s + //start position + the schema entry’s local offset. + var propertyNamesOffset = (long)schema.Header.SystemDataAlignmentOffset << 4; + if (propertyNamesOffset != 0) + { + this._reader.Position = (long)(startOffset + propertyNamesOffset); + //Uint32 Property name count + var nproperties = this._reader.ReadUInt(); + for (int i = 0; i < nproperties; i++) + { + //AnsiString Property name (zero byte delimited). These names are referred to by the + //schema’s schema property’s name index(paragraph 24.2.2.6.1.1).Name + //strings can be shared between multiple schema properties this way.See + //paragraph 24.2.2.6.1 for details about the schema. + schema.SchemaNames.Add(this.readNullTerminatedString()); + } + } + + return schema; } private FileSegmentHeader readSubItemHeader() @@ -98,43 +237,4 @@ private FileSegmentHeader readSubItemHeader() return header; } - - private FileHeader readFileHeader() - { - FileHeader fileHeader = new(); - - //UInt32 File signature - fileHeader.FileSignature = this._reader.ReadUInt(); - //Int32 File header size - fileHeader.FileHeaderSize = this._reader.ReadInt(); - //Int32 Unknown 1 (always 2?) - fileHeader.Unknown1 = this._reader.ReadInt(); - //Int32 Version(always 2 ?) - fileHeader.Version = this._reader.ReadInt(); - //Int32 Unknown 2 (always 0?) - fileHeader.Unknown2 = this._reader.ReadInt(); - //Int32 Data storage revision - fileHeader.DataStorageRevision = this._reader.ReadInt(); - //Int32 Segment index offset (the stream off set from the data store’s stream start - //position). See paragraph 24.2.2.1 for the segment index file segment. - fileHeader.SegmentIndexOffset = this._reader.ReadInt(); - //Int32 Segment index unknown - fileHeader.SegmentIndexUnknown = this._reader.ReadInt(); - //Int32 Segment index entry count - fileHeader.SegmentIndexEntryCount = this._reader.ReadInt(); - //Int32 Schema index segment index. This is the index into the segment index entry - //array(see paragraph 24.2.2.1) for the schema index file segment(see paragraph 24.2.2.4). - fileHeader.SchemaIndexSegmentIndex = this._reader.ReadInt(); - //Int32 Data index segment index. This is the index into the segment index entry array - //(see paragraph 24.2.2.1) for the data index file segment(see paragraph 24.2.2.2). - fileHeader.DataIndexSegmentIndex = this._reader.ReadInt(); - //Int32 Search segment index - fileHeader.SearchSegmentIndex = this._reader.ReadInt(); - //Int32 Previous save index - fileHeader.PreviousSaveIndex = this._reader.ReadInt(); - //Int32 File size - fileHeader.FileSize = this._reader.ReadInt(); - - return fileHeader; - } } \ No newline at end of file From e6033164b55d9b0c2825c9f8cabaecc1903a16b5 Mon Sep 17 00:00:00 2001 From: DomCR Date: Fri, 2 Oct 2026 18:01:40 +0200 Subject: [PATCH 11/81] Refactor ACDS schema storage and parsing Split ACDS storage into dedicated classes and update DWG/DXF readers to parse schema records, pointers, flags, type metadata, and raw value blobs. --- src/ACadSharp/DataStorage/AcdsSchema.cs | 2 +- src/ACadSharp/DataStorage/AcdsSchemaRecord.cs | 22 ++- .../DataStorage/CadFileDataStorage.cs | 92 ----------- src/ACadSharp/DataStorage/FileHeader.cs | 32 ++++ src/ACadSharp/DataStorage/FileSegment.cs | 11 ++ src/ACadSharp/DataStorage/FileSegmentIndex.cs | 25 +++ src/ACadSharp/DataStorage/SchemaData.cs | 8 + src/ACadSharp/DataStorage/SchemaIndex.cs | 32 ++++ .../DataStorage/SchemaRecordFlags.cs | 15 ++ .../DwgPrototype1bReaderV2.cs | 153 +++++++++++++++++- .../DxfAcdsDataSectionReader.cs | 5 +- 11 files changed, 294 insertions(+), 103 deletions(-) create mode 100644 src/ACadSharp/DataStorage/FileHeader.cs create mode 100644 src/ACadSharp/DataStorage/FileSegment.cs create mode 100644 src/ACadSharp/DataStorage/FileSegmentIndex.cs create mode 100644 src/ACadSharp/DataStorage/SchemaData.cs create mode 100644 src/ACadSharp/DataStorage/SchemaIndex.cs create mode 100644 src/ACadSharp/DataStorage/SchemaRecordFlags.cs diff --git a/src/ACadSharp/DataStorage/AcdsSchema.cs b/src/ACadSharp/DataStorage/AcdsSchema.cs index 9738f40a6..3530089bb 100644 --- a/src/ACadSharp/DataStorage/AcdsSchema.cs +++ b/src/ACadSharp/DataStorage/AcdsSchema.cs @@ -8,7 +8,7 @@ internal class AcdsSchema public uint Index { get; set; } - public List Indexes { get; } = new(); + public List Indices { get; } = new(); public string Name { get; set; } diff --git a/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs b/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs index d62fc9fc3..ff0f5fb58 100644 --- a/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs +++ b/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs @@ -1,14 +1,30 @@ -namespace ACadSharp.DataStorage; +using System; + +namespace ACadSharp.DataStorage; internal class AcdsSchemaRecord { + public SchemaRecordFlags Flags { get; set; } + public int Id { get; set; } public int Index { get; set; } public string Name { get; set; } - public short Value280 { get; set; } + public uint NameIndex { get; set; } + + public System.Collections.Generic.List Values { get; } = new System.Collections.Generic.List(); + + public uint Type { get; set; } + + public uint TypeSize { get; set; } + + public uint Unknown1 { get; set; } + + public uint Unknown2 { get; set; } public short Value291 { get; set; } -} + + public static readonly uint[] TypeSizes = new uint[] { 0, 0, 2, 1, 2, 4, 8, 1, 2, 4, 8, 4, 8, 0, 0, 0 }; +} \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/CadFileDataStorage.cs b/src/ACadSharp/DataStorage/CadFileDataStorage.cs index ad260654c..09ab0738f 100644 --- a/src/ACadSharp/DataStorage/CadFileDataStorage.cs +++ b/src/ACadSharp/DataStorage/CadFileDataStorage.cs @@ -11,96 +11,4 @@ internal class CadFileDataStorage public const string AsmData = "ASM_Data"; public const string Id = "AcDbDs::ID"; -} - -internal class FileSegment -{ - public FileSegmentHeader Header { get; set; } - - public override string ToString() - { - return $"{this.Header.Name}"; - } -} - -internal class FileSegmentIndex : FileSegment -{ - public List Entries { get; } = new(); - - public void AddEntry(ulong offset, uint size) - { - this.Entries.Add(new Entry { Offset = offset, Size = size }); - } - - public class Entry - { - public ulong Offset { get; set; } - - public uint Size { get; set; } - - public override string ToString() - { - return $"Offset: {this.Offset} | Size: {this.Size}"; - } - } -} - -internal class SchemaIndex : FileSegment -{ - public List PropertyPointers { get; set; } = new(); - - public List SchemaNames { get; set; } = new(); - - public List SchemaUnknownPropertyPointer { get; set; } = new(); - - public uint UnknownIndex1 { get; set; } - - public long UnknownMagic { get; set; } - - public List UnknownPropertyPointers { get; set; } = new(); - - public class Pointer - { - public uint Index { get; set; } - - public uint Offset { get; set; } - - public uint SchemaIndex { get; set; } - - public override string ToString() - { - return $"Index: {this.Index} | SchemaIndex: {this.SchemaIndex} | Offset: {this.Offset}"; - } - } -} - -internal class FileHeader -{ - public int DataIndexSegmentIndex { get; set; } - - public int DataStorageRevision { get; set; } - - public int FileHeaderSize { get; set; } - - public uint FileSignature { get; set; } - - public int FileSize { get; set; } - - public int PreviousSaveIndex { get; set; } - - public int SchemaIndexSegmentIndex { get; set; } - - public int SearchSegmentIndex { get; set; } - - public int SegmentIndexEntryCount { get; set; } - - public int SegmentIndexOffset { get; set; } - - public int SegmentIndexUnknown { get; set; } - - public int Unknown1 { get; set; } = 2; - - public int Unknown2 { get; set; } = 0; - - public int Version { get; set; } = 2; } \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/FileHeader.cs b/src/ACadSharp/DataStorage/FileHeader.cs new file mode 100644 index 000000000..1a6949a4a --- /dev/null +++ b/src/ACadSharp/DataStorage/FileHeader.cs @@ -0,0 +1,32 @@ +namespace ACadSharp.DataStorage; + +internal class FileHeader +{ + public int DataIndexSegmentIndex { get; set; } + + public int DataStorageRevision { get; set; } + + public int FileHeaderSize { get; set; } + + public uint FileSignature { get; set; } + + public int FileSize { get; set; } + + public int PreviousSaveIndex { get; set; } + + public int SchemaIndexSegmentIndex { get; set; } + + public int SearchSegmentIndex { get; set; } + + public int SegmentIndexEntryCount { get; set; } + + public int SegmentIndexOffset { get; set; } + + public int SegmentIndexUnknown { get; set; } + + public int Unknown1 { get; set; } = 2; + + public int Unknown2 { get; set; } = 0; + + public int Version { get; set; } = 2; +} \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/FileSegment.cs b/src/ACadSharp/DataStorage/FileSegment.cs new file mode 100644 index 000000000..4e5c6a812 --- /dev/null +++ b/src/ACadSharp/DataStorage/FileSegment.cs @@ -0,0 +1,11 @@ +namespace ACadSharp.DataStorage; + +internal class FileSegment +{ + public FileSegmentHeader Header { get; set; } + + public override string ToString() + { + return $"{this.Header.Name}"; + } +} diff --git a/src/ACadSharp/DataStorage/FileSegmentIndex.cs b/src/ACadSharp/DataStorage/FileSegmentIndex.cs new file mode 100644 index 000000000..a9bb95c78 --- /dev/null +++ b/src/ACadSharp/DataStorage/FileSegmentIndex.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace ACadSharp.DataStorage; + +internal class FileSegmentIndex : FileSegment +{ + public List Entries { get; } = new(); + + public void AddEntry(ulong offset, uint size) + { + this.Entries.Add(new Entry { Offset = offset, Size = size }); + } + + public class Entry + { + public ulong Offset { get; set; } + + public uint Size { get; set; } + + public override string ToString() + { + return $"Offset: {this.Offset} | Size: {this.Size}"; + } + } +} diff --git a/src/ACadSharp/DataStorage/SchemaData.cs b/src/ACadSharp/DataStorage/SchemaData.cs new file mode 100644 index 000000000..c26d8d1ba --- /dev/null +++ b/src/ACadSharp/DataStorage/SchemaData.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace ACadSharp.DataStorage; + +internal class SchemaData : FileSegment +{ + public List Schemes { get; } = new(); +} diff --git a/src/ACadSharp/DataStorage/SchemaIndex.cs b/src/ACadSharp/DataStorage/SchemaIndex.cs new file mode 100644 index 000000000..72937dd78 --- /dev/null +++ b/src/ACadSharp/DataStorage/SchemaIndex.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ACadSharp.DataStorage; + +internal class SchemaIndex : FileSegment +{ + public List PropertyPointers { get; set; } = new(); + + public List SchemaNames { get; set; } = new(); + + public List SchemaPointers { get; set; } = new(); + + public IEnumerable GetAllPointers() + { + return this.PropertyPointers.Concat(this.SchemaPointers); + } + + public class Pointer + { + public uint Index { get; set; } + + public uint LocalOffset { get; set; } + + public uint SegmentIndex { get; set; } + + public override string ToString() + { + return $"Index: {this.Index} | SegmentIndex: {this.SegmentIndex} | LocalOffset: {this.LocalOffset}"; + } + } +} diff --git a/src/ACadSharp/DataStorage/SchemaRecordFlags.cs b/src/ACadSharp/DataStorage/SchemaRecordFlags.cs new file mode 100644 index 000000000..d3b707789 --- /dev/null +++ b/src/ACadSharp/DataStorage/SchemaRecordFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace ACadSharp.DataStorage; + +[Flags] +public enum SchemaRecordFlags +{ + None = 0, + + Unknown0 = 1, + + NoType = 2, + + Unknown1 = 8, +} \ No newline at end of file diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs index 05efbfae4..6e94b9a12 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs @@ -2,6 +2,7 @@ using CSUtilities.Text; using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace ACadSharp.IO.DWG.DwgStreamReaders; @@ -18,6 +19,8 @@ internal class DwgPrototype1bReaderV2 : DwgSectionIO private FileSegmentIndex _fileSegmentIndex; + private SchemaIndex _schemaIndex; + public DwgPrototype1bReaderV2(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) { this._reader = reader; @@ -32,7 +35,10 @@ public void Read() { this._fileHeader = this.readFileHeader(); this._fileSegmentIndex = this.readFileSegmentIndex(); - this.readSchemaIndex(); + this._schemaIndex = this.readSchemaIndex(); + + var schemes = this.readSchemaDataEntries(); + storage.Schemes.AddRange(schemes.SelectMany(s => s.Schemes)); this._builder.DataStorage = storage; } @@ -45,6 +51,71 @@ public void Read() } } + private AcdsSchema readAcdsSchema() + { + AcdsSchema schema = new(); + + ushort indexCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + for (int i = 0; i < indexCount; i++) + { + schema.Indices.Add(this._reader.ReadRawULong()); + } + + ushort propCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + for (int i = 0; i < propCount; i++) + { + schema.EmbeddedRecords.Add(this.readAcdsSchemaRecord()); + } + + return schema; + } + + private AcdsSchemaRecord readAcdsSchemaRecord() + { + AcdsSchemaRecord record = new(); + + record.Flags = (SchemaRecordFlags)this._reader.ReadInt(); + record.NameIndex = this._reader.ReadUInt(); + + // Get type size + if ((record.Flags & SchemaRecordFlags.NoType) == 0) + { + record.Type = this._reader.ReadUInt(); + if (record.Type == 0xE) + { + record.TypeSize = this._reader.ReadUInt(); + } + else + { + record.TypeSize = AcdsSchemaRecord.TypeSizes[record.Type]; + } + } + + // Read unknown fields + if (record.Flags == SchemaRecordFlags.Unknown0) + { + record.Unknown1 = this._reader.ReadUInt(); + } + else if (record.Flags == SchemaRecordFlags.Unknown1) + { + record.Unknown2 = this._reader.ReadUInt(); + } + + // Read values + var valuesCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + if (record.TypeSize == 0) + { + return record; + } + + for (int i = 0; i < valuesCount; i++) + { + record.Values.Add(this._reader.ReadBytes((int)record.TypeSize)); + } + + return record; + } + private void readDataStorageFileSegment(FileSegment segment) { var pos = this._reader.Position; @@ -119,6 +190,76 @@ private string readNullTerminatedString() return this._reader.Encoding.GetString(bytes.ToArray()); } + private SchemaData readSchemaData(long offset) + { + this._reader.Position = offset; + + var schemaData = new SchemaData(); + + this.readDataStorageFileSegment(schemaData); + + long start = this._reader.Position; + + // Begin repeat schema unknown properties in the associated schema index file + //segment(paragraph 24.2.2.4), where the property’s segment index is equal to + //this file segment’s segment index(found in the header). + var unknownPropertyCount = this._schemaIndex.PropertyPointers.Count(p => p.SegmentIndex == schemaData.Header.SegmentIndex); + for (int i = 0; i < unknownPropertyCount; i++) + { + var dataSize = this._reader.ReadUInt(); + var unknownFlags = this._reader.ReadUInt(); + } + + // Read schemas + foreach (var pointer in this._schemaIndex.SchemaPointers.Where(p => p.SegmentIndex == schemaData.Header.SegmentIndex)) + { + AcdsSchema schema = this.readAcdsSchema(); + + schema.Index = pointer.Index; + schema.Name = this._schemaIndex.SchemaNames[(int)pointer.Index]; + schemaData.Schemes.Add(schema); + } + + // Align to the next 16 byte boundary + long boundary = this._reader.Position % 16; + if (boundary != 0) + { + byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); + } + + // Read schema property names + uint propertyNameCount = this._reader.ReadUInt(); + string[] propertyNames = new string[propertyNameCount]; + for (int i = 0; i < propertyNameCount; i++) + { + propertyNames[i] = this.readNullTerminatedString(); + } + + // Assign schema Property names + foreach (AcdsSchema schema in schemaData.Schemes) + { + foreach (AcdsSchemaRecord property in schema.EmbeddedRecords) + { + property.Name = propertyNames[(int)property.NameIndex]; + } + } + + return schemaData; + } + + private List readSchemaDataEntries() + { + List schemes = new(); + + foreach (var item in this._schemaIndex.GetAllPointers()) + { + var schema = this.readSchemaData((long)this._fileSegmentIndex.Entries[(int)item.SegmentIndex].Offset); + schemes.Add(schema); + } + + return schemes; + } + private SchemaIndex readSchemaIndex() { var startOffset = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.SchemaIndexSegmentIndex].Offset; @@ -142,11 +283,11 @@ private SchemaIndex readSchemaIndex() //UInt32 Local offset of the unknown schema property. This is a local offset in the //stream, relative to the schema data file segment’s stream start position. var offset = this._reader.ReadUInt(); - schema.UnknownPropertyPointers.Add(new SchemaIndex.Pointer + schema.SchemaPointers.Add(new SchemaIndex.Pointer { Index = index, - SchemaIndex = schemaIndex, - Offset = offset, + SegmentIndex = schemaIndex, + LocalOffset = offset, }); } @@ -168,8 +309,8 @@ private SchemaIndex readSchemaIndex() var index = this._reader.ReadUInt(); schema.PropertyPointers.Add(new SchemaIndex.Pointer { - SchemaIndex = schemaIndex, - Offset = offset, + SegmentIndex = schemaIndex, + LocalOffset = offset, Index = index, }); } diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs index 78060d426..6f1d5584f 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs @@ -162,11 +162,14 @@ private AcdsSchemaRecord readEmbeddedRecord() case 90: record.Index = (int)this._reader.ValueAsInt; break; + case 91: + record.Flags = (SchemaRecordFlags)this._reader.ValueAsInt; + break; case 95: record.Id = (int)this._reader.ValueAsShort; break; case 280: - record.Value280 = this._reader.ValueAsShort; + record.Type = (uint)this._reader.ValueAsInt; break; case 291: record.Value291 = this._reader.ValueAsShort; From 43c6e7b252655759116c0a1fc740cbd657eeb684 Mon Sep 17 00:00:00 2001 From: DomCR Date: Fri, 2 Oct 2026 19:07:32 +0200 Subject: [PATCH 12/81] Read and store DWG data index entries Add `DataIndexSegment` support in `CadFileDataStorage`, parse data index segments in `DwgPrototype1bReaderV2`, and remove the direct `DxfReader.Read(...)` call from `LocalSampleTests.ReadUserDxf`. --- src/ACadSharp.Tests/IO/LocalSampleTests.cs | 1 - .../DataStorage/CadFileDataStorage.cs | 5 ++- src/ACadSharp/DataStorage/DataIndexSegment.cs | 17 +++++++++ .../DwgPrototype1bReaderV2.cs | 38 +++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/ACadSharp/DataStorage/DataIndexSegment.cs diff --git a/src/ACadSharp.Tests/IO/LocalSampleTests.cs b/src/ACadSharp.Tests/IO/LocalSampleTests.cs index 7ea867432..2343c5f47 100644 --- a/src/ACadSharp.Tests/IO/LocalSampleTests.cs +++ b/src/ACadSharp.Tests/IO/LocalSampleTests.cs @@ -54,7 +54,6 @@ public void ReadUserDxf(FileModel test) if (string.IsNullOrEmpty(test.Path)) return; - CadDocument doc = DxfReader.Read(test.Path, this.onNotification); if (doc.Header.Version < ACadVersion.AC1012) diff --git a/src/ACadSharp/DataStorage/CadFileDataStorage.cs b/src/ACadSharp/DataStorage/CadFileDataStorage.cs index 09ab0738f..4ea9479bd 100644 --- a/src/ACadSharp/DataStorage/CadFileDataStorage.cs +++ b/src/ACadSharp/DataStorage/CadFileDataStorage.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using ACadSharp.Tables; +using System.Collections.Generic; namespace ACadSharp.DataStorage; @@ -11,4 +12,4 @@ internal class CadFileDataStorage public const string AsmData = "ASM_Data"; public const string Id = "AcDbDs::ID"; -} \ No newline at end of file +} diff --git a/src/ACadSharp/DataStorage/DataIndexSegment.cs b/src/ACadSharp/DataStorage/DataIndexSegment.cs new file mode 100644 index 000000000..1b55f2be5 --- /dev/null +++ b/src/ACadSharp/DataStorage/DataIndexSegment.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace ACadSharp.DataStorage; + +internal class DataIndexSegment : FileSegment +{ + public List Entries { get; set; } = new(); + + public class Entry + { + public uint SchemaIndex { get; set; } + + public uint LocalOffset { get; set; } + + public uint SegmentIndex { get; set; } + } +} \ No newline at end of file diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs index 6e94b9a12..85b0fa03d 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs @@ -40,6 +40,8 @@ public void Read() var schemes = this.readSchemaDataEntries(); storage.Schemes.AddRange(schemes.SelectMany(s => s.Schemes)); + this.readDataIndex(); + this._builder.DataStorage = storage; } catch (Exception ex) @@ -116,6 +118,42 @@ private AcdsSchemaRecord readAcdsSchemaRecord() return record; } + private void readDataIndex() + { + this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.DataIndexSegmentIndex].Offset; + + DataIndexSegment dataIndex = new(); + + this.readDataStorageFileSegment(dataIndex); + + //UInt32 Unknown property count + int entryCount = this._reader.ReadInt(); + //UInt32 Unknown (0) + var unknown1 = this._reader.ReadInt(); + for (int i = 0; i < entryCount; i++) + { + //UInt32 Segment index (0 means stub entry and can be ignored). + uint segmentIndex = this._reader.ReadUInt(); + //UInt32 Local offset. This is a local offset in the stream, relative to the file segment’s + //stream start position.This points to a data file segment, see paragraph 24.2.2.3. + uint localOffset = this._reader.ReadUInt(); + //UInt32 Schema index + uint schemaIndex = this._reader.ReadUInt(); + + if (segmentIndex == 0) + { + continue; + } + + dataIndex.Entries.Add(new DataIndexSegment.Entry + { + SegmentIndex = segmentIndex, + LocalOffset = localOffset, + SchemaIndex = schemaIndex, + }); + } + } + private void readDataStorageFileSegment(FileSegment segment) { var pos = this._reader.Position; From 1835dd13bfde02a3c9dfcbc83cdcdf67f05a0caa Mon Sep 17 00:00:00 2001 From: DomCR Date: Sun, 4 Oct 2026 14:49:57 +0200 Subject: [PATCH 13/81] comment --- .../IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs index 85b0fa03d..3523be9da 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs @@ -42,6 +42,9 @@ public void Read() this.readDataIndex(); + //TODO: the reader doesn't need the rest of the sections + //test if they are needed for the data storage + this._builder.DataStorage = storage; } catch (Exception ex) From 291680903feabfaf65affa49d26ba1d279d7cabd Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 6 Oct 2026 10:25:16 +0200 Subject: [PATCH 14/81] Add DWG Prototype 1b data storage parsing Wire Prototype 1b data index/entry parsing into `DwgReader`, populate `CadFileDataStorage.Records`, expand `ACadSharp.DataStorage` models and helpers, and clean up related tests/utilities. --- src/ACadSharp.Tests/CadDocumentTests.cs | 2 - .../IO/DXF/DxfWriterSingleObjectTests.cs | 2 +- src/ACadSharp.Tests/IO/IOTestsBase.cs | 2 +- .../IO/WriterSingleObjectTests.cs | 1 - .../Tables/Collections/LineTypesTableTests.cs | 3 +- src/ACadSharp/CadDocument.cs | 1 - src/ACadSharp/CadObject.cs | 1 - src/ACadSharp/DataStorage/AcdsSchema.cs | 2 +- src/ACadSharp/DataStorage/AcdsSchemaRecord.cs | 13 +- .../DataStorage/CadFileDataStorage.cs | 5 +- src/ACadSharp/DataStorage/DataField.cs | 68 ++++++++ src/ACadSharp/DataStorage/DataIndexSegment.cs | 18 +++ src/ACadSharp/DataStorage/SchemaIndex.cs | 5 + .../DwgPrototype1bReaderV2.cs | 150 ++++++++++++++++-- .../DwgStreamReaders/DwgStreamReaderBase.cs | 5 + .../DwgObjectWriter.Objects.cs | 1 - src/ACadSharp/IO/DwgReader.cs | 1 + ...MultiLeaderObjectContextData.LeaderLine.cs | 3 +- .../Prototype1b/Segments/DataField.cs | 16 +- 19 files changed, 261 insertions(+), 38 deletions(-) create mode 100644 src/ACadSharp/DataStorage/DataField.cs diff --git a/src/ACadSharp.Tests/CadDocumentTests.cs b/src/ACadSharp.Tests/CadDocumentTests.cs index 9be73b12d..c01c4b720 100644 --- a/src/ACadSharp.Tests/CadDocumentTests.cs +++ b/src/ACadSharp.Tests/CadDocumentTests.cs @@ -3,9 +3,7 @@ using ACadSharp.Objects; using ACadSharp.Tables; using ACadSharp.Tests.Common; -using ACadSharp.Tests.TestModels; using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Xunit; diff --git a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs index 0cb984d96..0fd724e8e 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs @@ -107,7 +107,7 @@ public void WriteHatchNormalizesBoundaryAngles(double startDegrees, double endDe Assert.False(ellipse.CounterClockWise); } - protected void writeDxfFile(SingleCaseGenerator data, ACadVersion version) + protected virtual void writeDxfFile(SingleCaseGenerator data, ACadVersion version) { Assert.True(data.HasExecuted, $"The writer has failed during it's execution."); diff --git a/src/ACadSharp.Tests/IO/IOTestsBase.cs b/src/ACadSharp.Tests/IO/IOTestsBase.cs index b336d8b9c..c7388158a 100644 --- a/src/ACadSharp.Tests/IO/IOTestsBase.cs +++ b/src/ACadSharp.Tests/IO/IOTestsBase.cs @@ -156,7 +156,7 @@ protected bool isSupportedVersion(ACadVersion version) } } - protected void onNotification(object sender, NotificationEventArgs e) + protected virtual void onNotification(object sender, NotificationEventArgs e) { if (e.NotificationType == NotificationType.Error) { diff --git a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs index bd3e8fcb8..cf79ede85 100644 --- a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs @@ -9,7 +9,6 @@ using CSMath; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using Xunit; diff --git a/src/ACadSharp.Tests/Tables/Collections/LineTypesTableTests.cs b/src/ACadSharp.Tests/Tables/Collections/LineTypesTableTests.cs index 79ddd0b1f..ef8318488 100644 --- a/src/ACadSharp.Tests/Tables/Collections/LineTypesTableTests.cs +++ b/src/ACadSharp.Tests/Tables/Collections/LineTypesTableTests.cs @@ -1,5 +1,4 @@ -using ACadSharp.Entities; -using ACadSharp.Tables; +using ACadSharp.Tables; using ACadSharp.Tables.Collections; namespace ACadSharp.Tests.Tables.Collections; diff --git a/src/ACadSharp/CadDocument.cs b/src/ACadSharp/CadDocument.cs index 10fd3e442..f9b639cff 100644 --- a/src/ACadSharp/CadDocument.cs +++ b/src/ACadSharp/CadDocument.cs @@ -3,7 +3,6 @@ using ACadSharp.Header; using ACadSharp.Objects; using ACadSharp.Objects.Collections; -using ACadSharp.Prototype1b; using ACadSharp.Tables; using ACadSharp.Tables.Collections; using CSUtilities.Extensions; diff --git a/src/ACadSharp/CadObject.cs b/src/ACadSharp/CadObject.cs index 1cec0ae82..461375f03 100644 --- a/src/ACadSharp/CadObject.cs +++ b/src/ACadSharp/CadObject.cs @@ -6,7 +6,6 @@ using ACadSharp.Tables; using ACadSharp.Tables.Collections; using ACadSharp.XData; -using CSMath; using CSMath.Extensions; using System; using System.Collections.Generic; diff --git a/src/ACadSharp/DataStorage/AcdsSchema.cs b/src/ACadSharp/DataStorage/AcdsSchema.cs index 3530089bb..04517c164 100644 --- a/src/ACadSharp/DataStorage/AcdsSchema.cs +++ b/src/ACadSharp/DataStorage/AcdsSchema.cs @@ -18,4 +18,4 @@ public override string ToString() { return $"{this.Index} : {this.Name}"; } -} \ No newline at end of file +} diff --git a/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs b/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs index ff0f5fb58..7db8df30a 100644 --- a/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs +++ b/src/ACadSharp/DataStorage/AcdsSchemaRecord.cs @@ -1,6 +1,4 @@ -using System; - -namespace ACadSharp.DataStorage; +namespace ACadSharp.DataStorage; internal class AcdsSchemaRecord { @@ -14,8 +12,6 @@ internal class AcdsSchemaRecord public uint NameIndex { get; set; } - public System.Collections.Generic.List Values { get; } = new System.Collections.Generic.List(); - public uint Type { get; set; } public uint TypeSize { get; set; } @@ -26,5 +22,12 @@ internal class AcdsSchemaRecord public short Value291 { get; set; } + public System.Collections.Generic.List Values { get; } = new System.Collections.Generic.List(); + public static readonly uint[] TypeSizes = new uint[] { 0, 0, 2, 1, 2, 4, 8, 1, 2, 4, 8, 4, 8, 0, 0, 0 }; + + public override string ToString() + { + return $"{this.Id} | {this.Name}"; + } } \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/CadFileDataStorage.cs b/src/ACadSharp/DataStorage/CadFileDataStorage.cs index 4ea9479bd..09ab0738f 100644 --- a/src/ACadSharp/DataStorage/CadFileDataStorage.cs +++ b/src/ACadSharp/DataStorage/CadFileDataStorage.cs @@ -1,5 +1,4 @@ -using ACadSharp.Tables; -using System.Collections.Generic; +using System.Collections.Generic; namespace ACadSharp.DataStorage; @@ -12,4 +11,4 @@ internal class CadFileDataStorage public const string AsmData = "ASM_Data"; public const string Id = "AcDbDs::ID"; -} +} \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/DataField.cs b/src/ACadSharp/DataStorage/DataField.cs new file mode 100644 index 000000000..ec00f7120 --- /dev/null +++ b/src/ACadSharp/DataStorage/DataField.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; + +namespace ACadSharp.DataStorage; + +internal class DataField : FileSegment +{ + public List DataEntries { get; set; } = new(); + + public class DataBlobReference + { + public uint LastPageSize { get; set; } + + public uint PageCount { get; set; } + + public uint PageSize { get; set; } + + public uint RecordSize { get; set; } + + public List<(uint, uint)> SegmentPointers { get; set; } + + public ulong TotalDataSize { get; set; } + + public uint Unknown1 { get; set; } + + public uint Unknown2 { get; set; } + } + + public class DataEntry + { + public DataBlobReference BlobReference { get; set; } + + public byte[] Data { get; set; } + + public uint DataSize { get; set; } + + public DataHeader Header { get; set; } + + public byte[] GetData() + { + if (this.Data != null) + { + return this.Data; + } + else if (this.BlobReference != null) + { + return null; + } + else + { + return null; + } + } + } + + public class DataHeader + { + public ulong Handle { get; set; } + + public uint LocalOffset { get; set; } + + public uint Size { get; set; } + + public override string ToString() + { + return $"Handle {this.Handle} | LocalOffset {this.LocalOffset}"; + } + } +} \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/DataIndexSegment.cs b/src/ACadSharp/DataStorage/DataIndexSegment.cs index 1b55f2be5..e4a4d9721 100644 --- a/src/ACadSharp/DataStorage/DataIndexSegment.cs +++ b/src/ACadSharp/DataStorage/DataIndexSegment.cs @@ -13,5 +13,23 @@ public class Entry public uint LocalOffset { get; set; } public uint SegmentIndex { get; set; } + + public override string ToString() + { + return $"SchemaIndex {this.SchemaIndex} | SegmentIndex {this.SegmentIndex} | LocalOffset: {this.LocalOffset}"; + } + } + + public List GetEntriesBySchemaIndex(uint segmentIndex) + { + List result = new(); + foreach (Entry entry in this.Entries) + { + if (entry.SegmentIndex == segmentIndex) + { + result.Add(entry); + } + } + return result; } } \ No newline at end of file diff --git a/src/ACadSharp/DataStorage/SchemaIndex.cs b/src/ACadSharp/DataStorage/SchemaIndex.cs index 72937dd78..5a3a744ee 100644 --- a/src/ACadSharp/DataStorage/SchemaIndex.cs +++ b/src/ACadSharp/DataStorage/SchemaIndex.cs @@ -16,6 +16,11 @@ public IEnumerable GetAllPointers() return this.PropertyPointers.Concat(this.SchemaPointers); } + public IEnumerable GetSchemaIndexes() + { + return this.SchemaPointers.Select(p => p.SegmentIndex).Distinct(); + } + public class Pointer { public uint Index { get; set; } diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs index 3523be9da..9fd033900 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs @@ -1,5 +1,4 @@ using ACadSharp.DataStorage; -using CSUtilities.Text; using System; using System.Collections.Generic; using System.Linq; @@ -15,6 +14,8 @@ internal class DwgPrototype1bReaderV2 : DwgSectionIO private readonly IDwgStreamReader _reader; + private DataIndexSegment _dataIndex; + private FileHeader _fileHeader; private FileSegmentIndex _fileSegmentIndex; @@ -40,10 +41,8 @@ public void Read() var schemes = this.readSchemaDataEntries(); storage.Schemes.AddRange(schemes.SelectMany(s => s.Schemes)); - this.readDataIndex(); - - //TODO: the reader doesn't need the rest of the sections - //test if they are needed for the data storage + this._dataIndex = this.readDataIndex(); + this.readDataFields(storage); this._builder.DataStorage = storage; } @@ -56,6 +55,37 @@ public void Read() } } + private void processDataValue(CadFileDataStorage storage, DataField.DataEntry data, DataIndexSegment.Entry shemaIndex) + { + AcdsRecord record = new(); + record.Index = shemaIndex.SchemaIndex; + + var schema = storage.Schemes.FirstOrDefault(s => s.Index == shemaIndex.SchemaIndex); + + foreach (var item in schema.EmbeddedRecords) + { + AcdsRecordColumn column = new AcdsRecordColumn(); + column.Name = item.Name; + column.DataType = (short)item.Type; + + switch (item.Name) + { + case CadFileDataStorage.Id: + column.CodeValuePair = new KeyValuePair(320, data.Header.Handle); + break; + case CadFileDataStorage.AsmData: + column.CodeValuePair = new KeyValuePair(310, data.GetData()); + break; + default: + break; + } + + record.Columns.Add(item.Name, column); + } + + storage.Records.Add(record); + } + private AcdsSchema readAcdsSchema() { AcdsSchema schema = new(); @@ -121,7 +151,108 @@ private AcdsSchemaRecord readAcdsSchemaRecord() return record; } - private void readDataIndex() + private void readDataFields(CadFileDataStorage storage) + { + foreach (var index in this._dataIndex.Entries.Select(e => e.SegmentIndex).Distinct()) + { + this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)index].Offset; + + var field = new DataField(); + + var headerStartPosition = this._reader.Position; + + this.readDataStorageFileSegment(field); + + if (field.Header.SegmentSize == 0) + { + continue; + } + + var headerEndPosition = this._reader.Position; + List shemaIndexes = this._dataIndex.GetEntriesBySchemaIndex(index); + if (shemaIndexes.Count == 0) + { + continue; + } + + List headers = new(); + foreach (var e in shemaIndexes) + { + this._reader.Position = headerEndPosition + e.LocalOffset; + DataField.DataHeader entry = new(); + + entry.Size = this._reader.ReadUInt(); + var unknown1 = this._reader.ReadUInt(); + entry.Handle = this._reader.ReadRawULong(); + entry.LocalOffset = this._reader.ReadUInt(); + + headers.Add(entry); + } + + var dataRefPosition = headerStartPosition + ((long)field.Header.ObjectDataAlignmentOffset << 4); + if (dataRefPosition != this._reader.Position) + { + // Many unreferenced / seemingly dangling DataHeader entries might be defined here, they would + // also have valid file data in the next step, where file contents are being read. This can be a lot of entries, e.g. + // 10 - 20 valid DataHeader entries have been seen + byte[] data = this._reader.ReadBytes((int)(dataRefPosition - this._reader.Position)); + } + + for (int i = 0; i < headers.Count; i++) + { + DataField.DataHeader dataHeader = headers[i]; + var recordStreamOffset = dataRefPosition + dataHeader.LocalOffset; + this._reader.Position = recordStreamOffset; + + uint maxRecordSize = i + 1 < headers.Count ? + (headers[i + 1].LocalOffset - dataHeader.LocalOffset) + : (field.Header.SegmentSize - (uint)(field.Header.ObjectDataAlignmentOffset << 4) - dataHeader.LocalOffset); + + DataField.DataEntry value = new(); + value.Header = dataHeader; + value.DataSize = this._reader.ReadUInt(); + if ((value.DataSize + 4) <= maxRecordSize) + { + value.Data = this._reader.ReadBytes((int)value.DataSize); + } + else if (value.DataSize == 0xbb106bb1) + { + var totalDataSize = this._reader.ReadRawULong(); + var pageCount = this._reader.ReadUInt(); + var recordSize = this._reader.ReadUInt(); + var pageSize = this._reader.ReadUInt(); + var lastPageSize = this._reader.ReadUInt(); + var unknown1 = this._reader.ReadUInt(); + var unknown2 = this._reader.ReadUInt(); + + value.BlobReference = new DataField.DataBlobReference() + { + TotalDataSize = totalDataSize, + PageCount = pageCount, + RecordSize = recordSize, + PageSize = pageSize, + LastPageSize = lastPageSize, + Unknown1 = unknown1, + Unknown2 = unknown2, + SegmentPointers = new List<(uint, uint)>() + }; + + for (int j = 0; j < value.BlobReference.PageCount; j++) + { + var segmentIndex = this._reader.ReadUInt(); + var size = this._reader.ReadUInt(); + + value.BlobReference.SegmentPointers.Add((segmentIndex, size)); + } + } + + field.DataEntries.Add(value); + processDataValue(storage, value, shemaIndexes[i]); + } + } + } + + private DataIndexSegment readDataIndex() { this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.DataIndexSegmentIndex].Offset; @@ -155,6 +286,8 @@ private void readDataIndex() SchemaIndex = schemaIndex, }); } + + return dataIndex; } private void readDataStorageFileSegment(FileSegment segment) @@ -291,10 +424,9 @@ private SchemaData readSchemaData(long offset) private List readSchemaDataEntries() { List schemes = new(); - - foreach (var item in this._schemaIndex.GetAllPointers()) + foreach (var index in this._schemaIndex.GetSchemaIndexes()) { - var schema = this.readSchemaData((long)this._fileSegmentIndex.Entries[(int)item.SegmentIndex].Offset); + var schema = this.readSchemaData((long)this._fileSegmentIndex.Entries[(int)index].Offset); schemes.Add(schema); } diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgStreamReaderBase.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgStreamReaderBase.cs index 0319e6339..834583864 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgStreamReaderBase.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgStreamReaderBase.cs @@ -1235,6 +1235,11 @@ private DateTime julianToDate(int jdate, int miliseconds) dtDateTime = DateTime.MinValue; } + if (miliseconds < 0) + { + return dtDateTime; + } + return dtDateTime.AddMilliseconds(miliseconds); } } diff --git a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs index 9cd5b527c..59c9f33a6 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs @@ -7,7 +7,6 @@ using CSUtilities.Text; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; diff --git a/src/ACadSharp/IO/DwgReader.cs b/src/ACadSharp/IO/DwgReader.cs index c9a6cfe21..a564a6c48 100644 --- a/src/ACadSharp/IO/DwgReader.cs +++ b/src/ACadSharp/IO/DwgReader.cs @@ -125,6 +125,7 @@ public override CadDocument Read() this.readAuxHeader(); this._builder.BuildDocument(); + this._builder.BuildDataStorage(); return this._document; } diff --git a/src/ACadSharp/Objects/MultiLeaderObjectContextData.LeaderLine.cs b/src/ACadSharp/Objects/MultiLeaderObjectContextData.LeaderLine.cs index 58bed5c1e..abe279b15 100644 --- a/src/ACadSharp/Objects/MultiLeaderObjectContextData.LeaderLine.cs +++ b/src/ACadSharp/Objects/MultiLeaderObjectContextData.LeaderLine.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using ACadSharp.Attributes; using ACadSharp.Tables; using CSMath; diff --git a/src/ACadSharp/Prototype1b/Segments/DataField.cs b/src/ACadSharp/Prototype1b/Segments/DataField.cs index 2b24f3c70..b42df41da 100644 --- a/src/ACadSharp/Prototype1b/Segments/DataField.cs +++ b/src/ACadSharp/Prototype1b/Segments/DataField.cs @@ -1,11 +1,11 @@ -using ACadSharp.Prototype1b.Segments; -using System.Collections.Generic; +using System.Collections.Generic; -namespace ACadSharp.Prototype1b +namespace ACadSharp.Prototype1b.Segments { public class DataField : IPrototype1bSegment - { - public SegmentHeader Header { get; set; } - public List Entries { get; set; } - } -} + { + public List Entries { get; set; } + + public SegmentHeader Header { get; set; } + } +} \ No newline at end of file From af4b0deb1893d52fbe2d42841aa56b97eef868ab Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 6 Oct 2026 10:29:13 +0200 Subject: [PATCH 15/81] #PR1268 v3.8.7 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 6645cf5e8..1fee3b62d 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.6 + 3.8.7 ../nupkg True ../ACadSharp.snk From e0d4d983c0b326151c0497f288a6839b969f32b2 Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 6 Oct 2026 11:54:20 +0200 Subject: [PATCH 16/81] Refactor Prototype 1b storage parsing Store ACDS handle/data separately, update DXF binary chunk handling, switch DWG reader to the new Prototype 1b model, add an obsolete compatibility reader, and mark DataStorage obsolete. --- src/ACadSharp/DataStorage/AcdsRecordColumn.cs | 8 +- src/ACadSharp/IO/CadDocumentBuilder.cs | 4 +- .../DwgStreamReaders/DwgPrototype1bReader.cs | 943 +++++++----------- .../DwgPrototype1bReaderV2.cs | 554 ---------- .../DwgPrototype1bReader_obsolete.cs | 746 ++++++++++++++ .../DxfAcdsDataSectionReader.cs | 18 +- src/ACadSharp/IO/DwgReader.cs | 9 +- src/ACadSharp/Prototype1b/DataStorage.cs | 2 + 8 files changed, 1149 insertions(+), 1135 deletions(-) delete mode 100644 src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs create mode 100644 src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader_obsolete.cs diff --git a/src/ACadSharp/DataStorage/AcdsRecordColumn.cs b/src/ACadSharp/DataStorage/AcdsRecordColumn.cs index 24ed16769..063bc6ff3 100644 --- a/src/ACadSharp/DataStorage/AcdsRecordColumn.cs +++ b/src/ACadSharp/DataStorage/AcdsRecordColumn.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.IO; namespace ACadSharp.DataStorage; @@ -8,10 +8,12 @@ internal class AcdsRecordColumn public string Name { get; set; } = string.Empty; - public KeyValuePair CodeValuePair { get; set; } + public ulong Handle { get; set; } + + public MemoryStream Data { get; set; } public override string ToString() { - return $"{this.Name} : {this.DataType} : {this.CodeValuePair}"; + return $"{this.Name} : {this.DataType}"; } } \ No newline at end of file diff --git a/src/ACadSharp/IO/CadDocumentBuilder.cs b/src/ACadSharp/IO/CadDocumentBuilder.cs index 2021dbaed..f078f7c4c 100644 --- a/src/ACadSharp/IO/CadDocumentBuilder.cs +++ b/src/ACadSharp/IO/CadDocumentBuilder.cs @@ -113,8 +113,8 @@ public void BuildDataStorage() continue; } - ulong handle = (ulong)idColumn.CodeValuePair.Value; - byte[] bytes = (byte[])dataColumn.CodeValuePair.Value; + ulong handle = (ulong)idColumn.Handle; + byte[] bytes = dataColumn.Data.ToArray(); if (!this.DocumentToBuild.TryGetCadObject(handle, out var geometry)) { diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs index 79bbfbfe3..bb498635f 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs @@ -1,8 +1,6 @@ -using ACadSharp.Prototype1b; -using ACadSharp.Prototype1b.Segments; +using ACadSharp.DataStorage; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -13,13 +11,17 @@ internal class DwgPrototype1bReader : DwgSectionIO { public override string SectionName => DwgSectionDefinition.AcDsPrototype; - public static readonly uint[] TYPE_SIZES = new uint[] { 0, 0, 2, 1, 2, 4, 8, 1, 2, 4, 8, 4, 8, 0, 0, 0 }; - private readonly DwgDocumentBuilder _builder; private readonly IDwgStreamReader _reader; - private readonly Prototype1b.DataStorage _storage = new Prototype1b.DataStorage(); + private DataIndexSegment _dataIndex; + + private FileHeader _fileHeader; + + private FileSegmentIndex _fileSegmentIndex; + + private SchemaIndex _schemaIndex; public DwgPrototype1bReader(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) { @@ -27,39 +29,23 @@ public DwgPrototype1bReader(ACadVersion version, DwgDocumentBuilder builder, IDw this._builder = builder; } - public Prototype1b.DataStorage Read() + public void Read() { + CadFileDataStorage storage = new(); + try { - this._storage.FileHeader = this.readFileHeader(); - - // Indices for reading values - this._storage.IndexPointers = new DataStoragePointers(); - this._storage.IndexPointers.SegmentIndex = this.readSegmentIndex(); - this._storage.IndexPointers.DataIndex = this.readDataIndex(); - this._storage.IndexPointers.SchemaIndex = this.readSchemaIndex(); - this._storage.IndexPointers.FreeSpace = this.readFreeSpace(); // Index to empty spaces (padding or so) within the file - - // Probably the file state before the last save - this._storage.PreviousSave = this.readPreviousSave(); + this._fileHeader = this.readFileHeader(); + this._fileSegmentIndex = this.readFileSegmentIndex(); + this._schemaIndex = this.readSchemaIndex(); - // Schema data lookup - this._storage.SchemaSearch = this.readSchemaSearch(); - foreach (SchemaSearchEntry search in this._storage.SchemaSearch.Entries) - { - search.SchemaName = this._storage.IndexPointers.SchemaIndex.SchemaNames[search.SchemaNameIndex]; - } + var schemes = this.readSchemaDataEntries(); + storage.Schemes.AddRange(schemes.SelectMany(s => s.Schemes)); - this._storage.SchemaFields = []; - this._storage.DataFields = []; - this._storage.Blobs = []; + this._dataIndex = this.readDataIndex(); + this.readDataFields(storage); - // From this point on it should be possible to read storage entries sequentially - // The issue is that sometimes there seemingly are empty padding sections (which should be - // referenced by the FreeSpace entry) that are missing from the FreeSpace definition or are larger - // than specified in the FreeSpace definition. - - this.ReadSegments(); + this._builder.DataStorage = storage; } catch (Exception ex) { @@ -68,328 +54,305 @@ public Prototype1b.DataStorage Read() this.notify("An error occurred while reading the Prototype1b", NotificationType.Error, ex); } - - return this._storage; } - internal IPrototype1bSegment ReadSegment() + private void processDataValue(CadFileDataStorage storage, DataField.DataEntry data, DataIndexSegment.Entry shemaIndex) { - IPrototype1bSegment segment; - long segmentStartPosition = this._reader.Position; - SegmentHeader header = this.readSubItemHeader(); - segment = this.ReadSegmentData(header); - - // Blob01 entries do not specify how large they are (The headers SegmentSize only has the size of the header without any blob data, blobs are aligned to the next 128 byte boundary) - if (header.IsBlob != 1) - { - // Skip to the end of this sub item - long readSize = this._reader.Position - segmentStartPosition; - long paddingDataSize = header.SegmentSize - readSize; - byte[] _padding = this._reader.ReadBytes((int)paddingDataSize); // Should be filled with only 0x70 values (TODO: Sometimes there are some other strange padding bytes) - } + AcdsRecord record = new(); + record.Index = shemaIndex.SchemaIndex; - return segment; - } - - internal IPrototype1bSegment ReadSegmentAt(ulong offset) - { - long pos = this._reader.Position; - this._reader.Position = (long)offset; - IPrototype1bSegment segment = this.ReadSegment(); - this._reader.Position = pos; - return segment; - } + var schema = storage.Schemes.FirstOrDefault(s => s.Index == shemaIndex.SchemaIndex); - internal IPrototype1bSegment ReadSegmentData(SegmentHeader header) => header.Name switch - { - "segidx" => this.readSegmentIndexValue(header), - "datidx" => this.readDataIndexValue(header), - "schidx" => this.readSchemaIndexValue(header), - "search" => this.readSchemaSearchValue(header), - "freesp" => this.readFreeSpaceValue(header), - "prvsav" => this.readPreviousSaveValue(header), - "schdat" => this.readSchemaDataValue(header), - "_data_" => this.readDataValue(header), - "blob01" => this.readBlob01(header), - _ => throw new InvalidDataException($"Unknown segment type \"{header.Name}\""), - }; - - internal void ReadSegments() - { - foreach (KeyValuePair entry in this._storage.IndexPointers.SegmentIndex.Pointers) + foreach (var item in schema.EmbeddedRecords) { - if (entry.Value.Size == 0) continue; - this._reader.Position = (long)entry.Value.Offset; + AcdsRecordColumn column = new AcdsRecordColumn(); + column.Name = item.Name; + column.DataType = (short)item.Type; - IPrototype1bSegment segment = this.ReadSegment(); - switch (segment) + switch (item.Name) { - // Those entries have already been parsed (as they were directly referenced in the header - // and there should probably not be any additional entries of the same type) - case SegmentIndex segidx: - case DataIndex datidx: - case SchemaIndex schidx: - case SchemaSearch search: - case FreeSpace freesp: - case PreviousSave prvsav: - break; - - // Add entries which are types that can appear multiple times to a collection of them - case SchemaData schdat: - this._storage.SchemaFields.Add(schdat); - break; - case DataField data: - this._storage.DataFields.Add(data); + case CadFileDataStorage.Id: + column.Handle = data.Header.Handle; break; - case Blob01 blob: - this._storage.Blobs.Add(blob); + case CadFileDataStorage.AsmData: + column.Data = new MemoryStream(); + column.Data.Write(data.GetData(), 0, data.GetData().Length); break; default: break; } + + record.Columns.Add(item.Name, column); } + + storage.Records.Add(record); } - private Blob01 readBlob01(SegmentHeader header) + private AcdsSchema readAcdsSchema() { - Blob01 blob = new() + AcdsSchema schema = new(); + + ushort indexCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + for (int i = 0; i < indexCount; i++) { - Header = header, - TotalDataSize = this._reader.ReadRawULong(), - PageStartOffset = this._reader.ReadRawULong(), - PageIndex = this._reader.ReadUInt(), - PageCount = this._reader.ReadUInt(), - PageDataSize = this._reader.ReadRawULong() - }; - blob.Data = this._reader.ReadBytes((int)blob.PageDataSize); - - // Align to the next 128 byte boundary - long boundary = this._reader.Position % 128; - if (boundary != 0) + schema.Indices.Add(this._reader.ReadRawULong()); + } + + ushort propCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + for (int i = 0; i < propCount; i++) { - byte[] _ = this._reader.ReadBytes((int)(128 - boundary)); + schema.EmbeddedRecords.Add(this.readAcdsSchemaRecord()); } - return blob; + return schema; } - private DataIndex readDataIndex() + private AcdsSchemaRecord readAcdsSchemaRecord() { - return (DataIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.DataIndexSegmentIndex].Offset); - } + AcdsSchemaRecord record = new(); - private DataIndex readDataIndexValue(SegmentHeader header) - { - DataIndex index = new() - { - Header = header - }; - int entryCount = this._reader.ReadInt(); - index.Unknown1 = this._reader.ReadInt(); + record.Flags = (SchemaRecordFlags)this._reader.ReadInt(); + record.NameIndex = this._reader.ReadUInt(); - index.Entries = []; - for (int j = 0; j < entryCount; j++) + // Get type size + if ((record.Flags & SchemaRecordFlags.NoType) == 0) { - uint dataSegmentIndex = this._reader.ReadUInt(); - uint dataSegmentOffset = this._reader.ReadUInt(); - uint schemaIndex = this._reader.ReadUInt(); - - // Segment index values of 0 can be ignored (stub entries) - if (dataSegmentIndex == 0) continue; - - // Get or create a data index entry - if (!index.Entries.TryGetValue(dataSegmentIndex, out DataIndexEntry entry)) + record.Type = this._reader.ReadUInt(); + if (record.Type == 0xE) { - entry = new DataIndexEntry - { - DataSegmentIndex = dataSegmentIndex, - Pointers = [] - }; + record.TypeSize = this._reader.ReadUInt(); } - - // Add the current pointer to the list of pointers - entry.Pointers.Add(new DataIndexEntryPointer + else { - Offset = dataSegmentOffset, - SchemaIndex = schemaIndex - }); - index.Entries[dataSegmentIndex] = entry; + record.TypeSize = AcdsSchemaRecord.TypeSizes[record.Type]; + } } - return index; - } - - private DataField readDataValue(SegmentHeader header) - { - DataField dataField = new() + // Read unknown fields + if (record.Flags == SchemaRecordFlags.Unknown0) { - Header = header, - Entries = [] - }; - - // Read headers - List headers = []; - uint headerEndPosition = (uint)this._reader.Position; - if (!this._storage.IndexPointers.DataIndex.Entries.TryGetValue(header.SegmentIndex, out DataIndexEntry entry)) + record.Unknown1 = this._reader.ReadUInt(); + } + else if (record.Flags == SchemaRecordFlags.Unknown1) { - return dataField; + record.Unknown2 = this._reader.ReadUInt(); } - // As entries can be out of order, keep updating the max position to resume correctly after reading all entries - long maxPos = 0; - foreach (DataIndexEntryPointer pointer in entry.Pointers) + // Read values + var valuesCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + if (record.TypeSize == 0) { - this._reader.Position = headerEndPosition + pointer.Offset; - headers.Add(new DataHeader - { - EntrySize = this._reader.ReadUInt(), - Unknown1 = this._reader.ReadUInt(), - Handle = this._reader.ReadRawULong(), - DataOffset = this._reader.ReadUInt(), - SchemaIndex = pointer.SchemaIndex - }); - maxPos = Math.Max(maxPos, this._reader.Position); + return record; } - this._reader.Position = maxPos; - // Sort headers to get ascending offset values to prevent wrong offset calculations (TODO: Is this ok or should the order maybe be preserved?) - headers = headers.OrderBy(x => x.DataOffset).ToList(); - - // Get the position to the beginning of the data content section and skip padding and unreferenced DataHeader entries - uint headerStartPosition = headerEndPosition - SegmentHeader.SIZE; - uint dataRefPosition = headerStartPosition + (uint)(header.ObjectDataAlignmentOffset << 4); - if (dataRefPosition != this._reader.Position) + for (int i = 0; i < valuesCount; i++) { - byte[] data = this._reader.ReadBytes((int)(dataRefPosition - this._reader.Position)); - // Many unreferenced / seemingly dangling DataHeader entries might be defined here, they would - // also have valid file data in the next step, where file contents are being read. This can be a lot of entries, e.g. - // 10 - 20 valid DataHeader entries have been seen + record.Values.Add(this._reader.ReadBytes((int)record.TypeSize)); } - // Read data associated with headers - for (int i = 0; i < headers.Count; i++) + return record; + } + + private void readDataFields(CadFileDataStorage storage) + { + foreach (var index in this._dataIndex.Entries.Select(e => e.SegmentIndex).Distinct()) { - DataValue value = new(); - DataHeader dataHeader = headers[i]; - uint recordStreamOffset = dataRefPosition + dataHeader.DataOffset; - uint maxRecordSize = i + 1 < headers.Count ? (headers[i + 1].DataOffset - dataHeader.DataOffset) : (header.SegmentSize - (uint)(header.ObjectDataAlignmentOffset << 4) - dataHeader.DataOffset); - - this._reader.Position = recordStreamOffset; - value.DataSize = this._reader.ReadUInt(); - if ((value.DataSize + 4) <= maxRecordSize) + this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)index].Offset; + + var field = new DataField(); + + var headerStartPosition = this._reader.Position; + + this.readDataStorageFileSegment(field); + + if (field.Header.SegmentSize == 0) { - value.Data = this._reader.ReadBytes((int)value.DataSize); - - //// Example how to detect the actual file type based on the file byte signature - //byte[] FILE_MAGIC_PNG = [137, 80, 78, 71, 13, 10, 26, 10]; - //byte[] FILE_MAGIC_ACIS_BINARY = Encoding.ASCII.GetBytes("ACIS BinaryFile"); - //byte[] FILE_MAGIC_ASM_BINARY = Encoding.ASCII.GetBytes("ASM BinaryFile"); - - //if (value.Data.Length >= FILE_MAGIC_PNG.Length && value.Data.AsSpan(0, FILE_MAGIC_PNG.Length).SequenceEqual(FILE_MAGIC_PNG)) { - // // The bytes represent a PNG file - //} - //else if (value.Data.Length >= FILE_MAGIC_ACIS_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ACIS_BINARY.Length).SequenceEqual(FILE_MAGIC_ACIS_BINARY)) { - // // The bytes represent an ACIS binary file - //} - //else if (value.Data.Length >= FILE_MAGIC_ASM_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ASM_BINARY.Length).SequenceEqual(FILE_MAGIC_ASM_BINARY)) { - // // The bytes represent an ACIS / ASM binary file - //} - //else { - // // The bytes represent a file other than a PNG and ACIS file - //} + continue; } - else if (value.DataSize == 0xbb106bb1) + + var headerEndPosition = this._reader.Position; + List shemaIndexes = this._dataIndex.GetEntriesBySchemaIndex(index); + if (shemaIndexes.Count == 0) { - value.BlobReference = new DataBlobReference() + continue; + } + + List headers = new(); + foreach (var e in shemaIndexes) + { + this._reader.Position = headerEndPosition + e.LocalOffset; + DataField.DataHeader entry = new(); + + entry.Size = this._reader.ReadUInt(); + var unknown1 = this._reader.ReadUInt(); + entry.Handle = this._reader.ReadRawULong(); + entry.LocalOffset = this._reader.ReadUInt(); + + headers.Add(entry); + } + + var dataRefPosition = headerStartPosition + ((long)field.Header.ObjectDataAlignmentOffset << 4); + if (dataRefPosition != this._reader.Position) + { + // Many unreferenced / seemingly dangling DataHeader entries might be defined here, they would + // also have valid file data in the next step, where file contents are being read. This can be a lot of entries, e.g. + // 10 - 20 valid DataHeader entries have been seen + byte[] data = this._reader.ReadBytes((int)(dataRefPosition - this._reader.Position)); + } + + for (int i = 0; i < headers.Count; i++) + { + DataField.DataHeader dataHeader = headers[i]; + var recordStreamOffset = dataRefPosition + dataHeader.LocalOffset; + this._reader.Position = recordStreamOffset; + + uint maxRecordSize = i + 1 < headers.Count ? + (headers[i + 1].LocalOffset - dataHeader.LocalOffset) + : (field.Header.SegmentSize - (uint)(field.Header.ObjectDataAlignmentOffset << 4) - dataHeader.LocalOffset); + + DataField.DataEntry value = new(); + value.Header = dataHeader; + value.DataSize = this._reader.ReadUInt(); + if ((value.DataSize + 4) <= maxRecordSize) { - TotalDataSize = this._reader.ReadRawULong(), - PageCount = this._reader.ReadUInt(), - RecordSize = this._reader.ReadUInt(), - PageSize = this._reader.ReadUInt(), - LastPageSize = this._reader.ReadUInt(), - Unknown1 = this._reader.ReadUInt(), - Unknown2 = this._reader.ReadUInt(), - SegmentPointers = [] - }; - for (int j = 0; j < value.BlobReference.PageCount; j++) + value.Data = this._reader.ReadBytes((int)value.DataSize); + } + else if (value.DataSize == 0xbb106bb1) { - value.BlobReference.SegmentPointers.Add(( - this._reader.ReadUInt(), // segment index - this._reader.ReadUInt() // size - )); + var totalDataSize = this._reader.ReadRawULong(); + var pageCount = this._reader.ReadUInt(); + var recordSize = this._reader.ReadUInt(); + var pageSize = this._reader.ReadUInt(); + var lastPageSize = this._reader.ReadUInt(); + var unknown1 = this._reader.ReadUInt(); + var unknown2 = this._reader.ReadUInt(); + + value.BlobReference = new DataField.DataBlobReference() + { + TotalDataSize = totalDataSize, + PageCount = pageCount, + RecordSize = recordSize, + PageSize = pageSize, + LastPageSize = lastPageSize, + Unknown1 = unknown1, + Unknown2 = unknown2, + SegmentPointers = new List<(uint, uint)>() + }; + + for (int j = 0; j < value.BlobReference.PageCount; j++) + { + var segmentIndex = this._reader.ReadUInt(); + var size = this._reader.ReadUInt(); + + value.BlobReference.SegmentPointers.Add((segmentIndex, size)); + } } + + field.DataEntries.Add(value); + processDataValue(storage, value, shemaIndexes[i]); } - else + } + } + + private DataIndexSegment readDataIndex() + { + this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.DataIndexSegmentIndex].Offset; + + DataIndexSegment dataIndex = new(); + + this.readDataStorageFileSegment(dataIndex); + + //UInt32 Unknown property count + int entryCount = this._reader.ReadInt(); + //UInt32 Unknown (0) + var unknown1 = this._reader.ReadInt(); + for (int i = 0; i < entryCount; i++) + { + //UInt32 Segment index (0 means stub entry and can be ignored). + uint segmentIndex = this._reader.ReadUInt(); + //UInt32 Local offset. This is a local offset in the stream, relative to the file segment’s + //stream start position.This points to a data file segment, see paragraph 24.2.2.3. + uint localOffset = this._reader.ReadUInt(); + //UInt32 Schema index + uint schemaIndex = this._reader.ReadUInt(); + + if (segmentIndex == 0) { - Debugger.Break(); // This should not be possible - value.DataSize = 0; + continue; } - dataField.Entries.Add(new DataEntry + dataIndex.Entries.Add(new DataIndexSegment.Entry { - Header = dataHeader, - Value = value + SegmentIndex = segmentIndex, + LocalOffset = localOffset, + SchemaIndex = schemaIndex, }); } - return dataField; + return dataIndex; } - private FileHeader readFileHeader() + private void readDataStorageFileSegment(FileSegment segment) { - uint fileSignature = this._reader.ReadUInt(); - short fileHeaderSize = this._reader.ReadShort(); - return new() - { - FileSignature = fileSignature, - FileHeaderSize = fileHeaderSize, - UnknownFlag = this._reader.ReadShort(), - Unknown1 = this._reader.ReadInt(), - Version = this._reader.ReadInt(), - Unknown2 = this._reader.ReadInt(), - DataStorageRevision = this._reader.ReadInt(), - SegmentIndexOffset = this._reader.ReadInt(), - SegmentIndexUnknown = this._reader.ReadInt(), - SegmentIndexEntryCount = this._reader.ReadInt(), - SchemaIndexSegmentIndex = this._reader.ReadInt(), - DataIndexSegmentIndex = this._reader.ReadInt(), - SearchSegmentIndex = this._reader.ReadInt(), - PreviousSaveIndex = this._reader.ReadInt(), - FileSize = this._reader.ReadInt(), - Unknown3 = this._reader.ReadInt(), - FreeSpaceSegmentIndex = this._reader.ReadInt(), - FreeSpaceEntryCount = this._reader.ReadInt(), - Unknown5 = this._reader.ReadInt(), - - UnknownRemaining = this._reader.ReadBytes(fileHeaderSize - 72) - }; + var pos = this._reader.Position; + segment.Header = this.readSubItemHeader(); } - private FreeSpace readFreeSpace() + private FileHeader readFileHeader() { - if (this._storage.FileHeader.FreeSpaceSegmentIndex == 0) return null; - return (FreeSpace)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.FreeSpaceSegmentIndex].Offset); + FileHeader fileHeader = new(); + + //UInt32 File signature + fileHeader.FileSignature = this._reader.ReadUInt(); + //Int32 File header size + fileHeader.FileHeaderSize = this._reader.ReadInt(); + //Int32 Unknown 1 (always 2?) + fileHeader.Unknown1 = this._reader.ReadInt(); + //Int32 Version(always 2 ?) + fileHeader.Version = this._reader.ReadInt(); + //Int32 Unknown 2 (always 0?) + fileHeader.Unknown2 = this._reader.ReadInt(); + //Int32 Data storage revision + fileHeader.DataStorageRevision = this._reader.ReadInt(); + //Int32 Segment index offset (the stream off set from the data store’s stream start + //position). See paragraph 24.2.2.1 for the segment index file segment. + fileHeader.SegmentIndexOffset = this._reader.ReadInt(); + //Int32 Segment index unknown + fileHeader.SegmentIndexUnknown = this._reader.ReadInt(); + //Int32 Segment index entry count + fileHeader.SegmentIndexEntryCount = this._reader.ReadInt(); + //Int32 Schema index segment index. This is the index into the segment index entry + //array(see paragraph 24.2.2.1) for the schema index file segment(see paragraph 24.2.2.4). + fileHeader.SchemaIndexSegmentIndex = this._reader.ReadInt(); + //Int32 Data index segment index. This is the index into the segment index entry array + //(see paragraph 24.2.2.1) for the data index file segment(see paragraph 24.2.2.2). + fileHeader.DataIndexSegmentIndex = this._reader.ReadInt(); + //Int32 Search segment index + fileHeader.SearchSegmentIndex = this._reader.ReadInt(); + //Int32 Previous save index + fileHeader.PreviousSaveIndex = this._reader.ReadInt(); + //Int32 File size + fileHeader.FileSize = this._reader.ReadInt(); + + return fileHeader; } - private FreeSpace readFreeSpaceValue(SegmentHeader header) + private FileSegmentIndex readFileSegmentIndex() { - FreeSpace space = new() + this._reader.Position = this._fileHeader.SegmentIndexOffset; + + FileSegmentIndex segmentIndex = new(); + + this.readDataStorageFileSegment(segmentIndex); + + for (int i = 0; i < this._fileHeader.SegmentIndexEntryCount; i++) { - Header = header, - Unknown = this._reader.ReadRawULong(), - FreeSpaces = new FreeSpaceArea[this._storage.FileHeader.FreeSpaceEntryCount] - }; - for (int i = 0; i < space.FreeSpaces.Length; i++) - { // TODO: Sometimes when there is an additional freespace definition, it might not match the `FreeSpaceEntryCount` count - ulong position = this._reader.ReadRawULong(); - uint size = this._reader.ReadUInt(); - space.FreeSpaces[i] = new FreeSpaceArea - { - Position = position, - Size = size - }; + var offset = this._reader.ReadRawULong(); + var size = this._reader.ReadUInt(); + segmentIndex.AddEntry(offset, size); } - return space; + + return segmentIndex; } private string readNullTerminatedString() @@ -400,65 +363,37 @@ private string readNullTerminatedString() { bytes.Add(b); } - return Encoding.UTF8.GetString(bytes.ToArray()); // TODO: Is this ASCII only or UTF-8 ? + return this._reader.Encoding.GetString(bytes.ToArray()); } - private PreviousSave readPreviousSave() + private SchemaData readSchemaData(long offset) { - if (this._storage.FileHeader.PreviousSaveIndex == 0) return null; - return (PreviousSave)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.PreviousSaveIndex].Offset); - } + this._reader.Position = offset; - private PreviousSave readPreviousSaveValue(SegmentHeader header) => new() - { - Header = header, - FileHeader = this.readFileHeader() - }; - - private Schema readSchema() - { - Schema schema = new(); - - ushort indexCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - schema.Indices = new ulong[indexCount]; - for (int i = 0; i < indexCount; i++) - { - schema.Indices[i] = this._reader.ReadRawULong(); - } + var schemaData = new SchemaData(); - ushort propCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - schema.Properties = new SchemaProperty[propCount]; - for (int i = 0; i < propCount; i++) - { - schema.Properties[i] = this.readSchemaProperty(); - } + this.readDataStorageFileSegment(schemaData); - return schema; - } + long start = this._reader.Position; - private SchemaData readSchemaDataValue(SegmentHeader header) - { - // Read unknown schema properties - List unknownProps = []; - foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaUnknownPropertyPointer) + // Begin repeat schema unknown properties in the associated schema index file + //segment(paragraph 24.2.2.4), where the property’s segment index is equal to + //this file segment’s segment index(found in the header). + var unknownPropertyCount = this._schemaIndex.PropertyPointers.Count(p => p.SegmentIndex == schemaData.Header.SegmentIndex); + for (int i = 0; i < unknownPropertyCount; i++) { - if (pointer.SchemaIndex != header.SegmentIndex) continue; - unknownProps.Add(new SchemaUnknownProperty - { - DataSize = this._reader.ReadUInt(), - UnknownFlags = this._reader.ReadUInt() - }); + var dataSize = this._reader.ReadUInt(); + var unknownFlags = this._reader.ReadUInt(); } // Read schemas - List schemaValues = []; - foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaPointer) + foreach (var pointer in this._schemaIndex.SchemaPointers.Where(p => p.SegmentIndex == schemaData.Header.SegmentIndex)) { - if (pointer.SchemaIndex != header.SegmentIndex) continue; - Schema schema = this.readSchema(); + AcdsSchema schema = this.readAcdsSchema(); + schema.Index = pointer.Index; - schema.Name = this._storage.IndexPointers.SchemaIndex.SchemaNames[pointer.Index]; - schemaValues.Add(schema); + schema.Name = this._schemaIndex.SchemaNames[(int)pointer.Index]; + schemaData.Schemes.Add(schema); } // Align to the next 16 byte boundary @@ -477,269 +412,145 @@ private SchemaData readSchemaDataValue(SegmentHeader header) } // Assign schema Property names - foreach (Schema schema in schemaValues) + foreach (AcdsSchema schema in schemaData.Schemes) { - foreach (SchemaProperty property in schema.Properties) + foreach (AcdsSchemaRecord property in schema.EmbeddedRecords) { - property.Name = propertyNames[property.NameIndex]; + property.Name = propertyNames[(int)property.NameIndex]; } } - return new SchemaData - { - Header = header, - SchemaUnknownProperties = unknownProps, - Values = schemaValues, - }; - } - - private SchemaIndex readSchemaIndex() - { - return (SchemaIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SchemaIndexSegmentIndex].Offset); + return schemaData; } - private SchemaIndex readSchemaIndexValue(SegmentHeader header) + private List readSchemaDataEntries() { - SchemaIndex schemaIndex = new() - { - Header = header - }; - uint unknownPropertyCount = this._reader.ReadUInt(); - schemaIndex.SchemaPointer = new SchemaPropertyPointer[unknownPropertyCount]; - schemaIndex.Unknown1 = this._reader.ReadUInt(); - - for (int i = 0; i < unknownPropertyCount; i++) + List schemes = new(); + foreach (var index in this._schemaIndex.GetSchemaIndexes()) { - schemaIndex.SchemaPointer[i] = new SchemaPropertyPointer - { - Index = this._reader.ReadUInt(), - SchemaIndex = this._reader.ReadUInt(), - Offset = this._reader.ReadUInt(), - }; + var schema = this.readSchemaData((long)this._fileSegmentIndex.Entries[(int)index].Offset); + schemes.Add(schema); } - schemaIndex.UnknownMagic = this._reader.ReadRawLong(); - uint propertyEntryCount = this._reader.ReadUInt(); - uint unknownCount = this._reader.ReadUInt(); // 0; 8 - - schemaIndex.UnknownPropertyEntryPointer = new SchemaPropertyPointer[propertyEntryCount]; - for (int i = 0; i < propertyEntryCount; i++) - { - schemaIndex.UnknownPropertyEntryPointer[i] = new SchemaPropertyPointer - { - SchemaIndex = this._reader.ReadUInt(), - Offset = this._reader.ReadUInt(), - Index = this._reader.ReadUInt(), - }; - } - - schemaIndex.SchemaUnknownPropertyPointer = new SchemaPropertyPointer[unknownCount]; - for (int i = 0; i < unknownCount; i++) - { - schemaIndex.SchemaUnknownPropertyPointer[i] = new SchemaPropertyPointer - { - Index = this._reader.ReadUInt(), - SchemaIndex = this._reader.ReadUInt(), - Offset = this._reader.ReadUInt(), - }; - } - - schemaIndex.UnknownIndex1 = this._reader.ReadUInt(); - - // Align to the next 16 byte boundary - long boundary = this._reader.Position % 16; - if (boundary != 0) - { - byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); - } - - // Read labels (NULL terminated strings) - uint stringCount = this._reader.ReadUInt(); - schemaIndex.SchemaNames = new string[stringCount]; - for (int i = 0; i < stringCount; i++) - { - schemaIndex.SchemaNames[i] = this.readNullTerminatedString(); - } - - return schemaIndex; + return schemes; } - private SchemaProperty readSchemaProperty() + private SchemaIndex readSchemaIndex() { - SchemaProperty prop = new() - { - PropertyFlags = this._reader.ReadUInt(), // 1 = Unknown / 2 = NoType / 8 = Unknown - NameIndex = this._reader.ReadUInt() - }; - - // Get type size - if (!((prop.PropertyFlags & (1 << 1)) != 0)) - { - prop.Type = this._reader.ReadUInt(); // 0 - 15 - if (prop.Type == 0xe) + var startOffset = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.SchemaIndexSegmentIndex].Offset; + this._reader.Position = startOffset; + + var schema = new SchemaIndex(); + + this.readDataStorageFileSegment(schema); + + //UInt32 Unknown property count + uint propertyCount = this._reader.ReadUInt(); + //UInt32 Unknown (0) + var unknown0 = this._reader.ReadUInt(); + for (int i = 0; i < propertyCount; i++) + { + //UInt32 Index (starting at 0) + var index = this._reader.ReadUInt(); + //UInt32 Segment index into the segment index file segment entry table (paragraph + //24.2.2.1) of the schema data file segment (paragraph 24.2.2.6) + var schemaIndex = this._reader.ReadUInt(); + //UInt32 Local offset of the unknown schema property. This is a local offset in the + //stream, relative to the schema data file segment’s stream start position. + var offset = this._reader.ReadUInt(); + schema.SchemaPointers.Add(new SchemaIndex.Pointer { - prop.TypeSize = this._reader.ReadUInt(); - } - else - { - prop.TypeSize = TYPE_SIZES[prop.Type.Value]; - } - } - - // Read unknown fields - if (prop.PropertyFlags == 1) - { - prop.Unknown1 = this._reader.ReadUInt(); - } - else if (prop.PropertyFlags == 8) - { - prop.Unknown2 = this._reader.ReadUInt(); + Index = index, + SegmentIndex = schemaIndex, + LocalOffset = offset, + }); } - // Read values - prop.PropertyValueCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - prop.Values = new byte[prop.PropertyValueCount, prop.TypeSize]; - if (prop.TypeSize != 0) + //Int64 Unknown (0x0af10c) + var unknown1 = this._reader.ReadRawULong(); + //UInt32 Property entry count + uint propertyEntryCount = this._reader.ReadUInt(); + //UInt32 Unknown (0) + uint unknownCount = this._reader.ReadUInt(); + for (int i = 0; i < propertyEntryCount; i++) { - for (int i = 0; i < prop.PropertyValueCount; i++) + //UInt32 Segment index into the segment index file segment entry table (paragraph + //24.2.2.1) of the schema data file segment(paragraph 24.2.2.6). + var schemaIndex = this._reader.ReadUInt(); + //UInt32 Local offset of the schema property. This is a local offset in the stream, relative + //to the schema data file segment’s stream start position. + var offset = this._reader.ReadUInt(); + //UInt32 Index + var index = this._reader.ReadUInt(); + schema.PropertyPointers.Add(new SchemaIndex.Pointer { - byte[] propertyValue = this._reader.ReadBytes((int)prop.TypeSize); - for (int j = 0; j < propertyValue.Length; j++) - { - prop.Values[0, j] = propertyValue[j]; - } - } - } - - return prop; - } - - private SchemaSearch readSchemaSearch() - { - return (SchemaSearch)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SearchSegmentIndex].Offset); - } - - private SchemaSearchEntry readSchemaSearchEntry() - { - SchemaSearchEntry search = new() - { - SchemaNameIndex = this._reader.ReadUInt() - }; - - ulong sortedIndexCount = this._reader.ReadRawULong(); - search.SortedIndices = new ulong[sortedIndexCount]; - for (ulong i = 0; i < sortedIndexCount; i++) - { - search.SortedIndices[i] = this._reader.ReadRawULong(); + SegmentIndex = schemaIndex, + LocalOffset = offset, + Index = index, + }); } - uint idIndexesCount = this._reader.ReadUInt(); - SearchEntryObject[][] idEntryObjects = new SearchEntryObject[idIndexesCount][]; + //Begin repeat schema unknown properties in the associated schema index file + //segment(paragraph 24.2.2.4), where the property’s segment index is equal to + //this file segment’s segment index(found in the header). - // TODO: Find out what it would mean if there were multiple index lists. Never happened in test files so far - if (idIndexesCount > 1) - { - Debugger.Break(); - } + //Begin repeat schema entries in the associated schema index file segment + //(paragraph 24.2.2.4), where the property’s segment index is equal to this file + //segment’s segment index(found in the header). - if (idIndexesCount > 0) + //A schema, see paragraph 24.2.2.6.1. The stream position is the file segment’s + //start position + the schema entry’s local offset. + var propertyNamesOffset = (long)schema.Header.SystemDataAlignmentOffset << 4; + if (propertyNamesOffset != 0) { - search.Unknown1 = this._reader.ReadUInt(); - - for (uint i = 0; i < idIndexesCount; i++) + this._reader.Position = (long)(startOffset + propertyNamesOffset); + //Uint32 Property name count + var nproperties = this._reader.ReadUInt(); + for (int i = 0; i < nproperties; i++) { - uint idIndexCount = this._reader.ReadUInt(); - - SearchEntryObject[] entryObjects = new SearchEntryObject[idIndexCount]; - for (uint j = 0; j < idIndexCount; j++) - { - ulong handle = this._reader.ReadRawULong(); - - ulong indexCount = this._reader.ReadRawULong(); - ulong[] indices = new ulong[indexCount]; - for (ulong k = 0; k < indexCount; k++) - { - indices[k] = this._reader.ReadRawULong(); - } - - entryObjects[j] = new SearchEntryObject - { - Handle = handle, - Indices = indices - }; - } - idEntryObjects[i] = entryObjects; + //AnsiString Property name (zero byte delimited). These names are referred to by the + //schema’s schema property’s name index(paragraph 24.2.2.6.1.1).Name + //strings can be shared between multiple schema properties this way.See + //paragraph 24.2.2.6.1 for details about the schema. + schema.SchemaNames.Add(this.readNullTerminatedString()); } } - search.IdEntryObjects = idEntryObjects; - - return search; - } - private SchemaSearch readSchemaSearchValue(SegmentHeader header) - { - SchemaSearch schemaSearch = new() - { - Header = header - }; - int schemaCount = this._reader.ReadInt(); - schemaSearch.Entries = []; - for (int j = 0; j < schemaCount; j++) - { - schemaSearch.Entries.Add(this.readSchemaSearchEntry()); - } - return schemaSearch; - } - - private SegmentIndex readSegmentIndex() - { - return (SegmentIndex)this.ReadSegmentAt((ulong)this._storage.FileHeader.SegmentIndexOffset); - } - - private SegmentIndex readSegmentIndexValue(SegmentHeader header) - { - SegmentIndex index = new() - { - Header = header, - Pointers = [] - }; - for (int j = 0; j < this._storage.FileHeader.SegmentIndexEntryCount; j++) - { - index.Pointers[j] = new SegmentIndexEntry - { - Offset = this._reader.ReadRawULong(), - Size = this._reader.ReadUInt() - }; - } - - // TODO: Sometimes additional bytes (that are not padding bytes) follow here until the segment size - // They actually seem to be garbage data and can be ignored. Often times there are seemingly - // random bytes without any pattern and sometimes there are bytes representing 2-Byte strings. - // Garbage string examples are e.g.: - // --> "nslation\0" followed by the bytes [0, 0, 7, 0, 176, 4] - // --> "k\\auto" - // --> "op> "utocad" - // --> "atio" - - return index; + return schema; } - private SegmentHeader readSubItemHeader() + private FileSegmentHeader readSubItemHeader() { - return new() - { - Signature = this._reader.ReadShort(), - Name = Encoding.ASCII.GetString(this._reader.ReadBytes(6)), - SegmentIndex = this._reader.ReadUInt(), - IsBlob = this._reader.ReadInt(), - SegmentSize = this._reader.ReadUInt(), // Multiple of 0x40 bytes (AutoCAD uses 0x80), padded wth 0x70 values - Unknown1 = this._reader.ReadInt(), - DataStorageRevision = this._reader.ReadInt(), - Unknown2 = this._reader.ReadInt(), - SystemDataAlignmentOffset = this._reader.ReadInt(), - ObjectDataAlignmentOffset = this._reader.ReadInt(), - AlignmentBytes = this._reader.ReadBytes(8) // Align to next 16 byte boundary - }; + var header = new FileSegmentHeader(); + + //Int16 Signature (always 0xd5ac?) + header.Signature = this._reader.ReadShort(); + //byte[6] Name (6 bytes). Names for the several file segments are: + header.Name = Encoding.ASCII.GetString(this._reader.ReadBytes(6)); + //Int32 Segment index + header.SegmentIndex = this._reader.ReadUInt(); + //Int32 Unknown 1 (0 or 1? 1 in blob01 segment). + header.IsBlob = this._reader.ReadInt(); + //Int32 Segment size (multiple of 0x40 bytes (AutoCAD uses 0x80), padded with 0x70 values). + header.SegmentSize = this._reader.ReadUInt(); + //Int32 Unknown 2 (always 0?) + header.Unknown2 = this._reader.ReadInt(); + //Int32 Data storage revision + header.DataStorageRevision = this._reader.ReadInt(); + //Int32 Unknown 3 (always 0?) + header.Unknown3 = this._reader.ReadInt(); + //Int32 System data alignment offset (calculate the stream position by shifting left 4 + //bits and adding to the file segment’s stream start position). This offset is used + //for schema index and schema data segments.So the name “system data” seems + //to refer to schema index/schema data. + header.SystemDataAlignmentOffset = this._reader.ReadInt(); + //Int32 Object data alignment offset (calculate the stream position by shifting left 4 + //bits and adding to the file segment’s stream start position). This offset is used + //for the data segment. + header.ObjectDataAlignmentOffset = this._reader.ReadInt(); + //byte[8] 8 alignment bytes (always 8 x 0x55?). + var alignmentBytes = this._reader.ReadBytes(8); + + return header; } } \ No newline at end of file diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs deleted file mode 100644 index 9fd033900..000000000 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReaderV2.cs +++ /dev/null @@ -1,554 +0,0 @@ -using ACadSharp.DataStorage; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace ACadSharp.IO.DWG.DwgStreamReaders; - -internal class DwgPrototype1bReaderV2 : DwgSectionIO -{ - public override string SectionName => DwgSectionDefinition.AcDsPrototype; - - private readonly DwgDocumentBuilder _builder; - - private readonly IDwgStreamReader _reader; - - private DataIndexSegment _dataIndex; - - private FileHeader _fileHeader; - - private FileSegmentIndex _fileSegmentIndex; - - private SchemaIndex _schemaIndex; - - public DwgPrototype1bReaderV2(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) - { - this._reader = reader; - this._builder = builder; - } - - public void Read() - { - CadFileDataStorage storage = new(); - - try - { - this._fileHeader = this.readFileHeader(); - this._fileSegmentIndex = this.readFileSegmentIndex(); - this._schemaIndex = this.readSchemaIndex(); - - var schemes = this.readSchemaDataEntries(); - storage.Schemes.AddRange(schemes.SelectMany(s => s.Schemes)); - - this._dataIndex = this.readDataIndex(); - this.readDataFields(storage); - - this._builder.DataStorage = storage; - } - catch (Exception ex) - { - if (!this._builder.Configuration.Failsafe) - throw; - - this.notify("An error occurred while reading the Prototype1b", NotificationType.Error, ex); - } - } - - private void processDataValue(CadFileDataStorage storage, DataField.DataEntry data, DataIndexSegment.Entry shemaIndex) - { - AcdsRecord record = new(); - record.Index = shemaIndex.SchemaIndex; - - var schema = storage.Schemes.FirstOrDefault(s => s.Index == shemaIndex.SchemaIndex); - - foreach (var item in schema.EmbeddedRecords) - { - AcdsRecordColumn column = new AcdsRecordColumn(); - column.Name = item.Name; - column.DataType = (short)item.Type; - - switch (item.Name) - { - case CadFileDataStorage.Id: - column.CodeValuePair = new KeyValuePair(320, data.Header.Handle); - break; - case CadFileDataStorage.AsmData: - column.CodeValuePair = new KeyValuePair(310, data.GetData()); - break; - default: - break; - } - - record.Columns.Add(item.Name, column); - } - - storage.Records.Add(record); - } - - private AcdsSchema readAcdsSchema() - { - AcdsSchema schema = new(); - - ushort indexCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - for (int i = 0; i < indexCount; i++) - { - schema.Indices.Add(this._reader.ReadRawULong()); - } - - ushort propCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - for (int i = 0; i < propCount; i++) - { - schema.EmbeddedRecords.Add(this.readAcdsSchemaRecord()); - } - - return schema; - } - - private AcdsSchemaRecord readAcdsSchemaRecord() - { - AcdsSchemaRecord record = new(); - - record.Flags = (SchemaRecordFlags)this._reader.ReadInt(); - record.NameIndex = this._reader.ReadUInt(); - - // Get type size - if ((record.Flags & SchemaRecordFlags.NoType) == 0) - { - record.Type = this._reader.ReadUInt(); - if (record.Type == 0xE) - { - record.TypeSize = this._reader.ReadUInt(); - } - else - { - record.TypeSize = AcdsSchemaRecord.TypeSizes[record.Type]; - } - } - - // Read unknown fields - if (record.Flags == SchemaRecordFlags.Unknown0) - { - record.Unknown1 = this._reader.ReadUInt(); - } - else if (record.Flags == SchemaRecordFlags.Unknown1) - { - record.Unknown2 = this._reader.ReadUInt(); - } - - // Read values - var valuesCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); - if (record.TypeSize == 0) - { - return record; - } - - for (int i = 0; i < valuesCount; i++) - { - record.Values.Add(this._reader.ReadBytes((int)record.TypeSize)); - } - - return record; - } - - private void readDataFields(CadFileDataStorage storage) - { - foreach (var index in this._dataIndex.Entries.Select(e => e.SegmentIndex).Distinct()) - { - this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)index].Offset; - - var field = new DataField(); - - var headerStartPosition = this._reader.Position; - - this.readDataStorageFileSegment(field); - - if (field.Header.SegmentSize == 0) - { - continue; - } - - var headerEndPosition = this._reader.Position; - List shemaIndexes = this._dataIndex.GetEntriesBySchemaIndex(index); - if (shemaIndexes.Count == 0) - { - continue; - } - - List headers = new(); - foreach (var e in shemaIndexes) - { - this._reader.Position = headerEndPosition + e.LocalOffset; - DataField.DataHeader entry = new(); - - entry.Size = this._reader.ReadUInt(); - var unknown1 = this._reader.ReadUInt(); - entry.Handle = this._reader.ReadRawULong(); - entry.LocalOffset = this._reader.ReadUInt(); - - headers.Add(entry); - } - - var dataRefPosition = headerStartPosition + ((long)field.Header.ObjectDataAlignmentOffset << 4); - if (dataRefPosition != this._reader.Position) - { - // Many unreferenced / seemingly dangling DataHeader entries might be defined here, they would - // also have valid file data in the next step, where file contents are being read. This can be a lot of entries, e.g. - // 10 - 20 valid DataHeader entries have been seen - byte[] data = this._reader.ReadBytes((int)(dataRefPosition - this._reader.Position)); - } - - for (int i = 0; i < headers.Count; i++) - { - DataField.DataHeader dataHeader = headers[i]; - var recordStreamOffset = dataRefPosition + dataHeader.LocalOffset; - this._reader.Position = recordStreamOffset; - - uint maxRecordSize = i + 1 < headers.Count ? - (headers[i + 1].LocalOffset - dataHeader.LocalOffset) - : (field.Header.SegmentSize - (uint)(field.Header.ObjectDataAlignmentOffset << 4) - dataHeader.LocalOffset); - - DataField.DataEntry value = new(); - value.Header = dataHeader; - value.DataSize = this._reader.ReadUInt(); - if ((value.DataSize + 4) <= maxRecordSize) - { - value.Data = this._reader.ReadBytes((int)value.DataSize); - } - else if (value.DataSize == 0xbb106bb1) - { - var totalDataSize = this._reader.ReadRawULong(); - var pageCount = this._reader.ReadUInt(); - var recordSize = this._reader.ReadUInt(); - var pageSize = this._reader.ReadUInt(); - var lastPageSize = this._reader.ReadUInt(); - var unknown1 = this._reader.ReadUInt(); - var unknown2 = this._reader.ReadUInt(); - - value.BlobReference = new DataField.DataBlobReference() - { - TotalDataSize = totalDataSize, - PageCount = pageCount, - RecordSize = recordSize, - PageSize = pageSize, - LastPageSize = lastPageSize, - Unknown1 = unknown1, - Unknown2 = unknown2, - SegmentPointers = new List<(uint, uint)>() - }; - - for (int j = 0; j < value.BlobReference.PageCount; j++) - { - var segmentIndex = this._reader.ReadUInt(); - var size = this._reader.ReadUInt(); - - value.BlobReference.SegmentPointers.Add((segmentIndex, size)); - } - } - - field.DataEntries.Add(value); - processDataValue(storage, value, shemaIndexes[i]); - } - } - } - - private DataIndexSegment readDataIndex() - { - this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.DataIndexSegmentIndex].Offset; - - DataIndexSegment dataIndex = new(); - - this.readDataStorageFileSegment(dataIndex); - - //UInt32 Unknown property count - int entryCount = this._reader.ReadInt(); - //UInt32 Unknown (0) - var unknown1 = this._reader.ReadInt(); - for (int i = 0; i < entryCount; i++) - { - //UInt32 Segment index (0 means stub entry and can be ignored). - uint segmentIndex = this._reader.ReadUInt(); - //UInt32 Local offset. This is a local offset in the stream, relative to the file segment’s - //stream start position.This points to a data file segment, see paragraph 24.2.2.3. - uint localOffset = this._reader.ReadUInt(); - //UInt32 Schema index - uint schemaIndex = this._reader.ReadUInt(); - - if (segmentIndex == 0) - { - continue; - } - - dataIndex.Entries.Add(new DataIndexSegment.Entry - { - SegmentIndex = segmentIndex, - LocalOffset = localOffset, - SchemaIndex = schemaIndex, - }); - } - - return dataIndex; - } - - private void readDataStorageFileSegment(FileSegment segment) - { - var pos = this._reader.Position; - segment.Header = this.readSubItemHeader(); - } - - private FileHeader readFileHeader() - { - FileHeader fileHeader = new(); - - //UInt32 File signature - fileHeader.FileSignature = this._reader.ReadUInt(); - //Int32 File header size - fileHeader.FileHeaderSize = this._reader.ReadInt(); - //Int32 Unknown 1 (always 2?) - fileHeader.Unknown1 = this._reader.ReadInt(); - //Int32 Version(always 2 ?) - fileHeader.Version = this._reader.ReadInt(); - //Int32 Unknown 2 (always 0?) - fileHeader.Unknown2 = this._reader.ReadInt(); - //Int32 Data storage revision - fileHeader.DataStorageRevision = this._reader.ReadInt(); - //Int32 Segment index offset (the stream off set from the data store’s stream start - //position). See paragraph 24.2.2.1 for the segment index file segment. - fileHeader.SegmentIndexOffset = this._reader.ReadInt(); - //Int32 Segment index unknown - fileHeader.SegmentIndexUnknown = this._reader.ReadInt(); - //Int32 Segment index entry count - fileHeader.SegmentIndexEntryCount = this._reader.ReadInt(); - //Int32 Schema index segment index. This is the index into the segment index entry - //array(see paragraph 24.2.2.1) for the schema index file segment(see paragraph 24.2.2.4). - fileHeader.SchemaIndexSegmentIndex = this._reader.ReadInt(); - //Int32 Data index segment index. This is the index into the segment index entry array - //(see paragraph 24.2.2.1) for the data index file segment(see paragraph 24.2.2.2). - fileHeader.DataIndexSegmentIndex = this._reader.ReadInt(); - //Int32 Search segment index - fileHeader.SearchSegmentIndex = this._reader.ReadInt(); - //Int32 Previous save index - fileHeader.PreviousSaveIndex = this._reader.ReadInt(); - //Int32 File size - fileHeader.FileSize = this._reader.ReadInt(); - - return fileHeader; - } - - private FileSegmentIndex readFileSegmentIndex() - { - this._reader.Position = this._fileHeader.SegmentIndexOffset; - - FileSegmentIndex segmentIndex = new(); - - this.readDataStorageFileSegment(segmentIndex); - - for (int i = 0; i < this._fileHeader.SegmentIndexEntryCount; i++) - { - var offset = this._reader.ReadRawULong(); - var size = this._reader.ReadUInt(); - segmentIndex.AddEntry(offset, size); - } - - return segmentIndex; - } - - private string readNullTerminatedString() - { - byte b; - List bytes = []; - while ((b = this._reader.ReadByte()) != 0) - { - bytes.Add(b); - } - return this._reader.Encoding.GetString(bytes.ToArray()); - } - - private SchemaData readSchemaData(long offset) - { - this._reader.Position = offset; - - var schemaData = new SchemaData(); - - this.readDataStorageFileSegment(schemaData); - - long start = this._reader.Position; - - // Begin repeat schema unknown properties in the associated schema index file - //segment(paragraph 24.2.2.4), where the property’s segment index is equal to - //this file segment’s segment index(found in the header). - var unknownPropertyCount = this._schemaIndex.PropertyPointers.Count(p => p.SegmentIndex == schemaData.Header.SegmentIndex); - for (int i = 0; i < unknownPropertyCount; i++) - { - var dataSize = this._reader.ReadUInt(); - var unknownFlags = this._reader.ReadUInt(); - } - - // Read schemas - foreach (var pointer in this._schemaIndex.SchemaPointers.Where(p => p.SegmentIndex == schemaData.Header.SegmentIndex)) - { - AcdsSchema schema = this.readAcdsSchema(); - - schema.Index = pointer.Index; - schema.Name = this._schemaIndex.SchemaNames[(int)pointer.Index]; - schemaData.Schemes.Add(schema); - } - - // Align to the next 16 byte boundary - long boundary = this._reader.Position % 16; - if (boundary != 0) - { - byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); - } - - // Read schema property names - uint propertyNameCount = this._reader.ReadUInt(); - string[] propertyNames = new string[propertyNameCount]; - for (int i = 0; i < propertyNameCount; i++) - { - propertyNames[i] = this.readNullTerminatedString(); - } - - // Assign schema Property names - foreach (AcdsSchema schema in schemaData.Schemes) - { - foreach (AcdsSchemaRecord property in schema.EmbeddedRecords) - { - property.Name = propertyNames[(int)property.NameIndex]; - } - } - - return schemaData; - } - - private List readSchemaDataEntries() - { - List schemes = new(); - foreach (var index in this._schemaIndex.GetSchemaIndexes()) - { - var schema = this.readSchemaData((long)this._fileSegmentIndex.Entries[(int)index].Offset); - schemes.Add(schema); - } - - return schemes; - } - - private SchemaIndex readSchemaIndex() - { - var startOffset = (long)this._fileSegmentIndex.Entries[(int)this._fileHeader.SchemaIndexSegmentIndex].Offset; - this._reader.Position = startOffset; - - var schema = new SchemaIndex(); - - this.readDataStorageFileSegment(schema); - - //UInt32 Unknown property count - uint propertyCount = this._reader.ReadUInt(); - //UInt32 Unknown (0) - var unknown0 = this._reader.ReadUInt(); - for (int i = 0; i < propertyCount; i++) - { - //UInt32 Index (starting at 0) - var index = this._reader.ReadUInt(); - //UInt32 Segment index into the segment index file segment entry table (paragraph - //24.2.2.1) of the schema data file segment (paragraph 24.2.2.6) - var schemaIndex = this._reader.ReadUInt(); - //UInt32 Local offset of the unknown schema property. This is a local offset in the - //stream, relative to the schema data file segment’s stream start position. - var offset = this._reader.ReadUInt(); - schema.SchemaPointers.Add(new SchemaIndex.Pointer - { - Index = index, - SegmentIndex = schemaIndex, - LocalOffset = offset, - }); - } - - //Int64 Unknown (0x0af10c) - var unknown1 = this._reader.ReadRawULong(); - //UInt32 Property entry count - uint propertyEntryCount = this._reader.ReadUInt(); - //UInt32 Unknown (0) - uint unknownCount = this._reader.ReadUInt(); - for (int i = 0; i < propertyEntryCount; i++) - { - //UInt32 Segment index into the segment index file segment entry table (paragraph - //24.2.2.1) of the schema data file segment(paragraph 24.2.2.6). - var schemaIndex = this._reader.ReadUInt(); - //UInt32 Local offset of the schema property. This is a local offset in the stream, relative - //to the schema data file segment’s stream start position. - var offset = this._reader.ReadUInt(); - //UInt32 Index - var index = this._reader.ReadUInt(); - schema.PropertyPointers.Add(new SchemaIndex.Pointer - { - SegmentIndex = schemaIndex, - LocalOffset = offset, - Index = index, - }); - } - - //Begin repeat schema unknown properties in the associated schema index file - //segment(paragraph 24.2.2.4), where the property’s segment index is equal to - //this file segment’s segment index(found in the header). - - //Begin repeat schema entries in the associated schema index file segment - //(paragraph 24.2.2.4), where the property’s segment index is equal to this file - //segment’s segment index(found in the header). - - //A schema, see paragraph 24.2.2.6.1. The stream position is the file segment’s - //start position + the schema entry’s local offset. - var propertyNamesOffset = (long)schema.Header.SystemDataAlignmentOffset << 4; - if (propertyNamesOffset != 0) - { - this._reader.Position = (long)(startOffset + propertyNamesOffset); - //Uint32 Property name count - var nproperties = this._reader.ReadUInt(); - for (int i = 0; i < nproperties; i++) - { - //AnsiString Property name (zero byte delimited). These names are referred to by the - //schema’s schema property’s name index(paragraph 24.2.2.6.1.1).Name - //strings can be shared between multiple schema properties this way.See - //paragraph 24.2.2.6.1 for details about the schema. - schema.SchemaNames.Add(this.readNullTerminatedString()); - } - } - - return schema; - } - - private FileSegmentHeader readSubItemHeader() - { - var header = new FileSegmentHeader(); - - //Int16 Signature (always 0xd5ac?) - header.Signature = this._reader.ReadShort(); - //byte[6] Name (6 bytes). Names for the several file segments are: - header.Name = Encoding.ASCII.GetString(this._reader.ReadBytes(6)); - //Int32 Segment index - header.SegmentIndex = this._reader.ReadUInt(); - //Int32 Unknown 1 (0 or 1? 1 in blob01 segment). - header.IsBlob = this._reader.ReadInt(); - //Int32 Segment size (multiple of 0x40 bytes (AutoCAD uses 0x80), padded with 0x70 values). - header.SegmentSize = this._reader.ReadUInt(); - //Int32 Unknown 2 (always 0?) - header.Unknown2 = this._reader.ReadInt(); - //Int32 Data storage revision - header.DataStorageRevision = this._reader.ReadInt(); - //Int32 Unknown 3 (always 0?) - header.Unknown3 = this._reader.ReadInt(); - //Int32 System data alignment offset (calculate the stream position by shifting left 4 - //bits and adding to the file segment’s stream start position). This offset is used - //for schema index and schema data segments.So the name “system data” seems - //to refer to schema index/schema data. - header.SystemDataAlignmentOffset = this._reader.ReadInt(); - //Int32 Object data alignment offset (calculate the stream position by shifting left 4 - //bits and adding to the file segment’s stream start position). This offset is used - //for the data segment. - header.ObjectDataAlignmentOffset = this._reader.ReadInt(); - //byte[8] 8 alignment bytes (always 8 x 0x55?). - var alignmentBytes = this._reader.ReadBytes(8); - - return header; - } -} \ No newline at end of file diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader_obsolete.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader_obsolete.cs new file mode 100644 index 000000000..4f38b1163 --- /dev/null +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader_obsolete.cs @@ -0,0 +1,746 @@ +using ACadSharp.Prototype1b; +using ACadSharp.Prototype1b.Segments; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace ACadSharp.IO.DWG.DwgStreamReaders; + +[Obsolete] +internal class DwgPrototype1bReader_obsolete : DwgSectionIO +{ + public override string SectionName => DwgSectionDefinition.AcDsPrototype; + + public static readonly uint[] TYPE_SIZES = new uint[] { 0, 0, 2, 1, 2, 4, 8, 1, 2, 4, 8, 4, 8, 0, 0, 0 }; + + private readonly DwgDocumentBuilder _builder; + + private readonly IDwgStreamReader _reader; + + private readonly Prototype1b.DataStorage _storage = new Prototype1b.DataStorage(); + + public DwgPrototype1bReader_obsolete(ACadVersion version, DwgDocumentBuilder builder, IDwgStreamReader reader) : base(version) + { + this._reader = reader; + this._builder = builder; + } + + public Prototype1b.DataStorage Read() + { + try + { + this._storage.FileHeader = this.readFileHeader(); + + // Indices for reading values + this._storage.IndexPointers = new DataStoragePointers(); + this._storage.IndexPointers.SegmentIndex = this.readSegmentIndex(); + this._storage.IndexPointers.DataIndex = this.readDataIndex(); + this._storage.IndexPointers.SchemaIndex = this.readSchemaIndex(); + this._storage.IndexPointers.FreeSpace = this.readFreeSpace(); // Index to empty spaces (padding or so) within the file + + // Probably the file state before the last save + this._storage.PreviousSave = this.readPreviousSave(); + + // Schema data lookup + this._storage.SchemaSearch = this.readSchemaSearch(); + foreach (SchemaSearchEntry search in this._storage.SchemaSearch.Entries) + { + search.SchemaName = this._storage.IndexPointers.SchemaIndex.SchemaNames[search.SchemaNameIndex]; + } + + this._storage.SchemaFields = []; + this._storage.DataFields = []; + this._storage.Blobs = []; + + // From this point on it should be possible to read storage entries sequentially + // The issue is that sometimes there seemingly are empty padding sections (which should be + // referenced by the FreeSpace entry) that are missing from the FreeSpace definition or are larger + // than specified in the FreeSpace definition. + + this.ReadSegments(); + } + catch (Exception ex) + { + if (!this._builder.Configuration.Failsafe) + throw; + + this.notify("An error occurred while reading the Prototype1b", NotificationType.Error, ex); + } + + return this._storage; + } + + internal IPrototype1bSegment ReadSegment() + { + IPrototype1bSegment segment; + long segmentStartPosition = this._reader.Position; + SegmentHeader header = this.readSubItemHeader(); + segment = this.ReadSegmentData(header); + + // Blob01 entries do not specify how large they are (The headers SegmentSize only has the size of the header without any blob data, blobs are aligned to the next 128 byte boundary) + if (header.IsBlob != 1) + { + // Skip to the end of this sub item + long readSize = this._reader.Position - segmentStartPosition; + long paddingDataSize = header.SegmentSize - readSize; + byte[] _padding = this._reader.ReadBytes((int)paddingDataSize); // Should be filled with only 0x70 values (TODO: Sometimes there are some other strange padding bytes) + } + + return segment; + } + + internal IPrototype1bSegment ReadSegmentAt(ulong offset) + { + long pos = this._reader.Position; + this._reader.Position = (long)offset; + IPrototype1bSegment segment = this.ReadSegment(); + this._reader.Position = pos; + return segment; + } + + internal IPrototype1bSegment ReadSegmentData(SegmentHeader header) => header.Name switch + { + "segidx" => this.readSegmentIndexValue(header), + "datidx" => this.readDataIndexValue(header), + "schidx" => this.readSchemaIndexValue(header), + "search" => this.readSchemaSearchValue(header), + "freesp" => this.readFreeSpaceValue(header), + "prvsav" => this.readPreviousSaveValue(header), + "schdat" => this.readSchemaDataValue(header), + "_data_" => this.readDataValue(header), + "blob01" => this.readBlob01(header), + _ => throw new InvalidDataException($"Unknown segment type \"{header.Name}\""), + }; + + internal void ReadSegments() + { + foreach (KeyValuePair entry in this._storage.IndexPointers.SegmentIndex.Pointers) + { + if (entry.Value.Size == 0) continue; + this._reader.Position = (long)entry.Value.Offset; + + IPrototype1bSegment segment = this.ReadSegment(); + switch (segment) + { + // Those entries have already been parsed (as they were directly referenced in the header + // and there should probably not be any additional entries of the same type) + case SegmentIndex segidx: + case DataIndex datidx: + case SchemaIndex schidx: + case SchemaSearch search: + case FreeSpace freesp: + case PreviousSave prvsav: + break; + + // Add entries which are types that can appear multiple times to a collection of them + case SchemaData schdat: + this._storage.SchemaFields.Add(schdat); + break; + case DataField data: + this._storage.DataFields.Add(data); + break; + case Blob01 blob: + this._storage.Blobs.Add(blob); + break; + default: + break; + } + } + } + + private Blob01 readBlob01(SegmentHeader header) + { + Blob01 blob = new() + { + Header = header, + TotalDataSize = this._reader.ReadRawULong(), + PageStartOffset = this._reader.ReadRawULong(), + PageIndex = this._reader.ReadUInt(), + PageCount = this._reader.ReadUInt(), + PageDataSize = this._reader.ReadRawULong() + }; + blob.Data = this._reader.ReadBytes((int)blob.PageDataSize); + + // Align to the next 128 byte boundary + long boundary = this._reader.Position % 128; + if (boundary != 0) + { + byte[] _ = this._reader.ReadBytes((int)(128 - boundary)); + } + + return blob; + } + + private DataIndex readDataIndex() + { + return (DataIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.DataIndexSegmentIndex].Offset); + } + + private DataIndex readDataIndexValue(SegmentHeader header) + { + DataIndex index = new() + { + Header = header + }; + int entryCount = this._reader.ReadInt(); + index.Unknown1 = this._reader.ReadInt(); + + index.Entries = []; + for (int j = 0; j < entryCount; j++) + { + uint dataSegmentIndex = this._reader.ReadUInt(); + uint dataSegmentOffset = this._reader.ReadUInt(); + uint schemaIndex = this._reader.ReadUInt(); + + // Segment index values of 0 can be ignored (stub entries) + if (dataSegmentIndex == 0) continue; + + // Get or create a data index entry + if (!index.Entries.TryGetValue(dataSegmentIndex, out DataIndexEntry entry)) + { + entry = new DataIndexEntry + { + DataSegmentIndex = dataSegmentIndex, + Pointers = [] + }; + } + + // Add the current pointer to the list of pointers + entry.Pointers.Add(new DataIndexEntryPointer + { + Offset = dataSegmentOffset, + SchemaIndex = schemaIndex + }); + index.Entries[dataSegmentIndex] = entry; + } + + return index; + } + + private DataField readDataValue(SegmentHeader header) + { + DataField dataField = new() + { + Header = header, + Entries = [] + }; + + // Read headers + List headers = []; + uint headerEndPosition = (uint)this._reader.Position; + if (!this._storage.IndexPointers.DataIndex.Entries.TryGetValue(header.SegmentIndex, out DataIndexEntry entry)) + { + return dataField; + } + + // As entries can be out of order, keep updating the max position to resume correctly after reading all entries + long maxPos = 0; + foreach (DataIndexEntryPointer pointer in entry.Pointers) + { + this._reader.Position = headerEndPosition + pointer.Offset; + headers.Add(new DataHeader + { + EntrySize = this._reader.ReadUInt(), + Unknown1 = this._reader.ReadUInt(), + Handle = this._reader.ReadRawULong(), + DataOffset = this._reader.ReadUInt(), + SchemaIndex = pointer.SchemaIndex + }); + maxPos = Math.Max(maxPos, this._reader.Position); + } + this._reader.Position = maxPos; + + // Sort headers to get ascending offset values to prevent wrong offset calculations (TODO: Is this ok or should the order maybe be preserved?) + headers = headers.OrderBy(x => x.DataOffset).ToList(); + + // Get the position to the beginning of the data content section and skip padding and unreferenced DataHeader entries + uint headerStartPosition = headerEndPosition - SegmentHeader.SIZE; + uint dataRefPosition = headerStartPosition + (uint)(header.ObjectDataAlignmentOffset << 4); + if (dataRefPosition != this._reader.Position) + { + byte[] data = this._reader.ReadBytes((int)(dataRefPosition - this._reader.Position)); + // Many unreferenced / seemingly dangling DataHeader entries might be defined here, they would + // also have valid file data in the next step, where file contents are being read. This can be a lot of entries, e.g. + // 10 - 20 valid DataHeader entries have been seen + } + + // Read data associated with headers + for (int i = 0; i < headers.Count; i++) + { + DataValue value = new(); + DataHeader dataHeader = headers[i]; + uint recordStreamOffset = dataRefPosition + dataHeader.DataOffset; + uint maxRecordSize = i + 1 < headers.Count ? (headers[i + 1].DataOffset - dataHeader.DataOffset) : (header.SegmentSize - (uint)(header.ObjectDataAlignmentOffset << 4) - dataHeader.DataOffset); + + this._reader.Position = recordStreamOffset; + value.DataSize = this._reader.ReadUInt(); + if ((value.DataSize + 4) <= maxRecordSize) + { + value.Data = this._reader.ReadBytes((int)value.DataSize); + + //// Example how to detect the actual file type based on the file byte signature + //byte[] FILE_MAGIC_PNG = [137, 80, 78, 71, 13, 10, 26, 10]; + //byte[] FILE_MAGIC_ACIS_BINARY = Encoding.ASCII.GetBytes("ACIS BinaryFile"); + //byte[] FILE_MAGIC_ASM_BINARY = Encoding.ASCII.GetBytes("ASM BinaryFile"); + + //if (value.Data.Length >= FILE_MAGIC_PNG.Length && value.Data.AsSpan(0, FILE_MAGIC_PNG.Length).SequenceEqual(FILE_MAGIC_PNG)) { + // // The bytes represent a PNG file + //} + //else if (value.Data.Length >= FILE_MAGIC_ACIS_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ACIS_BINARY.Length).SequenceEqual(FILE_MAGIC_ACIS_BINARY)) { + // // The bytes represent an ACIS binary file + //} + //else if (value.Data.Length >= FILE_MAGIC_ASM_BINARY.Length && value.Data.AsSpan(0, FILE_MAGIC_ASM_BINARY.Length).SequenceEqual(FILE_MAGIC_ASM_BINARY)) { + // // The bytes represent an ACIS / ASM binary file + //} + //else { + // // The bytes represent a file other than a PNG and ACIS file + //} + } + else if (value.DataSize == 0xbb106bb1) + { + value.BlobReference = new DataBlobReference() + { + TotalDataSize = this._reader.ReadRawULong(), + PageCount = this._reader.ReadUInt(), + RecordSize = this._reader.ReadUInt(), + PageSize = this._reader.ReadUInt(), + LastPageSize = this._reader.ReadUInt(), + Unknown1 = this._reader.ReadUInt(), + Unknown2 = this._reader.ReadUInt(), + SegmentPointers = [] + }; + for (int j = 0; j < value.BlobReference.PageCount; j++) + { + value.BlobReference.SegmentPointers.Add(( + this._reader.ReadUInt(), // segment index + this._reader.ReadUInt() // size + )); + } + } + else + { + Debugger.Break(); // This should not be possible + value.DataSize = 0; + } + + dataField.Entries.Add(new DataEntry + { + Header = dataHeader, + Value = value + }); + } + + return dataField; + } + + private FileHeader readFileHeader() + { + uint fileSignature = this._reader.ReadUInt(); + short fileHeaderSize = this._reader.ReadShort(); + return new() + { + FileSignature = fileSignature, + FileHeaderSize = fileHeaderSize, + UnknownFlag = this._reader.ReadShort(), + Unknown1 = this._reader.ReadInt(), + Version = this._reader.ReadInt(), + Unknown2 = this._reader.ReadInt(), + DataStorageRevision = this._reader.ReadInt(), + SegmentIndexOffset = this._reader.ReadInt(), + SegmentIndexUnknown = this._reader.ReadInt(), + SegmentIndexEntryCount = this._reader.ReadInt(), + SchemaIndexSegmentIndex = this._reader.ReadInt(), + DataIndexSegmentIndex = this._reader.ReadInt(), + SearchSegmentIndex = this._reader.ReadInt(), + PreviousSaveIndex = this._reader.ReadInt(), + FileSize = this._reader.ReadInt(), + Unknown3 = this._reader.ReadInt(), + FreeSpaceSegmentIndex = this._reader.ReadInt(), + FreeSpaceEntryCount = this._reader.ReadInt(), + Unknown5 = this._reader.ReadInt(), + + UnknownRemaining = this._reader.ReadBytes(fileHeaderSize - 72) + }; + } + + private FreeSpace readFreeSpace() + { + if (this._storage.FileHeader.FreeSpaceSegmentIndex == 0) return null; + return (FreeSpace)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.FreeSpaceSegmentIndex].Offset); + } + + private FreeSpace readFreeSpaceValue(SegmentHeader header) + { + FreeSpace space = new() + { + Header = header, + Unknown = this._reader.ReadRawULong(), + FreeSpaces = new FreeSpaceArea[this._storage.FileHeader.FreeSpaceEntryCount] + }; + for (int i = 0; i < space.FreeSpaces.Length; i++) + { // TODO: Sometimes when there is an additional freespace definition, it might not match the `FreeSpaceEntryCount` count + ulong position = this._reader.ReadRawULong(); + uint size = this._reader.ReadUInt(); + space.FreeSpaces[i] = new FreeSpaceArea + { + Position = position, + Size = size + }; + } + return space; + } + + private string readNullTerminatedString() + { + byte b; + List bytes = []; + while ((b = this._reader.ReadByte()) != 0) + { + bytes.Add(b); + } + return Encoding.UTF8.GetString(bytes.ToArray()); // TODO: Is this ASCII only or UTF-8 ? + } + + private PreviousSave readPreviousSave() + { + if (this._storage.FileHeader.PreviousSaveIndex == 0) return null; + return (PreviousSave)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.PreviousSaveIndex].Offset); + } + + private PreviousSave readPreviousSaveValue(SegmentHeader header) => new() + { + Header = header, + FileHeader = this.readFileHeader() + }; + + private Schema readSchema() + { + Schema schema = new(); + + ushort indexCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + schema.Indices = new ulong[indexCount]; + for (int i = 0; i < indexCount; i++) + { + schema.Indices[i] = this._reader.ReadRawULong(); + } + + ushort propCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + schema.Properties = new SchemaProperty[propCount]; + for (int i = 0; i < propCount; i++) + { + schema.Properties[i] = this.readSchemaProperty(); + } + + return schema; + } + + private SchemaData readSchemaDataValue(SegmentHeader header) + { + // Read unknown schema properties + List unknownProps = []; + foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaUnknownPropertyPointer) + { + if (pointer.SchemaIndex != header.SegmentIndex) continue; + unknownProps.Add(new SchemaUnknownProperty + { + DataSize = this._reader.ReadUInt(), + UnknownFlags = this._reader.ReadUInt() + }); + } + + // Read schemas + List schemaValues = []; + foreach (SchemaPropertyPointer pointer in this._storage.IndexPointers.SchemaIndex.SchemaPointer) + { + if (pointer.SchemaIndex != header.SegmentIndex) continue; + Schema schema = this.readSchema(); + schema.Index = pointer.Index; + schema.Name = this._storage.IndexPointers.SchemaIndex.SchemaNames[pointer.Index]; + schemaValues.Add(schema); + } + + // Align to the next 16 byte boundary + long boundary = this._reader.Position % 16; + if (boundary != 0) + { + byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); + } + + // Read schema property names + uint propertyNameCount = this._reader.ReadUInt(); + string[] propertyNames = new string[propertyNameCount]; + for (int i = 0; i < propertyNameCount; i++) + { + propertyNames[i] = this.readNullTerminatedString(); + } + + // Assign schema Property names + foreach (Schema schema in schemaValues) + { + foreach (SchemaProperty property in schema.Properties) + { + property.Name = propertyNames[property.NameIndex]; + } + } + + return new SchemaData + { + Header = header, + SchemaUnknownProperties = unknownProps, + Values = schemaValues, + }; + } + + private SchemaIndex readSchemaIndex() + { + return (SchemaIndex)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SchemaIndexSegmentIndex].Offset); + } + + private SchemaIndex readSchemaIndexValue(SegmentHeader header) + { + SchemaIndex schemaIndex = new() + { + Header = header + }; + uint unknownPropertyCount = this._reader.ReadUInt(); + schemaIndex.SchemaPointer = new SchemaPropertyPointer[unknownPropertyCount]; + schemaIndex.Unknown1 = this._reader.ReadUInt(); + + for (int i = 0; i < unknownPropertyCount; i++) + { + schemaIndex.SchemaPointer[i] = new SchemaPropertyPointer + { + Index = this._reader.ReadUInt(), + SchemaIndex = this._reader.ReadUInt(), + Offset = this._reader.ReadUInt(), + }; + } + + schemaIndex.UnknownMagic = this._reader.ReadRawLong(); + uint propertyEntryCount = this._reader.ReadUInt(); + uint unknownCount = this._reader.ReadUInt(); // 0; 8 + + schemaIndex.UnknownPropertyEntryPointer = new SchemaPropertyPointer[propertyEntryCount]; + for (int i = 0; i < propertyEntryCount; i++) + { + schemaIndex.UnknownPropertyEntryPointer[i] = new SchemaPropertyPointer + { + SchemaIndex = this._reader.ReadUInt(), + Offset = this._reader.ReadUInt(), + Index = this._reader.ReadUInt(), + }; + } + + schemaIndex.SchemaUnknownPropertyPointer = new SchemaPropertyPointer[unknownCount]; + for (int i = 0; i < unknownCount; i++) + { + schemaIndex.SchemaUnknownPropertyPointer[i] = new SchemaPropertyPointer + { + Index = this._reader.ReadUInt(), + SchemaIndex = this._reader.ReadUInt(), + Offset = this._reader.ReadUInt(), + }; + } + + schemaIndex.UnknownIndex1 = this._reader.ReadUInt(); + + // Align to the next 16 byte boundary + long boundary = this._reader.Position % 16; + if (boundary != 0) + { + byte[] _ = this._reader.ReadBytes((int)(16 - boundary)); + } + + // Read labels (NULL terminated strings) + uint stringCount = this._reader.ReadUInt(); + schemaIndex.SchemaNames = new string[stringCount]; + for (int i = 0; i < stringCount; i++) + { + schemaIndex.SchemaNames[i] = this.readNullTerminatedString(); + } + + return schemaIndex; + } + + private SchemaProperty readSchemaProperty() + { + SchemaProperty prop = new() + { + PropertyFlags = this._reader.ReadUInt(), // 1 = Unknown / 2 = NoType / 8 = Unknown + NameIndex = this._reader.ReadUInt() + }; + + // Get type size + if (!((prop.PropertyFlags & (1 << 1)) != 0)) + { + prop.Type = this._reader.ReadUInt(); // 0 - 15 + if (prop.Type == 0xe) + { + prop.TypeSize = this._reader.ReadUInt(); + } + else + { + prop.TypeSize = TYPE_SIZES[prop.Type.Value]; + } + } + + // Read unknown fields + if (prop.PropertyFlags == 1) + { + prop.Unknown1 = this._reader.ReadUInt(); + } + else if (prop.PropertyFlags == 8) + { + prop.Unknown2 = this._reader.ReadUInt(); + } + + // Read values + prop.PropertyValueCount = BitConverter.ToUInt16(this._reader.ReadBytes(2), 0); + prop.Values = new byte[prop.PropertyValueCount, prop.TypeSize]; + if (prop.TypeSize != 0) + { + for (int i = 0; i < prop.PropertyValueCount; i++) + { + byte[] propertyValue = this._reader.ReadBytes((int)prop.TypeSize); + for (int j = 0; j < propertyValue.Length; j++) + { + prop.Values[0, j] = propertyValue[j]; + } + } + } + + return prop; + } + + private SchemaSearch readSchemaSearch() + { + return (SchemaSearch)this.ReadSegmentAt(this._storage.IndexPointers.SegmentIndex.Pointers[this._storage.FileHeader.SearchSegmentIndex].Offset); + } + + private SchemaSearchEntry readSchemaSearchEntry() + { + SchemaSearchEntry search = new() + { + SchemaNameIndex = this._reader.ReadUInt() + }; + + ulong sortedIndexCount = this._reader.ReadRawULong(); + search.SortedIndices = new ulong[sortedIndexCount]; + for (ulong i = 0; i < sortedIndexCount; i++) + { + search.SortedIndices[i] = this._reader.ReadRawULong(); + } + + uint idIndexesCount = this._reader.ReadUInt(); + SearchEntryObject[][] idEntryObjects = new SearchEntryObject[idIndexesCount][]; + + // TODO: Find out what it would mean if there were multiple index lists. Never happened in test files so far + if (idIndexesCount > 1) + { + Debugger.Break(); + } + + if (idIndexesCount > 0) + { + search.Unknown1 = this._reader.ReadUInt(); + + for (uint i = 0; i < idIndexesCount; i++) + { + uint idIndexCount = this._reader.ReadUInt(); + + SearchEntryObject[] entryObjects = new SearchEntryObject[idIndexCount]; + for (uint j = 0; j < idIndexCount; j++) + { + ulong handle = this._reader.ReadRawULong(); + + ulong indexCount = this._reader.ReadRawULong(); + ulong[] indices = new ulong[indexCount]; + for (ulong k = 0; k < indexCount; k++) + { + indices[k] = this._reader.ReadRawULong(); + } + + entryObjects[j] = new SearchEntryObject + { + Handle = handle, + Indices = indices + }; + } + idEntryObjects[i] = entryObjects; + } + } + search.IdEntryObjects = idEntryObjects; + + return search; + } + + private SchemaSearch readSchemaSearchValue(SegmentHeader header) + { + SchemaSearch schemaSearch = new() + { + Header = header + }; + int schemaCount = this._reader.ReadInt(); + schemaSearch.Entries = []; + for (int j = 0; j < schemaCount; j++) + { + schemaSearch.Entries.Add(this.readSchemaSearchEntry()); + } + return schemaSearch; + } + + private SegmentIndex readSegmentIndex() + { + return (SegmentIndex)this.ReadSegmentAt((ulong)this._storage.FileHeader.SegmentIndexOffset); + } + + private SegmentIndex readSegmentIndexValue(SegmentHeader header) + { + SegmentIndex index = new() + { + Header = header, + Pointers = [] + }; + for (int j = 0; j < this._storage.FileHeader.SegmentIndexEntryCount; j++) + { + index.Pointers[j] = new SegmentIndexEntry + { + Offset = this._reader.ReadRawULong(), + Size = this._reader.ReadUInt() + }; + } + + // TODO: Sometimes additional bytes (that are not padding bytes) follow here until the segment size + // They actually seem to be garbage data and can be ignored. Often times there are seemingly + // random bytes without any pattern and sometimes there are bytes representing 2-Byte strings. + // Garbage string examples are e.g.: + // --> "nslation\0" followed by the bytes [0, 0, 7, 0, 176, 4] + // --> "k\\auto" + // --> "op> "utocad" + // --> "atio" + + return index; + } + + private SegmentHeader readSubItemHeader() + { + return new() + { + Signature = this._reader.ReadShort(), + Name = Encoding.ASCII.GetString(this._reader.ReadBytes(6)), + SegmentIndex = this._reader.ReadUInt(), + IsBlob = this._reader.ReadInt(), + SegmentSize = this._reader.ReadUInt(), // Multiple of 0x40 bytes (AutoCAD uses 0x80), padded wth 0x70 values + Unknown1 = this._reader.ReadInt(), + DataStorageRevision = this._reader.ReadInt(), + Unknown2 = this._reader.ReadInt(), + SystemDataAlignmentOffset = this._reader.ReadInt(), + ObjectDataAlignmentOffset = this._reader.ReadInt(), + AlignmentBytes = this._reader.ReadBytes(8) // Align to next 16 byte boundary + }; + } +} \ No newline at end of file diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs index 251fb3c8e..4b53f891a 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfAcdsDataSectionReader.cs @@ -91,7 +91,6 @@ private AcdsRecordColumn readAcdsRecordColumn() { AcdsRecordColumn column = new(); column.Name = this._reader.ValueAsString; - MemoryStream binaryData = null; this._reader.ReadNext(); @@ -101,26 +100,27 @@ private AcdsRecordColumn readAcdsRecordColumn() { switch (this._reader.Code) { + case 94: + //Bin data length + break; case 280: column.DataType = this._reader.ValueAsShort; break; case 310: - binaryData ??= new(); - binaryData.Write(this._reader.ValueAsBinaryChunk, 0, this._reader.ValueAsBinaryChunk.Length); + column.Data ??= new MemoryStream(); + column.Data.Write(this._reader.ValueAsBinaryChunk, 0, this._reader.ValueAsBinaryChunk.Length); + break; + case 320: + column.Handle = this._reader.ValueAsHandle; break; default: - column.CodeValuePair = new KeyValuePair(this._reader.Code, this._reader.Value); + this._builder.Notify($"Unexpected code {this._reader.Code} in ACDS record column.", NotificationType.Warning); break; } this._reader.ReadNext(); } - if (binaryData != null) - { - column.CodeValuePair = new KeyValuePair(310, binaryData.ToArray()); - } - return column; } diff --git a/src/ACadSharp/IO/DwgReader.cs b/src/ACadSharp/IO/DwgReader.cs index a564a6c48..574e9f4e9 100644 --- a/src/ACadSharp/IO/DwgReader.cs +++ b/src/ACadSharp/IO/DwgReader.cs @@ -594,9 +594,16 @@ private void readDsPrototype_1b() return; } - var reader = new DwgPrototype1bReaderV2(this._fileHeader.AcadVersion, this._builder, sreader); + var reader = new DwgPrototype1bReader(this._fileHeader.AcadVersion, this._builder, sreader); reader.OnNotification += this.onNotificationEvent; reader.Read(); + + //Backwards compatibility with older versions of the library + //Remove on 3.9.0 + sreader.Position = 0; + var obs = new DwgPrototype1bReader_obsolete(this._fileHeader.AcadVersion, this._builder, sreader); + obs.OnNotification += onNotificationEvent; + this._document.DataStorage = obs.Read(); } /// diff --git a/src/ACadSharp/Prototype1b/DataStorage.cs b/src/ACadSharp/Prototype1b/DataStorage.cs index d086b1295..7133d68bf 100644 --- a/src/ACadSharp/Prototype1b/DataStorage.cs +++ b/src/ACadSharp/Prototype1b/DataStorage.cs @@ -1,10 +1,12 @@ using ACadSharp.Entities; using ACadSharp.Prototype1b.Segments; +using System; using System.Collections.Generic; using System.Linq; namespace ACadSharp.Prototype1b { + [Obsolete] public class DataStorage { /// From c549fe40fb8b826213d06813db9dbc60cfbf4f93 Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 6 Oct 2026 14:20:46 +0200 Subject: [PATCH 17/81] Store DWG blobs and decode data entries Replace SegmentPointers with Blob01 collection, read blob payloads directly in DwgPrototype1bReader, and combine blob-backed data in DataEntry. --- src/ACadSharp/DataStorage/Blob01.cs | 16 ++++++++++++++++ src/ACadSharp/DataStorage/DataField.cs | 13 ++++++++++--- .../DwgStreamReaders/DwgPrototype1bReader.cs | 19 +++++++++++++++---- 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 src/ACadSharp/DataStorage/Blob01.cs diff --git a/src/ACadSharp/DataStorage/Blob01.cs b/src/ACadSharp/DataStorage/Blob01.cs new file mode 100644 index 000000000..4d34c7de8 --- /dev/null +++ b/src/ACadSharp/DataStorage/Blob01.cs @@ -0,0 +1,16 @@ +namespace ACadSharp.DataStorage; + +internal class Blob01 : FileSegment +{ + public byte[] Data { get; set; } + + public uint PageCount { get; set; } + + public ulong PageDataSize { get; set; } + + public uint PageIndex { get; set; } + + public ulong PageStartOffset { get; set; } + + public ulong TotalDataSize { get; set; } +} diff --git a/src/ACadSharp/DataStorage/DataField.cs b/src/ACadSharp/DataStorage/DataField.cs index ec00f7120..0c19a11ab 100644 --- a/src/ACadSharp/DataStorage/DataField.cs +++ b/src/ACadSharp/DataStorage/DataField.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; namespace ACadSharp.DataStorage; @@ -16,13 +17,13 @@ public class DataBlobReference public uint RecordSize { get; set; } - public List<(uint, uint)> SegmentPointers { get; set; } - public ulong TotalDataSize { get; set; } public uint Unknown1 { get; set; } public uint Unknown2 { get; set; } + + public List Blobs { get; set; } = new(); } public class DataEntry @@ -43,7 +44,13 @@ public byte[] GetData() } else if (this.BlobReference != null) { - return null; + MemoryStream stream = new MemoryStream(); + foreach (Blob01 blob in this.BlobReference.Blobs) + { + stream.Write(blob.Data, 0, blob.Data.Length); + } + + return stream.ToArray(); } else { diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs index bb498635f..19ba9f601 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs @@ -235,8 +235,7 @@ private void readDataFields(CadFileDataStorage storage) PageSize = pageSize, LastPageSize = lastPageSize, Unknown1 = unknown1, - Unknown2 = unknown2, - SegmentPointers = new List<(uint, uint)>() + Unknown2 = unknown2 }; for (int j = 0; j < value.BlobReference.PageCount; j++) @@ -244,12 +243,24 @@ private void readDataFields(CadFileDataStorage storage) var segmentIndex = this._reader.ReadUInt(); var size = this._reader.ReadUInt(); - value.BlobReference.SegmentPointers.Add((segmentIndex, size)); + this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)segmentIndex].Offset; + + Blob01 blob = new(); + this.readDataStorageFileSegment(blob); + + blob.TotalDataSize = this._reader.ReadRawULong(); + blob.PageStartOffset = this._reader.ReadRawULong(); + blob.PageIndex = this._reader.ReadUInt(); + blob.PageCount = this._reader.ReadUInt(); + blob.PageDataSize = this._reader.ReadRawULong(); + + blob.Data = this._reader.ReadBytes((int)blob.PageDataSize); + value.BlobReference.Blobs.Add(blob); } } field.DataEntries.Add(value); - processDataValue(storage, value, shemaIndexes[i]); + this.processDataValue(storage, value, shemaIndexes[i]); } } } From a1a125ffdba0b444b7897ed2e5ead109940657d9 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:26:13 -0500 Subject: [PATCH 18/81] Fix PSTYLEMODE when reading the DWG header The header flags store PSTYLEMODE in bit 0x2000, but the reader assigned the masked value directly, so a color-dependent drawing reported PlotStyleMode = 8192 instead of 1. Map the bit to 1/0 as the DXF reader and the DWG writer do. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp/IO/DWG/DwgStreamReaders/DwgHeaderReader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgHeaderReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgHeaderReader.cs index 95a047569..def250fdc 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgHeaderReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgHeaderReader.cs @@ -915,7 +915,8 @@ public void Read(int acadMaintenanceVersion, out DwgHeaderHandlesCollection obje //EXTNAMES Flags & 0x0800 _header.ExtendedNames = (flags & 0x800) == 1; //PSTYLEMODE Flags & 0x2000 - _header.PlotStyleMode = (short)(flags & 0x2000); + //PSTYLEMODE: 1 = color-dependent (CTB), 0 = named (STB), as in DXF and the writer + _header.PlotStyleMode = (short)((flags & 0x2000) != 0 ? 1 : 0); //OLESTARTUP Flags & 0x4000 _header.LoadOLEObject = (flags & 0x4000) == 1; From 371c44facdcc4144ff366b98b0513e858fd5a1cb Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:26:14 -0500 Subject: [PATCH 19/81] Fix Hatch spline edge conversion to Spline A spline boundary edge is one open piece of the loop, so the converted Spline must not be flagged as closed. Non-rational edges may store 0 as the weight of every control point; use 1 in that case so the resulting Spline evaluates correctly. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp/Entities/Hatch.BoundaryPath.Spline.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ACadSharp/Entities/Hatch.BoundaryPath.Spline.cs b/src/ACadSharp/Entities/Hatch.BoundaryPath.Spline.cs index 996cf8aeb..9ec1fc4a5 100644 --- a/src/ACadSharp/Entities/Hatch.BoundaryPath.Spline.cs +++ b/src/ACadSharp/Entities/Hatch.BoundaryPath.Spline.cs @@ -184,7 +184,8 @@ public List PolygonalVertexes(int precision) public override Entity ToEntity() { Entities.Spline spline = new(); - spline.IsClosed = true; + // a boundary edge is an open piece of the loop, not a closed curve + spline.IsClosed = false; spline.Degree = this.Degree; spline.Flags = this.IsPeriodic ? spline.Flags |= (SplineFlags.Periodic) : spline.Flags; spline.Flags = this.IsRational ? spline.Flags |= (SplineFlags.Rational) : spline.Flags; @@ -193,7 +194,8 @@ public override Entity ToEntity() spline.EndTangent = this.EndTangent.Convert(); spline.ControlPoints.AddRange(this.ControlPoints.Select(cp => new XYZ(cp.X, cp.Y, 0))); - spline.Weights.AddRange(this.ControlPoints.Select(x => x.Z)); + // non-rational edges may store 0 weights; they mean 1 + spline.Weights.AddRange(this.ControlPoints.Select(x => this.IsRational && x.Z > 0 ? x.Z : 1.0)); spline.FitPoints.AddRange(this.FitPoints.Select(x => x.Convert())); spline.Knots.AddRange(this.Knots); From b484f01a6b0ce09f010535cd237063950c6a1116 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:26:14 -0500 Subject: [PATCH 20/81] Fix proxy graphics true color and tolerate truncated graphics The true color value is 0xmmRRGGBB stored little-endian, so the bytes come in blue, green, red order; red and blue were swapped. A truncated or unexpected record in the proxy graphics threw and lost every geometry already read. Stop at the bad record and keep what was read so far. Co-Authored-By: Claude Opus 5.5 --- .../Entities/ProxyGraphics/ProxyGeometryReader.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/Entities/ProxyGraphics/ProxyGeometryReader.cs b/src/ACadSharp/Entities/ProxyGraphics/ProxyGeometryReader.cs index bc2018f27..2e08af5ad 100644 --- a/src/ACadSharp/Entities/ProxyGraphics/ProxyGeometryReader.cs +++ b/src/ACadSharp/Entities/ProxyGraphics/ProxyGeometryReader.cs @@ -23,6 +23,8 @@ internal static IEnumerable ReadGeometries(CadDocumentBuilder bu var size = stream.ReadInt(); var count = stream.ReadInt(); + try + { for (int i = 0; i < count; i++) { var objSize = stream.ReadInt(); //Includes size and type @@ -150,6 +152,12 @@ internal static IEnumerable ReadGeometries(CadDocumentBuilder bu } } + } + catch (System.Exception) + { + // a truncated or unexpected record ends the graphics; what was read so far is kept + } + return geometries; } @@ -869,7 +877,8 @@ private static IProxyGeometry readSubentTrueColor(StreamIO stream) trueColor.Color = Color.ByBlock; break; case ProxyColorMethod.ByColor: - trueColor.Color = new Color(b1, b2, b3); + // the value is 0xmmRRGGBB stored little-endian: blue, green, red, then the method byte + trueColor.Color = new Color(b3, b2, b1); break; case ProxyColorMethod.ByACI: trueColor.Color = new Color(b1); From cc61a1d96436b129780d65fb5eb40e754d89566b Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:26:49 -0500 Subject: [PATCH 21/81] Do not reject MText with an out of range background transparency The MText background transparency (BL 441) is stored like an entity transparency, with the type in the high byte and the alpha in the low byte. AutoCAD can leave stale bits in it when the type is ByLayer, and passing the raw value to the Transparency constructor threw, so the whole MText was lost. Accept plain 0-90 values as before, decode values with a type byte as an alpha value and fall back to ByLayer otherwise. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 236068b06..63631e325 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -4095,7 +4095,11 @@ private CadTemplate readMText(CadTextEntityTemplate template, bool readCommonDat //Background color CMC 63 mtext.BackgroundColor = this._mergedReaders.ReadCmColor(); //Background transparency BL 441 - mtext.BackgroundTransparency = new Transparency((short)this._objectReader.ReadBitLong()); + //Stored like an entity transparency (type in the high byte, alpha in the low byte); AutoCAD + //leaves stale bits in it when the type byte is 0 (ByLayer), so never reject the entity over it. + int transparency = this._objectReader.ReadBitLong(); + mtext.BackgroundTransparency = transparency >= 0 && transparency <= 90 ? new Transparency((short)transparency) + : (transparency >> 24) != 0 ? Transparency.FromAlphaValue(transparency) : Transparency.ByLayer; } } From 1d529b73f9ea3615c4a015892135ff524c0743d3 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:26:50 -0500 Subject: [PATCH 22/81] Keep ACAD_TABLE entities when their cell data cannot be read The insert data of a table (position, scale and the anonymous block with its graphics) is read before the cell data. If parsing the cell data fails, the whole entity was dropped. Read the cell data in a separate method, report a warning on failure and keep the table with its graphics. A partly read cell may have no cell style template, so guard the build against it. Co-Authored-By: Claude Opus 5.5 --- .../DwgObjectReader.Entities.cs | 19 +++++++++++++++++-- ...ntityTemplate.CadTableComponentTemplate.cs | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs index f5a2e1ef9..431ffd15f 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Entities.cs @@ -716,6 +716,22 @@ private CadTemplate readTableEntity() this.readInsertCommonData(template); this.readInsertCommonHandles(template); + // The insert data above (position, scale, the *T graphics block) is enough to plot the + // table. If the cell data below cannot be parsed, keep the table rather than drop it. + try + { + this.readTableEntityData(table, template); + } + catch (System.Exception ex) + { + this._builder.Notify($"ACAD_TABLE {table.Handle}: cell data could not be read; the table graphics are kept", NotificationType.Warning, ex); + } + + return template; + } + + private void readTableEntityData(TableEntity table, CadTableEntityTemplate template) + { if (this.R2010Plus) { //RC Unknown (default 0) @@ -797,7 +813,7 @@ private CadTemplate readTableEntity() table.BreakRowRanges.Add(breakRowRange); } - return template; + return; } //Until R2007 @@ -1283,7 +1299,6 @@ private CadTemplate readTableEntity() } } - return template; } private XY? readValueXY() diff --git a/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableComponentTemplate.cs b/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableComponentTemplate.cs index 86aa28058..b85e42ff4 100644 --- a/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableComponentTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadTableEntityTemplate.CadTableComponentTemplate.cs @@ -36,7 +36,7 @@ public void Build(CadDocumentBuilder builder, TableStyle table) break; } - this.CellStyleTemplate.Build(builder); + this.CellStyleTemplate?.Build(builder); // null when the cell data was only partly read } } } \ No newline at end of file From 5353cb7c3df664dd2bad4befb51c2d7e704fc369 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:28:26 -0500 Subject: [PATCH 23/81] Add tests for PSTYLEMODE and hatch spline edge conversion Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp.Tests/Entities/HatchTests.cs | 30 +++++++++++++++++++ src/ACadSharp.Tests/IO/DWG/DwgWriterTests.cs | 31 ++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/ACadSharp.Tests/Entities/HatchTests.cs b/src/ACadSharp.Tests/Entities/HatchTests.cs index 15389bfcf..bb3e6be00 100644 --- a/src/ACadSharp.Tests/Entities/HatchTests.cs +++ b/src/ACadSharp.Tests/Entities/HatchTests.cs @@ -44,6 +44,36 @@ public void BoundaryPathToEntityTest() Assert.Equal(l.End.Convert(), line.EndPoint); } + [Fact] + public void BoundaryPathSplineToEntityTest() + { + var s = new Hatch.BoundaryPath.Spline + { + Degree = 2, + IsRational = false, + }; + //Non-rational edges may store 0 as weight + s.ControlPoints.Add(new XYZ(0, 0, 0)); + s.ControlPoints.Add(new XYZ(5, 5, 0)); + s.ControlPoints.Add(new XYZ(10, 0, 0)); + s.Knots.AddRange(new double[] { 0, 0, 0, 1, 1, 1 }); + + Spline spline = (Spline)s.ToEntity(); + + Assert.False(spline.IsClosed); + Assert.Equal(new double[] { 1, 1, 1 }, spline.Weights); + Assert.Equal(new XYZ(5, 2.5, 0), spline.PointOnSpline(0.5)); + + s.IsRational = true; + s.ControlPoints[0] = new XYZ(0, 0, 1); + s.ControlPoints[1] = new XYZ(5, 5, 0.5); + s.ControlPoints[2] = new XYZ(10, 0, 1); + + spline = (Spline)s.ToEntity(); + + Assert.Equal(new double[] { 1, 0.5, 1 }, spline.Weights); + } + [Fact] public void CreateHatch() { diff --git a/src/ACadSharp.Tests/IO/DWG/DwgWriterTests.cs b/src/ACadSharp.Tests/IO/DWG/DwgWriterTests.cs index c13901c5e..80a1cd1b7 100644 --- a/src/ACadSharp.Tests/IO/DWG/DwgWriterTests.cs +++ b/src/ACadSharp.Tests/IO/DWG/DwgWriterTests.cs @@ -42,6 +42,37 @@ public void WriteEmptyTest(ACadVersion version) } } + [Theory] + [MemberData(nameof(Versions))] + public void WriteHeaderPlotStyleModeTest(ACadVersion version) + { + //PSTYLEMODE is stored in the header flags from R2000 + if (!this.isSupportedVersion(version) || version < ACadVersion.AC1015) + { + return; + } + + foreach (short mode in new short[] { 0, 1 }) + { + CadDocument doc = new CadDocument(); + doc.Header.Version = version; + doc.Header.PlotStyleMode = mode; + + MemoryStream stream = new MemoryStream(); + using (var wr = new DwgWriter(stream, doc)) + { + wr.Write(); + } + + stream = new MemoryStream(stream.ToArray()); + using (var re = new DwgReader(stream, this.onNotification)) + { + CadHeader header = re.ReadHeader(); + Assert.Equal(mode, header.PlotStyleMode); + } + } + } + [Theory] [MemberData(nameof(Versions))] public void WriteHeaderTest(ACadVersion version) From 01036a88b4678a2a84f0bb9ef309ee2d73f0a514 Mon Sep 17 00:00:00 2001 From: DomCR Date: Wed, 7 Oct 2026 05:57:47 +0000 Subject: [PATCH 24/81] Update project version to 3.8.8 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 1fee3b62d..23b7b9e35 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.7 + 3.8.8 ../nupkg True ../ACadSharp.snk From fed6458048bdd7c910dc9bc8e73255d1e1285bd8 Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Wed, 7 Oct 2026 10:42:45 +0200 Subject: [PATCH 25/81] Return false from TryGetCadObject when the object is not of the requested type --- src/ACadSharp.Tests/CadDocumentTests.cs | 15 ++++++ .../Internal/CadDocumentBuilderTests.cs | 54 +++++++++++++++++++ src/ACadSharp/CadDocument.cs | 4 +- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs diff --git a/src/ACadSharp.Tests/CadDocumentTests.cs b/src/ACadSharp.Tests/CadDocumentTests.cs index c01c4b720..54ff8a9b7 100644 --- a/src/ACadSharp.Tests/CadDocumentTests.cs +++ b/src/ACadSharp.Tests/CadDocumentTests.cs @@ -443,4 +443,19 @@ public void SetCurrentTest() doc.SetCurrent(new MultiLeaderStyle(multiLeaderStyleName)); Assert.True(doc.MLeaderStyles.ContainsKey(multiLeaderStyleName)); } + + [Fact] + public void TryGetCadObjectTypeMismatch() + { + CadDocument doc = new CadDocument(); + + Line line = new Line(); + doc.Entities.Add(line); + + Assert.True(doc.TryGetCadObject(line.Handle, out Line found)); + Assert.Equal(line, found); + + Assert.False(doc.TryGetCadObject(line.Handle, out Circle circle)); + Assert.Null(circle); + } } \ No newline at end of file diff --git a/src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs b/src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs new file mode 100644 index 000000000..4c3635429 --- /dev/null +++ b/src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs @@ -0,0 +1,54 @@ +using ACadSharp.Classes; +using ACadSharp.DataStorage; +using ACadSharp.Entities; +using ACadSharp.IO; +using ACadSharp.IO.DWG; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace ACadSharp.Tests.Internal; + +public class CadDocumentBuilderTests +{ + [Fact] + public void BuildDataStorageSkipsOtherEntities() + { + CadDocument doc = new CadDocument(); + + Solid3D solid = new Solid3D(); + doc.Entities.Add(solid); + + UnknownEntity surface = new UnknownEntity(new DxfClass { DxfName = "REVOLVEDSURFACE", CppClassName = "AcDbRevolvedSurface" }); + doc.Entities.Add(surface); + + byte[] solidData = new byte[] { 1, 2, 3 }; + + DwgDocumentBuilder builder = new DwgDocumentBuilder(ACadVersion.AC1032, doc, new DwgReaderConfiguration { KeepUnknownEntities = true }); + builder.DataStorage = new CadFileDataStorage(); + builder.DataStorage.Records.Add(this.createAsmRecord(solid.Handle, solidData)); + builder.DataStorage.Records.Add(this.createAsmRecord(surface.Handle, new byte[] { 4, 5, 6 })); + + List notifications = new List(); + builder.OnNotification += (sender, e) => notifications.Add(e); + + builder.BuildDataStorage(); + + Assert.Equal(solidData, solid.AcisData); + NotificationEventArgs warning = Assert.Single(notifications); + Assert.Equal(NotificationType.Warning, warning.NotificationType); + } + + private AcdsRecord createAsmRecord(ulong handle, byte[] data) + { + AcdsRecord record = new AcdsRecord(); + + record.Columns.Add(CadFileDataStorage.Id, new AcdsRecordColumn { Name = CadFileDataStorage.Id, Handle = handle }); + + MemoryStream stream = new MemoryStream(); + stream.Write(data, 0, data.Length); + record.Columns.Add(CadFileDataStorage.AsmData, new AcdsRecordColumn { Name = CadFileDataStorage.AsmData, Data = stream }); + + return record; + } +} diff --git a/src/ACadSharp/CadDocument.cs b/src/ACadSharp/CadDocument.cs index f9b639cff..9cd5e4330 100644 --- a/src/ACadSharp/CadDocument.cs +++ b/src/ACadSharp/CadDocument.cs @@ -490,9 +490,9 @@ public bool TryGetCadObject(ulong handle, out T cadObject) if (handle == this.Handle) return false; - if (this._cadObjects.TryGetValue(handle, out IHandledCadObject obj)) + if (this._cadObjects.TryGetValue(handle, out IHandledCadObject obj) && obj is T) { - cadObject = obj as T; + cadObject = (T)obj; return true; } From bd79b042235695087ea7bae4800687951f6f417f Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Thu, 8 Oct 2026 10:22:32 +0200 Subject: [PATCH 26/81] Remove the CadDocumentBuilder test --- .../Internal/CadDocumentBuilderTests.cs | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs diff --git a/src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs b/src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs deleted file mode 100644 index 4c3635429..000000000 --- a/src/ACadSharp.Tests/Internal/CadDocumentBuilderTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using ACadSharp.Classes; -using ACadSharp.DataStorage; -using ACadSharp.Entities; -using ACadSharp.IO; -using ACadSharp.IO.DWG; -using System.Collections.Generic; -using System.IO; -using Xunit; - -namespace ACadSharp.Tests.Internal; - -public class CadDocumentBuilderTests -{ - [Fact] - public void BuildDataStorageSkipsOtherEntities() - { - CadDocument doc = new CadDocument(); - - Solid3D solid = new Solid3D(); - doc.Entities.Add(solid); - - UnknownEntity surface = new UnknownEntity(new DxfClass { DxfName = "REVOLVEDSURFACE", CppClassName = "AcDbRevolvedSurface" }); - doc.Entities.Add(surface); - - byte[] solidData = new byte[] { 1, 2, 3 }; - - DwgDocumentBuilder builder = new DwgDocumentBuilder(ACadVersion.AC1032, doc, new DwgReaderConfiguration { KeepUnknownEntities = true }); - builder.DataStorage = new CadFileDataStorage(); - builder.DataStorage.Records.Add(this.createAsmRecord(solid.Handle, solidData)); - builder.DataStorage.Records.Add(this.createAsmRecord(surface.Handle, new byte[] { 4, 5, 6 })); - - List notifications = new List(); - builder.OnNotification += (sender, e) => notifications.Add(e); - - builder.BuildDataStorage(); - - Assert.Equal(solidData, solid.AcisData); - NotificationEventArgs warning = Assert.Single(notifications); - Assert.Equal(NotificationType.Warning, warning.NotificationType); - } - - private AcdsRecord createAsmRecord(ulong handle, byte[] data) - { - AcdsRecord record = new AcdsRecord(); - - record.Columns.Add(CadFileDataStorage.Id, new AcdsRecordColumn { Name = CadFileDataStorage.Id, Handle = handle }); - - MemoryStream stream = new MemoryStream(); - stream.Write(data, 0, data.Length); - record.Columns.Add(CadFileDataStorage.AsmData, new AcdsRecordColumn { Name = CadFileDataStorage.AsmData, Data = stream }); - - return record; - } -} From 22f59a570f4f23cdb6b2a0d577bdb79398810600 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 8 Oct 2026 11:32:01 +0200 Subject: [PATCH 27/81] #PR1273 v3.8.9 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 23b7b9e35..b9928e04a 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.8 + 3.8.9 ../nupkg True ../ACadSharp.snk From 15831b0d14be61235b56cd17f79c40deaf0d9aae Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 8 Oct 2026 11:45:45 +0200 Subject: [PATCH 28/81] #PR1155 v3.8.10 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index b9928e04a..667bd1b15 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.9 + 3.8.10 ../nupkg True ../ACadSharp.snk From b63db81e2d7023d899543b9920c7037a2ede29fa Mon Sep 17 00:00:00 2001 From: Li Yanxu Date: Thu, 8 Oct 2026 23:27:44 +0800 Subject: [PATCH 29/81] feat: read the retained data of proxy entities and objects `readCommonProxyData` stopped right before the retained data of the original object, with a TODO noting that the section cannot be read. That data is the only place where a custom object survives once it has been saved as a proxy, so leaving it unread makes the payload of any custom object inaccessible to a consumer of the file. The section has no length of its own: it extends from the end of the proxy fields up to the start of the handle section. The reader already computes that offset when it splits the object stream for R2010 and later, so the value is kept in a field and the remaining bits are read as the data. ProxyObject already exposed a Data stream, filled from group 311 in DXF. ProxyEntity had no such member, so it gets one with the same shape and DXF code. Before R2010 the handle section offset is not encoded in the file, sections without a length cannot be delimited, and the data is left unset as it was. Verified on real drawings, comparing the reader with and without the change over 21 DWG files (1,607,215 entities in total): - proxy data decoded: 0 bytes before, 2358 bytes after - entity count and a per-document fingerprint of entity types and handles: identical in both runs, no file differs, so consuming the section does not disturb the reads that follow it --- src/ACadSharp/Entities/ProxyEntity.cs | 14 +++++++ .../DWG/DwgStreamReaders/DwgObjectReader.cs | 38 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/Entities/ProxyEntity.cs b/src/ACadSharp/Entities/ProxyEntity.cs index e02b1baf0..68aca8a36 100644 --- a/src/ACadSharp/Entities/ProxyEntity.cs +++ b/src/ACadSharp/Entities/ProxyEntity.cs @@ -1,6 +1,7 @@ using ACadSharp.Attributes; using ACadSharp.Classes; using CSMath; +using System.IO; namespace ACadSharp.Entities { @@ -55,6 +56,7 @@ public class ProxyEntity : Entity, IProxy //92 Size of graphics data in bytes //310 Binary graphics data(multiple entries can appear) (optional) + //The graphics are read as common entity data and exposed through Entity.ProxyGeometries. //93 Size of entity data in bits //310 Binary entity data(multiple entries can appear) (optional) @@ -64,6 +66,18 @@ public class ProxyEntity : Entity, IProxy //An object ID(multiple entries can appear) (optional) //94 0 (indicates end of object ID section) + /// + /// Gets or sets the data of the original object as it was stored while the object is a proxy. + /// + /// + /// This is the payload the original application would read back, and it is the only place where + /// the custom object data survives once the object has been saved as a proxy. In the file the + /// section has no length of its own: it runs from the end of the proxy fields up to the start of + /// the handle section. + /// + [DxfCodeValue(311)] + public Stream Data { get; set; } + /// public ACadVersion Version { get; set; } diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 88239dd14..650c6057f 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -72,6 +72,15 @@ internal partial class DwgObjectReader : DwgSectionIO /// private IDwgStreamReader _handlesReader; + /// + /// Bit position where the handle section of the current object starts, or -1 when it is unknown. + /// + /// + /// Some sections, notably the retained data of a proxy object, have no length of their own and + /// run until the handle section begins. + /// + private long _handleSectionOffset = -1; + private IDwgStreamReader _mergedReaders; private long _objectInitialPos = 0; @@ -236,6 +245,9 @@ private ObjectType getEntityType(long offset) //Find the handles offset ulong handleSectionOffset = (ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize; + //Keep it, some sections run until the handle section starts + this._handleSectionOffset = (long)handleSectionOffset; + //Create a handler section reader this._objectReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); this._objectReader.SetPositionInBits(this._crcReader.PositionInBits()); @@ -263,6 +275,9 @@ private ObjectType getEntityType(long offset) this._handlesReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); this._textReader = this._objectReader; + //The handle section offset is not encoded before R2010, sections without a length cannot be delimited + this._handleSectionOffset = -1; + //set the initial position and get the object type this._objectInitialPos = this._objectReader.PositionInBits(); type = this._objectReader.ReadObjectType(); @@ -1181,8 +1196,29 @@ private void readCommonProxyData(IProxy proxy) //Common: //Databits X databits, however many there are to the handles + //The retained data of the original object has no length of its own, it extends from the + //current position up to the start of the handle section. + if (this._handleSectionOffset > 0) + { + long dataBits = this._handleSectionOffset - this._objectReader.PositionInBits(); + if (dataBits > 0) + { + //The section is measured in bits, read the bytes that contain them + int dataBytes = (int)((dataBits + 7L) / 8L); + byte[] data = this._objectReader.ReadBytes(dataBytes); - //TODO: Investigate how to read the data in proxies, it can contain data, strings and handles + Stream stream = new MemoryStream(data, writable: false); + switch (proxy) + { + case ProxyEntity entity: + entity.Data = stream; + break; + case ProxyObject proxyObject: + proxyObject.Data = stream; + break; + } + } + } } private CadTemplate readDbColor() From 1b38281eca10cfcc114148a32187014323e0d8b4 Mon Sep 17 00:00:00 2001 From: s1lentcs <33484685+s1lentcs@users.noreply.github.com> Date: Fri, 9 Oct 2026 11:51:20 +1000 Subject: [PATCH 30/81] Avoid re-reading attributes when copying DxfProperty --- src/ACadSharp/DxfProperty.cs | 2 +- src/ACadSharp/DxfPropertyBase.cs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/DxfProperty.cs b/src/ACadSharp/DxfProperty.cs index fd8c48a21..f8f335c56 100644 --- a/src/ACadSharp/DxfProperty.cs +++ b/src/ACadSharp/DxfProperty.cs @@ -49,7 +49,7 @@ public DxfProperty(int code, PropertyInfo property) : this(property) /// DxfProperty instance. /// /// The DxfProperty instance from which to copy property information. Cannot be null. - public DxfProperty(DxfProperty value) : this(value.AssignedCode, value._property) + public DxfProperty(DxfProperty value) : base(value) { } diff --git a/src/ACadSharp/DxfPropertyBase.cs b/src/ACadSharp/DxfPropertyBase.cs index a9ed4c199..d12edeffa 100644 --- a/src/ACadSharp/DxfPropertyBase.cs +++ b/src/ACadSharp/DxfPropertyBase.cs @@ -149,6 +149,16 @@ protected DxfPropertyBase(PropertyInfo property) this._property = property; } + /// + /// Copies the property mapping from an existing instance without re-reading its attribute. + /// + protected DxfPropertyBase(DxfPropertyBase other) + { + this._attributeData = other._attributeData; + this._property = other._property; + this._assignedCode = other._assignedCode; + } + /// /// Set the value for the property using the AssignedCode /// From bbc638a1c0fa718c1a5c517299dbbfb0bad1fbf2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 09:45:59 +0000 Subject: [PATCH 31/81] Keep the first definition of a duplicated DXF table entry When Failsafe is enabled, DxfTablesSectionReader.readEntries replaced a duplicated table entry with its last definition by removing the existing entry and adding the new one. That failed in two cases: default entries (layer "0", "Continuous", "Standard"...) cannot be removed, so the following Add threw ArgumentException (DomCR/ACadSharp#1242), and removing an entry before the default entry had been read threw KeyNotFoundException while reassigning references to the default entry (DomCR/ACadSharp#1239). Discard the duplicated entry and keep the first definition instead, as suggested in the review of DomCR/ACadSharp#1243. The duplicate is still reported as a warning. Table stays unchanged, the recovery belongs to the reader. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0117oubfvTvHkTqFLvKGn6nU --- .../IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs index 2590fdbfc..0d6f6a85c 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs @@ -223,16 +223,13 @@ private void readEntries(CadTableTemplate tableTemplate) if (tableTemplate.CadObject.Contains(template.Name) && this._builder.Configuration.Failsafe) { + //Keep the first definition, the duplicated entry is discarded this._builder.Notify($"Duplicated entry with name {template.Name} found in {template.CadObject.ObjectName}", NotificationType.Warning); - - tableTemplate.CadObject.Remove(template.Name); - tableTemplate.CadObject.Add((T)template.CadObject); - } - else - { - tableTemplate.CadObject.Add((T)template.CadObject); + continue; } + tableTemplate.CadObject.Add((T)template.CadObject); + //Add the object and the template to the builder this._builder.AddTemplate(template); } From e2e91041865d6d8c5b974974b5583b4400c687a2 Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 13 Oct 2026 12:22:51 +0200 Subject: [PATCH 32/81] #PR1243 v3.8.11 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 667bd1b15..0fc474673 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.10 + 3.8.11 ../nupkg True ../ACadSharp.snk From 6d404b56ce8c81dca38db96e6ce725bba1cf0bbe Mon Sep 17 00:00:00 2001 From: s1lentcs <33484685+s1lentcs@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:40:06 +1000 Subject: [PATCH 33/81] Remove comment on DxfPropertyBase --- src/ACadSharp/DxfPropertyBase.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ACadSharp/DxfPropertyBase.cs b/src/ACadSharp/DxfPropertyBase.cs index d12edeffa..a93f40c00 100644 --- a/src/ACadSharp/DxfPropertyBase.cs +++ b/src/ACadSharp/DxfPropertyBase.cs @@ -149,9 +149,6 @@ protected DxfPropertyBase(PropertyInfo property) this._property = property; } - /// - /// Copies the property mapping from an existing instance without re-reading its attribute. - /// protected DxfPropertyBase(DxfPropertyBase other) { this._attributeData = other._attributeData; From 252c848bfaf110e3ed1b3a2e5c8aefe1973d70ed Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 13 Oct 2026 14:11:01 +0200 Subject: [PATCH 34/81] #PR1276 v3.8.12 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 0fc474673..76107248c 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.11 + 3.8.12 ../nupkg True ../ACadSharp.snk From b14a0aee0082ba17acb4a8a390ac653200553312 Mon Sep 17 00:00:00 2001 From: Petr Sommer Date: Tue, 13 Oct 2026 12:48:34 +0000 Subject: [PATCH 35/81] Move the zero handle seed fix from CadDocument to DxfReader --- src/ACadSharp/CadDocument.cs | 6 ------ src/ACadSharp/IO/DxfReader.cs | 5 +++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ACadSharp/CadDocument.cs b/src/ACadSharp/CadDocument.cs index 9632b61ac..10fd3e442 100644 --- a/src/ACadSharp/CadDocument.cs +++ b/src/ACadSharp/CadDocument.cs @@ -641,12 +641,6 @@ internal void AddCadObject(CadObject cadObject) { var nextHandle = this.Header.HandleSeed; - //The seed read from a file may point to a used handle, e.g. $HANDSEED 0 collides with the document itself - while (this._cadObjects.ContainsKey(nextHandle)) - { - nextHandle++; - } - cadObject.Handle = nextHandle; this.Header.HandleSeed = nextHandle + 1; } diff --git a/src/ACadSharp/IO/DxfReader.cs b/src/ACadSharp/IO/DxfReader.cs index 86139e926..2f475d81c 100644 --- a/src/ACadSharp/IO/DxfReader.cs +++ b/src/ACadSharp/IO/DxfReader.cs @@ -173,6 +173,11 @@ public override CadDocument Read() case DxfFileToken.HeaderSection: this._document.Header = this.ReadHeader(); this._document.Header.Document = this._document; + //Handle 0 belongs to the document, a seed of 0 would hand it out again + if (this._document.Header.HandleSeed == 0) + { + this._document.Header.HandleSeed = 1; + } this._builder.InitialHandSeed = this._document.Header.HandleSeed; break; case DxfFileToken.ClassesSection: From 64f70ec159ff8d28521fef0f6000bf752ea7b099 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:31:15 -0500 Subject: [PATCH 36/81] Read the dimension block of dimension object context data Annotative dimensions have one ACDB_*DIMOBJECTCONTEXTDATA_CLASS object per annotation scale, each pointing to the anonymous block with the dimension graphics for that scale. These objects were read as unknown non-graphical objects, so the per-scale blocks could not be found. Add DimensionObjectContextData with the scale and the block, and read it for every DIMOBJECTCONTEXTDATA class. Only the handles are read, not the data fields, so both writers skip it like other partially supported objects. Co-Authored-By: Claude Opus 5.5 --- .../DwgObjectReader.Objects.cs | 9 ++++++++ .../DWG/DwgStreamReaders/DwgObjectReader.cs | 5 +++++ .../DwgObjectWriter.Objects.cs | 1 + .../DxfObjectsSectionWriter.cs | 1 + .../CadDimensionObjectContextDataTemplate.cs | 21 +++++++++++++++++++ .../Objects/DimensionObjectContextData.cs | 16 ++++++++++++++ 6 files changed, 53 insertions(+) create mode 100644 src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs create mode 100644 src/ACadSharp/Objects/DimensionObjectContextData.cs diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs index 7983ff343..c41da66f3 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs @@ -29,6 +29,15 @@ private void readAnnotScaleObjectContextData(CadAnnotScaleObjectContextDataTempl template.ScaleHandle = this.handleReference(); } + private CadTemplate readDimensionObjectContextData() + { + var template = new CadDimensionObjectContextDataTemplate(new DimensionObjectContextData()); + this.readAnnotScaleObjectContextData(template); + //H 2 dimension block for this scale; the data fields are not read + template.BlockHandle = this.handleReference(); + return template; + } + private CadTemplate readBlkRefObjectContextData() { BlockReferenceObjectContextData contextData = new BlockReferenceObjectContextData(); diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 88239dd14..e8c21427c 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -6214,6 +6214,11 @@ private CadTemplate readUnlistedType(short classNumber) break; } + if (template == null && c.DxfName.EndsWith("DIMOBJECTCONTEXTDATA_CLASS")) + { + template = this.readDimensionObjectContextData(); + } + if (template == null && c.IsAnEntity) { template = this.readUnknownEntity(c); diff --git a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs index 59c9f33a6..9ea1ce14b 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamWriters/DwgObjectWriter.Objects.cs @@ -50,6 +50,7 @@ private bool skipEntry(NonGraphicalObject entry, out bool notify) case AecCleanupGroup: case AecBinRecord: case DimensionAssociation: + case DimensionObjectContextData: case UnknownNonGraphicalObject: case VisualStyle: case ProxyObject: diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs index 511a53472..9dbb04db5 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs @@ -741,6 +741,7 @@ private bool isObjectSupported(CadObject co) case AecCleanupGroup: case AecBinRecord: case DimensionAssociation: + case DimensionObjectContextData: case Material: case MultiLeaderObjectContextData: case VisualStyle: diff --git a/src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs b/src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs new file mode 100644 index 000000000..9d60bd88e --- /dev/null +++ b/src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs @@ -0,0 +1,21 @@ +using ACadSharp.Objects; +using ACadSharp.Tables; + +namespace ACadSharp.IO.Templates; + +internal class CadDimensionObjectContextDataTemplate : CadAnnotScaleObjectContextDataTemplate +{ + public ulong? BlockHandle { get; set; } + + public CadDimensionObjectContextDataTemplate(DimensionObjectContextData cadObject) : base(cadObject) { } + + protected override void build(CadDocumentBuilder builder) + { + base.build(builder); + + if (builder.TryGetCadObject(this.BlockHandle, out BlockRecord block)) + { + ((DimensionObjectContextData)this.CadObject).Block = block; + } + } +} diff --git a/src/ACadSharp/Objects/DimensionObjectContextData.cs b/src/ACadSharp/Objects/DimensionObjectContextData.cs new file mode 100644 index 000000000..28f991dd7 --- /dev/null +++ b/src/ACadSharp/Objects/DimensionObjectContextData.cs @@ -0,0 +1,16 @@ +using ACadSharp.Tables; + +namespace ACadSharp.Objects; + +/// +/// Annotation scale representation of a dimension (ACDB_*DIMOBJECTCONTEXTDATA_CLASS): the dimension's +/// geometry block for one annotation scale. Only the block is read. +/// +public class DimensionObjectContextData : AnnotScaleObjectContextData +{ + /// Anonymous block holding the dimension's graphics at . + public BlockRecord Block { get; set; } + + /// + public override string ObjectName => "ACDB_DIMOBJECTCONTEXTDATA_CLASS"; +} From dac72da4c8e9af61f18314f127ac51f83c9916fc Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:26:43 -0500 Subject: [PATCH 37/81] Read dimension object context data from the readUnlistedType switch Address review: dispatch through the switch with DxfFileToken constants instead of a separate check. DWG files store the concrete ACDB_*DIMOBJECTCONTEXTDATA_CLASS subclass names, so each is listed. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp/DxfFileToken.cs | 14 ++++++++++++++ .../IO/DWG/DwgStreamReaders/DwgObjectReader.cs | 14 +++++++++----- .../Objects/DimensionObjectContextData.cs | 2 +- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/ACadSharp/DxfFileToken.cs b/src/ACadSharp/DxfFileToken.cs index c43321492..dcd85a668 100644 --- a/src/ACadSharp/DxfFileToken.cs +++ b/src/ACadSharp/DxfFileToken.cs @@ -14,12 +14,20 @@ public static class DxfFileToken public const string AcmPartRef = "ACMPARTREF"; + public const string AlignedDimensionObjectContextData = "ACDB_ALDIMOBJECTCONTEXTDATA_CLASS"; + + public const string AngularDimensionObjectContextData = "ACDB_ANGDIMOBJECTCONTEXTDATA_CLASS"; + public const string BeginSection = "SECTION"; public const string BlkRefObjectContextData = "ACDB_BLKREFOBJECTCONTEXTDATA_CLASS"; public const string BlkRefToken = "{BLKREFS"; + public const string DiametricDimensionObjectContextData = "ACDB_DMDIMOBJECTCONTEXTDATA_CLASS"; + + public const string DimensionObjectContextData = "ACDB_DIMOBJECTCONTEXTDATA_CLASS"; + public const string Block = "BLOCK"; public const string BlocksSection = "BLOCKS"; @@ -178,6 +186,12 @@ public static class DxfFileToken public const string MTextAttributeObjectContextData = "ACDB_MTEXTATTRIBUTEOBJECTCONTEXTDATA_CLASS"; + public const string OrdinateDimensionObjectContextData = "ACDB_ORDDIMOBJECTCONTEXTDATA_CLASS"; + + public const string RadialDimensionLargeObjectContextData = "ACDB_RADIMLGOBJECTCONTEXTDATA_CLASS"; + + public const string RadialDimensionObjectContextData = "ACDB_RADIMOBJECTCONTEXTDATA_CLASS"; + public const string ObjectAecCleanupGroupDef = "AEC_CLEANUP_GROUP_DEF"; public const string ObjectAecWallStyle = "AEC_WALL_STYLE"; diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index e8c21427c..0fac98b0a 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -6212,11 +6212,15 @@ private CadTemplate readUnlistedType(short classNumber) case DxfFileToken.ObjectWipeoutVariables: template = this.readWipeoutVariables(); break; - } - - if (template == null && c.DxfName.EndsWith("DIMOBJECTCONTEXTDATA_CLASS")) - { - template = this.readDimensionObjectContextData(); + case DxfFileToken.DimensionObjectContextData: + case DxfFileToken.AlignedDimensionObjectContextData: + case DxfFileToken.AngularDimensionObjectContextData: + case DxfFileToken.DiametricDimensionObjectContextData: + case DxfFileToken.OrdinateDimensionObjectContextData: + case DxfFileToken.RadialDimensionObjectContextData: + case DxfFileToken.RadialDimensionLargeObjectContextData: + template = this.readDimensionObjectContextData(); + break; } if (template == null && c.IsAnEntity) diff --git a/src/ACadSharp/Objects/DimensionObjectContextData.cs b/src/ACadSharp/Objects/DimensionObjectContextData.cs index 28f991dd7..9dad26103 100644 --- a/src/ACadSharp/Objects/DimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/DimensionObjectContextData.cs @@ -12,5 +12,5 @@ public class DimensionObjectContextData : AnnotScaleObjectContextData public BlockRecord Block { get; set; } /// - public override string ObjectName => "ACDB_DIMOBJECTCONTEXTDATA_CLASS"; + public override string ObjectName => DxfFileToken.DimensionObjectContextData; } From 9b24f213cb0a43157647d956b2c8530ca70af09c Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Thu, 8 Oct 2026 06:39:49 -0500 Subject: [PATCH 38/81] Add a dimension object context data class for each dimension type DWG files store the concrete ACDB_*DIMOBJECTCONTEXTDATA_CLASS names; there is no ACDB_DIMOBJECTCONTEXTDATA_CLASS class. Make DimensionObjectContextData an abstract base (AcDbDimensionObjectContextData) and add one class per type with its real DXF name and subclass marker: aligned, angular, diametric, ordinate, radial and radial large. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp/DxfFileToken.cs | 2 -- src/ACadSharp/DxfSubclassMarker.cs | 14 ++++++++++++++ .../DwgStreamReaders/DwgObjectReader.Objects.cs | 4 ++-- .../IO/DWG/DwgStreamReaders/DwgObjectReader.cs | 13 +++++++++++-- .../AlignedDimensionObjectContextData.cs | 17 +++++++++++++++++ .../AngularDimensionObjectContextData.cs | 17 +++++++++++++++++ .../DiametricDimensionObjectContextData.cs | 17 +++++++++++++++++ .../Objects/DimensionObjectContextData.cs | 12 ++++++++---- .../OrdinateDimensionObjectContextData.cs | 17 +++++++++++++++++ .../RadialDimensionLargeObjectContextData.cs | 17 +++++++++++++++++ .../Objects/RadialDimensionObjectContextData.cs | 17 +++++++++++++++++ 11 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs create mode 100644 src/ACadSharp/Objects/AngularDimensionObjectContextData.cs create mode 100644 src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs create mode 100644 src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs create mode 100644 src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs create mode 100644 src/ACadSharp/Objects/RadialDimensionObjectContextData.cs diff --git a/src/ACadSharp/DxfFileToken.cs b/src/ACadSharp/DxfFileToken.cs index dcd85a668..245abb0a4 100644 --- a/src/ACadSharp/DxfFileToken.cs +++ b/src/ACadSharp/DxfFileToken.cs @@ -26,8 +26,6 @@ public static class DxfFileToken public const string DiametricDimensionObjectContextData = "ACDB_DMDIMOBJECTCONTEXTDATA_CLASS"; - public const string DimensionObjectContextData = "ACDB_DIMOBJECTCONTEXTDATA_CLASS"; - public const string Block = "BLOCK"; public const string BlocksSection = "BLOCKS"; diff --git a/src/ACadSharp/DxfSubclassMarker.cs b/src/ACadSharp/DxfSubclassMarker.cs index 406a61ad7..e5d40ca9d 100644 --- a/src/ACadSharp/DxfSubclassMarker.cs +++ b/src/ACadSharp/DxfSubclassMarker.cs @@ -18,6 +18,10 @@ public static class DxfSubclassMarker public const string Angular3PointDimension = "AcDb3PointAngularDimension"; + public const string AlignedDimensionObjectContextData = "AcDbAlignedDimensionObjectContextData"; + + public const string AngularDimensionObjectContextData = "AcDbAngularDimensionObjectContextData"; + public const string AnnotScaleObjectContextData = "AcDbAnnotScaleObjectContextData"; public const string ApplicationId = "AcDbRegAppTableRecord"; @@ -202,6 +206,10 @@ public static class DxfSubclassMarker public const string ModelerGeometry = "AcDbModelerGeometry"; + public const string DiametricDimensionObjectContextData = "AcDbDiametricDimensionObjectContextData"; + + public const string DimensionObjectContextData = "AcDbDimensionObjectContextData"; + public const string MText = "AcDbMText"; public const string MTextAttributeObjectContextData = "AcDbMTextAttributeObjectContextData"; @@ -216,6 +224,12 @@ public static class DxfSubclassMarker public const string Ole2Frame = "AcDbOle2Frame"; + public const string OrdinateDimensionObjectContextData = "AcDbOrdinateDimensionObjectContextData"; + + public const string RadialDimensionLargeObjectContextData = "AcDbRadialDimensionLargeObjectContextData"; + + public const string RadialDimensionObjectContextData = "AcDbRadialDimensionObjectContextData"; + public const string OrdinateDimension = "AcDbOrdinateDimension"; public const string PartList = "AcmPartList"; diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs index c41da66f3..9c04bafda 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs @@ -29,9 +29,9 @@ private void readAnnotScaleObjectContextData(CadAnnotScaleObjectContextDataTempl template.ScaleHandle = this.handleReference(); } - private CadTemplate readDimensionObjectContextData() + private CadTemplate readDimensionObjectContextData(DimensionObjectContextData contextData) { - var template = new CadDimensionObjectContextDataTemplate(new DimensionObjectContextData()); + var template = new CadDimensionObjectContextDataTemplate(contextData); this.readAnnotScaleObjectContextData(template); //H 2 dimension block for this scale; the data fields are not read template.BlockHandle = this.handleReference(); diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 0fac98b0a..1eea4dc9e 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -6212,14 +6212,23 @@ private CadTemplate readUnlistedType(short classNumber) case DxfFileToken.ObjectWipeoutVariables: template = this.readWipeoutVariables(); break; - case DxfFileToken.DimensionObjectContextData: case DxfFileToken.AlignedDimensionObjectContextData: + template = this.readDimensionObjectContextData(new AlignedDimensionObjectContextData()); + break; case DxfFileToken.AngularDimensionObjectContextData: + template = this.readDimensionObjectContextData(new AngularDimensionObjectContextData()); + break; case DxfFileToken.DiametricDimensionObjectContextData: + template = this.readDimensionObjectContextData(new DiametricDimensionObjectContextData()); + break; case DxfFileToken.OrdinateDimensionObjectContextData: + template = this.readDimensionObjectContextData(new OrdinateDimensionObjectContextData()); + break; case DxfFileToken.RadialDimensionObjectContextData: + template = this.readDimensionObjectContextData(new RadialDimensionObjectContextData()); + break; case DxfFileToken.RadialDimensionLargeObjectContextData: - template = this.readDimensionObjectContextData(); + template = this.readDimensionObjectContextData(new RadialDimensionLargeObjectContextData()); break; } diff --git a/src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs b/src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs new file mode 100644 index 000000000..504c3ed02 --- /dev/null +++ b/src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs @@ -0,0 +1,17 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Objects; + +/// +/// Annotation scale representation of an aligned or linear dimension. +/// +[DxfName(DxfFileToken.AlignedDimensionObjectContextData)] +[DxfSubClass(DxfSubclassMarker.AlignedDimensionObjectContextData)] +public class AlignedDimensionObjectContextData : DimensionObjectContextData +{ + /// + public override string ObjectName => DxfFileToken.AlignedDimensionObjectContextData; + + /// + public override string SubclassMarker => DxfSubclassMarker.AlignedDimensionObjectContextData; +} diff --git a/src/ACadSharp/Objects/AngularDimensionObjectContextData.cs b/src/ACadSharp/Objects/AngularDimensionObjectContextData.cs new file mode 100644 index 000000000..47f91a112 --- /dev/null +++ b/src/ACadSharp/Objects/AngularDimensionObjectContextData.cs @@ -0,0 +1,17 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Objects; + +/// +/// Annotation scale representation of an angular dimension. +/// +[DxfName(DxfFileToken.AngularDimensionObjectContextData)] +[DxfSubClass(DxfSubclassMarker.AngularDimensionObjectContextData)] +public class AngularDimensionObjectContextData : DimensionObjectContextData +{ + /// + public override string ObjectName => DxfFileToken.AngularDimensionObjectContextData; + + /// + public override string SubclassMarker => DxfSubclassMarker.AngularDimensionObjectContextData; +} diff --git a/src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs b/src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs new file mode 100644 index 000000000..aef5630be --- /dev/null +++ b/src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs @@ -0,0 +1,17 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Objects; + +/// +/// Annotation scale representation of a diametric dimension. +/// +[DxfName(DxfFileToken.DiametricDimensionObjectContextData)] +[DxfSubClass(DxfSubclassMarker.DiametricDimensionObjectContextData)] +public class DiametricDimensionObjectContextData : DimensionObjectContextData +{ + /// + public override string ObjectName => DxfFileToken.DiametricDimensionObjectContextData; + + /// + public override string SubclassMarker => DxfSubclassMarker.DiametricDimensionObjectContextData; +} diff --git a/src/ACadSharp/Objects/DimensionObjectContextData.cs b/src/ACadSharp/Objects/DimensionObjectContextData.cs index 9dad26103..3c84d1a2d 100644 --- a/src/ACadSharp/Objects/DimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/DimensionObjectContextData.cs @@ -1,16 +1,20 @@ +using ACadSharp.Attributes; using ACadSharp.Tables; namespace ACadSharp.Objects; /// -/// Annotation scale representation of a dimension (ACDB_*DIMOBJECTCONTEXTDATA_CLASS): the dimension's -/// geometry block for one annotation scale. Only the block is read. +/// Annotation scale representation of a dimension: the dimension's geometry block for one annotation scale. /// -public class DimensionObjectContextData : AnnotScaleObjectContextData +/// +/// Only the block is read, the dimension data fields are not. +/// +[DxfSubClass(DxfSubclassMarker.DimensionObjectContextData)] +public abstract class DimensionObjectContextData : AnnotScaleObjectContextData { /// Anonymous block holding the dimension's graphics at . public BlockRecord Block { get; set; } /// - public override string ObjectName => DxfFileToken.DimensionObjectContextData; + public override string SubclassMarker => DxfSubclassMarker.DimensionObjectContextData; } diff --git a/src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs b/src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs new file mode 100644 index 000000000..67606aa36 --- /dev/null +++ b/src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs @@ -0,0 +1,17 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Objects; + +/// +/// Annotation scale representation of an ordinate dimension. +/// +[DxfName(DxfFileToken.OrdinateDimensionObjectContextData)] +[DxfSubClass(DxfSubclassMarker.OrdinateDimensionObjectContextData)] +public class OrdinateDimensionObjectContextData : DimensionObjectContextData +{ + /// + public override string ObjectName => DxfFileToken.OrdinateDimensionObjectContextData; + + /// + public override string SubclassMarker => DxfSubclassMarker.OrdinateDimensionObjectContextData; +} diff --git a/src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs b/src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs new file mode 100644 index 000000000..9e54696c7 --- /dev/null +++ b/src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs @@ -0,0 +1,17 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Objects; + +/// +/// Annotation scale representation of a jogged radial dimension. +/// +[DxfName(DxfFileToken.RadialDimensionLargeObjectContextData)] +[DxfSubClass(DxfSubclassMarker.RadialDimensionLargeObjectContextData)] +public class RadialDimensionLargeObjectContextData : DimensionObjectContextData +{ + /// + public override string ObjectName => DxfFileToken.RadialDimensionLargeObjectContextData; + + /// + public override string SubclassMarker => DxfSubclassMarker.RadialDimensionLargeObjectContextData; +} diff --git a/src/ACadSharp/Objects/RadialDimensionObjectContextData.cs b/src/ACadSharp/Objects/RadialDimensionObjectContextData.cs new file mode 100644 index 000000000..f9be80959 --- /dev/null +++ b/src/ACadSharp/Objects/RadialDimensionObjectContextData.cs @@ -0,0 +1,17 @@ +using ACadSharp.Attributes; + +namespace ACadSharp.Objects; + +/// +/// Annotation scale representation of a radial dimension. +/// +[DxfName(DxfFileToken.RadialDimensionObjectContextData)] +[DxfSubClass(DxfSubclassMarker.RadialDimensionObjectContextData)] +public class RadialDimensionObjectContextData : DimensionObjectContextData +{ + /// + public override string ObjectName => DxfFileToken.RadialDimensionObjectContextData; + + /// + public override string SubclassMarker => DxfSubclassMarker.RadialDimensionObjectContextData; +} From 23555b6bc092b95af9f04bd8821d2db1fabc04f4 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Thu, 8 Oct 2026 07:05:14 -0500 Subject: [PATCH 39/81] Read dimension object context data fields from DWG and DXF Read the per-scale text location, text rotation, default text location flag, overridden dimension variables (DIMTOFL, DIMSOXD, DIMTIX and the override flags), arrow flips and the type-specific points of each ACDB_*DIMOBJECTCONTEXTDATA_CLASS object, and read these objects from DXF. The DWG bit layout was decoded from a drawing made in AutoCAD and checked against its DXF export: text location is first, and DIMATFIT and DIMTMOVE are stored as single override bits, so their values are only read from DXF. The jogged radial class name is ACDB_RADIMLRGOBJECTCONTEXTDATA_CLASS and its data extends the radial one. Add samples/annotative with the drawing (7 annotative dimensions at 1:1 and 1:20, some with per-scale overrides) and tests comparing DWG and DXF. Co-Authored-By: Claude Opus 5.5 --- .../annotative_dimensions_AC1032.dwg | Bin 0 -> 123201 bytes .../annotative_dimensions_AC1032.dxf | 28854 ++++++++++++++++ .../IO/DimensionObjectContextDataTests.cs | 161 + src/ACadSharp/DxfFileToken.cs | 2 +- .../DwgObjectReader.Objects.cs | 66 +- .../DxfObjectsSectionReader.cs | 31 + .../CadDimensionObjectContextDataTemplate.cs | 4 +- .../AlignedDimensionObjectContextData.cs | 7 + .../AngularDimensionObjectContextData.cs | 9 +- .../DiametricDimensionObjectContextData.cs | 13 + .../Objects/DimensionObjectContextData.cs | 107 +- .../OrdinateDimensionObjectContextData.cs | 13 + .../RadialDimensionLargeObjectContextData.cs | 15 +- .../RadialDimensionObjectContextData.cs | 7 + 14 files changed, 29279 insertions(+), 10 deletions(-) create mode 100644 samples/annotative/annotative_dimensions_AC1032.dwg create mode 100644 samples/annotative/annotative_dimensions_AC1032.dxf create mode 100644 src/ACadSharp.Tests/IO/DimensionObjectContextDataTests.cs diff --git a/samples/annotative/annotative_dimensions_AC1032.dwg b/samples/annotative/annotative_dimensions_AC1032.dwg new file mode 100644 index 0000000000000000000000000000000000000000..0518d4873ceca55d42fb8773bc11b9659c585136 GIT binary patch literal 123201 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

khWbBk+!q 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 %<\AcVar ViewDetailId>%\PSCALE %<\AcVar ViewScale \f "%sn">% + 71 + 4 +340 +12 + 90 + 25 + 62 + 256 +340 +12 + 90 + 25 + 62 + 256 +280 + 0 + 0 +LAYOUT + 5 +19D +102 +{ACAD_REACTORS +330 +15 +102 +} +330 +15 +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 +Letter_(8.50_x_11.00_Inches) + 6 + + 40 +6.349999904632568 + 41 +6.349999904632568 + 42 +6.350006103515625 + 43 +6.350006103515625 + 44 +215.8999938964844 + 45 +279.3999938964844 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 14992 + 72 + 0 + 73 + 0 + 74 + 5 + 7 + + 75 + 16 +147 +1.0 + 76 + 0 + 77 + 2 + 78 + 300 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 + 1 + 71 + 1 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +0.0 + 24 +0.0 + 34 +0.0 + 15 +0.0 + 25 +0.0 + 35 +0.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +19C +1001 +ACAD_PSEXT +1000 +None +1000 +None +1000 +Not applicable +1000 +The layout will not be plotted unless a new plotter configuration name is selected. +1070 + 0 + 0 +LAYOUT + 5 +14 +102 +{ACAD_XDICTIONARY +360 +16 +102 +} +102 +{ACAD_REACTORS +330 +15 +102 +} +330 +15 +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 +Letter_(8.50_x_11.00_Inches) + 6 + + 40 +6.349999904632568 + 41 +6.349999904632568 + 42 +6.350006103515625 + 43 +6.350006103515625 + 44 +215.8999938964844 + 45 +279.3999938964844 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +27.46773581402492 + 70 + 11920 + 72 + 0 + 73 + 1 + 74 + 0 + 7 + + 75 + 0 +147 +0.0364063498633697 + 76 + 0 + 77 + 2 + 78 + 300 +148 +4363.230665151171 +149 +437.5794662898717 +100 +AcDbLayout + 1 +Model + 70 + 1 + 71 + 0 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +0.0 + 24 +0.0 + 34 +0.0 + 15 +0.0 + 25 +0.0 + 35 +0.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +2 +331 +2E +1001 +ACAD_PSEXT +1000 +None +1000 +None +1000 +Not applicable +1000 +The layout will not be plotted unless a new plotter configuration name is selected. +1070 + 0 + 0 +MATERIAL + 5 +3D +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMaterial + 1 +ByBlock + 73 + 1 + 78 + 1 +172 + 1 +176 + 1 +270 + 1 +274 + 1 + 94 + 63 + 0 +MATERIAL + 5 +3C +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMaterial + 1 +ByLayer + 73 + 1 + 78 + 1 +172 + 1 +176 + 1 +270 + 1 +274 + 1 + 94 + 63 + 0 +MATERIAL + 5 +E +102 +{ACAD_XDICTIONARY +360 +10 +102 +} +102 +{ACAD_REACTORS +330 +F +102 +} +330 +F +100 +AcDbMaterial + 1 +Global + 73 + 1 + 78 + 1 +172 + 1 +176 + 1 +270 + 1 +274 + 1 + 94 + 63 + 0 +MLEADERSTYLE + 5 +185 +102 +{ACAD_REACTORS +330 +41 +102 +} +330 +41 +100 +AcDbMLeaderStyle +179 + 2 +170 + 2 +171 + 1 +172 + 0 + 90 + 2 + 40 +0.0 + 41 +0.0 +173 + 1 + 91 +-1056964608 +340 +29 + 92 + -2 +290 + 1 + 42 +0.09 +291 + 1 + 43 +0.36 + 3 +Standard + 44 +0.18 +300 + +342 +26 +174 + 1 +178 + 6 +175 + 1 +176 + 0 + 93 +-1056964608 + 45 +0.18 +292 + 0 +297 + 0 + 46 +0.18 + 94 +-1056964608 + 47 +1.0 + 49 +1.0 +140 +1.0 +293 + 1 +141 +0.0 +294 + 1 +177 + 0 +142 +1.0 +295 + 1 +296 + 0 +143 +0.125 +271 + 0 +272 + 9 +273 + 9 +298 + 1 + 0 +MLINESTYLE + 5 +51 +102 +{ACAD_REACTORS +330 +3A +102 +} +330 +3A +100 +AcDbMlineStyle + 2 +STANDARD + 70 + 0 + 3 + + 62 + 256 + 51 +90.0 + 52 +90.0 + 71 + 2 + 49 +0.5 + 62 + 256 + 6 +BYLAYER + 49 +-0.5 + 62 + 256 + 6 +BYLAYER + 0 +ACDBPLACEHOLDER + 5 +9 +102 +{ACAD_REACTORS +330 +A +102 +} +330 +A + 0 +ACDBPLACEHOLDER + 5 +D +102 +{ACAD_REACTORS +330 +A +102 +} +330 +A + 0 +SCALE + 5 +56 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1:20 +140 +1.0 +141 +20.0 +290 + 0 + 0 +SCALE + 5 +57 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1" = 1' +140 +1.0 +141 +1.0 +290 + 0 + 0 +SCALE + 5 +58 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1:1 +140 +1.0 +141 +1.0 +290 + 1 + 0 +SCALE + 5 +59 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1/128" = 1'-0" +140 +0.0078125 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +5A +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1/64" = 1'-0" +140 +0.015625 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +5B +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1/32" = 1'-0" +140 +0.03125 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +5C +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1/16" = 1'-0" +140 +0.0625 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +5D +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +3/32" = 1'-0" +140 +0.09375 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +5E +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1/8" = 1'-0" +140 +0.125 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +5F +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +3/16" = 1'-0" +140 +0.1875 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +60 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1/4" = 1'-0" +140 +0.25 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +61 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +3/8" = 1'-0" +140 +0.375 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +62 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1/2" = 1'-0" +140 +0.5 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +63 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +3/4" = 1'-0" +140 +0.75 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +64 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1" = 1'-0" +140 +1.0 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +65 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1-1/2" = 1'-0" +140 +1.5 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +66 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +3" = 1'-0" +140 +3.0 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +67 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +6" = 1'-0" +140 +6.0 +141 +12.0 +290 + 0 + 0 +SCALE + 5 +68 +102 +{ACAD_REACTORS +330 +3F +102 +} +330 +3F +100 +AcDbScale + 70 + 0 +300 +1'-0" = 1'-0" +140 +12.0 +141 +12.0 +290 + 0 + 0 +ACDBSECTIONVIEWSTYLE + 5 +187 +102 +{ACAD_REACTORS +330 +186 +102 +} +330 +186 +100 +AcDbModelDocViewStyle + 70 + 0 + 3 +Imperial24 +290 + 1 +300 +Imperial24 + 90 + 0 +100 +AcDbSectionViewStyle + 70 + 0 + 71 + 0 + 90 + 78 + 71 + 1 +340 +26 + 62 + 256 + 40 +0.24 +340 +0 +340 +0 + 62 + 256 + 40 +0.24 +300 +I, O, Q, S, X, Z + 40 +0.48 + 90 + 3 + 40 +0.18 + 90 + 1 + 71 + 2 +340 +12 + 90 + 25 + 62 + 256 +340 +12 + 90 + 50 + 62 + 256 + 40 +0.24 + 40 +0.0 + 40 +0.24 + 71 + 3 +340 +26 + 62 + 256 + 40 +0.24 + 90 + 1 + 40 +0.75 + 90 + 1 +300 +%<\AcVar ViewType \f "%tc1">% %<\AcVar ViewSectionStartId>%-%<\AcVar ViewSectionEndId>%\PSCALE %<\AcVar ViewScale \f "%sn">% + 71 + 4 + 62 + 256 + 62 + 257 +300 +ANSI31 + 40 +1.0 + 90 + 0 +290 + 0 +290 + 0 + 90 + 6 + 40 +0.0 + 40 +1.570796326794896 + 40 +0.2617993877991494 + 40 +1.308996938995747 + 40 +-0.2617993877991494 + 40 +1.832595714594046 + 0 +TABLESTYLE + 5 +194 +102 +{ACAD_XDICTIONARY +360 +1B3 +102 +} +102 +{ACAD_REACTORS +330 +40 +102 +} +330 +40 +100 +AcDbTableStyle +280 + 0 + 3 + + 70 + 0 + 71 + 3 + 40 +0.06 + 41 +0.06 +280 + 0 +281 + 0 + 7 +Legend +140 +0.18 +170 + 4 + 62 + 0 + 63 + 7 +283 + 0 + 90 + 512 + 91 + 0 + 1 + +274 + -2 +284 + 0 + 64 + 0 +275 + -2 +285 + 0 + 65 + 0 +276 + -2 +286 + 0 + 66 + 0 +277 + -2 +287 + 0 + 67 + 0 +278 + -2 +288 + 0 + 68 + 0 +279 + -2 +289 + 0 + 69 + 0 + 7 +Legend +140 +0.25 +170 + 5 + 62 + 0 + 63 + 7 +283 + 0 + 90 + 512 + 91 + 0 + 1 + +274 + -2 +284 + 0 + 64 + 0 +275 + -2 +285 + 0 + 65 + 0 +276 + -2 +286 + 0 + 66 + 0 +277 + -2 +287 + 0 + 67 + 0 +278 + -2 +288 + 0 + 68 + 0 +279 + -2 +289 + 0 + 69 + 0 + 7 +Legend +140 +0.18 +170 + 4 + 62 + 0 + 63 + 7 +283 + 0 + 90 + 512 + 91 + 0 + 1 + +274 + -2 +284 + 0 + 64 + 0 +275 + -2 +285 + 0 + 65 + 0 +276 + -2 +286 + 0 + 66 + 0 +277 + -2 +287 + 0 + 67 + 0 +278 + -2 +288 + 0 + 68 + 0 +279 + -2 +289 + 0 + 69 + 0 + 0 +TABLESTYLE + 5 +52 +102 +{ACAD_XDICTIONARY +360 +53 +102 +} +102 +{ACAD_REACTORS +330 +40 +102 +} +330 +40 +100 +AcDbTableStyle +280 + 0 + 3 +Standard + 70 + 0 + 71 + 0 + 40 +0.06 + 41 +0.06 +280 + 0 +281 + 0 + 7 +Standard +140 +0.18 +170 + 2 + 62 + 0 + 63 + 7 +283 + 0 + 90 + 4 + 91 + 0 + 1 + +274 + -2 +284 + 1 + 64 + 0 +275 + -2 +285 + 1 + 65 + 0 +276 + -2 +286 + 1 + 66 + 0 +277 + -2 +287 + 1 + 67 + 0 +278 + -2 +288 + 1 + 68 + 0 +279 + -2 +289 + 1 + 69 + 0 + 7 +Standard +140 +0.25 +170 + 5 + 62 + 0 + 63 + 7 +283 + 0 + 90 + 4 + 91 + 0 + 1 + +274 + -2 +284 + 1 + 64 + 0 +275 + -2 +285 + 1 + 65 + 0 +276 + -2 +286 + 1 + 66 + 0 +277 + -2 +287 + 1 + 67 + 0 +278 + -2 +288 + 1 + 68 + 0 +279 + -2 +289 + 1 + 69 + 0 + 7 +Standard +140 +0.18 +170 + 5 + 62 + 0 + 63 + 7 +283 + 0 + 90 + 4 + 91 + 0 + 1 + +274 + -2 +284 + 1 + 64 + 0 +275 + -2 +285 + 1 + 65 + 0 +276 + -2 +286 + 1 + 66 + 0 +277 + -2 +287 + 1 + 67 + 0 +278 + -2 +288 + 1 + 68 + 0 +279 + -2 +289 + 1 + 69 + 0 + 0 +VISUALSTYLE + 5 +2F +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +2dWireframe + 70 + 4 +177 + 3 +291 + 0 + 70 + 58 + 90 + 0 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +55 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +3dWireframe + 70 + 5 +177 + 3 +291 + 0 + 70 + 58 + 90 + 0 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +174 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Basic + 70 + 7 +177 + 3 +291 + 1 + 70 + 58 + 90 + 1 +176 + 1 + 90 + 0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 0 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +179 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Brighten + 70 + 12 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +50.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +17D +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +ColorChange + 70 + 16 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 8 +420 + 8421504 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 8 +420 + 8421504 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +177 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Conceptual + 70 + 9 +177 + 3 +291 + 0 + 70 + 58 + 90 + 3 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +179.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +178 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Dim + 70 + 11 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +-50.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +7C +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +EdgeColorOff + 70 + 22 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 0 + 90 + 2 +176 + 0 + 90 + 0 +176 + 0 + 90 + 0 +176 + 0 + 40 +0.6 +176 + 0 + 40 +30.0 +176 + 0 + 62 + 7 +420 + 16777215 +176 + 0 + 90 + 1 +176 + 0 + 90 + 4 +176 + 0 + 62 + 7 +176 + 0 + 62 + 257 +176 + 0 + 90 + 1 +176 + 0 + 90 + 1 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 8 +176 + 2 + 62 + 7 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 1 +176 + 0 + 90 + 6 +176 + 0 + 90 + 2 +176 + 0 + 62 + 7 +176 + 0 + 90 + 5 +176 + 0 + 90 + 0 +176 + 0 + 90 + 0 +176 + 0 +290 + 0 +176 + 0 + 90 + 1 +176 + 0 + 40 +0.0 +176 + 0 + 90 + 0 +176 + 0 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +17C +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Facepattern + 70 + 15 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +170 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Flat + 70 + 0 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 13 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +171 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +FlatWithEdges + 70 + 1 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 0 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 13 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +172 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Gouraud + 70 + 2 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 13 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +173 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +GouraudWithEdges + 70 + 3 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 0 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 13 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +176 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Hidden + 70 + 6 +177 + 3 +291 + 0 + 70 + 58 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 40 +40.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +183 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +JitterOff + 70 + 20 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 0 + 90 + 2 +176 + 0 + 90 + 0 +176 + 0 + 90 + 0 +176 + 0 + 40 +0.6 +176 + 0 + 40 +30.0 +176 + 0 + 62 + 7 +420 + 16777215 +176 + 0 + 90 + 1 +176 + 0 + 90 + 4 +176 + 0 + 62 + 7 +176 + 0 + 62 + 257 +176 + 0 + 90 + 1 +176 + 0 + 90 + 1 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 10 +176 + 2 + 62 + 7 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 1 +176 + 0 + 90 + 6 +176 + 0 + 90 + 2 +176 + 0 + 62 + 7 +176 + 0 + 90 + 5 +176 + 0 + 90 + 0 +176 + 0 + 90 + 0 +176 + 0 +290 + 0 +176 + 0 + 90 + 1 +176 + 0 + 40 +0.0 +176 + 0 + 90 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 1 +176 + 0 +290 + 1 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 + 90 + 50 +176 + 0 + 40 +0.0 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 0 +176 + 0 + 62 + 18 +420 + 0 +176 + 0 + 90 + 50 +176 + 0 + 90 + 3 +176 + 0 + 62 + 5 +420 + 255 +176 + 0 +290 + 0 +176 + 0 + 90 + 50 +176 + 0 + 90 + 50 +176 + 0 + 90 + 50 +176 + 0 +290 + 0 +176 + 0 + 90 + 50 +176 + 0 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 0 + 1 +strokes_ogs.tif +176 + 0 +290 + 0 +176 + 0 + 40 +1.0 +176 + 0 + 40 +1.0 +176 + 0 + 0 +VISUALSTYLE + 5 +17B +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Linepattern + 70 + 14 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 7 +176 + 1 + 90 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +184 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +OverhangOff + 70 + 21 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 0 + 90 + 2 +176 + 0 + 90 + 0 +176 + 0 + 90 + 0 +176 + 0 + 40 +0.6 +176 + 0 + 40 +30.0 +176 + 0 + 62 + 7 +420 + 16777215 +176 + 0 + 90 + 1 +176 + 0 + 90 + 4 +176 + 0 + 62 + 7 +176 + 0 + 62 + 257 +176 + 0 + 90 + 1 +176 + 0 + 90 + 1 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 9 +176 + 2 + 62 + 7 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 1 +176 + 0 + 90 + 6 +176 + 0 + 90 + 2 +176 + 0 + 62 + 7 +176 + 0 + 90 + 5 +176 + 0 + 90 + 0 +176 + 0 + 90 + 0 +176 + 0 +290 + 0 +176 + 0 + 90 + 1 +176 + 0 + 40 +0.0 +176 + 0 + 90 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 1 +176 + 0 +290 + 1 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 +290 + 0 +176 + 0 + 90 + 50 +176 + 0 + 40 +0.0 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 0 +176 + 0 + 62 + 18 +420 + 0 +176 + 0 + 90 + 50 +176 + 0 + 90 + 3 +176 + 0 + 62 + 5 +420 + 255 +176 + 0 +290 + 0 +176 + 0 + 90 + 50 +176 + 0 + 90 + 50 +176 + 0 + 90 + 50 +176 + 0 +290 + 0 +176 + 0 + 90 + 50 +176 + 0 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 0 + 1 +strokes_ogs.tif +176 + 0 +290 + 0 +176 + 0 + 40 +1.0 +176 + 0 + 40 +1.0 +176 + 0 + 0 +VISUALSTYLE + 5 +54 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Realistic + 70 + 8 +177 + 3 +291 + 0 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 0 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 8 +420 + 7895160 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 13 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +182 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Shaded + 70 + 27 +177 + 3 +291 + 0 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 0 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 8 +420 + 7895160 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 5 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +181 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Shaded with edges + 70 + 26 +177 + 3 +291 + 0 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 5 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +17E +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Shades of Gray + 70 + 23 +177 + 3 +291 + 0 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 62 + 7 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +40.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +17F +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Sketchy + 70 + 24 +177 + 3 +291 + 0 + 70 + 58 + 90 + 1 +176 + 1 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 62 + 7 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +40.0 +176 + 1 + 90 + 11 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 6 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +17A +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Thicken + 70 + 13 +177 + 3 +291 + 1 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 12 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 5 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +175 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +Wireframe + 70 + 5 +177 + 3 +291 + 0 + 70 + 58 + 90 + 0 +176 + 1 + 90 + 2 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +0.6 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 4 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 257 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +VISUALSTYLE + 5 +180 +102 +{ACAD_REACTORS +330 +30 +102 +} +330 +30 +100 +AcDbVisualStyle + 2 +X-Ray + 70 + 25 +177 + 3 +291 + 0 + 70 + 58 + 90 + 2 +176 + 1 + 90 + 2 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +0.5 +176 + 1 + 40 +30.0 +176 + 1 + 62 + 7 +420 + 16777215 +176 + 1 + 90 + 1 +176 + 1 + 90 + 0 +176 + 1 + 62 + 7 +176 + 1 + 62 + 257 +176 + 1 + 90 + 1 +176 + 1 + 90 + 1 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 8 +176 + 1 + 62 + 7 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 1 +176 + 1 + 90 + 6 +176 + 1 + 90 + 2 +176 + 1 + 62 + 7 +176 + 1 + 90 + 3 +176 + 1 + 90 + 0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 13 +176 + 1 + 40 +0.0 +176 + 1 + 90 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 1 +176 + 1 +290 + 1 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 40 +0.0 +176 + 1 + 40 +1.0 +176 + 1 + 90 + 0 +176 + 1 + 62 + 18 +420 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 3 +176 + 1 + 62 + 5 +420 + 255 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 + 90 + 50 +176 + 1 +290 + 0 +176 + 1 + 90 + 50 +176 + 1 + 62 + 256 +176 + 0 + 40 +1.0 +176 + 0 + 90 + 2 +176 + 1 + 1 +strokes_ogs.tif +176 + 1 +290 + 0 +176 + 1 + 40 +1.0 +176 + 1 + 40 +1.0 +176 + 1 + 0 +DICTIONARYVAR + 5 +43 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +1:20 + 0 +DICTIONARYVAR + 5 +44 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +STANDARD + 0 +DICTIONARYVAR + 5 +45 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +STANDARD + 0 +DICTIONARYVAR + 5 +46 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +Imperial24 + 0 +DICTIONARYVAR + 5 +47 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +Imperial24 + 0 +DICTIONARYVAR + 5 +48 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +44 + 0 +DICTIONARYVAR + 5 +49 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +BYLAYER + 0 +DICTIONARYVAR + 5 +4A +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +ByLayer + 0 +DICTIONARYVAR + 5 +4B +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +1 + 0 +DICTIONARYVAR + 5 +4C +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +15 + 0 +DICTIONARYVAR + 5 +4D +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +0 + 0 +DICTIONARYVAR + 5 +4E +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +1.000000 + 0 +DICTIONARYVAR + 5 +4F +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +0 + 0 +DICTIONARYVAR + 5 +50 +102 +{ACAD_REACTORS +330 +42 +102 +} +330 +42 +100 +DictionaryVariables +280 + 0 + 1 +Left + 0 +XRECORD + 5 +1B0 +102 +{ACAD_REACTORS +330 +1A1 +102 +} +330 +1A1 +100 +AcDbXrecord +280 + 1 +1000 +ADE02C001 +1070 + 1 +1070 + 0 +1002 +{ +1002 +} +1002 +{ +1002 +} +1002 +{ +1002 +} +1000 + + 0 +XRECORD + 5 +1A4 +102 +{ACAD_REACTORS +330 +1A3 +102 +} +330 +1A3 +100 +AcDbXrecord +280 + 1 + 0 +DMSTYLIZEDENTITIESTABLE + 5 +19B +102 +{ACAD_REACTORS +330 +161 +102 +} +330 +161 +100 +AcMapDMStylizedEntitiesTable + 70 + 1 + 0 +MAPRESOURCEMANAGEROBJECT + 5 +167 +102 +{ACAD_REACTORS +330 +161 +102 +} +330 +161 +100 +AcMapResourceManager + 70 + 1 + 90 + 0 + 0 +DATATABLE + 5 +165 +102 +{ACAD_REACTORS +330 +161 +102 +} +330 +161 +100 +AcDbDataTable + 70 + 2 + 90 + 4 + 91 + 15 + 1 + + 92 + 3 + 2 +OriginalClassName + 3 +AcMapDMDefaultStyle + 3 +AcMapDMEntityStyle + 3 +AcMapDMRasterStyle + 3 +AcMapDMSEAnnotationStyle + 3 +AcMapDMSEHatchStyle + 3 +AcMapDMSETextStyle + 3 +AcMapDMThematicStyle + 3 +AcMapDMBaseElement + 3 +AcMapDMAttachedDwgsQueryElement + 3 +AcMapDMFeatureElement + 3 +AcMapDMLayerElement + 3 +AcMapDMCurrentDwgQueryElement + 3 +AcMapDMRasterElement + 3 +AcMapDMTopoElement + 3 +AcMapDMTopoQueryElement + 92 + 3 + 2 +ProxyDisplayName + 3 +Default + 3 +Entity + 3 +Raster + 3 +Annotation + 3 +Hatch + 3 +Text + 3 +Thematic + 3 +Map Base + 3 +Ade Query + 3 +Feature Class + 3 +Layer + 3 +Ade Project Query + 3 +Raster + 3 +Topology + 3 +Topology Query + 92 + 1 + 2 +CustomObjectType + 93 + 2 + 93 + 2 + 93 + 2 + 93 + 2 + 93 + 2 + 93 + 2 + 93 + 2 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 92 + 1 + 2 +StyleCoExistanceType + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 1 + 93 + 2 + 93 + 0 + 93 + 0 + 93 + 0 + 93 + 0 + 93 + 0 + 93 + 0 + 93 + 0 + 93 + 0 + 0 +DMMAPMANAGER + 5 +162 +102 +{ACAD_REACTORS +330 +161 +102 +} +330 +161 +100 +AcDbDictionary +281 + 1 + 3 +DisplayMap1 +350 +192 +100 +AcDbDictionaryWithDefault +340 +192 +100 +AcMapDMMapManager + 70 + 1 +340 +192 + 0 +DMSTYLELIBRARY + 5 +163 +102 +{ACAD_REACTORS +330 +161 +102 +} +330 +161 +100 +AcMapDMStyleLibrary + 70 + 1 + 70 + 1 +360 +164 + 0 +DICTIONARY + 5 +166 +102 +{ACAD_REACTORS +330 +161 +102 +} +330 +161 +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +74 +102 +{ACAD_REACTORS +330 +70 +102 +} +330 +70 +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +75 + 3 +*A2 +350 +85 + 0 +DICTIONARY + 5 +95 +102 +{ACAD_REACTORS +330 +94 +102 +} +330 +94 +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +96 + 3 +*A2 +350 +A3 + 0 +DICTIONARY + 5 +B3 +102 +{ACAD_REACTORS +330 +B2 +102 +} +330 +B2 +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +B4 + 3 +*A2 +350 +C2 + 0 +DICTIONARY + 5 +D3 +102 +{ACAD_REACTORS +330 +D2 +102 +} +330 +D2 +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +D4 + 3 +*A2 +350 +DF + 0 +DICTIONARY + 5 +ED +102 +{ACAD_REACTORS +330 +EC +102 +} +330 +EC +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +EE + 3 +*A2 +350 +F9 + 0 +DICTIONARY + 5 +107 +102 +{ACAD_REACTORS +330 +106 +102 +} +330 +106 +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +108 + 3 +*A2 +350 +113 + 0 +DICTIONARY + 5 +121 +102 +{ACAD_REACTORS +330 +120 +102 +} +330 +120 +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +122 + 3 +*A2 +350 +12E + 0 +DICTIONARY + 5 +13D +102 +{ACAD_REACTORS +330 +13C +102 +} +330 +13C +100 +AcDbDictionary +281 + 1 + 3 +*A1 +350 +13E + 3 +*A2 +350 +14D + 0 +DICTIONARY + 5 +16 +330 +14 +100 +AcDbDictionary +280 + 1 +281 + 1 + 0 +DICTIONARY + 5 +10 +330 +E +100 +AcDbDictionary +280 + 1 +281 + 1 + 3 +FBXASSET +360 +11 + 0 +DICTIONARY + 5 +1B3 +330 +194 +100 +AcDbDictionary +280 + 1 +281 + 1 + 3 +ACAD_ROUNDTRIP_2008_TABLESTYLE_CELLSTYLEMAP +360 +1B4 + 0 +DICTIONARY + 5 +53 +330 +52 +100 +AcDbDictionary +280 + 1 +281 + 1 + 3 +ACAD_ROUNDTRIP_2008_TABLESTYLE_CELLSTYLEMAP +360 +1B2 + 0 +DMMAP + 5 +192 +102 +{ACAD_REACTORS +330 +162 +330 +195 +102 +} +330 +162 +100 +AcMapDMMap + 70 + 1 + 1 +Default + 70 + 1 +330 +0 +100 +AcMapDMMap + 70 + 1 + 90 + 1 +360 +195 + 90 + 1 + 40 +1.797693134862314E+99 + 70 + 1 +100 +AcMapDMMap + 70 + 6 + 70 + 0 + 40 +1.797693134862314E+99 +290 + 1 +290 + 1 + 40 +1.0 + 40 +3.0 + 1 +Legend + 90 + 1 + 40 +1.797693134862314E+99 + 1 + + 70 + 0 + 40 +0.5 + 70 + 1 + 90 + 0 + 90 + 1 +360 +196 + 40 +8.162545102682873 +290 + 0 +290 + 1 + 40 +1.0 + 90 + 40 +290 + 0 +290 + 1 + 40 +-171.7807348484713 + 40 +-17.22753804290833 + 40 +116.6304781011532 + 40 +78.42728737915658 + 1 + +290 + 1 + 40 +45.0 + 40 +45.0 + 0 +DMSTYLECATEGORY + 5 +164 +102 +{ACAD_REACTORS +330 +163 +102 +} +330 +163 +100 +AcMapDMStyleCategory + 70 + 1 + 1 +Display Styles + 70 + 0 + 0 +ACDB_ALDIMOBJECTCONTEXTDATA_CLASS + 5 +75 +102 +{ACAD_REACTORS +330 +74 +102 +} +330 +74 +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D0 +293 + 0 + 10 +13.55439437635185 + 20 +65.26080749819752 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbAlignedDimensionObjectContextData + 11 +53.60000000000001 + 21 +95.19999999999999 + 31 +0.0 + 0 +ACDB_ALDIMOBJECTCONTEXTDATA_CLASS + 5 +85 +102 +{ACAD_REACTORS +330 +74 +102 +} +330 +74 +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D15 +293 + 0 + 10 +12.68788752703678 + 20 +66.41614996395095 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 1 + 71 + 0 +280 + 8 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbAlignedDimensionObjectContextData + 11 +53.60000000000001 + 21 +95.19999999999999 + 31 +0.0 + 0 +ACDB_ALDIMOBJECTCONTEXTDATA_CLASS + 5 +96 +102 +{ACAD_REACTORS +330 +95 +102 +} +330 +95 +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D1 +293 + 0 + 10 +50.0 + 20 +-19.92399062725306 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbAlignedDimensionObjectContextData + 11 +100.0 + 21 +-20.0 + 31 +0.0 + 0 +ACDB_ALDIMOBJECTCONTEXTDATA_CLASS + 5 +A3 +102 +{ACAD_REACTORS +330 +95 +102 +} +330 +95 +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D14 +293 + 0 + 10 +50.0 + 20 +-18.47981254506128 +294 + 1 +140 +0.0 +298 + 1 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 1 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbAlignedDimensionObjectContextData + 11 +100.0 + 21 +-20.0 + 31 +0.0 + 0 +ACDB_ANGDIMOBJECTCONTEXTDATA_CLASS + 5 +B4 +102 +{ACAD_REACTORS +330 +B3 +102 +} +330 +B3 +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D2 +293 + 0 + 10 +60.07210882242031 + 20 +20.02403627414009 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbAngularDimensionObjectContextData + 11 +61.79614645668088 + 21 +13.46240257548576 + 31 +0.0 + 0 +ACDB_ANGDIMOBJECTCONTEXTDATA_CLASS + 5 +C2 +102 +{ACAD_REACTORS +330 +B3 +102 +} +330 +B3 +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D13 +293 + 0 + 10 +61.44217644840627 + 20 +20.48072548280209 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 3 +292 + 0 + 71 + 0 +280 + 4 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbAngularDimensionObjectContextData + 11 +61.79614645668088 + 21 +13.46240257548576 + 31 +0.0 +1001 +ACAD +1000 +DSTYLE +1002 +{ +1070 + 289 +1070 + 3 +1002 +} + 0 +ACDB_DMDIMOBJECTCONTEXTDATA_CLASS + 5 +D4 +102 +{ACAD_REACTORS +330 +D3 +102 +} +330 +D3 +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D3 +293 + 0 + 10 +239.9999999999999 + 20 +29.99999999999999 +294 + 0 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbDiametricDimensionObjectContextData + 11 +223.9999999999999 + 21 +17.99999999999997 + 31 +0.0 + 12 +175.9999999999999 + 22 +-18.00000000000001 + 32 +0.0 + 0 +ACDB_DMDIMOBJECTCONTEXTDATA_CLASS + 5 +DF +102 +{ACAD_REACTORS +330 +D3 +102 +} +330 +D3 +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D12 +293 + 0 + 10 +239.9999999999999 + 20 +30.0 +294 + 0 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 2 +280 + 16 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbDiametricDimensionObjectContextData + 11 +223.9999999999999 + 21 +17.99999999999997 + 31 +0.0 + 12 +175.9999999999999 + 22 +-18.00000000000001 + 32 +0.0 +1001 +ACAD +1000 +DSTYLE +1002 +{ +1070 + 279 +1070 + 2 +1002 +} + 0 +ACDB_RADIMOBJECTCONTEXTDATA_CLASS + 5 +EE +102 +{ACAD_REACTORS +330 +ED +102 +} +330 +ED +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D4 +293 + 0 + 10 +360.0 + 20 +30.00000000000002 +294 + 0 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbRadialDimensionObjectContextData + 11 +335.7770876399966 + 21 +17.8885438199983 + 31 +0.0 + 0 +ACDB_RADIMOBJECTCONTEXTDATA_CLASS + 5 +F9 +102 +{ACAD_REACTORS +330 +ED +102 +} +330 +ED +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D11 +293 + 0 + 10 +360.0 + 20 +30.00000000000002 +294 + 0 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbRadialDimensionObjectContextData + 11 +335.7770876399966 + 21 +17.8885438199983 + 31 +0.0 + 0 +ACDB_ORDDIMOBJECTCONTEXTDATA_CLASS + 5 +108 +102 +{ACAD_REACTORS +330 +107 +102 +} +330 +107 +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D5 +293 + 0 + 10 +119.9239906272531 + 20 +-40.21674477289119 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbOrdinateDimensionObjectContextData + 11 +0.0 + 21 +0.0 + 31 +0.0 + 12 +120.0000000000001 + 22 +-40.00000000000002 + 32 +0.0 + 0 +ACDB_ORDDIMOBJECTCONTEXTDATA_CLASS + 5 +113 +102 +{ACAD_REACTORS +330 +107 +102 +} +330 +107 +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D10 +293 + 0 + 10 +118.4798125450613 + 20 +-44.33489545782269 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbOrdinateDimensionObjectContextData + 11 +0.0 + 21 +0.0 + 31 +0.0 + 12 +120.0000000000001 + 22 +-40.00000000000002 + 32 +0.0 + 0 +ACDB_RADIMLRGOBJECTCONTEXTDATA_CLASS + 5 +122 +102 +{ACAD_REACTORS +330 +121 +102 +} +330 +121 +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D6 +293 + 0 + 10 +319.8451511830173 + 20 +79.38060473206892 +294 + 0 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbRadialDimensionObjectContextData + 11 +309.7014250014534 + 21 +38.80570000581324 + 31 +0.0 +100 +AcDbRadialDimensionLargeObjectContextData + 12 +300.0 + 22 +100.0000000000001 + 32 +0.0 + 13 +305.2208732372958 + 23 +70.88349294918329 + 33 +0.0 + 0 +ACDB_RADIMLRGOBJECTCONTEXTDATA_CLASS + 5 +12E +102 +{ACAD_REACTORS +330 +121 +102 +} +330 +121 +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D9 +293 + 0 + 10 +319.8451511830173 + 20 +79.38060473206892 +294 + 0 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbRadialDimensionObjectContextData + 11 +309.7014250014534 + 21 +38.80570000581324 + 31 +0.0 +100 +AcDbRadialDimensionLargeObjectContextData + 12 +300.0 + 22 +100.0000000000001 + 32 +0.0 + 13 +305.2208732372958 + 23 +70.88349294918329 + 33 +0.0 + 0 +ACDB_ANGDIMOBJECTCONTEXTDATA_CLASS + 5 +13E +102 +{ACAD_REACTORS +330 +13D +102 +} +330 +13D +100 +AcDbObjectContextData + 70 + 4 +290 + 1 +100 +AcDbAnnotScaleObjectContextData +340 +58 +100 +AcDbDimensionObjectContextData + 2 +*D7 +293 + 0 + 10 +450.0 + 20 +90.07600937274693 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 0 +297 + 0 +100 +AcDbAngularDimensionObjectContextData + 11 +495.0 + 21 +77.94228634059948 + 31 +0.0 + 0 +ACDB_ANGDIMOBJECTCONTEXTDATA_CLASS + 5 +14D +102 +{ACAD_REACTORS +330 +13D +102 +} +330 +13D +100 +AcDbObjectContextData + 70 + 4 +290 + 0 +100 +AcDbAnnotScaleObjectContextData +340 +56 +100 +AcDbDimensionObjectContextData + 2 +*D8 +293 + 0 + 10 +450.0 + 20 +91.52018745493872 +294 + 1 +140 +0.0 +298 + 0 +291 + 0 + 70 + 0 +292 + 0 + 71 + 0 +280 + 0 +295 + 0 +296 + 1 +297 + 0 +100 +AcDbAngularDimensionObjectContextData + 11 +495.0 + 21 +77.94228634059948 + 31 +0.0 + 0 +XRECORD + 5 +11 +102 +{ACAD_REACTORS +330 +10 +102 +} +330 +10 +100 +AcDbXrecord +280 + 1 + 70 + 1 + 90 +2070111275 + 71 + 1 + 1 +1726B2AA-BEA8-4E36-92B1-C4E9E05F2AAB +311 +504B03041400060808003D7AEE4A3D73C5AC26010000A80100004F00000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F446566696E6974696F6E4974657261746F7250726F706572746965732E62696E3590BD4EC33010C70D038881018907C8C0C0 +311 +62D2D8693E90108A6B1B31B0C003809BBA6D2437ADEA447C4C1DFB14A88FD6F76080736D2C9D7F77FF3BDF9DBC4508FD86032E3A02DBC07D0E7CD0AD5E37F54B3DD70B854E9C62966365AEC09392F042528E8928339C1605C56CC4723C4C04CDB9AC722946C750770AC6F554F5A6732D9F1A5B6B6354AB97BDBD0021CC8816 +311 +AA8359CADCB8476708396CC0D07FC2BA4E33BF915BF21D4C4D56B7F7CA5ADDDDC59412C9B2A2C4192114A73CE198D19C622E13C648252A2958FCACEDB25FD7DAC6494E32902BCC4455E054D00C97842578948A520C8612522CEEE6BDFB9A4BB7C6B8558D89077158E175D5CE40467BFC650FBCF68C7E3E0FF16E12D878EE1F +311 +3F0E7A340E9C7AEEBB107FFBBAB7C0DDCEC7D1D6F30F504B03041400060808003D7AEE4AB71BFCCC12010000300200004500000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F496E7374616E636550726F706572746965732E62696EEB606060F80F +311 +0540260323103700495E20ED9E9A975A94991C9C9C919A9BA802143034373273327274D4757275B4D035713536D3B5347232D4753671B574353075034A39B1233482B4181B9AB8B81A995AEA9A9A9818EB9A981919EA3AB939B9E81A9A1B1B389B393B5B3ABA5A029581ED65D08CE93FF455E38A3D9066028A3500318C0FA6 +311 +817C20F8600F51CDC020E0C020001602132A0E08365616502304C01958950105E10A3E3003790D400CD5FA004AF300853001281C113270433014A6EC74C0104311C0D40A325A0349CD077B16200F145B7C401C5C9E58929CA1EB54949897929A0292E107623028CA4F294D2E89CF4C61F8CF01F10A58182E7FE71E2C34A11A +311 +20145C1EAA3F2F31371528D30000504B03041400060808003D7AEE4A88E57597CD3F0000904100006B00000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533362D393242312D433445394530354632414142 +311 +2F7468756D626E61696C2F302F67656E657269635F706E67355B773CD4FF1FBF73E71C0E67EF7576C9DEF3EC995D46C6A1EC5046649CBDF79E192942562825CE965D8A868C2842C8DE9FDFD1F7E7F138F7078FCF6B3F5FCFD7EBFDFEC41B196891913091804020321D6D7513FC77D8C5070EC3FFEE51B21F068154CC75D455 +311 +CC028A3EA68992803810109FDDE5D5CEC5521B28A1B422BF80741695D5B529DA1C2D058AD2388BDA9F476F7C2CADC939304173DBF48A452BF8A700C0C70C3D121C1AA15A5270C22E2B9E28763651F7DA0D12CE11077B2ECD0D560B5E3DE2368B1E65C5E1C87384A4CC5F2D9FB8BE39E9B77C753783A37998F5CEF1B4FEE553 +311 +463245C9712270880DF339E0B882A6BA713D865F6F384C842D440934EF264F4A304F8DE4EEFA1E2BF5E2D91F5ADD9613D6E31DDB33ED31DB1D7916CAF091286EA3D31B9CFECB78BB0060245B943C4C040119FF780A74DD308BA9D028678674EB5504ECF284EE814DAED07440BAF649BE4B55DA2890F4D73E2D489A59EE54FD +311 +B0A85B59F2F52C4858A02705C4815A792B0C4A996A2023ECB195073E115E3E3647F64243A8338F2000BCB58DA5692C7E7C3D413F963FEDF3CF00FBB0F831A9D51360FECA43819F527B89FA260EB63C5DA676830AA3626A3693B23F1A17A0DFFCE3EE164E31B5365EDF0C89EF64D8E38AB0BA9A1117D7C3A0EC44F44F402309 +311 +5E80BA610DE336D0F99593A88BFC5306E72DF98F7FD132ED95878DF2964EA7E43071359FD83B2BDF9C6B5BDF768F0F3C0FC351199526F53D9460A34511F75D1B63E488178569472C91A94497807A3FD4A390C6558F26D416365C3656DE6E769DA9D8FC73774E1709812842DD8EF96AED1960F79D21B395CDDFE6F92CB4EB96 +311 +2A4668C76990E2EF29798C8941D67DA50F4A5F947872460F1F6FDB794E8A29FD7AC0C0D6E0714339CE258FB17AE8535221E3464A10F29A0BE5EEECEFC5675926A89F49EB14E5711894F6586DB5754EB4D5DA9C6437EBA57DB97A24F8B8A8DB295FFD73266CF57E82694DF5166DDF93573AFE9C9C5EB05F8FEF8E89E0A8917B +311 +0B3724DB7BEBB53252581EFC3E38166BA131A3A5B42FE2AD00A769BBFA4458D2DE7822F8C4F63D0B8E21B285AE13325F3410A435C648C9D91FDEB109E73CEDA7BC796961AF79175E1A3CE4C5E8B0D009B0C9E29EFD98BC8E97554E3D8D838424E270F7685226E5AC268E021EDE3522D583E8F20C0BDE2959EF5BBFEEDAB32F +311 +E9A3ED0E39F211A86AA9DEBFC68182EDED132AB8BB794442D2D7EA9F4D8BFF52BC4D55BB165124EE90A42D101E67BBBB292FFB4F66DE5BF2300E44546757DA7CEF8932BFC9F92A0707953491A55C5A5ACEDABD9FA1AEA01BAC3DC5A24E22E7C47213493996EFBC369D086ED9397C9BA6C7F9855C7BD5E15BFC8C53DB3D2A4C +311 +5901A57ACD2FBDF4D1676E95338B2C2BCE65FDA4C64F5FEFDC7D9CA5AA1FCF11E193F2FDABF0F4916BF13FB916A224380E6462A9815F2300DC49CC4DF8D055CEF76462F4B7A7ACA7365C2707891370BC1D0AA6203C44CCAB47084FEC057F27FFCD57F89B8B9852459798A675C66BD7CE572CAF71FF7E6D7ECF7BF62CAB7B3F +311 +1107DCD04DC55A3F9961700286141C94A4F771FEB2BCDA2DDEE0CB0BAE402FF1DB0100F8E475DFFFC074DDB4498AB7D186A7654DDB1CB680D703D47CEB4E72F8CB13D86A4AA68116E26387B93FD6738779B34FEDB8E517EDF2BAF28D9BE93A1D3A164320E07D42E514725C29515FF361754213EDE06B079A2E99EB1CA82A02 +311 +019EADEEB87B9AF005BD04BBCECB14AACD6B24EFBE482194A97DD32400685FF1FEF1544D1B14EFB1E7F08E6D2CD2AF52DE757F35F5E62332EE88EA26417AD5C3E0E319D68F25414E6D672BD4522B278C57D18F0113921C71D96527E091F00CEB15AE23D24667369BD3CEFA10C7549D930890BA4366F4EBC55F01F746109F98 +311 +ABCDD5A3EE06619C6AD78AB33E48E4C9BD21BF7478B385EC05C4F8987E2C1BFA0A60EBEDAA0AE0B015C2720ECE180FB6F2DEFBEB822D96338C9B63822183BBF52FB664A7AE57003AABCE512BF77E7E31BDA2E739AC4C131657DDFE5225E900FB56CC01CDE808FA4A9C9DA16AC679656BB64796283D89812B8F91136AE18EE5 +311 +EBBAF47A73BE3C153EB5D4CD491B4BFB4FE690A4CF9ABA08C0D8AA64220C438C96EA6CA80B58FE5761E697B3A292EB9FBDA502FA863E49FE694ADDFFF9D62C520BDD1A22014147A0903A05B9B729F54272EF83931FEEC057BFF8C8055C279EAC5C0011804828734024F6CFA319C9CC6867C727184465CD9BB7ED386AFFD96B +311 +294A85E3802FAE47244602D8273615952B98C4F23F7EAE4463D9D638EEE374A20959E0FE519C4CFB0FBFE5DD80C2AA3F9DB961D1BDE57E67C9BB47B77FDD74EE3094F5DBBE3EBC1FBDB7393F5116263009E2EFE1AF9A221B2AE234C902BF70E9F280ED23E73908361565964333A3F01D0B006AF32FB21AA1316744B489D5A1 +311 +98E85299241735BE559675130955C902A8ED761FC0DBE337A9F91919AF5CE32AF902ADF9AB2615E00E8A94586BA1A90839B61832FF1BFB76D2EFADE6DB1BA04CCAD3DA649E7E2A0219F592E22F2AD54847EEA8E42271011E9074F8F3DD9FBA6CDF88FFC9FC7E811AD1CA8F4557CE4AFC7BBEDCF0114A48689F20A584520C74 +311 +DA8127124B767E2FB91A6B186246727BE63C9445BAE02088D58B8EFDC0F1072407CD9B89A13F6303B4A928F593AD489B58E6869325790ADC406036C591729FE14021F94B7796BB11848D463536A092A26F077F94639F7CEEE2A724C66EC885F4A24F8B8A0AC321F8FD149BA529460FAA34E4663C02D41FDF80BCA222D42F0A +311 +BFF8490F4FA74C673807C2051D8E467B47B616072860069A9A303ABA9B319D58404461401B0A0F9BE5E1E151C14BC33B32ABE03DDE91708DFAEB15E7C0D593C86BB7D7EC17BE56E57975F077BFD5525F3A1A361D584A75E08CF29FC7B44B2B3085014F3A5A14517377C3F6B8F37E59514C3B12F8AACDEDDA4DFC49BE571010 +311 +1534CD6FB6DE15B50F11A09472AABA727F0C49D9CB906B158030065F48EB3625E010CEC1D511D0053474B11E6A4F01C0D6EF1BED3A944FDEC70E0DDCDC756C86545910E0DAE3BE3AA9A404574C2995CD5CAF9549226BC94B4F4F0F5F9086F418F6B32346464610D8BC7391D3C3A3CCF04CFC1FB2A3A3170E369511868F14EE +311 +A4E5E438CC73DB1F3248AA94B6B2FF33B222FF3D1E7BF146CAD229770158063BCE4F5391964DF62F7E52A7BE0EEA829C9E13C4C147D6F93FE59E111DAB5E4594D6F3CD1AD375EE039930EFE5AF2BB2D41C83C7DABD8C659BAFBE8FAB392CA634F3D15C1D945DB4588D2B0824A4EC928AAC565557CA2F0DAA588CA5BE30B3A8 +311 +004F7BF005E96CBF891D7F18112A3619FDF530DC8AA64954258E1811789B5189B49B4EB53F5F89F7D37523894F3EB57B373763C91E502F52CC99EB7BDDF933FC324C4FFC2561DFFA7339FAFDD0A4BFBE6D3A8FF9ED1FC49898139864602458968913181918F719CCF5A68B2EE9C1FF050E34E63107003B8120FA982C21B30A +311 +874E4F5E4B38D5A8290C4B8D9BE48A0BE31EA995BDC37AC6DE90BA17D259DEF53605D55922FEB1855D915B659FBEB350EE0DFB2F9F766E1C84CC8964B75D08667445EAB966015F63A18A9E0A325C1B3DC628C0D3CB60F767FA93CC65F25458BD23C3B312716E5F40E5858AC06EBA9AB14F95DB99147B832069201541D09A0D +311 +856E12C629CF32683760EE855537D1901988D5EBC156676C7DD51D7207CF3267AF1D76C2AD867315E44618015313A98B19941C696FED3461CAA9B5415F918556D7802EDACEFD6B2E45853A9735DFC9DBF7E440F9319F8B6805A973BCCDC76C069A9144E51F187EDCC12FC31B5DEAAF0A76137199B106EC84ADA6B6EE697D4E +311 +6A96E007417F4E880E8B1E89B4BCE9D81EF611E2ED8F0C1A6C25F620FAFAAB656A7C42D464880E8AFAB0E0FC8EA38CA9A8AEEAEE3E4648BA4680270A89743CDD3E6C6CBF34B7B640EFA285ABC7EA0A1E28F3CC963D114A80123EE66D0AF7D5380EB41909B605AB4765B709B808590458157E69BA1378FEC4B48CAF6A730E3B +311 +E3C5EF2DD1A5EAF94B1DDE50CEB55DDFD7EE9A16747FA4B223359FB1FCC69E1194892BABB93A1D5C4E807AC0EA2E30180811E77A2291F6E7F7FAA5E0F60215BC6024E48DE436C511F0628B8CA189982D4D83CCA17D305A7E923C37941CFDFDCD93ACAF735EF5F0E61BBD3E6C41A59B242790D3A3BAC7064DCD89AA2E37214E +311 +C839820949D5CAA31E240D2948E48F690298974A96BA9A8432BC90F5071D6537C9666EA4564796B7AFFFA548077BEEA75F63083435074BA182F3699557397F71548383FDA8CA34D99B981E72835ECDA5DDDC151315173D05A21F651AAF8524E88209E2E7A737ED1045800CE3390050BCFB8BE588DF111617874C62575039CA +311 +588EEFDEFEFE319B739DE2EDC147092A262611E159650E787917885EF08F1F51B8F2D8A71D60B58B190FEF9541A1BE94BC19FEA50784DA1419FE4B0FB51B8D36B5FF8E1286FD8AF42B91E59A5F1CC4D60174E764045FD8C57B18209B217F89A62252BD20BE0E63E12B8E8F029FE19E0E8D0870F58721A557082250D5424F79 +311 +09369376E41666C0EE77CD87CB283F661D5FB6D0DA023CB67320A20DC7BE661E9E1BFE5463085C557DA1FF9BE835733766C3D139F2CF9E2208873AD0EB4814DE9E6BF2CAF81614D4D43906ED1C05A3265CABBFE192770FC2F64F940CB6B17B5F166F133C9AEEAD2641807C3E69515171DD74C4A85311513D6EC9530B3B601B +311 +6BA3EBFF3B5C3D2F7D69765141E345F75433953D013ADDF8F6AA7DC3F4A7563FE5C7D367937E2900D7EAD0F9FC9A4A1A9B5BDF1FEF08532660C01C5D6DE22199AA774383E306DAB87E301ABB7D213887F9869D4942D0D71E94B27EDF216A318BD488D45DF84529EF575AF3C3938C0AAA0215B01994153E2054A0B894DA5C78 +311 +4980633AAD48576957CF4AEAEAFADC7C587368551C73DBE1E57C58AC713303A8BFC258BD04B51724459FF9E24C03A81E0E0679CEB9B348C0DC998FEB9C6D6EFA77D247CC8F92D3848599ED89A23218D0636465AD1CEEB7DC140D7426F83491C4E8E8281C2FB35240918FFECE880CA2A42D70E8B20B34DF5221C7DB0CF90ECC +311 +B9C745B37A47E9B716FF7A97B7A252C1F3814DC7B1E1A7F72B8751D524EF7CAE45B0C73B3BE7D08ED39781B380DDFC9AA4A4749C9493DB5BAD07A6EA6C092FBD0E85DB004DF06B8C9D3089C5320187E4CE1846158C7EA7DDAE8AD9AFFF21AA3A1026BB06A259845E8232DEE62811A4DA9CEAB3A4887320B76E2AE5615EAB7B +311 +DF738CBDD01F1E64064BC13D9EC53576751CA8CACD58B3CEB0E6CA585BD089B2A59B9FC101403387AADBDBE70586B93D64BF3AD4453EF9C3B6A93817E8346991798601344F79A83C51F3D50F7BF2683A3605F51A72449489E0EE6169C749C60B1035651103AEE24D24E51A759C840DD3FC6504DA6FE107510E447B43461C79 +311 +D22970B75DE5E1BD3007D1AFC619A4A4A69B5626AF5E9B8374E20677F77E2F8E73DF868E8BEC7EBA7B6A75EEF7205E1AB9486CB642741D6E03FF13FCCEDB1C4AC129886268B01CDF4B124AF797CF784F3C6DEC97A17D4B558070A0F1C04DA4FF9BDBF34159AEFE958479824B3F58EBD1E19088A2AFDE501B6F2052368A3FD0 +311 +E650A179EFAB1561E1D27527077136E652676C3670F42DCB3352F48F6BC86D2C3B0051727E41A0A4DC6CE2BE9D47F5CAA2935A3C0539AF080665997194ABECD723B08129CC5B6B21D2E7D54CD4D12610E7E9FB8306771A89576C7A982002775ED44F3F744CF21EEBB27A631671D921938AAEE3EB0D19CD1E083727F607823E +311 +817BF599DCED5394E37EEBC2E88782E638920CA641EF74D4CDFCDEF06F4278ECB18E31E7E9876F2D783C8BF429C75395F57E98C2B495A3DA077EB3AC3C158608B6B57FE3EA48484D9A480096FCEE0DE880950E7E5A5714131C5C41AAA29FD19AE81AC145FA1B9E85C7169A5AB837B213493DF7E9AA4B5FBF9CDEB36EBD21EF +311 +1181B7CF7366AB2A951D01E37AF4EB835D1BE8C117CFBE7625D2EBCDB0B20C5A4B45CED0BCDE3EE8DA9DA78C2E21DEAAFFD1197E463C33CC6A80FB785669B856EFF7A8CCCA2841B5A5369173320B1170C368B2748D81062EA0296E5E2D95D7667A4A46D98A6FF475C76AAB0A8C8D45E226FAFC9FE0133EA9F60111F99CA81A +311 +CE03A1D14C89E8B0D69079C91464FA1D99E651D0EAF606F345C42A8A1AA9BA391031582B96D5B4176A77AADB01F12FD298A75DEEBB2C82F0B8650E880722F8DDD90C56A7A058E5FDEF1AC93545BA27367C76F5D8F9690CB6F8DAE141EDD7971F0FB548A2AE435F224D6E5954CA155512F7332029D5A1230854B79F258EC5FF +311 +DB09F5F6343A8EB193229B7F7E79B3E48F8E183860B38C8269D1B53738FF1E0BACF6AADA6D4869E9DE708E79B5DADCC09B1BC76F649731CFAF59A83FD8BAE4C749D66FA87A3810D4D62FFE826FA400C0F325D2BC0F3CA35F5847B82A2ACC7961745C823E4D863D54460AF792CE6F79B5C7A33B25C3AEA6CDE15A919B6367D7 +311 +81E9D44F61BF828672594A3E1D9EDD6DF179A81F27EAC05B936575D73E05C209DDD2115CFB705DF9BBF3C911589B3640735E9E618FC9C36A7326C82B2205E244C55D56FDB9D5DC93CE958D4BF8174D2B3DA54CB3C09F2C2A6DFF73D23B2197355761AD421EC581284A7EF8DE2CE3497C0C804D49B41DFD6A7360844E5BEFFB +311 +495CFCDB97A4595911CDBA623274CE852B5E278AF0BEB1770E9D37CD977B2766A7DED0E94B840E1E95E8C77DE5CB6354AF7871427C28ADBEEF7BA0A28E19DE9ACD75F379FE373EAE6DDDAE3834692F9C2B03B4FCA8FCA9ABE8EEEBEBB725B88A7C90941E850682CA04061A7DF0CE82ED49BFAFFD42C309818FAE14755CAAE7 +311 +8B0F73243ECCCAD7DF58FCB9CDFAE7A4D3E6671A613639130D056C8383C8F4FCF4808C331E544DD26E4755D09EA05253B3049A1E32587CC25D72E073E2CD96D04A71BBCB0038FEC3F53D254199C3CDC67CD66640269431860225392B2CB3ECD0B4498AAE78BA1994246AE1EE78F2EDFB79F6C6CB6B5E279C4D373FBC21CB26 +311 +98C9ACBBEB6A4BB6B39E16AB0E421CCBFC7654D81ED86022F9BB522146F9F4772E2D0E4FA70120C95A85A417BF5078B232F4754363EB1750B2AC2148EB46B1EA72AD9B4D23ADCBC589D70106C3EC2ABEEED84DFE28E718C6C83502C28E9FDD3B09DA0A6F6F7B72B706A31C9E2A331EE70DA56A083D2177BFD63C55B11D8D94 +311 +9FAFCF90B23A1E24B9B5018B9A17E99F2890EC302F330FE7936E2C23F0D237EC8916F5166AB84B62A21BB0B950146962D754F92CC2C8877BED8FC7B7B25726DD19545392570B3A2C66B58F027BEF412E552D12A588C4A3CC5C3193FCB3485EFFF700D0B9EEC077754A33DC1FB9E25AF814628F91DDFECB667D3F7690803F38 +311 +D13F8AE39353523BB08FC6760144AFA2CE28236F65EDBB3D5F59A57D612B99E3D7E5F8A851666BF9B5A6658EFC96EEB37A378ABB0EB3FDDF1303AF32C41BD1C8560745D7BD81066B0D6313359CF488563D3BA3F8E2185D1FAA3EBA1397C9D0E3DD232180A4DCADB6AF25DB3AC3CC26589C8239E66E81547BC7E2F328F98847 +311 +64CA37EB5DBE390DFC03A5246B456A7C3684C4377D0EEF9A350680FA9A077E8B2957B489E09AB1AD41316944C2254B5984830F691C93CAA767EECF976FA5647538830B5E63236A6F0365C1E0F3E327F24FE35EDDBD7360B0F7374837409F7A6311BECC12B7026D4FA1AE204FA3F5FE904B1F420C7A9044AE58E403E7C4848E +311 +7DA25DC9EDAC79DD756FAA26B9EA83ACE8A214A2612A22AFA19DAE7DBAC5F8CDE1292A485F98DAC731A29FE01A3966E96BCB9FB5A4C8830E1B9AF0C54B87FBE221A097036E5D28D1F869F810C06A9BB0AC0612F69AEBAF893C6E7AD59460208BAB3A6B09CA4F6BDDAB8CF0D1F84B72487498395A70DCD754796E271099A567 +311 +A55CF242BBED64696378BB6CF25EF0FCDB589F3A82F1116D010A9AB9FA4358809EE0C973A2B4229BAA876A70B03AB49CBA7FBE71DB1832FFD32F9BF2C8E98075CACAFB36921235D0F552B6A67E94E094097A5D7DF681B40623DA535ADA7A9AD8E9A62407D7C9A5AE51D62A17A83A23728DE86BB56115804D3F2675F2C6B875 +311 +41A0FC497BF0CA86420F558C9859EBE9FAD62747E323E3AADC03FDCDDFE7407B09DA90B44E3185723C5381EAEF589765E2F7A099BA46695BAD60CEF3491B8A171F5F56444F7CE9455312C3498C74173AEA0214EC029833B34B650CBB5A5508D5091665309642791708ECF27CF11DB3D797D6CFE474453BD4BFADB768094B21 +311 +47814D81C7CBAF8228DCAF5564B5891F5EF6A860EB2E323CB82AB868D8EA048880D63B0DE5341F1B3D53297B928A11E15F25E5148990F710747C3509ED14909C552EBE3973246FCE55D00564D8310BC3EBC5085D08D806799B77D6C57FDF5E55837F9E934F09C03C729271B64F12D0A9F9E938107CAF7BF1EC8CF1BB9B4236 +311 +3D33070F4CACB425E605DDB0A34A12BB936DE286B9887A4E2E558F0886498BFF49C311D96C277CF6A9FC0D75AD0ECC5226D7E0E3572012E56D7FDE4410CEC7A6D0F997F225A2055B8B92E3956E875AE4993CB3D4C9063AF98FD45E1BAB165640A43439A2C9181A7C355B297C0A0CCFFAE19B6FA3C2BA238A885FBC98B8F6DA +311 +F4B94B97304878DF4D8BFF6A4AEDF79E90ED4E68F00CD7299F4E0D83E35601AF0CBEF18C1E870E74758FA91F5131075004DB3CFCF09AE1DABC11957FD9808C92E8A678CE9DBE40DDA0E0D1D505AAFBCBA208232BFB6FB3704E8985E61ADE53DE74152F5C7DE74B1DEDCD37DC3E8FB30D7EBA8B9EFFC3606BF90B8D438279CB +311 +7709FC7C00D327F41817EEA71CD9E49FE9C8C9A81C70915D9F0F15958EC5AA4C3BBFAF46986B90F188BDB24B89DE3C6F29EC3E6F66D84856EE96A35E657CA41B41EFD8DBFA16AD6E91439EA92B40B21C053249F51AE50EB0B24C770FFAF40DF12C8436366819C48E36547D4E7B72FE2BBF10828583057814A668BDF183F6B2 +311 +E58DFB151103E56303E63D00F39C27C4661605DB644D19175D02E3F0241E8FC3458D17AAAADB117CE9EEA690D0DB01E634878C04C2C9310B69FCAB831151E3AD293BEF6E46B0ABCECB50F5939B39C613D41E81FAE8D34CAE3C4566B761D37185FE864AC32FCB71AEF39DAA5BAB88609BB980CD2F07A8F941653B529567726E +311 +2B2A58D079DD57E1E5E31426CF184A04D7B03B985F276D47406AF276FE32E3328BA866EB1FD39BEF57863A317634AA5FE467409B3873F5069EF0D71AF00AD189D17BA1974ECDB2D623E9E14086386A6AAB5807825C37B015BFE892AFD154920EC437EB945D958E18622E1E0FE2FA06557CE4BB9BBD05A2D40E774DA2B65473 +311 +791E314B7D6C886820B2FCFE4AA272B3B8B15E7344A145E8AF919B5454D89D36D413E87211186BF6F645E5EC94DCCE8E1C799022E4EC9BC86D7A0F2AFB1FC4B2CE48F84B3F49ECE65BAD24CADCC07EFF39AB06BE9E6B27A059938EA7208838455383DDBCFCD967DDE4650EF92A14FF933C975DB20BD7FEEBC4487576485963 +311 +D757D16340D9599FEB362F775F38CA4185A86F74B629C81B133644A3B18FD056E997E0734F9BE26D11C0AAFC669E2AC9FC41D281F8B2E1C35AE27BCE05F3DA295488096688719D974DF1B5D182DB7792288697F8763CFB43562C1CDEE11C4AABD5C22B6E33F6F389CFC3477C5919DC0D533F8A7A1846B7ADB60ABDC26C43D0 +311 +CFC8910B7C15EE22675BD02702ED39DB77A87A192CA7A543E6205B979B9F5A6B512A3CA799E198B2D5797C0AF8D1D872CA455D4D431A7879D4DD76110B5FF7F4176B54477A9EB5E4A5D11214FDA585F0382751D8E2582BA5D2075053298FEB8F1537304E32CB22C2E2B693DC0A760B1DFCA751BDC445AFDCBB12820ED85F6D +311 +5298933C7EF1211E9E057684056CDA3FEF3BB6052355E162DF27C59E965A8DDB7F9437EB72CDDA3A090DE65A5B2F50326247B7EC67B73ACC7047FC9B353D6EE990F772207C6EC52B39DC2F3C0754821DAB559F7C1809C7D0DD5B0499D0442E0A6E391B3D5DB46D11E731E6794EC77523FBDAA242ED67F9FA1FB684255671F6 +311 +3C9BCFE6E7E896D51001A6113EF2298BA494E0B0980D1FA5B9D60796554C02256B1E0345FB7C6186B6A19D2DCA270416262845FA4F42DB5D9B0A0B6F07FCA2E56C242522E001E0CEB734AF310FC8BFB57E38E03AACE93B3326A2197891E68452B26A6307FDABFEE0223D3C03C3C73D2D5BC054C8311F12004CAE488E5C2928 +311 +7F8156A2CAA4AD0D7BE6B97264E4139F437CE73ADF52E5134C52B819777C18B323A5924E4F67AFD13B1CDC3A61793A79FAF5C0CEB718E441071E797C6DCCB9EFCF7E4A1E9F3DF3C2F9C9E8294E9586868788C972857FB0556D7A26941A7EEB9B580AD78EC40B85DC906B4120A563CA3B87797ED612D3B035962BB75F6515A3 +311 +93F4CB057F564CDF54F2E5A024514E6A1F901DB80C7E70B1FC05E356A7E72FABF4775B3E3FB8F70A2A8E7B6A94542BB304157978CCC999E707A29CC795E67A59CC546A5D47B368954CA7B6E75EC9E1F6FC73CE3B4F71F8CB1A6B15923D61EAAEB14626AAA9D04A26EB9C66CD1BE86CA31A5D8703DE04CD1EBCE4F5CCB62F93 +311 +909AD11E55799A781A956ED27D3E738F4538CB04ADABC7DAEF20BC35BA6A90BCC1DC61BA05F20D8832FFB9BC9B15E2DB838A0AFBB7E0C82A562127E0446894F427794C3037186F622BF24DE1B00AA10A23BFA47B2A180D6DA5AD6F7326FEB8C100C2D7142EB2A0B2A9D1008A09BA343D70E054AB69E2A7EDB783CBD92537FA +311 +F4A44D1296AC2792729104B3276C276C951D0CF3CF3F1E6B017720A7B8505A5FF9589365979678C36F1BAF759EAF57C60F1FF5FE9DCF5F8E764FF25A2D06CF25117A8FED51884C13B2B750DB0E4F5E3D64124FCF11F0AD8F1CC3B3DA4B38ED22BF1CB2585EE6BFEBBA63FBFD44F9537B9B37BDD6B358720F98A8125AED4349 +311 +CCFEB0E135F19E208A04CA7E2B10711185D72E381F448036EF57C9F874E54A971CA37A639E986AC0226DC5AA00D5B14FEA9192234B3DE617814FC221C92F33F1D392F297731B567FD98FD91D989379AF567A65B8ADF177591DB90FBEA99BF4ED11B16E50A0AB1DBF1156D034BBE04D69D08F939D8E92BF525A1DDAFF80ECBC +311 +95D8F45D3FDD289FD23F7D8B452F8EBB34EC082DEEE68E18EDDF072060FAD2AB1504DD4879BF67099FC58F6E9DEFBF645D16334A3E9A127C6F1AF6132CF287E433B43A0EE93812B4AA0B194E03EE3FF91EF9C6ED0074A2F6EB18A96A7C9890B1FCDA1F2095E944DB911ED54C60DF16FB74C8CE9BDC7C54C4F14078CF73C2FF +311 +F1F1BBCCD7CF64F71407EA1683DBC410D226627F66E509060D5C0AFD611DE681D509DB8DD12FB1265B03C6F5C55FF4C30BD94187FF692B4F152982502F193098A789526A038AB529A52BC0DC4FB6285CBAC10A8B8DB75DCD9BDABBB221E27D1F8DA9F5EE5926A1C8AA9BA9BC09239C6A7F12914EA3B59EFF7AAEC3FFCCE56F +311 +1C5C2980AD072793527565E774F4631DB178CEEC691B1F367BEA0CF2907E1D6B854D527065979DDEF002D8B2ACEA7CE21AE297737EDE1D45B31233B73FEA35BCFDD0B09721FDBAF6432681F2FD10C3E9EFB246FD6E854B344ACC9D8A0EEF876A7314B49B6A499FC650B099D650DBFEC7638ADFD245E2B7839DA5AC5F7D5623 +311 +FC0ECEEC8653FC9B2BDD1AD4D00A566FBA7186DD3C9CD26512551637498CADB950FEC71ADB9A5524285DE328B8EC7EC75D2345AEC6A27C42AF6D9B92155315C80DBFCABBB7DA0FAE425C4FB896B15C9BFAC4FE67DF5A23D30C93DA4D8F36FB15D100050DA6062E22AEC5F4DAC4F6FEEF97AE05DE63C6D38F9E068A20BC5E3B +311 +6BCD75320C5090924E7F93640A7735B1A43451C2D91EE62C6238B797349E59890AF3A520DF4A87095F3ABEC2E61F0593416CE46CDCBE0E00E2523FCCD04408588B5B6C9DB6459C786CE378770B54F51DA3A5E1DF2A3349BEAAC234A6CF8993B598B3B25B4B113E353A4D750FA7534749430F3EBF2512E7EA3FD43C6AB9A1F7 +311 +65A58463B306721A15D224FAFE8C6F5E3432D57EE697DA0DD5A9D4866A8BCCAAA9DFF1F06047851408FEC060471D0E3EB86BFAF46E4B5E60311DC3E3CEA147C75CF971BE74689507154FF2D2E92F15CDB2B9182B11459FD8ACAC5A85F174BC94A7F4EE4C4F5CAFA7C02E37BF0BC77B7DCD0C047BC316F562A01A441AFA7931 +311 +CC84A28CB9F4AA367EB515778F29BFE61195FA671F4352E5E46E3A64773F1F76EE5EB36D7BCE9BCEAD837A4D77F8DB8C92AEEF6711920D7C7D0F76B0FD0EB7AB83724CD35BCA7D36DDDF5FBB7FF6F04A91F3E2BB3B8F1A43CD659639507FF6331C5F697BCC782AE57D757A27D97F1AB9E0AEDB43F92F1B2AACDF5FEC8ADBAF +311 +9F814E2027003BA2F9E144B1938A7A04050AE5D953B82C45619165C813A10E76DD6906B3C16438064D14CDA713AEC420034613E1AC7241904A49FBF87409AFB1FCB9B72FE7933A87D8FBEDACECE4EDCF6A4FE9FB94D5DEB74FAB7CFEAE17EE965DF980DCCA2D04134994F9A2E94D2265DA4FE6AA7B7AC15FACAC2B5ABBCD7D +311 +FF542C5314FDAB2E1B9D8B65220B8E2B638BE008E8DCA22678C820C5053664D15E89F2D735B51A84955E81A443545E9FF787BE0FA700A76D136C27DA179A7C6E8939E01BE756E19CB292798063D8B97F44E8A27BCF3A614FEB8BE974A718E86F7EC99B53CA35FA24F61F2B07266684623A3D4CF073E511F3A10A8FDAFAF007 +311 +8C577F4DF9D339FE7862E5FA47CF4344209E5BF76D783B1B48E152AD281B15FCB934DCDA49CEC5706B1EE8B460F435324A5CAD154725E21AB8044B75A557A79F6C807B9340AA3D92503E866F4274412926C8C504E4D5EF8C0E68DD0B028781A6B4054F11CF18941F101C57CCC796773ABC8DED9CD91FE5788806C05E548895 +311 +354AEAC96546D55B0E94A59F148D061F42AA398D822CDECA4CDE79E13469BDCC44FDC990F366BA5978C15F97ED80CBFD73B0CD9B8B83A4F6845BDFBFAF034019473FB9A63BD55D3A4A7284757071574029A4D016DCCBE95DD679EF3C4880BF169422F8C89B68A988D2B13793CAED4F74DE7486C72DC94DF46B8E93D8E02EFC +311 +B1FF9B3BA10C8AE7F653DF94F4AF466CDDCEF5776687F7C4C14B63E4EED1719DC2EC482D7DBCC7AE518CDE3D5FF86D39000EC3673C0044E1D308BF05F74D1B5F033A7D1BE7F74BC55BBE3E4A20F920946CA0271EB17BD2B80E5660ACB8A29A79F5E4BBA49D4D945B6EEF4A7EFEAA65E5555BBB57A7E15D2F83C69F30BBB978 +311 +D2B39F1F26FC097424D39BB33CA3D87ABF64E307938B685BAD44F71556A30B7E712CA4FE600613C3E554B69ADE949A7CFE214DF1FFF05CB82151EC6FC31950239F94CF7AD39DC473F6EA356E5EC2C4EFA04383CEFE985A127484366FBEE3C0EC4FEA363E4F6632AE781392FD45D67B63FB8DF70E82734F431FD3443172A0E3 +311 +0A83D60982D55FA6A2CF86DBA8382502ACC6CDEF3B907982EED1160E14863FA242F9757347E1544275CCDCF71ECD5F8621CBEA326B8BA884AD7F825781923E66971F04010D574C541D8427A438A37EEF8AF88240EED7362C953F5A81DE2D56109CDE2524EA0F5D2F87308C0C47BD97AD605878AB8589E17CA83930CB5E0DCC +311 +D75853D68B1AB136594926DE426C367428E9334AFAFF90ECE221BB5DC9E5BB53D2CDEF954EAA9CD814F0E5725F1965DD458E5F8487683AD86B9D0076BE3EE91EB1CECC5331A67A3F79D8B27587C12EC2A6A70474749F27B4C0BBAFB94B34189E88DC18964BE4121B9137FD516C7755356E50D6714E31ACD222621EBBCD1CFF +311 +81D87479056A9DA0E0B8EA19D24CF937DA89F78E8650CCBDC110AAB6C4E46DCBFD0446BA9E2060E81F314AC2031E88135E5476AC0160EF5C0DD2EEF1B8B5CB2D198BE9A554A9093B5F7D7E4B5407DDFCA1D9C152FD97F55BFFFDFE802954DFEC8995B983E28AC92F1971E28F531DC407A1280EB125F64A72E617EFB27DFBCE +311 +DCDA2D6F2BDADA36873EE46B9BE1782998C63DA7182BC383FB6FD15DA472B175D7E8B4FC88F38D6E07FEAA3FA3CBB8F6B449ECF14D2175E5C5CCA3A42ABF24CA6C9395AF74535F4DDB38CB84F56614B0FD32D7E19CA5CF988DB4449C40E23DF2B16772594554579094450F56F253CD25EA7F77366FB1CFE79696C428BB64D5 +311 +A41E986189427E5E4D7E2DAE3D5BB4972B3DC44EAABDDDD63A2264ECFA5C786EB0EE1FDC5BE34FA0F08376869FD1E61EA06CAE15191135445EE8F0394161C3266A596AA035572BB0C8876180D8B9C77D307DF9DCDF7365ABDA6F3A2366436457A4DFC1F259666569A9E4A79C817BDD5610E5200FCE894A358EC72858D50EF6 +311 +7545DA81C6FACE3D457BAF70367E1E6BDD9A952FB4F56247F58F523B97E640246051A72F79BE42FFA0B3187F63097F91C48B868586F714B0EBD5A00A8412C1236E1298F60C72BE6C96B82B404CADAA304AE9F303176B14BA53FBC2D5E86377E35542019E24D97B96A49B2C0E5006E7795954DEAF5DA58958839043C6C70BD3 +311 +B1B33B6DC5C31316D8A6923F142E26F6711A2258AF977DDF1F970ECC4F1F2B434B96D81BEB6E89BF3BBB3C7C8CC80505070723D6D7D7D3C8CA27CA164A26024FED94B12A86031867241DA53D929636DA600F050BC40CE48B8BA03D69224720DEE2ED2127390BDF5E6EA64527BBD4C4C63FA76BB801FEEC0E61218EC98A8A86 +311 +CD8313781617877E66692839B9468CF4DB8E50A73986E558E309C0D6F676F921BE02F1185454DC75B111572FF9D27D85EA14C05AD7271225B391C4275659CA4A43759D0BA44639BE4C30A84DB7F67A41399FB95CBF923B1B1672ABC807C1590557FB18D811983FA97F5E609516D62281D38B1281F68FC75528C35E4CD7DF0F +311 +7DBB5E5DD69C414EBEA2F4A5B241B996BA77AE6C772E367408C633EA3E438B68563EA8BE7667F3A4C4FDEEA52ABD78A2820F443B7914A15F180018D231E64519C56A12F6CA7C8DAE18F06F6B769DBF92329EF6A5D18FBCE600F36BD8E8198DE5CDAA96181F3A756EBA5501CEE58F497EA4F418A2B82565B9BCC611713C0B70 +311 +8CCA1F398B78C5F7D03B3437CC18F13061F55C7BEDADD5ACB1DB0E36D3CE47FF6EA1A563A57E9649EA5D14D1A50EAA457463381C073777EF1600607BAF6428A7DDC079D47A204EF1E1F1F0589FB054D45E02CE623B3823C317C601E589E22B1E88B448524B86A82828E32345EC18AE17214B42F8C41C6D428016D9BA31DDCB +311 +47572561E5A402E5BA4FCE55F78AF7268524AA04130F3A20BA5D1E9F9948EA48FC8F5314FDB35E21898198060034ACE5E3E2BBCA34092518F275190AF65F45867FF0900AA4B81F25427414C372BF51DC29AA855C5428EE1EEDD6B849AD60F6E73475C2D2523F8A2772C840A478DED18A8C5C91B085F255DC4F54EA7AA89473 +311 +54FD17ECF8FB3B4A0DE7B483CAF3A1165EE05BBA413E5C1FEF3DE1D6B8F2F3F3251C17D9FCAB48A9AAF63AC773A0E413BBC46A9ADF84A566FC920E4D8C52C9769EE3301CADEEEDD92B312E02E2A7E35B7F1E7D203FE5A7D75578B06E99D0E46FFADB8FDA17ED8880390DA29094202D6F645F76F6F96409CDA05495A96E84B0 +311 +4A00D1A6C6C81D7F3DE9376E828CCF144EE50E364ACCE7237EA0BFB5607ED1AC3CFE73794E5B61739D14DF1743441B1AC98F00ECEDE9E7301FA29755FBEFA68C91EF2B314CF510537EF1ABFC9A762496280B546F5EDAAF8A5B387D469FC9A878A92828C88608453ACEC6094DCA54111E7AD5B2663DFE2DD151B7F123B9F4FE +311 +F8EE36AB125FD7AD52A59B1B765B3E3F73E3F73DEA93875004D2AFFE3CDE9493B87A9904DD052028949041A91DC0E1BA47BA714343FD5B25E0A6A09503AFC17C790058A836A324FE31D7893512E28230332F153181C1E018E1124EBE1BA262AA428A518237D1EA7275E48C5C561BD19ED6912EDD8367DA0F31E40EEAE9683D +311 +A9257525AEA7B632B97119E17145A90231768A4F3B9120FF32E8319B015E3CBE26F1A754177D8185345817C096B0CE443A955D13AC15E1FB5DEEA0239EC32A3C92241A237B0B02FFCB4F93CF2B02570C8D192E2F2A68CEEB42F12E6F8CEEFF46C251E22B4341C56FB08CCE550E7A9D6F4D6564B9EA52A83D5BF2C6E03FA19D +311 +2775E15A8A7ACF30A13C259EE7F5EF2AC3BA354F1500E1A07FC63FB4686E2F12EC69719F9ECF5FE3F35598DF2CE944008CE78F0911BA5BE5D0A34E0098FF038669C26030BA9B300303039852A8F6F496341737CA7E0D2E3AAA36DE0A70AA7D9CA0D54D9AD84B18A44C9E625475E1C97B93BEBB38DCE7D227E67A2D00F53143 +311 +6136355F243D1FC0D712C5765754FA22FCFFAE97919161F46D6E61ACB37D83777B385889770B50765B127768443A5C4D3A4E35A3151111F146B7CA116F918A049F1C272F2CD7976C69A0068773451659A97E0F4A08FE604C3FF6CE7385DB19F4D3ABC8A0D80DA8A42096AC377DB49382B36E270B6FA7570188B4B7D1DDDDA8 +311 +2ACA487CCA5D38BD3BD039ADBAFA4ACFF27809239B722081E2EB9F145320553D183F28471C388BC8814E5045676767AB58A8E72D1CED2CA731379515D2E533E4CF0B39DFC625F97AC4836831A4DE330E87A8126FA17EBE583D614E71E22A8DBF2825599743E0640037303E8E1B1E8EA4E1ABFA4F62907B5E4E0E5DCFDAB417 +311 +C36C6848A2B8D15F1E505434C0707E16B1CFC2A1C36A42BBF55734F8F4301DDC2A5110CEE8C0747F69519265012A2174BF4E955ECD4FDE095A3ABC70536B7AD0B9D7892F81287D667C108703C9B66A221E9A0929D030008F77FF6F5D2108EF5706FC87A3069B976F7C9FC3239D13D283CDEA83597461079FEEE049B0F39288 +311 +9898B7D87D31F8D60ED7B56B91823A8E4EDDB36C6172ECEE42BD42B12CD67994FA0F0DFAE9389356F4CD4425A4B8AA55CBF7ECD585C6A2C2316791E43D5C06841ADBB226A87FD61515FFBB0167B74E56BD7F3E90A57A97FEDCBDF483A0D6699A8C0849CAEB4947FAA90D8E332D9DEBD9F4738C37DA0CAF9CF3DAA68BCB67C4 +311 +20459C7A64498D6E848E5CADE92BB6901F607AA331104A67F7CD650EE169B1DAE94D2C31C2F993632259B6957E246EFE4C5481107B07118607700040E5812A2A2A38DA0076875258C753F60E0F2662C748EDD5F3F3686B8FC3BD6FE631ECCAA1E8B1C1E565AD117E54893E79F4672E32F9E46B2A183DFDCF3EECB4D4D077DB +311 +EA0DA254D256045109670050129E9B9B1A9E4A11344D61497A992778095151510C4BB7EF2009B63AA4360635312E4CF668B7E0D3D85A8688E2BD83B569439892ADB63461930D428E0DE42E4AC57CE48DA5CC1449530D8BD3D6A0F08DF20C8E8F570F2CFFFCEA286E30969B4654D498A58D9DA782E19FE3F002181838925592 +311 +C4A1EF0B332D88B61A92C644547BED490D3A42CFFAB70E36D961F694F6307F72FF15A1C79D5B1B5CEFD353D9E2B52566DEC8F8F09CB3E8359C0F68EBBB7898243E34E12A015B2685AB303C04D3A9E00671FF3DFF0A184E4A2ABEB2B666AF5B3A209C73D8EF29DCDB069C9F391EEE9DD0F74C7BCDE16F8F02E3B8816122BBEB +311 +50665775B600549D8BB33A54A6BF940C028118BC6878C79958D6784F9DEAA6E26748386FFAF8D0D0101E2847B6563F5D8DC1D78F787BE00E2C941AE12148D4A81599FE8F68D4DA5CDED75530ADE138240300EE13B0FC13A1687FED6774662C05A2B7DFB186732F0D7A93545D8B0A6390AE5054D9CB582DA846D7A061B3358B +311 +45E2B52DDE63E114397EDB7E09233A579155D795BE76102CF277F4DF8B188C4F2879BEAE3C08347124BF9ED23B128259FE107D31E34A652F090B30B39E0D9AF917B76A2806135621A8A60681F16AF216514BBD02711585B5B1AE823F4B2D0369EA5B6BCB863072727FFC871CA6C9AB79A814D989057E9470A3D238BC87CBB4 +311 +A0EA49AFE2B86F22E499793CAA9047080450166BDC357524561E258C77CB697F1ABB32562CF87C3B7AA12F911EF19FB3E5C02222E887D08202A60A17151F5E053A86B437771088B4FEE14D652C43E8D939995D34545E12E2FF93D4E366EAD25F10CB7DED141563C3D0C447AF06078A91135FD4B9611583CB4BF3BD167F2399 +311 +CEAB0E0100F8FFC305F00F1711717173138B4B48C83022295DE657EFEDD97F238ECA80F528AFB4F8D67AD4E2594B9A3AA86D7487EFC313BA95745C186F0CDD84CC12DC9CB0424B42203D351FD9C0A3AF10CC7F388E0329279087386421F194F4A29464C1DEF7EF8BE515E4A199DCD5B909342598D8FE6A231F71336248D5CE +311 +C5454455C7BE383B3BC7B260FED27F703FDC39A170F3827239102F59D4DEE7FEF82EAA1A5E4BA94DCC4CC44E7E2EDE20F24244E4454369D2E87C3F823F193FD4FD27E1C23984B01898D15ABC20711A8A558054951A6AE280096BA36CBA439603B7B0B4C45F248C14B2383B4B5CD8ECECE4FF859AD4A6CA2326D020EF090F40 +311 +41D28DB66E5256B56232CC52829DE8060BAE6BBF37D4B7469B822454FE93210A3E8D15E69FFCF01123222611C51C4FD0A041A0CFA2471CF6BE38D2B20FA4C3525B5B8B586759475CF80ADFE67A284AC24651E52B8E683D24C2622642F27E7C3C22783BCF63648BC398BD6CD0D9C97E7E7E3EACB6126DB7A6C1FD9F1031302E +311 +E67ED96F22B77C7CA0239BB324207559B105E4693EB26EC46182A92C9C0DBD24F29B31C5C5AC1F84300E0E17CEEAC15F5736FA4A98971E2F1423BFCD68D4BBD5F86E74B40A369F39507C88FCDB35D2DD8DEBEA4AF999E23CF2816D204200DF6E611781B9884C1AC8888B4DE397320472959434323A5A36B22A8E1F721A270D +311 +F3BD65E4A31C11FF048AFB0D8B7B6881423793963A23CFCE935FC189ED36FAE35C46DA3574C979E4394BE52084F3081837BE033396D2EBD80F1F691B273F660D2B32FA27A33B1DE4ADA5F0FA4651511102DFA410DDA86E065272CA314D5249DF91B06BB92807B28EFD54F08F7B525252782CD276DD02E6B0319B13DF87B1F6 +311 +361AD6B28FC0D711CF445429B9E5A433CD493051C49A9E03DDB1B147D880253514A5DED88009018620EE229101C041950E2D8AA8E1F5F5438E0C0C0C60EEDEE9E3E0C051948C69F84BB7B2A48A1F3C8EB9E6864687F4F4F6F6E2FA87608ADAF9C5E117B7C1A737E710769DA1D15ECE6A84502AA855398712D5FB51BE92E4EF +311 +824969CE18278798AC303C2C7980DD2500ADF8FB1E6D6C2FAAFF9987178923334A244758BAA01A9A1B70FA5097D01E1CC2A58DAD0EC94CE6430AA9B0C10D75E3C6BAC670EFBADEE1607739CE0FC18A2587E3EC3058562014C64219C69BC269082D5C2F4A659278D585755912176D78A1B641719C2A394CED495A1AC5D583FD +311 +2F5EDD93308EA7EAE8A6DD396598BDBD3D4CFED62D18A5BDFDE0DD5C80D1A89BA62697B131AE7CE9A6594138582F1B37D68F1749AC7CB67A70695D48A4A056AC1FBD4E82358CEAE38F54B95A9016F9000F9879C0D9D131768635CA798895F27D6F1614F11F2838A83611AA51C9A3DDB539D42979C29F10465655C17BEC3E27 +311 +60B80DCBF609198C7B171A531905F3EC218D696025975E986C6B4C666666787271F202805577DD5A3EF0CAFAF91B62DC4B2DB4217A7606282F31D8DDBC290593BAA93EA6353AFA9939B877978481E15CFC9F2755CDC00C538F63A4BA3E80AE009B73CA0185687D339A507D165B74290D6EE46D5C58148F020C3668ABE9EF2F +311 +7549170DC80DB41FFC1D678FF19AEB10C7133832B104B80F691B3138F97A92D26E551F1F110400D45AC71A9B9AC41A1A1B1DAEED0B0810638EFFB511D46B121C6FEA0F38CD00E03CFFEB9713FE1F9C263E4DDEC160D0497FA9A132BA608584057FB15380825DE6C7FC3C667EE947D488A09898D8E939C4953BF4EC9836344D +311 +2CC6B2971B2A21A572B06096F4982D2CC7232D46E8EA69DC83722E267754D777F97F96D5DA74E1E75178084D0E70F12AD33E5F71EF2AFA599B2BEBF60A5BE475F444F240CBFE8143C334E7ED9150FEEFEF7EB020C4DBED388DD1D6A167A76F3CBF119B578AE8CFA7736ACAFA4440A1C47185638A0D2F3EB5BDDEFDA958FB13 +311 +CE4AA4923D5BF840392D45544329525749734866684BF7F8D9D54D892E19BCA9F829A0F8BFCBEEA6CFDFC61D74EA1A645ED7A1FF241739F15694BD835B66FEC5677E40FE69BD3F91AE402F036537218A401CB0828604AD189E575015C6F3F6BBC8AC79CF6C70653847254B31551EFCBEB5978B9DB85D58D0FA41A9DFE9C624 +311 +834AA04949D24B5AE6D299E072D152D12097920F14AA92C31C3B97E28B6C2E6E9C23D63043ECBBC0A60FDF5BD0C33C127EA8A3308B8B6AFFEB28B4455BF6E7AF4FC6222728357EB8E46E85053E6A8843AB23295BF838ABDA94658FEF3F832F147046B4ACD6F7D48E8FCC9618582961C898D1C7A253C91BFECBDA6BA18A1FCF +311 +70755DCCEE2222F41B76D23CA98D0CB3D95F0CF66D57FFF65E2A90F5DF40FC3187621E981BD0BC1D992787212688976B94A8F0C6EF646624B570B78E068826F87CE27041A95C9A4871FF65CC17FCB1639629AD6A9517000D63AC231F3D741CB8430777E7ECF80CE7A3F73C0F162F7E56AA71F4DA80A33CD6ACEE78E2C11FAC +311 +03DAFCE929B106F8F6B3ED2DC71FF994BD9CEF4E9A94896EF1B6E2B7B4978168BCF0847A3D2A4F1B0500B926E634B174548E9FBAD27153FB5FC36799B1E6C7C38A94E1F9E996796D7B485E635D5287A2AE76FBD11A341DB3F8F810385BB7296391973DC6C37C452A9305AEBBE0CDB6C566DB59FE60C8E94B1FD090354BF514 +311 +39FFD1B48D725228FFA88C84F686F547107A7A235665A4593B7C34D748C1AC8D56DD5DA861EEFBADDECBBD65117E65851F90351AEB65F0EFCFF4BCFEF24CB7A5879699FAC437A7AF9099186CF006BB923A59E37D7A36A29FD8D2CBB914A5E9D0A8FD212FBF796AE9A67958FF1D5D8B85F78F96219D4714EB919929241B6C01 +311 +8AAE0C02069F8D919961EC5DDFBFB7573E3AC8E08BFB7C8534BE6DE045B3CD63DBD71F7638BA2309CAE398EA3A7D8F98BBBC7AEB367EFA5CFA27A9F8F2F52FE878058E6209C026453F8E7E8A237DFC389E9590F6059B397CFD65183BBF936E86896612A5B5FEE49DAC8455D026218AFEBEC12B3523A45B57B82C9C97E02ACF +311 +A1184DB18E48CF615DBF39C3CC6F0B4AE6BBEEA2E39D944E290302FA3DE33FA7E40296E2EF7BF1CD855BBECFAD0FD622BEE9746FC4FDFDE545AE6E3BC234CADF83A0FB76DFB145BF3828AE3436368AC24989454F8FF70A177672C711CAA1A76107218CF948CA01F873520E7A2B118EBB161ABAD432932029628B6761590C98 +311 +AB590C8C8C79798CE9F715BA696E839E9131A4BB68BE923F1747FBFF165331FFDD4D05997ACD168F12CA75348DAC04C7F509FE83898A8B421181AF492861CE81E4A2DD0A30850CFA95E05F097FF70F9E777310D584A75BA7018922E271DAB1D785671157685B7D383D3E074DDB4F1E44EF04B07BAAC75A062077C54B9752C7 +311 +38C9D804D402E02851F3F89605AF189738C18C021D2139EAF72513824E10DDD3047BACC8521ED7E98B257D1166903B1B08FA6F918FD7E162913FEC7D0EF4787E29C35FBA8C6E2C9C20E9AB30AE42CA2A01A090E7D010C1B5BF9CEE2A304BF3A987FB40D836979B79E18D70F257DA61E808472DD85B82761825779D6B90CAEB +311 +0F23360C6F80A7D7D77545E95B3DFF0CF96A827F9B5BCECD2C9698718417EF80FEEDEE938AFF55859D0D540B7FE09435583A6F04874A0C7E8CF234DF4879722000167F5872CEC6BF3EF7E5A8CC8121E6A359227C8A29C95212F9F735BC72A887D56B8F309FC525BFB4FD767AAEC4AEFF249CFBBD2DE1AB9969A6155C7D8DAB +311 +C307E47324C1F916C7BCFF40ABA5644E9FFB9238D19ADADA49D9610F01ABBA57E4DFCBDCCBB2B93C8953206C91DE01B0E2651A6D6C31721915B31F7B7CBCE55466F6D70402788FA91D73BE4ADCBA99A35F17D6B83383704917D6833F0FA3CD45C926ADB8BC17999A4A0AFBCE2541F7B8CF4C2F5FBA611B3E626D64407CB32C +311 +122DFF4EF8674D6EDBB86C8F7C6A16D50FA685E2CCA34F8F7D1D8265BB0042E9D7F1F666F7BE5FAA5184DF58E12122A42EF02F461B3F369AC746F96528AD208512AB56844CF75B97F352DF7995024FB02855385854F3798670D01CAC771AC3E1889EC25F61A20C1F8CAB46898817F9C47E2CDA1F286BF2F358298709FE567C +311 +91133E2638C8B53975E753EA8BFD3578B5F7186B7BF084EBA3F77BAC3132A5779902EC22AF2D2E79F42D324E083386DAEE9810D1B44A5DAA54513C7A71E52A4AB9257D975B621308FE4D2141CC3DCFE946C154A3C1DFCCA7629EC69ACFA831634ADDA93C0388D2A9A2DDAFCF957C6995BCE7FBB6DC4EBAE79E63EB08E9A25F +311 +52F67076F7093CA545CA3053B5BF576DEAD4E0017143C5F759F32B2D6F145B52B67B48909B7C7FCA6586EE385A2189C16932CB6DDFD9152AE317C430492A578ED27B72D85D40C7C93C097939E1B50F4D67F05334FE9DC362C30B7FF561B95259138ECFD9274C8D695C31B14D4EAA9F3D7FA8DDF652F8C5DEFA23284C799B7D +311 +94416DEE81E05A866302DC70192A5368EA65E1CED8BF107A1E2D3119A71E85DC1A7B0C1E93274CE04DD0F2ABC8AC7C987FDB002B6F81757B43502EF7A0D92E416494FAEEA4C9C090CD9557AA4D532F4996A272B94355C5A0E4974EEA9627476F30809B94D8D8D860062E02204643BEC7D0438AF800FC7E2B26955D595421E8 +311 +3C69E1A07E0E3122285835F014AD538188CF9737CE76D464A353FA55A001AB2526B84E985083693BC76FE211CA1D000679C889E3C009832C87E1AC2EDD05C302557422DF0800E316FCAC86978A371F025A6C7DAA9F333E7BD160403A1A06EA75AA9888FF01504B03041400060808003D7AEE4A28BB58AF800100009E050000 +311 +3D00000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F536368656D61732F436F6D6D6F6E536368656D612E786D6CBD544D4FC23018BEFB2B96DE15493C78601010A34B880709DEDFB52FD0D8B54BD70DF6EFED3AC0B12E6442E2716BFB3CEFF3D18E26FB440405EA8C2B1992 +311 +E1C323095052C5B8DC842437EBFB673219DF8DA659866649B798C078B48AE6410122242F2A4994AC7F93815DF85C049C8564CA0A901499DD886B2EB9B1E02448F358701A9235880C49A0119892A20C89D1B9FD768815CAD268CB5E0355B40B1E47F3E37A2020464BED06AA57CE8FCC20C3C3446D46761A2632A8C1283D0703 +311 +1EFF2AE7CCB1BFEEE916E406DFAC8E36FFD99A9D60A6944090EEDC3B670C7DC13DE86B6B2C5C240D6E503BB85ACD579D513F1B9F9A69DC9CC22AFA80C426D40AF0B21EA168559156B6276F0F726A6BFF04DCD513A87A62CAD41FB24FCB2858B3952E3D852084DA25B9303C15687973CC8E8AAE11DF2836C38C6A9E765E8C1B +311 +A1BFB1DC29CDFE518C8B5A7655A4ED7E77291ABEB80DBD82EC866ADE1B8D05AF9E35CF09D7A061F38A986D9EC412B8F0F65E5D81534D9B231D1E5A8FE577A241F3A5FD01504B03041400060808003D7AEE4AE336299B9F040000341300003E00000033333246423638392D363232332D344431442D423337332D4446314242 +311 +324145414645422F536368656D61732F47656E65726963536368656D612E786D6CD5585B6FE238147EDF5F61E5BD1498E9A895A02398AA5D1E2A5665BAAF96710C78D6B9C87168D95FBFE7F81242134A968E469A4855437C72AEDFB965F4F53551642B7421B3741C0D7AFD88889467B14CD7E3A834AB8BEBE8EBED1FA34951 +311 +08B3E01B91B0DBD1F3EC8E6C991A470F22155A72F73CBABC1D4D5921DCD1934863A11F990102A6F61466977B8AC49FB13C174CB3940BE4504A22E3713489B7F82406516225536940BD88E4E552493E8E564C1522225AB0384BD56E1C195DC26FAB13882C2EBD62E1FF9E5B0FCC453132C99548446A1872B62267078F1EF14C +311 +3DB1DD1952DDBBB384AD456779F387C51992E0ADCE12FED2622BC5CB1952FC9BDF3295E9206E961AB116DA7ACE45F76F07A26EFC3FD762FD7362FC3C0BAA4DB34C09E682CA5167BADCD16CF94370E331E2F1A3D852008AAD5D64BA2373478338AEB1583B905396D158182655E1B938D07926CF007CC7E85E6709999B0D3827 +311 +C0BF406BEF55C68CF5579DA32C8C85BE03EFE788787E8FEC95DC85C3E31A615658D81F9A34499612004CE69CAB12531B15A887ACA642C110F8C1A8C1974A85853F407794695C905C688E69B116550ACA946B9B4785FC179E0E229240B62665328E86D5BD2C800CF5D80289F35AC25E3DD51508F4BF9A74A0F5A557BB3D284B +311 +C6FF59312E282F956A75C5D453906F48D11A876599E49425602400842995BDF02C4D012E226658F78A710416AC552831FDDEF0E6F0BABEBEA9DC36718CCEF4DAC5A0DF871AECBDD8F448F015F8DA113A473609D1751672B723874B2CAB21ECD6E284E51DCC7D1A47A00F60EC21DC4CC3CDC406DC03760A4E6C772F2F4D561A +311 +9AE58C4B03F5B4838701472139EDCB36819A66C472B52AB1139C648956F43E0DAEC067B56B0818F5961D3D446B8F1E1E78C0EAD7EE02AF2895D813E88AC5014A7B436DBB20F778D40A1D9737EFE45B47D49CC8360F9966B15AABAC802C1005548A93EE469F5D55317CD8BF0AA86CABADB2A009D4D6F60CFE53AE370AFE8C45 +311 +415B15D362A5A038E766E34B00F8C2E3E7098E2093B1C96349FDAEA158903B4BDAEA665F473FECE72186E3BDDCACCADA31839CD6D4FA7D47DF5469006ED340EBE71DF960DDEE88A3E17557039B58C278A1795BA8079419DA8FC5BA1BAA1A66238BF69C7B2BE4E6274839122B7D00BE8B7D52CF61B2D63216046008C8B330FC +311 +10F8907787DEF061F47965BBA02FD8F53BA12F9827614979F53563D0BB1AD67A03DC0E00E2FB2CF3EFB457E70E65A3D7C78E7D700DBB45132AE9BBA5E468CD861408961AD8B00A55C2FCC677F4456039FD9F1907A5B3E2D0EE845FDBA2DAFA88C64195671AD6D282DA2E45B1F50A0D632B0D0B6718757DCC7C84A70A3656F2 +311 +22CDA6DBF07E204AB358961D1BE31E53A82C2C0D56DBF61A5608B5A252A932A1DC6E32462430853353EA30417C8199A662E946A4EF352268B8CDB9A9C6762515ACE8E09BBC031C608A02C4BA89C9DDC074E46E0E26A17BCBD4ED43270D03E3645ADB81F6D62CC07A3243EB81C07E01681DDF2DB4730631E6DDA6CBBD048BE9 +311 +F06AEB3CF06B31DD6C9575E37E9FF17161342C4C76D95519672A6509E0F5C4E71B4B1916440059F57D0921F496237E473A83E3E4E06353BD9D27593594D7B6707C08D22FEBDFBFFE03504B03041400060808003D7AEE4A8DFFFC99640100009B0300004500000033333246423638392D363232332D344431442D423337332D +311 +4446314242324145414645422F536368656D61732F52656E6465724D6174657269616C536368656D612E786D6CA5535D6BC230147DDFAF08799F5FDB600FADA20EA430D968A7AF12D36B1B481349529DFF7EF9A85A718860DFDA73EFB987734EA3D16FC5D10E946652C4B8DFE9610482CA9C8922C6B5D93CBFE3D1F0291A6B +311 +0D26A3255464182D920FB4233CC629881CD49C18508CF000E3EE309A100D61622AAB4A8A33B2483F11CB639C545B0E1508438C3D3C0361192846DB7ACD198DF186700D182920B914FC1063A36AFBEE8FBA03FFD3CC1D1F4FC9E151A2AF59F628C5B7821D83FDFD3489305080F2EE04BF962195FB285E9D2DD352320A9E827A +311 +E357594914E42B1F5FE35F83688F60C4C91A6C946ED0668E8F244BC2EB0BA6E3BCCFC0B6A4B507F9F9F8D51EB315D9BA9E08D308E89F96931666E577837E2B4172199C68C4FE306156D47DF51C698C7B9D97FE5BEFE2190C3C38BB054E6E8163FB039CB4B993C80BF18D969203116D6BBD26238B821F9BB909AD6D9C71B85B +311 +CD8C33D66FEA3D31B4BC0AD45BEA46BBEDDFEC0F504B03041400060808003D7AEE4A5319E9034F000000570000004700000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F41737365745461626C654F66436F6E74656E74732E62696E536160603034 +311 +373273327274D4757275B4D035713536D3B5347232D4753671B574353075034A390901D5E52696A4166526E6241614A4261625E625A70205C1801748BAA7E601659383933352731301504B03041400060808003D7AEE4AF865834058000000680000004500000033333246423638392D363232332D344431442D423337332D +311 +4446314242324145414645422F4173736574446174612F53747265616D56657273696F6E5461626C652E696E66558CBD0A80300C06F73E4B117FB66E451741D041DC5BF98480A492E4FDD18EAEC7DD4555D89EF28DF51A0B1BD8340C4DE7DD848B988C0ACF0649566493F2408C508DDEBB99D5129FF8F1AF8CF5B9503E20FA +311 +E5A16B5AF702504B03041400060808003D7AEE4A218FBC1938010000A20400003600000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F636E782E786D6CCD94314FC3301085FF4A648931765251152AC71582A51348540C6C57E7D25A75ECC8BE20F8 +311 +F7D86D0650115387AEA7EFEE9E9FFC4EAE3E7B5B7C6088C6BB86D5BC62053AED5BE3760D1BA92BEF58B15232784F3F305638E8B161B9CC94DC053F0E53A9C50E469BABD6B8C3547C881169035B8BCFDDA377848E222BB485181B16F51E7BE030926F311E780B04BCE601A31F83C634894C8FD3241D10085BA66655BD28AB45 +311 +59DF6EEAF9B25E2CE7F7EF526452C9ADF776E24D5A16FC7033ABF4692F5395141950720C66A2F26B98FA4B25DF1A274522958C14922B53077D0DA90386C11A0D94CC135E13529920845E8A13ACA4C82EFCF2E2093BE34C6E592769403EBC248118C8E0157AF29FDACB7BB37691C069BC6647CE355EDE87D7E32F7A3BC5F298 +311 +9BEB4BCBB9466E5C7781AC88E33949D1C9D7457D03504B03041400060808003D7AEE4A507FFEB3E7000000600100006700000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533362D393242312D4334453945 +311 +303546324141422F7468756D626E61696C2F302F636E782E786D6C4D90C16E03210C447F6585D4E32E4BD4286DC4929FC8A997CA659D2D0A8B1198AAFDFB42CB21A7B1C64F9667F4E57BF7C317A6EC282C424DB31830585A5DD81651F836BE88E1627422E2074C0C01765C44B385D15BA212BBB5E20D8A6FAE77E1DECD0D03 +311 +2667DF63D8C4603DE4BC886C3F7187090AD38AF93EADC030A92961A6922CD603EC76EC076C42605C8539CCEA34CEA7513D5FD5F1AC4EE7E3EB9B968D34FA83C877DE05C644F1E9305BAA63A8FFCC5A36C0E8925CA75A08611E9ED3B22E8DCE9C6AFE0EF14FAC10C4E89D05AE3549B28C3C560861D7F21F365AB6BC55FECAA8 +311 +DABA31BF504B03041400060808003D7AEE4AA233C2345D000000800000006500000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533362D393242312D4334453945303546324141422F7468756D626E61696C +311 +2F636E782E786D6C4D8B490E80201004BF42E6EE76F3C0F216234B883063108CCF170C074F95AE5473F5C4C06E932E4F28601967600677D21E9D8092EDB002539227A2FCCB80E1168D80A6417297A89C5D6963B7129A0D1E8F2EE73AA7B62BBEB8B27DE50B504B03041400060808003D7AEE4ABE8976236300000088000000 +311 +5B00000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533362D393242312D4334453945303546324141422F636E782E786D6C4DCC3B0E80201004D0AB90EDFD75167CCE820A4A845D83603CBE602CAC267999 +311 +19AEEEE0D965E2E908050C6D0FCCE04C8BC355404EB6198129C92351FAD580A10E464065907C8D948F8F166375F655BDC3FDC3B4E530A176BE7057BDC43B2A593FE403504B03041400060808003D7AEE4AF34F79557E000000A30000003600000033333246423638392D363232332D344431442D423337332D444631424232 +311 +4145414645422F5265736F75726365732F636E782E786D6C4D8CD10E822018855F85FDF79890996E808346EFE1121D0BA191B81E3F685E7475B6EF9CF3B1E1B33AB49BF8B6C17320550DC8F84798AC5F38A46DC61DA041B018C2F63703E4C7D5702818045B6248AF034D661E932BD459FF3C20B9D2565129B1D2B2C38D3EB7 +311 +B8A78AE05BA37B5D5FEEB952F9712A971C3F5FCEA2175F504B03041400060808003D7AEE4A46E7C3B70C010000550300003400000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F536368656D61732F636E782E786D6CCD93CD4E032114855F6542E27260686C461B862E5CB8 +311 +72A3AEDC21DCB6A4C09DF063F4ED853A8B3671A78BEEE0E4CBCD3907AED87E7AD77D404C16C344381D480741A3B1613F919277FD1DE9B65244C47C86912E280F13693291621FB1CC8B6460A78A6BAAB3E1B8880FE83D86177D00AF48A79D4A6922E974A5AA6434908ED4A8AC28A7111296A8A14EC8D6C332414750190C91AB +311 +818FFD30F6FCF695AF377CDCACEFDF046BA414EF886EE16DC81071BE590D1AEB31544383600D90A244BB502D0591E7EE68ED43B04A489172AC2D2C64FE9A2BA9E6D959AD722D8B9DC01F460AD6C25E447E8400D1EA6BCD7C61EFDF423F4330109FEA4B45ABDCB566FFCDE5DF2A60A70DA8DFA02D84FC06504B030414000608 +311 +08003D7AEE4AE3603EBE7A000000C20000002C00000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F636E782E786D6C6DCD410EC2201085E1AB90D9DBEACE05D09878023D01A1D34A8419C380F1F82DA68B9AB8FDFEC93C3D7C52546FCC12980C9CBA232824CF63A0D9402DD3 +311 +E10C6AB03A3397DD192872090D3406ABE7CCF5B5D18893ABB1690CF4DCF02282E5EA8A5BB96FFE536F285CB347F95BEFFE81C9ED5AFF9D5B1FB575BB00504B03041400060808003D7AEE4A22FED86E7E000000A300000007000000636E782E786D6C4D8CD10AC22018465F45FE7B2B35D602754C9CEF319A1B92D361337AFC +311 +3476D1D50787EF1CDE7D568FDE36BD5C0C02C8E902C886479C5C5804E47DC62DA04EF214E3FE770314C6D50AA818245F52CCDB81263B8FD957EA5D781E90316A54D3DE714329C3574D3456ECC6B0364429DA0FBD195431CE5529F3EB95AD79F905504B03041400060808003D7AEE4ACA1DF9C9A90000007301000013000000 +311 +5B436F6E74656E745F54797065735D2E786D6CA590410EC2300C04BF12E5DEBA70400835E500FC800F84E0B411AD13252E2ABF27A55740421CEDDDD9B55CEFA7A117778CC979527255565220197F75D42A39B22DB652EC9BFAFC089844F65252B2630E3B80643A1C742A7D40CA8AF571D09CC7D842D0E6A65B8475556DC078 +311 +62242E78CE904D7D44ABC79EC569CAEBA537E3521C16DF5CA5A40EA17746733E0B6615DE7217475F386F18B9481C510F1F021CD9FF027EA0E1F5C4E609504B03041400060808003D7AEE4A3DEE336E64000000790000001B0000006175746F6465736B2D64657369676E2D7061636B6167652E786D6C35CBC10E83200C00D0 +311 +5F21DCA56CA76D41FC96062B120735B69A7DBE268BB7777961F8D5AF396893C2ADB70FE7ADA196782C2DF776D7A97B5933C430F15651E5469C55D70F80A4992A3ADC954792C525AE70A1E4D6AD9816CC044FEFDF01FE3FDE907802504B03041400060808003D7AEE4A432F74D7F90000009601000037000000333332464236 +311 +38392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F636F72652E786D6C8550CB6E833010FC15B477831D4821917114207C414FBD59C62156828DFCA8FAF9B5132AB5A79E76353B33BB3BF4F4B53CB24F699D32BA059263C8A41666527A6E21F82B6A203B316A8DF1BF6890 +311 +69BEC816120C8CCED684758384D15EEA846A33C90DE4CE490F9978C4A605276E72E1390F3E32DC3D5FB9B8F359E6247FF1180D414D9B544DD14D5D95B4C01A42CABAC367F436128CAA61BCA0AE1A7BD4F4653F1C70472EB8A1451233EAD5F2B35D58C9BD9C80ED30A911AE11A9DEC9FE48EAE3FEF0418BC464D4791B7FDE96 +311 +A6EF809DD3D503F79C16AFE91FD7B04EFFBB06A57DB9DB5C5F0A078CC4239F03468B94522CCF08634D89B26F504B03041400060808003D7AEE4AEBDCC332F40000008E0100006800000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F313732364232 +311 +41412D424541382D344533362D393242312D4334453945303546324141422F7468756D626E61696C2F302F636F72652E786D6C8550CB6E833010FC15B477831D482191715448F8829E7AB36C875A0936F2A3EAE7D74EA8D49E7ADAD5ECCCECEED0D3D7722F3E95F3DA9A1E4889A1504658A9CDDC430C57D4417162D4591B7E +311 +D1A0307C513D6418189D9D8DEB06096B8232193556AA0DE4DEAB0085B8A7A6072F3ED4C24B1E4362F85BB97271E3B32A49F9E4311AA3969B54CBE4A6AF5A39601D21753BE057F432118C9AF3744143338DA81BEBF17CC003B9E08E5659CC68D0CBCF76E1140F4A02DB61D222DC22D2BC91FD91B4C7FDE19D5699C9A80F2EFD +311 +BC2DCDDF01C3B47AA27FDCE22AFF778BDA847AB7B93D151E1849C73D068C56399D541ED1A59A9364DF504B03041400060808003D7AEE4A23E145D5FB000000960100006600000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D +311 +424541382D344533362D393242312D4334453945303546324141422F7468756D626E61696C2F636F72652E786D6C8550CB6E833010FC15B477831D482191715448F8829E7A73B143AC808DFCA8FAF9B5132AB5A79E76353B33BB3BF4F4B5CCD9A7B44E19DD02C93164528F46283DB510FC1535909D18B5C6F85F34C8345F64 +311 +0B090646276BC2BA41A3D15EEA846A23E40672E7A4876C9C63D3821B6F72E1390F3E32DC3D5FF978E793CC49FEE4311A82129B5489E8A6AE4A5A600D2165DDE157F432108CAAF370415D35F4A8E9CBFE7CC01DB9E0861649CCA857CBCFF6D14AEEA500B6C3A446B846A47A23FB23A98FFBC33B2D129351E76DFC795B9ABE03 +311 +E66F61F9D05CCDB4784EFFB88655FCEF1A94F6E56E737D2A1C30128F7C0C182D524AB13C228C3525CABE01504B03041400060808003D7AEE4A0226906D0D010000B10100005C00000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241 +311 +412D424541382D344533362D393242312D4334453945303546324141422F636F72652E786D6C8590DD6E842010855FC5708F82FF6E908DB8FA04BDEA1D51D6925DC108347DFCC26A93F6AA5743CE9CC33733E4FAB53EA34FB11BA9550B708C4024D4A467A99616387B873588AE94EC5ADB5F361029BE8A16041950B2ECDA6D +311 +A7346965850AAAD2B338456E8CB0209A9EFED102337D8895C7DC59EF308F78E3D3832F22C6F1E1A3C439399F5139FBDFE45D8A1DD01AE3AC62A883E58811CC6FE300593EF6B0EEB3FED62086075493248429B172FDA14FBBE056CC80A60857105510E76FB8B8E0EA5234EF24094E4A8CDDFDCE27346C0728AED292A55D07D9 +311 +D0D5301FB2123629C3B0CF876640C5E85B8C2447F00FD06DF3FF402795CDD21378248C67FAF95F0D4A9270405F5ED7F5351C9B7E03504B03041400060808003D7AEE4AD773138AFB000000960100003700000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F757263 +311 +65732F636F72652E786D6C8550496EC3300CFC8AC1BB6C294E6A279015348B1F50F4D49B20338E90580AB4147D7EA5D805DA534F2486334372F8FE6BBA179FE8BCB6A603565228D0283B68337610C385B450EC0577D6865F34288C9CB0830C83E0A3B3F1B140CA9A8026A3C60EB880D27B0C50A87B6A3AF0EA8A932C650C89 +311 +E16FE543AA9B1CB164E5CC133C463D2C523D24377DD1E840B48CD5CD81BE92979E51B23EF5677258F747D21EEBE3694B0FEC4C5B5E65B1E0414F3FDB9543197000B1A2AC21B4216CFDCE363BD6EC36DB0F5E65A6E03EB8F4F3B2347F07E20DBD8D4EA1E7D53CFDE31A1FC3FFAE519B50AF16D759E141B074E473207895534A +311 +E51961AA3951F10D504B03041400060808003D7AEE4A5B72B2D5F8000000940100003500000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F536368656D61732F636F72652E786D6C8550CB6E833010FC15B477831D482191715448F881F6D49B851D6225D8C88FAA9F5F9B50 +311 +A93DF5B4ABD999D9DDA1A7AFF9917D4AEB94D12D901C4326F56884D2530BC15F5103D989516B8CFF45834CF359B690606074B2262C1B341AEDA54EA836426E20774E7AC8C6476C5A70E34DCE3CE7C14786BBE70B1FEF7C9239C99F3C46435062932A11DDD455490BAC21A4AC3BFC8A5E068251751E2EA8AB861E357DD99F0F +311 +B82317DCD0228919F56AFED93E5AC9BD14C07698D408D78854EF647F24F5717FF8A0456232EABC8D3F6F4BD377C0DED63B1D2D9EB33F9E6111FF7B06A57DB9DB3C9F0A078CC413D701A345CA289635C058539EEC1B504B03041400060808003D7AEE4AB4BDDA7A02010000D80100002D00000033333246423638392D363232 +311 +332D344431442D423337332D4446314242324145414645422F636F72652E786D6C9591416EC3201045AF62CD1E3B40122711268AE5F8045D7547CDC44589C1025CF5F8C5B52BA5BB7605FAFC37C39F11E7CFE1917DA00FC6D90A68BE810C6DE7B4B17D0553BC9103646729BC73F1C9069955035630CB2045EFDD34AE52E76C +311 +443BABD6695C45150246C8BA47BA5410BA771C54AEA6981CE19E8FAABBAB1E739A2F3E29A2197ED0CEA38AA841B20D2DC9A62474FB4277275A9E76C75751CC4E29266323676B331CDE50676E8C2952C2A82896E7649B8C5E4D46A74F9A9B410F9273D6D6FBC391EC19E364DBD086D4BCE4A469695DB3CBF5D25EEB5424C152 +311 +84E8D368D622F310FE8A2FE0AF68D3A8FF196D2102C8A750C53C67298AEF25A473DE89FC02504B03041400060808003D7AEE4A7E066DEB08010000B201000008000000636F72652E786D6C8D50C172832014FC15E6DD5188A69A0C92A9267E414FBD5121868982239869FFBE10ED4C7BEBE931BBFBDE2ECB4E9FE3801E6A76 +311 +DA9A0A68420029D359A94D5FC1E2AFB80474E26CB6D6FF920132625415441838EB67BB4C1BD459E39589A8B1526DA0704E7940DD101E15B8EEA6469188C50785BB2793E8EEA257094D561D675E8F3FABDDAC845712F88ED0029302D3FC8DEE8FB438EE0FEF2C8D4ACE9645CBCD4ACBE0AEAF5ACDC04B4AB3A226AFF8A5A504 +311 +E7E7F682EBBC6D70D964CDF9406A7A21254BE33267CECFE1CFDB9190D323271ED197A52BC5D987B5C32608A124B266F8024E581A893FA19749FE23B4363EDB6D07D70D079C86404F2218C706C378D61B666C9B7F03504B010214001400060808003D7AEE4A3D73C5AC26010000A80100004F00000000000000000000000000 +311 +0000000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F446566696E6974696F6E4974657261746F7250726F706572746965732E62696E504B010214001400060808003D7AEE4AB71BFCCC120100003002000045000000000000000000000000009301 +311 +000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F496E7374616E636550726F706572746965732E62696E504B010214001400060808003D7AEE4A88E57597CD3F0000904100006B000000000000000000000000000803000033333246423638392D36 +311 +3232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533362D393242312D4334453945303546324141422F7468756D626E61696C2F302F67656E657269635F706E67504B010214001400060808003D7AEE4A28BB58AF800100009E0500003D00 +311 +0000000000000000000000005E43000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F536368656D61732F436F6D6D6F6E536368656D612E786D6C504B010214001400060808003D7AEE4AE336299B9F040000341300003E000000000000000000000000003945000033333246 +311 +423638392D363232332D344431442D423337332D4446314242324145414645422F536368656D61732F47656E65726963536368656D612E786D6C504B010214001400060808003D7AEE4A8DFFFC99640100009B0300004500000000000000000000000000344A000033333246423638392D363232332D344431442D42333733 +311 +2D4446314242324145414645422F536368656D61732F52656E6465724D6174657269616C536368656D612E786D6C504B010214001400060808003D7AEE4A5319E9034F000000570000004700000000000000000000000000FB4B000033333246423638392D363232332D344431442D423337332D4446314242324145414645 +311 +422F4173736574446174612F41737365745461626C654F66436F6E74656E74732E62696E504B010214001400060808003D7AEE4AF865834058000000680000004500000000000000000000000000AF4C000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174 +311 +612F53747265616D56657273696F6E5461626C652E696E66504B010214001400060808003D7AEE4A218FBC1938010000A204000036000000000000000000000000006A4D000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F636E782E786D6C504B01 +311 +0214001400060808003D7AEE4A507FFEB3E7000000600100006700000000000000000000000000F64E000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533362D393242312D4334453945303546324141422F +311 +7468756D626E61696C2F302F636E782E786D6C504B010214001400060808003D7AEE4AA233C2345D0000008000000065000000000000000000000000006250000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D34 +311 +4533362D393242312D4334453945303546324141422F7468756D626E61696C2F636E782E786D6C504B010214001400060808003D7AEE4ABE89762363000000880000005B000000000000000000000000004251000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75 +311 +726365732F31373236423241412D424541382D344533362D393242312D4334453945303546324141422F636E782E786D6C504B010214001400060808003D7AEE4AF34F79557E000000A300000036000000000000000000000000001E52000033333246423638392D363232332D344431442D423337332D4446314242324145 +311 +414645422F5265736F75726365732F636E782E786D6C504B010214001400060808003D7AEE4A46E7C3B70C010000550300003400000000000000000000000000F052000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F536368656D61732F636E782E786D6C504B0102140014 +311 +00060808003D7AEE4AE3603EBE7A000000C20000002C000000000000000000000000004E54000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F636E782E786D6C504B010214001400060808003D7AEE4A22FED86E7E000000A300000007000000000000000000000000001255 +311 +0000636E782E786D6C504B010214001400060808003D7AEE4ACA1DF9C9A9000000730100001300000000000000000000000000B55500005B436F6E74656E745F54797065735D2E786D6C504B010214001400060808003D7AEE4A3DEE336E64000000790000001B000000000000000000000000008F5600006175746F646573 +311 +6B2D64657369676E2D7061636B6167652E786D6C504B010214001400060808003D7AEE4A432F74D7F90000009601000037000000000000000000000000002C57000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F4173736574446174612F636F72652E786D6C504B01021400 +311 +1400060808003D7AEE4AEBDCC332F40000008E01000068000000000000000000000000007A58000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533362D393242312D4334453945303546324141422F746875 +311 +6D626E61696C2F302F636F72652E786D6C504B010214001400060808003D7AEE4A23E145D5FB000000960100006600000000000000000000000000F459000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F75726365732F31373236423241412D424541382D344533 +311 +362D393242312D4334453945303546324141422F7468756D626E61696C2F636F72652E786D6C504B010214001400060808003D7AEE4A0226906D0D010000B10100005C00000000000000000000000000735B000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F5265736F7572 +311 +6365732F31373236423241412D424541382D344533362D393242312D4334453945303546324141422F636F72652E786D6C504B010214001400060808003D7AEE4AD773138AFB000000960100003700000000000000000000000000FA5C000033333246423638392D363232332D344431442D423337332D4446314242324145 +311 +414645422F5265736F75726365732F636F72652E786D6C504B010214001400060808003D7AEE4A5B72B2D5F80000009401000035000000000000000000000000004A5E000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F536368656D61732F636F72652E786D6C504B010214 +311 +001400060808003D7AEE4AB4BDDA7A02010000D80100002D00000000000000000000000000955F000033333246423638392D363232332D344431442D423337332D4446314242324145414645422F636F72652E786D6C504B010214001400060808003D7AEE4A7E066DEB08010000B201000008000000000000000000000000 +311 +00E2600000636F72652E786D6C504B0506000000001A001A00210B0000106200000000 +310 +504B03041400060808003D7AEE4A000000000000000000000000370000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F617474726962757465732E62696E504B03041400060808003D7AEE4A000000000000000000000000350000006662782F3237383530353136 +310 +2D364138362D344639422D393343302D4530433546313143304643442F76657274696365732E62696E504B03041400060808003D7AEE4AF270F1330600000004000000360000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F747269616E676C65732E62696E6366 +310 +60600000504B03041400060808003D7AEE4A88E57597CD3F000090410000420000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265736F75726365732F322F305F67656E657269632E706E67355B773CD4FF1FBF73E71C0E67EF7576C9DEF3EC995D46C6A1EC50 +310 +46649CBDF79E192942562825CE965D8A868C2842C8DE9FDFD1F7E7F138F7078FCF6B3F5FCFD7EBFDFEC41B196891913091804020321D6D7513FC77D8C5070EC3FFEE51B21F068154CC75D455CC028A3EA68992803810109FDDE5D5CEC5521B28A1B422BF80741695D5B529DA1C2D058AD2388BDA9F476F7C2CADC939304173 +310 +DBF48A452BF8A700C0C70C3D121C1AA15A5270C22E2B9E28763651F7DA0D12CE11077B2ECD0D560B5E3DE2368B1E65C5E1C87384A4CC5F2D9FB8BE39E9B77C753783A37998F5CEF1B4FEE553463245C9712270880DF339E0B882A6BA713D865F6F384C842D440934EF264F4A304F8DE4EEFA1E2BF5E2D91F5ADD9613D6E31D +310 +DB33ED31DB1D7916CAF091286EA3D31B9CFECB78BB0060245B943C4C040119FF780A74DD308BA9D028678674EB5504ECF284EE814DAED07440BAF649BE4B55DA2890F4D73E2D489A59EE54FDB0A85B59F2F52C4858A02705C4815A792B0C4A996A2023ECB195073E115E3E3647F64243A8338F2000BCB58DA5692C7E7C3D41 +310 +3F963FEDF3CF00FBB0F831A9D51360FECA43819F527B89FA260EB63C5DA676830AA3626A3693B23F1A17A0DFFCE3EE164E31B5365EDF0C89EF64D8E38AB0BA9A1117D7C3A0EC44F44F4023095E80BA610DE336D0F99593A88BFC5306E72DF98F7FD132ED95878DF2964EA7E43071359FD83B2BDF9C6B5BDF768F0F3C0FC351 +310 +199526F53D9460A34511F75D1B63E488178569472C91A94497807A3FD4A390C6558F26D416365C3656DE6E769DA9D8FC73774E1709812842DD8EF96AED1960F79D21B395CDDFE6F92CB4EB962A4668C76990E2EF29798C8941D67DA50F4A5F947872460F1F6FDB794E8A29FD7AC0C0D6E0714339CE258FB17AE8535221E346 +310 +4A10F29A0BE5EEECEFC5675926A89F49EB14E5711894F6586DB5754EB4D5DA9C6437EBA57DB97A24F8B8A8DB295FFD73266CF57E82694DF5166DDF93573AFE9C9C5EB05F8FEF8E89E0A8917B0B3724DB7BEBB53252581EFC3E38166BA131A3A5B42FE2AD00A769BBFA4458D2DE7822F8C4F63D0B8E21B285AE13325F3410A4 +310 +35C648C9D91FDEB109E73CEDA7BC796961AF79175E1A3CE4C5E8B0D009B0C9E29EFD98BC8E97554E3D8D838424E270F7685226E5AC268E021EDE3522D583E8F20C0BDE2959EF5BBFEEDAB32FE9A3ED0E39F211A86AA9DEBFC68182EDED132AB8BB794442D2D7EA9F4D8BFF52BC4D55BB165124EE90A42D101E67BBBB292FFB +310 +4F66DE5BF2300E44546757DA7CEF8932BFC9F92A0707953491A55C5A5ACEDABD9FA1AEA01BAC3DC5A24E22E7C47213493996EFBC369D086ED9397C9BA6C7F9855C7BD5E15BFC8C53DB3D2A4C5901A57ACD2FBDF4D1676E95338B2C2BCE65FDA4C64F5FEFDC7D9CA5AA1FCF11E193F2FDABF0F4916BF13FB916A224380E6462 +310 +A9815F2300DC49CC4DF8D055CEF76462F4B7A7ACA7365C2707891370BC1D0AA6203C44CCAB47084FEC057F27FFCD57F89B8B9852459798A675C66BD7CE572CAF71FF7E6D7ECF7BF62CAB7B3F1107DCD04DC55A3F9961700286141C94A4F771FEB2BCDA2DDEE0CB0BAE402FF1DB0100F8E475DFFFC074DDB4498AB7D186A765 +310 +4DDB1CB680D703D47CEB4E72F8CB13D86A4AA68116E26387B93FD6738779B34FEDB8E517EDF2BAF28D9BE93A1D3A164320E07D42E514725C29515FF361754213EDE06B079A2E99EB1CA82A02019EADEEB87B9AF005BD04BBCECB14AACD6B24EFBE482194A97DD32400685FF1FEF1544D1B14EFB1E7F08E6D2CD2AF52DE757F +310 +35F5E62332EE88EA26417AD5C3E0E319D68F25414E6D672BD4522B278C57D18F0113921C71D96527E091F00CEB15AE23D24667369BD3CEFA10C7549D930890BA4366F4EBC55F01F746109F98ABCDD5A3EE06619C6AD78AB33E48E4C9BD21BF7478B385EC05C4F8987E2C1BFA0A60EBEDAA0AE0B015C2720ECE180FB6F2DEFB +310 +EB822D96338C9B63822183BBF52FB664A7AE57003AABCE512BF77E7E31BDA2E739AC4C131657DDFE5225E900FB56CC01CDE808FA4A9C9DA16AC679656BB64796283D89812B8F91136AE18EE5EBBAF47A73BE3C153EB5D4CD491B4BFB4FE690A4CF9ABA08C0D8AA64220C438C96EA6CA80B58FE5761E697B3A292EB9FBDA502 +310 +FA863E49FE694ADDFFF9D62C520BDD1A22014147A0903A05B9B729F54272EF83931FEEC057BFF8C8055C279EAC5C0011804828734024F6CFA319C9CC6867C727184465CD9BB7ED386AFFD96B294A85E3802FAE47244602D8273615952B98C4F23F7EAE4463D9D638EEE374A20959E0FE519C4CFB0FBFE5DD80C2AA3F9DB961 +310 +D1BDE57E67C9BB47B77FDD74EE3094F5DBBE3EBC1FBDB7393F5116263009E2EFE1AF9A221B2AE234C902BF70E9F280ED23E73908361565964333A3F01D0B006AF32FB21AA1316744B489D5A198E85299241735BE559675130955C902A8ED761FC0DBE337A9F91919AF5CE32AF902ADF9AB2615E00E8A94586BA1A90839B618 +310 +32FF1BFB76D2EFADE6DB1BA04CCAD3DA649E7E2A0219F592E22F2AD54847EEA8E42271011E9074F8F3DD9FBA6CDF88FFC9FC7E811AD1CA8F4557CE4AFC7BBEDCF0114A48689F20A584520C74DA8127124B767E2FB91A6B186246727BE63C9445BAE02088D58B8EFDC0F1072407CD9B89A13F6303B4A928F593AD489B58E686 +310 +9325790ADC406036C591729FE14021F94B7796BB11848D463536A092A26F077F94639F7CEEE2A724C66EC885F4A24F8B8A0AC321F8FD149BA529460FAA34E4663C02D41FDF80BCA222D42F0ABFF8490F4FA74C673807C2051D8E467B47B616072860069A9A303ABA9B319D58404461401B0A0F9BE5E1E151C14BC33B32ABE0 +310 +3DDE91708DFAEB15E7C0D593C86BB7D7EC17BE56E57975F077BFD5525F3A1A361D584A75E08CF29FC7B44B2B3085014F3A5A14517377C3F6B8F37E59514C3B12F8AACDEDDA4DFC49BE5710101534CD6FB6DE15B50F11A09472AABA727F0C49D9CB906B158030065F48EB3625E010CEC1D511D0053474B11E6A4F01C0D6EF1B +310 +ED3A944FDEC70E0DDCDC756C86545910E0DAE3BE3AA9A404574C2995CD5CAF9549226BC94B4F4F0F5F9086F418F6B32346464610D8BC7391D3C3A3CCF04CFC1FB2A3A3170E369511868F14EEA4E5E438CC73DB1F3248AA94B6B2FF33B222FF3D1E7BF146CAD229770158063BCE4F5391964DF62F7E52A7BE0EEA829C9E13C4 +310 +C147D6F93FE59E111DAB5E4594D6F3CD1AD375EE039930EFE5AF2BB2D41C83C7DABD8C659BAFBE8FAB392CA634F3D15C1D945DB4588D2B0824A4EC928AAC565557CA2F0DAA588CA5BE30B3A8004F7BF005E96CBF891D7F18112A3619FDF530DC8AA64954258E1811789B5189B49B4EB53F5F89F7D37523894F3EB57B373763 +310 +C91E502F52CC99EB7BDDF933FC324C4FFC2561DFFA7339FAFDD0A4BFBE6D3A8FF9ED1FC49898139864602458968913181918F719CCF5A68B2EE9C1FF050E34E63107003B8120FA982C21B30A874E4F5E4B38D5A8290C4B8D9BE48A0BE31EA995BDC37AC6DE90BA17D259DEF53605D55922FEB1855D915B659FBEB350EE0DFB +310 +2F9F766E1C84CC8964B75D08667445EAB966015F63A18A9E0A325C1B3DC628C0D3CB60F767FA93CC65F25458BD23C3B312716E5F40E5858AC06EBA9AB14F95DB99147B832069201541D09A0D856E12C629CF32683760EE855537D1901988D5EBC156676C7DD51D7207CF3267AF1D76C2AD867315E44618015313A98B19941C +310 +696FED3461CAA9B5415F918556D7802EDACEFD6B2E45853A9735DFC9DBF7E440F9319F8B6805A973BCCDC76C069A9144E51F187EDCC12FC31B5DEAAF0A76137199B106EC84ADA6B6EE697D4E6A96E007417F4E880E8B1E89B4BCE9D81EF611E2ED8F0C1A6C25F620FAFAAB656A7C42D464880E8AFAB0E0FC8EA38CA9A8AEEA +310 +EE3E4648BA4680270A89743CDD3E6C6CBF34B7B640EFA285ABC7EA0A1E28F3CC963D114A80123EE66D0AF7D5380EB41909B605AB4765B709B808590458157E69BA1378FEC4B48CAF6A730E3BE3C5EF2DD1A5EAF94B1DDE50CEB55DDFD7EE9A16747FA4B223359FB1FCC69E1194892BABB93A1D5C4E807AC0EA2E30180811E7 +310 +7A2291F6E7F7FAA5E0F60215BC6024E48DE436C511F0628B8CA189982D4D83CCA17D305A7E923C37941CFDFDCD93ACAF735EF5F0E61BBD3E6C41A59B242790D3A3BAC7064DCD89AA2E37214EC839820949D5CAA31E240D2948E48F690298974A96BA9A8432BC90F5071D6537C9666EA4564796B7AFFFA548077BEEA75F6308 +310 +3435074BA182F3699557397F71548383FDA8CA34D99B981E72835ECDA5DDDC151315173D05A21F651AAF8524E88209E2E7A737ED1045800CE3390050BCFB8BE588DF111617874C62575039CA588EEFDEFEFE319B739DE2EDC147092A262611E159650E787917885EF08F1F51B8F2D8A71D60B58B190FEF9541A1BE94BC19FE +310 +A50784DA1419FE4B0FB51B8D36B5FF8E1286FD8AF42B91E59A5F1CC4D60174E764045FD8C57B18209B217F89A62252BD20BE0E63E12B8E8F029FE19E0E8D0870F58721A557082250D5424F7909369376E41666C0EE77CD87CB283F661D5FB6D0DA023CB67320A20DC7BE661E9E1BFE5463085C557DA1FF9BE835733766C3D1 +310 +39F2CF9E2208873AD0EB4814DE9E6BF2CAF81614D4D43906ED1C05A3265CABBFE192770FC2F64F940CB6B17B5F166F133C9AEEAD2641807C3E69515171DD74C4A85311513D6EC9530B3B601B6BA3EBFF3B5C3D2F7D69765141E345F75433953D013ADDF8F6AA7DC3F4A7563FE5C7D367937E2900D7EAD0F9FC9A4A1A9B5BDF +310 +1FEF08532660C01C5D6DE22199AA774383E306DAB87E301ABB7D213887F9869D4942D0D71E94B27EDF216A318BD488D45DF84529EF575AF3C3938C0AAA0215B01994153E2054A0B894DA5C784980633AAD48576957CF4AEAEAFADC7C587368551C73DBE1E57C58AC713303A8BFC258BD04B51724459FF9E24C03A81E0E0679 +310 +CEB9B348C0DC998FEB9C6D6EFA77D247CC8F92D3848599ED89A23218D0636465AD1CEEB7DC140D7426F83491C4E8E8281C2FB35240918FFECE880CA2A42D70E8B20B34DF5221C7DB0CF90ECCB9C745B37A47E9B716FF7A97B7A252C1F3814DC7B1E1A7F72B8751D524EF7CAE45B0C73B3BE7D08ED39781B380DDFC9AA4A474 +310 +9C9493DB5BAD07A6EA6C092FBD0E85DB004DF06B8C9D3089C5320187E4CE1846158C7EA7DDAE8AD9AFFF21AA3A1026BB06A259845E8232DEE62811A4DA9CEAB3A4887320B76E2AE5615EAB7BDF738CBDD01F1E64064BC13D9EC53576751CA8CACD58B3CEB0E6CA585BD089B2A59B9FC101403387AADBDBE70586B93D64BF3A +310 +D4453EF9C3B6A93817E8346991798601344F79A83C51F3D50F7BF2683A3605F51A72449489E0EE6169C749C60B1035651103AEE24D24E51A759C840DD3FC6504DA6FE107510E447B43461C79D22970B75DE5E1BD3007D1AFC619A4A4A69B5626AF5E9B8374E20677F77E2F8E73DF868E8BEC7EBA7B6A75EEF7205E1AB9486C +310 +B642741D6E03FF13FCCEDB1C4AC129886268B01CDF4B124AF797CF784F3C6DEC97A17D4B558070A0F1C04DA4FF9BDBF34159AEFE958479824B3F58EBD1E19088A2AFDE501B6F2052368A3FD0E650A179EFAB1561E1D27527077136E652676C3670F42DCB3352F48F6BC86D2C3B0051727E41A0A4DC6CE2BE9D47F5CAA2935A +310 +3C0539AF080665997194ABECD723B08129CC5B6B21D2E7D54CD4D12610E7E9FB8306771A89576C7A982002775ED44F3F744CF21EEBB27A631671D921938AAEE3EB0D19CD1E083727F607823E817BF599DCED5394E37EEBC2E88782E638920CA641EF74D4CDFCDEF06F4278ECB18E31E7E9876F2D783C8BF429C75395F57E98 +310 +C2B495A3DA077EB3AC3C158608B6B57FE3EA48484D9A480096FCEE0DE880950E7E5A5714131C5C41AAA29FD19AE81AC145FA1B9E85C7169A5AB837B213493DF7E9AA4B5FBF9CDEB36EBD21EF1181B7CF7366AB2A951D01E37AF4EB835D1BE8C117CFBE7625D2EBCDB0B20C5A4B45CED0BCDE3EE8DA9DA78C2E21DEAAFFD119 +310 +7E463C33CC6A80FB785669B856EFF7A8CCCA2841B5A5369173320B1170C368B2748D81062EA0296E5E2D95D7667A4A46D98A6FF475C76AAB0A8C8D45E226FAFC9FE0133EA9F60111F99CA81ACE03A1D14C89E8B0D69079C91464FA1D99E651D0EAF606F345C42A8A1AA9BA391031582B96D5B4176A77AADB01F12FD298A75D +310 +EEBB2C82F0B8650E880722F8DDD90C56A7A058E5FDEF1AC93545BA27367C76F5D8F9690CB6F8DAE141EDD7971F0FB548A2AE435F224D6E5954CA155512F7332029D5A1230854B79F258EC5FFDB09F5F6343A8EB193229B7F7E79B3E48F8E183860B38C8269D1B53738FF1E0BACF6AADA6D4869E9DE708E79B5DADCC09B1BC7 +310 +6F649731CFAF59A83FD8BAE4C749D66FA87A3810D4D62FFE826FA400C0F325D2BC0F3CA35F5847B82A2ACC7961745C823E4D863D54460AF792CE6F79B5C7A33B25C3AEA6CDE15A919B6367D781E9D44F61BF828672594A3E1D9EDD6DF179A81F27EAC05B936575D73E05C209DDD2115CFB705DF9BBF3C911589B3640735E9E +310 +618FC9C36A7326C82B2205E244C55D56FDB9D5DC93CE958D4BF8174D2B3DA54CB3C09F2C2A6DFF73D23B2197355761AD421EC581284A7EF8DE2CE3497C0C804D49B41DFD6A7360844E5BEFFB495CFCDB97A4595911CDBA623274CE852B5E278AF0BEB1770E9D37CD977B2766A7DED0E94B840E1E95E8C77DE5CB6354AF7871 +310 +427C28ADBEEF7BA0A28E19DE9ACD75F379FE373EAE6DDDAE3834692F9C2B03B4FCA8FCA9ABE8EEEBEBB725B88A7C90941E850682CA04061A7DF0CE82ED49BFAFFD42C309818FAE14755CAAE78B0F73243ECCCAD7DF58FCB9CDFAE7A4D3E6671A613639130D056C8383C8F4FCF4808C331E544DD26E4755D09EA05253B3049A +310 +1E32587CC25D72E073E2CD96D04A71BBCB0038FEC3F53D254199C3CDC67CD66640269431860225392B2CB3ECD0B4498AAE78BA1994246AE1EE78F2EDFB79F6C6CB6B5E279C4D373FBC21CB2698C9ACBBEB6A4BB6B39E16AB0E421CCBFC7654D81ED86022F9BB522146F9F4772E2D0E4FA70120C95A85A417BF5078B232F475 +310 +4363EB1750B2AC2148EB46B1EA72AD9B4D23ADCBC589D70106C3EC2ABEEED84DFE28E718C6C83502C28E9FDD3B09DA0A6F6F7B72B706A31C9E2A331EE70DA56A083D2177BFD63C55B11D8D949FAFCF90B23A1E24B9B5018B9A17E99F2890EC302F330FE7936E2C23F0D237EC8916F5166AB84B62A21BB0B950146962D754F9 +310 +2CC2C8877BED8FC7B7B25726DD19545392570B3A2C66B58F027BEF412E552D12A588C4A3CC5C3193FCB3485EFFF700D0B9EEC077754A33DC1FB9E25AF814628F91DDFECB667D3F7690803F38D13F8AE39353523BB08FC6760144AFA2CE28236F65EDBB3D5F59A57D612B99E3D7E5F8A851666BF9B5A6658EFC96EEB37A378A +310 +BB0EB3FDDF1303AF32C41BD1C8560745D7BD81066B0D6313359CF488563D3BA3F8E2185D1FAA3EBA1397C9D0E3DD232180A4DCADB6AF25DB3AC3CC26589C8239E66E81547BC7E2F328F9884764CA37EB5DBE390DFC03A5246B456A7C3684C4377D0EEF9A350680FA9A077E8B2957B489E09AB1AD41316944C2254B5984830F +310 +691C93CAA767EECF976FA5647538830B5E63236A6F0365C1E0F3E327F24FE35EDDBD7360B0F7374837409F7A6311BECC12B7026D4FA1AE204FA3F5FE904B1F420C7A9044AE58E403E7C4848E7DA25DC9EDAC79DD756FAA26B9EA83ACE8A214A2612A22AFA19DAE7DBAC5F8CDE1292A485F98DAC731A29FE01A3966E96BCB9F +310 +B5A4C8830E1B9AF0C54B87FBE221A097036E5D28D1F869F810C06A9BB0AC0612F69AEBAF893C6E7AD59460208BAB3A6B09CA4F6BDDAB8CF0D1F84B72487498395A70DCD754796E271099A567A55CF242BBED64696378BB6CF25EF0FCDB589F3A82F1116D010A9AB9FA4358809EE0C973A2B4229BAA876A70B03AB49CBA7FBE +310 +71DB1832FFD32F9BF2C8E98075CACAFB36921235D0F552B6A67E94E094097A5D7DF681B40623DA535ADA7A9AD8E9A62407D7C9A5AE51D62A17A83A23728DE86BB56115804D3F2675F2C6B87541A0FC497BF0CA86420F558C9859EBE9FAD62747E323E3AADC03FDCDDFE7407B09DA90B44E3185723C5381EAEF589765E2F7A0 +310 +99BA46695BAD60CEF3491B8A171F5F56444F7CE9455312C3498C74173AEA0214EC029833B34B650CBB5A5508D5091665309642791708ECF27CF11DB3D797D6CFE474453BD4BFADB768094B2147814D81C7CBAF8228DCAF5564B5891F5EF6A860EB2E323CB82AB868D8EA048880D63B0DE5341F1B3D53297B928A11E15F25E5 +310 +148990F710747C3509ED14909C552EBE3973246FCE55D00564D8310BC3EBC5085D08D806799B77D6C57FDF5E55837F9E934F09C03C729271B64F12D0A9F9E938107CAF7BF1EC8CF1BB9B42363D33070F4CACB425E605DDB0A34A12BB936DE286B9887A4E2E558F0886498BFF49C311D96C277CF6A9FC0D75AD0ECC5226D7E0 +310 +E3572012E56D7FDE4410CEC7A6D0F997F225A2055B8B92E3956E875AE4993CB3D4C9063AF98FD45E1BAB165640A43439A2C9181A7C355B297C0A0CCFFAE19B6FA3C2BA238A885FBC98B8F6DAF4B94B97304878DF4D8BFF6A4AEDF79E90ED4E68F00CD7299F4E0D83E35601AF0CBEF18C1E870E74758FA91F5131075004DB3C +310 +FCF09AE1DABC11957FD9808C92E8A678CE9DBE40DDA0E0D1D505AAFBCBA208232BFB6FB3704E8985E61ADE53DE74152F5C7DE74B1DEDCD37DC3E8FB30D7EBA8B9EFFC3606BF90B8D438279CB7709FC7C00D327F41817EEA71CD9E49FE9C8C9A81C70915D9F0F15958EC5AA4C3BBFAF46986B90F188BDB24B89DE3C6F29EC3E +310 +6F66D84856EE96A35E657CA41B41EFD8DBFA16AD6E91439EA92B40B21C053249F51AE50EB0B24C770FFAF40DF12C8436366819C48E36547D4E7B72FE2BBF10828583057814A668BDF183F6B2E58DFB151103E56303E63D00F39C27C4661605DB644D19175D02E3F0241E8FC3458D17AAAADB117CE9EEA690D0DB01E634878C +310 +04C2C9310B69FCAB831151E3AD293BEF6E46B0ABCECB50F5939B39C613D41E81FAE8D34CAE3C4566B761D37185FE864AC32FCB71AEF39DAA5BAB88609BB980CD2F07A8F941653B529567726E2B2A58D079DD57E1E5E31426CF184A04D7B03B985F276D47406AF276FE32E3328BA866EB1FD39BEF57863A317634AA5FE46740 +310 +9B3873F5069EF0D71AF00AD189D17BA1974ECDB2D623E9E14086386A6AAB5807825C37B015BFE892AFD154920EC437EB945D958E18622E1E0FE2FA06557CE4BB9BBD05A2D40E774DA2B65473791E314B7D6C886820B2FCFE4AA272B3B8B15E7344A145E8AF919B5454D89D36D413E87211186BF6F645E5EC94DCCE8E1C7990 +310 +22E4EC9BC86D7A0F2AFB1FC4B2CE48F84B3F49ECE65BAD24CADCC07EFF39AB06BE9E6B27A059938EA7208838455383DDBCFCD967DDE4650EF92A14FF933C975DB20BD7FEEBC4487576485963D757D16340D9599FEB362F775F38CA4185A86F74B629C81B133644A3B18FD056E997E0734F9BE26D11C0AAFC669E2AC9FC41D2 +310 +81F8B2E1C35AE27BCE05F3DA295488096688719D974DF1B5D182DB7792288697F8763CFB43562C1CDEE11C4AABD5C22B6E33F6F389CFC3477C5919DC0D533F8A7A1846B7ADB60ABDC26C43D0CFC8910B7C15EE22675BD02702ED39DB77A87A192CA7A543E6205B979B9F5A6B512A3CA799E198B2D5797C0AF8D1D872CA455D +310 +4D431A7879D4DD76110B5FF7F4176B54477A9EB5E4A5D11214FDA585F0382751D8E2582BA5D2075053298FEB8F1537304E32CB22C2E2B693DC0A760B1DFCA751BDC445AFDCBB12820ED85F6D5298933C7EF1211E9E057684056CDA3FEF3BB6052355E162DF27C59E965A8DDB7F9437EB72CDDA3A090DE65A5B2F50326247B7 +310 +EC67B73ACC7047FC9B353D6EE990F772207C6EC52B39DC2F3C0754821DAB559F7C1809C7D0DD5B0499D0442E0A6E391B3D5DB46D11E731E6794EC77523FBDAA242ED67F9FA1FB684255671F63C9BCFE6E7E896D51001A6113EF2298BA494E0B0980D1FA5B9D60796554C02256B1E0345FB7C6186B6A19D2DCA270416262845 +310 +FA4F42DB5D9B0A0B6F07FCA2E56C242522E001E0CEB734AF310FC8BFB57E38E03AACE93B3326A2197891E68452B26A6307FDABFEE0223D3C03C3C73D2D5BC054C8311F12004CAE488E5C29287F8156A2CAA4AD0D7BE6B97264E4139F437CE73ADF52E5134C52B819777C18B323A5924E4F67AFD13B1CDC3A61793A79FAF5C0 +310 +CEB718E441071E797C6DCCB9EFCF7E4A1E9F3DF3C2F9C9E8294E9586868788C972857FB0556D7A26941A7EEB9B580AD78EC40B85DC906B4120A563CA3B87797ED612D3B035962BB75F6515A393F4CB057F564CDF54F2E5A024514E6A1F901DB80C7E70B1FC05E356A7E72FABF4775B3E3FB8F70A2A8E7B6A94542BB3041579 +310 +78CCC999E707A29CC795E67A59CC546A5D47B368954CA7B6E75EC9E1F6FC73CE3B4F71F8CB1A6B15923D61EAAEB14626AAA9D04A26EB9C66CD1BE86CA31A5D8703DE04CD1EBCE4F5CCB62F93909AD11E55799A781A956ED27D3E738F4538CB04ADABC7DAEF20BC35BA6A90BCC1DC61BA05F20D8832FFB9BC9B15E2DB838A0A +310 +FBB7E0C82A562127E0446894F427794C3037186F622BF24DE1B00AA10A23BFA47B2A180D6DA5AD6F7326FEB8C100C2D7142EB2A0B2A9D1008A09BA343D70E054AB69E2A7EDB783CBD92537FAF4A44D1296AC2792729104B3276C276C951D0CF3CF3F1E6B017720A7B8505A5FF9589365979678C36F1BAF759EAF57C60F1FF5 +310 +FE9DCF5F8E764FF25A2D06CF25117A8FED51884C13B2B750DB0E4F5E3D64124FCF11F0AD8F1CC3B3DA4B38ED22BF1CB2585EE6BFEBBA63FBFD44F9537B9B37BDD6B358720F98A8125AED4349CCFEB0E135F19E208A04CA7E2B10711185D72E381F448036EF57C9F874E54A971CA37A639E986AC0226DC5AA00D5B14FEA9192 +310 +234B3DE617814FC221C92F33F1D392F297731B567FD98FD91D989379AF567A65B8ADF177591DB90FBEA99BF4ED11B16E50A0AB1DBF1156D034BBE04D69D08F939D8E92BF525A1DDAFF80ECBC95D8F45D3FDD289FD23F7D8B452F8EBB34EC082DEEE68E18EDDF072060FAD2AB1504DD4879BF67099FC58F6E9DEFBF645D1633 +310 +4A3E9A127C6F1AF6132CF287E433B43A0EE93812B4AA0B194E03EE3FF91EF9C6ED0074A2F6EB18A96A7C9890B1FCDA1F2095E944DB911ED54C60DF16FB74C8CE9BDC7C54C4F14078CF73C2FFF1F1BBCCD7CF64F71407EA1683DBC410D226627F66E509060D5C0AFD611DE681D509DB8DD12FB1265B03C6F5C55FF4C30BD941 +310 +87FF692B4F152982502F193098A789526A038AB529A52BC0DC4FB6285CBAC10A8B8DB75DCD9BDABBB221E27D1F8DA9F5EE5926A1C8AA9BA9BC09239C6A7F12914EA3B59EFF7AAEC3FFCCE56F1C5C2980AD072793527565E774F4631DB178CEEC691B1F367BEA0CF2907E1D6B854D527065979DDEF002D8B2ACEA7CE21AE297 +310 +737EDE1D45B31233B73FEA35BCFDD0B09721FDBAF6432681F2FD10C3E9EFB246FD6E854B344ACC9D8A0EEF876A7314B49B6A499FC650B099D650DBFEC7638ADFD245E2B7839DA5AC5F7D5623FC0ECEEC8653FC9B2BDD1AD4D00A566FBA7186DD3C9CD26512551637498CADB950FEC71ADB9A5524285DE328B8EC7EC75D2345 +310 +AEC6A27C42AF6D9B92155315C80DBFCABBB7DA0FAE425C4FB896B15C9BFAC4FE67DF5A23D30C93DA4D8F36FB15D100050DA6062E22AEC5F4DAC4F6FEEF97AE05DE63C6D38F9E068A20BC5E3B6BCD75320C5090924E7F93640A7735B1A43451C2D91EE62C6238B797349E59890AF3A520DF4A87095F3ABEC2E61F0593416CE4 +310 +6CDCBE0E00E2523FCCD04408588B5B6C9DB6459C786CE378770B54F51DA3A5E1DF2A3349BEAAC234A6CF8993B598B3B25B4B113E353A4D750FA7534749430F3EBF2512E7EA3FD43C6AB9A1F765A58463B306721A15D224FAFE8C6F5E3432D57EE697DA0DD5A9D4866A8BCCAAA9DFF1F06047851408FEC060471D0E3EB86BFA +310 +F46E4B5E60311DC3E3CEA147C75CF971BE74689507154FF2D2E92F15CDB2B9182B11459FD8ACAC5A85F174BC94A7F4EE4C4F5CAFA7C02E37BF0BC77B7DCD0C047BC316F562A01A441AFA7931CC84A28CB9F4AA367EB515778F29BFE61195FA671F4352E5E46E3A64773F1F76EE5EB36D7BCE9BCEAD837A4D77F8DB8C92AEEF +310 +6711920D7C7D0F76B0FD0EB7AB83724CD35BCA7D36DDDF5FBB7FF6F04A91F3E2BB3B8F1A43CD659639507FF6331C5F697BCC782AE57D757A27D97F1AB9E0AEDB43F92F1B2AACDF5FEC8ADBAF9F814E2027003BA2F9E144B1938A7A04050AE5D953B82C45619165C813A10E76DD6906B3C16438064D14CDA713AEC420034613 +310 +E1AC7241904A49FBF87409AFB1FCB9B72FE7933A87D8FBEDACECE4EDCF6A4FE9FB94D5DEB74FAB7CFEAE17EE965DF980DCCA2D04134994F9A2E94D2265DA4FE6AA7B7AC15FACAC2B5ABBCD7DFF542C5314FDAB2E1B9D8B65220B8E2B638BE008E8DCA22678C820C5053664D15E89F2D735B51A84955E81A443545E9FF787BE +310 +0FA700A76D136C27DA179A7C6E8939E01BE756E19CB292798063D8B97F44E8A27BCF3A614FEB8BE974A718E86F7EC99B53CA35FA24F61F2B07266684623A3D4CF073E511F3A10A8FDAFAF0078C577F4DF9D339FE7862E5FA47CF4344209E5BF76D783B1B48E152AD281B15FCB934DCDA49CEC5706B1EE8B460F435324A5CAD +310 +154725E21AB8044B75A557A79F6C807B9340AA3D92503E866F4274412926C8C504E4D5EF8C0E68DD0B028781A6B4054F11CF18941F101C57CCC796773ABC8DED9CD91FE5788806C05E548895354AEAC96546D55B0E94A59F148D061F42AA398D822CDECA4CDE79E13469BDCC44FDC990F366BA5978C15F97ED80CBFD73B0CD +310 +9B8B83A4F6845BDFBFAF034019473FB9A63BD55D3A4A7284757071574029A4D016DCCBE95DD679EF3C4880BF169422F8C89B68A988D2B13793CAED4F74DE7486C72DC94DF46B8E93D8E02EFCB1FF9B3BA10C8AE7F653DF94F4AF466CDDCEF5776687F7C4C14B63E4EED1719DC2EC482D7DBCC7AE518CDE3D5FF86D39000EC3 +310 +673C0044E1D308BF05F74D1B5F033A7D1BE7F74BC55BBE3E4A20F920946CA0271EB17BD2B80E5660ACB8A29A79F5E4BBA49D4D945B6EEF4A7EFEAA65E5555BBB57A7E15D2F83C69F30BBB978D2B39F1F26FC097424D39BB33CA3D87ABF64E307938B685BAD44F71556A30B7E712CA4FE600613C3E554B69ADE949A7CFE214D +310 +F1FFF05CB82151EC6FC31950239F94CF7AD39DC473F6EA356E5EC2C4EFA04383CEFE985A127484366FBEE3C0EC4FEA363E4F6632AE781392FD45D67B63FB8DF70E82734F431FD3443172A0E30A83D60982D55FA6A2CF86DBA8382502ACC6CDEF3B907982EED1160E14863FA242F9757347E1544275CCDCF71ECD5F8621CBEA +310 +326B8BA884AD7F825781923E66971F04010D574C541D8427A438A37EEF8AF88240EED7362C953F5A81DE2D56109CDE2524EA0F5D2F87308C0C47BD97AD605878AB8589E17CA83930CB5E0DCCD75853D68B1AB136594926DE426C367428E9334AFAFF90ECE221BB5DC9E5BB53D2CDEF954EAA9CD814F0E5725F1965DD458E5F +310 +8487683AD86B9D0076BE3EE91EB1CECC5331A67A3F79D8B27587C12EC2A6A70474749F27B4C0BBAFB94B34189E88DC18964BE4121B9137FD516C7755356E50D6714E31ACD222621EBBCD1CFF81D87479056A9DA0E0B8EA19D24CF937DA89F78E8650CCBDC110AAB6C4E46DCBFD0446BA9E2060E81F314AC2031E88135E5476 +310 +AC0160EF5C0DD2EEF1B8B5CB2D198BE9A554A9093B5F7D7E4B5407DDFCA1D9C152FD97F55BFFFDFE802954DFEC8995B983E28AC92F1971E28F531DC407A1280EB125F64A72E617EFB27DFBCEDCDA2D6F2BDADA36873EE46B9BE1782998C63DA7182BC383FB6FD15DA472B175D7E8B4FC88F38D6E07FEAA3FA3CBB8F6B449EC +310 +F14D2175E5C5CCA3A42ABF24CA6C9395AF74535F4DDB38CB84F56614B0FD32D7E19CA5CF988DB4449C40E23DF2B16772594554579094450F56F253CD25EA7F77366FB1CFE79696C428BB64D5A41E986189427E5E4D7E2DAE3D5BB4972B3DC44EAABDDDD63A2264ECFA5C786EB0EE1FDC5BE34FA0F08376869FD1E61EA06CAE +310 +15191135445EE8F0394161C3266A596AA035572BB0C8876180D8B9C77D307DF9DCDF7365ABDA6F3A2366436457A4DFC1F259666569A9E4A79C817BDD5610E5200FCE894A358EC72858D50EF67545DA81C6FACE3D457BAF70367E1E6BDD9A952FB4F56247F58F523B97E640246051A72F79BE42FFA0B3187F63097F91C48B86 +310 +8586F714B0EBD5A00A8412C1236E1298F60C72BE6C96B82B404CADAA304AE9F303176B14BA53FBC2D5E86377E35542019E24D97B96A49B2C0E5006E7795954DEAF5DA58958839043C6C70BD3B1B33B6DC5C31316D8A6923F142E26F6711A2258AF977DDF1F970ECC4F1F2B434B96D81BEB6E89BF3BBB3C7C8CC80505070723 +310 +D6D7D7D3C8CA27CA164A26024FED94B12A86031867241DA53D929636DA600F050BC40CE48B8BA03D69224720DEE2ED2127390BDF5E6EA64527BBD4C4C63FA76BB801FEEC0E61218EC98A8A86CD8313781617877E66692839B9468CF4DB8E50A73986E558E309C0D6F676F921BE02F1185454DC75B111572FF9D27D85EA14C0 +310 +5AD7271225B391C4275659CA4A43759D0BA44639BE4C30A84DB7F67A41399FB95CBF923B1B1672ABC807C1590557FB18D811983FA97F5E609516D62281D38B1281F68FC75528C35E4CD7DF0F7DBB5E5DD69C414EBEA2F4A5B241B996BA77AE6C772E367408C633EA3E438B68563EA8BE7667F3A4C4FDEEA52ABD78A2820F44 +310 +3B7914A15F180018D231E64519C56A12F6CA7C8DAE18F06F6B769DBF92329EF6A5D18FBCE600F36BD8E8198DE5CDAA96181F3A756EBA5501CEE58F497EA4F418A2B82565B9BCC611713C0B708CCA1F398B78C5F7D03B3437CC18F13061F55C7BEDADD5ACB1DB0E36D3CE47FF6EA1A563A57E9649EA5D14D1A50EAA45746338 +310 +1C073777EF1600607BAF6428A7DDC079D47A204EF1E1F1F0589FB054D45E02CE623B3823C317C601E589E22B1E88B448524B86A82828E32345EC18AE17214B42F8C41C6D428016D9BA31DDCB47572561E5A402E5BA4FCE55F78AF7268524AA04130F3A20BA5D1E9F9948EA48FC8F5314FDB35E21898198060034ACE5E3E2BB +310 +CA34092518F275190AF65F45867FF0900AA4B81F25427414C372BF51DC29AA855C5428EE1EEDD6B849AD60F6E73475C2D2523F8A2772C840A478DED18A8C5C91B085F255DC4F54EA7AA8947354FD17ECF8FB3B4A0DE7B483CAF3A1165EE05BBA413E5C1FEF3DE1D6B8F2F3F3251C17D9FCAB48A9AAF63AC773A0E413BBC46A +310 +9ADF84A566FC920E4D8C52C9769EE3301CADEEEDD92B312E02E2A7E35B7F1E7D203FE5A7D75578B06E99D0E46FFADB8FDA17ED8880390DA29094202D6F645F76F6F96409CDA05495A96E84B04A00D1A6C6C81D7F3DE9376E828CCF144EE50E364ACCE7237EA0BFB5607ED1AC3CFE73794E5B61739D14DF1743441B1AC98F00 +310 +ECEDE9E7301FA29755FBEFA68C91EF2B314CF510537EF1ABFC9A762496280B546F5EDAAF8A5B387D469FC9A878A92828C88608453ACEC6094DCA54111E7AD5B2663DFE2DD151B7F123B9F4FEF8EE36AB125FD7AD52A59B1B765B3E3F73E3F73DEA93875004D2AFFE3CDE9493B87A9904DD052028949041A91DC0E1BA47BA71 +310 +4343FD5B25E0A6A09503AFC17C790058A836A324FE31D7893512E28230332F153181C1E018E1124EBE1BA262AA428A518237D1EA7275E48C5C561BD19ED6912EDD8367DA0F31E40EEAE9683DA9257525AEA7B632B97119E17145A90231768A4F3B9120FF32E8319B015E3CBE26F1A754177D8185345817C096B0CE443A955D +310 +13AC15E1FB5DEEA0239EC32A3C92241A237B0B02FFCB4F93CF2B02570C8D192E2F2A68CEEB42F12E6F8CEEFF46C251E22B4341C56FB08CCE550E7A9D6F4D6564B9EA52A83D5BF2C6E03FA19D2775E15A8A7ACF30A13C259EE7F5EF2AC3BA354F1500E1A07FC63FB4686E2F12EC69719F9ECF5FE3F35598DF2CE944008CE78F +310 +0911BA5BE5D0A34E0098FF038669C26030BA9B300303039852A8F6F496341737CA7E0D2E3AAA36DE0A70AA7D9CA0D54D9AD84B18A44C9E625475E1C97B93BEBB38DCE7D227E67A2D00F531436136355F243D1FC0D712C5765754FA22FCFFAE97919161F46D6E61ACB37D83777B385889770B50765B127768443A5C4D3A4E35 +310 +A3151111F146B7CA116F918A049F1C272F2CD7976C69A0068773451659A97E0F4A08FE604C3FF6CE7385DB19F4D3ABC8A0D80DA8A42096AC377DB49382B36E270B6FA7570188B4B7D1DDDDA82ACA487CCA5D38BD3BD039ADBAFA4ACFF27809239B722081E2EB9F145320553D183F28471C388BC8814E5045676767AB58A8E7 +310 +2D1CED2CA731379515D2E533E4CF0B39DFC625F97AC4836831A4DE330E87A8126FA17EBE583D614E71E22A8DBF2825599743E0640037303E8E1B1E8EA4E1ABFA4F62907B5E4E0E5DCFDAB417C36C6848A2B8D15F1E505434C0707E16B1CFC2A1C36A42BBF55734F8F4301DDC2A5110CEE8C0747F69519265012A2174BF4E95 +310 +5ECD4FDE095A3ABC70536B7AD0B9D7892F81287D667C108703C9B66A221E9A0929D030008F77FF6F5D2108EF5706FC87A3069B976F7C9FC3239D13D283CDEA83597461079FEEE049B0F392889898B7D87D31F8D60ED7B56B91823A8E4EDDB36C6172ECEE42BD42B12CD67994FA0F0DFAE9389356F4CD4425A4B8AA55CBF7EC +310 +D585C6A2C2316791E43D5C06841ADBB226A87FD61515FFBB0167B74E56BD7F3E90A57A97FEDCBDF483A0D6699A8C0849CAEB4947FAA90D8E332D9DEBD9F4738C37DA0CAF9CF3DAA68BCB67C420459C7A64498D6E848E5CADE92BB6901F607AA331104A67F7CD650EE169B1DAE94D2C31C2F993632259B6957E246EFE4C5481 +310 +107B07118607700040E5812A2A2A38DA0076875258C753F60E0F2662C748EDD5F3F3686B8FC3BD6FE631ECCAA1E8B1C1E565AD117E54893E79F4672E32F9E46B2A183DFDCF3EECB4D4D077DBEA0DA254D256045109670050129E9B9B1A9E4A11344D61497A992778095151510C4BB7EF2009B63AA4360635312E4CF668B7E0 +310 +D3D85A8688E2BD83B569439892ADB63461930D428E0DE42E4AC57CE48DA5CC1449530D8BD3D6A0F08DF20C8E8F570F2CFFFCEA286E30969B4654D498A58D9DA782E19FE3F002181838925592C4A1EF0B332D88B61A92C644547BED490D3A42CFFAB70E36D961F694F6307F72FF15A1C79D5B1B5CEFD353D9E2B52566DEC8F8 +310 +F09CB3E8359C0F68EBBB7898243E34E12A015B2685AB303C04D3A9E00671FF3DFF0A184E4A2ABEB2B666AF5B3A209C73D8EF29DCDB069C9F391EEE9DD0F74C7BCDE16F8F02E3B8816122BBEB50665775B600549D8BB33A54A6BF940C028118BC6878C79958D6784F9DEAA6E26748386FFAF8D0D0101E2847B6563F5D8DC1D7 +310 +8F787BE00E2C941AE12148D4A81599FE8F68D4DA5CDED75530ADE138240300EE13B0FC13A1687FED6774662C05A2B7DFB186732F0D7A93545D8B0A6390AE5054D9CB582DA846D7A061B3358B45E2B52DDE63E114397EDB7E09233A579155D795BE76102CF277F4DF8B188C4F2879BEAE3C08347124BF9ED23B128259FE107D +310 +31E34A652F090B30B39E0D9AF917B76A2806135621A8A60681F16AF216514BBD02711585B5B1AE823F4B2D0369EA5B6BCB863072727FFC871CA6C9AB79A814D989057E9470A3D238BC87CBB4A0EA49AFE2B86F22E499793CAA9047080450166BDC357524561E258C77CB697F1ABB32562CF87C3B7AA12F911EF19FB3E5C022 +310 +22E887D08202A60A17151F5E053A86B437771088B4FEE14D652C43E8D939995D34545E12E2FF93D4E366EAD25F10CB7DED141563C3D0C447AF06078A91135FD4B9611583CB4BF3BD167F2399CEAB0E0100F8FFC305F00F1711717173138B4B48C83022295DE657EFEDD97F238ECA80F528AFB4F8D67AD4E2594B9A3AA86D74 +310 +87EFC313BA95745C186F0CDD84CC12DC9CB0424B42203D351FD9C0A3AF10CC7F388E0329279087386421F194F4A29464C1DEF7EF8BE515E4A199DCD5B909342598D8FE6A231F71336248D5CEC5454455C7BE383B3BC7B260FED27F703FDC39A170F3827239102F59D4DEE7FEF82EAA1A5E4BA94DCC4CC44E7E2EDE20F24244 +310 +E4454369D2E87C3F823F193FD4FD27E1C23984B01898D15ABC20711A8A558054951A6AE280096BA36CBA439603B7B0B4C45F248C14B2383B4B5CD8ECECE4FF859AD4A6CA2326D020EF090F4041D28DB66E5256B56232CC52829DE8060BAE6BBF37D4B7469B822454FE93210A3E8D15E69FFCF01123222611C51C4FD0A041A0 +310 +CFA2471CF6BE38D2B20FA4C3525B5B8B586759475CF80ADFE67A284AC24651E52B8E683D24C2622642F27E7C3C22783BCF63648BC398BD6CD0D9C97E7E7E3EACB6126DB7A6C1FD9F1031302EE67ED96F22B77C7CA0239BB324207559B105E4693EB26EC46182A92C9C0DBD24F29B31C5C5AC1F84300E0E17CEEAC15F5736FA +310 +4A98971E2F1423BFCD68D4BBD5F86E74B40A369F39507C88FCDB35D2DD8DEBEA4AF999E23CF2816D204200DF6E611781B9884C1AC8888B4DE397320472959434323A5A36B22A8E1F721A270DF3BD65E4A31C11FF048AFB0D8B7B6881423793963A23CFCE935FC189ED36FAE35C46DA3574C979E4394BE52084F3081837BE03 +310 +3396D2EBD80F1F691B273F660D2B32FA27A33B1DE4ADA5F0FA4651511102DFA410DDA86E065272CA314D5249DF91B06BB92807B28EFD54F08F7B525252782CD276DD02E6B0319B13DF87B1F6361AD6B28FC0D711CF445429B9E5A433CD493051C49A9E03DDB1B147D880253514A5DED88009018620EE229101C041950E2D8A +310 +A8E1F5F5438E0C0C0C60EEDEE9E3E0C051948C69F84BB7B2A48A1F3C8EB9E6864687F4F4F6F6E2FA87608ADAF9C5E117B7C1A737E710769DA1D15ECE6A84502AA855398712D5FB51BE92E4EF824969CE18278798AC303C2C7980DD2500ADF8FB1E6D6C2FAAFF9987178923334A244758BAA01A9A1B70FA5097D01E1CC2A58D +310 +AD0EC94CE6430AA9B0C10D75E3C6BAC670EFBADEE1607739CE0FC18A2587E3EC3058562014C64219C69BC269082D5C2F4A659278D585755912176D78A1B641719C2A394CED495A1AC5D583FD2F5EDD93308EA7EAE8A6DD396598BDBD3D4CFED62D18A5BDFDE0DD5C80D1A89BA62697B131AE7CE9A6594138582F1B37D68F17 +310 +49AC7CB67A70695D48A4A056AC1FBD4E82358CEAE38F54B95A9016F9000F9879C0D9D131768635CA798895F27D6F1614F11F2838A83611AA51C9A3DDB539D42979C29F10465655C17BEC3E2760B80DCBF609198C7B171A531905F3EC218D696025975E986C6B4C666666787271F202805577DD5A3EF0CAFAF91B62DC4B2DB4 +310 +217A7606282F31D8DDBC290593BAA93EA6353AFA9939B877978481E15CFC9F2755CDC00C538F63A4BA3E80AE009B73CA0185687D339A507D165B74290D6EE46D5C58148F020C3668ABE9EF2F7549170DC80DB41FFC1D678FF19AEB10C7133832B104B80F691B3138F97A92D26E551F1F110400D45AC71A9B9AC41A1A1B1DAE +310 +ED0B0810638EFFB511D46B121C6FEA0F38CD00E03CFFEB9713FE1F9C263E4DDEC160D0497FA9A132BA608584057FB15380825DE6C7FC3C667EE947D488A09898D8E939C4953BF4EC9836344D2CC6B2971B2A21A572B06096F4982D2CC7232D46E8EA69DC83722E267754D777F97F96D5DA74E1E75178084D0E70F12AD33E5F +310 +71EF2AFA599B2BEBF60A5BE475F444F240CBFE8143C334E7ED9150FEEFEF7EB020C4DBED388DD1D6A167A76F3CBF119B578AE8CFA7736ACAFA4440A1C47185638A0D2F3EB5BDDEFDA958FB13CE4AA4923D5BF840392D45544329525749734866684BF7F8D9D54D892E19BCA9F829A0F8BFCBEEA6CFDFC61D74EA1A645ED7A1 +310 +FF241739F15694BD835B66FEC5677E40FE69BD3F91AE402F036537218A401CB0828604AD189E575015C6F3F6BBC8AC79CF6C70653847254B31551EFCBEB5978B9DB85D58D0FA41A9DFE9C624834AA04949D24B5AE6D299E072D152D12097920F14AA92C31C3B97E28B6C2E6E9C23D63043ECBBC0A60FDF5BD0C33C127EA8A3 +310 +308B8B6AFFEB28B4455BF6E7AF4FC6222728357EB8E46E85053E6A8843AB23295BF838ABDA94658FEF3F832F147046B4ACD6F7D48E8FCC9618582961C898D1C7A253C91BFECBDA6BA18A1FCF70755DCCEE2222F41B76D23CA98D0CB3D95F0CF66D57FFF65E2A90F5DF40FC3187621E981BD0BC1D992787212688976B94A8F0 +310 +C6EF646624B570B78E068826F87CE27041A95C9A4871FF65CC17FCB1639629AD6A9517000D63AC231F3D741CB8430777E7ECF80CE7A3F73C0F162F7E56AA71F4DA80A33CD6ACEE78E2C11FAC03DAFCE929B106F8F6B3ED2DC71FF994BD9CEF4E9A94896EF1B6E2B7B4978168BCF0847A3D2A4F1B0500B926E634B174548E9F +310 +BAD27153FB5FC36799B1E6C7C38A94E1F9E996796D7B485E635D5287A2AE76FBD11A341DB3F8F810385BB7296391973DC6C37C452A9305AEBBE0CDB6C566DB59FE60C8E94B1FD090354BF51439FFD1B48D725228FFA88C84F686F547107A7A235665A4593B7C34D748C1AC8D56DD5DA861EEFBADDECBBD65117E65851F9035 +310 +1AEB65F0EFCFF4BCFEF24CB7A5879699FAC437A7AF9099186CF006BB923A59E37D7A36A29FD8D2CBB914A5E9D0A8FD212FBF796AE9A67958FF1D5D8B85F78F96219D4714EB919929241B6C018AAE0C02069F8D919961EC5DDFBFB7573E3AC8E08BFB7C8534BE6DE045B3CD63DBD71F7638BA2309CAE398EA3A7D8F98BBBC7A +310 +EB367EFA5CFA27A9F8F2F52FE878058E6209C026453F8E7E8A237DFC389E9590F6059B397CFD65183BBF936E86896612A5B5FEE49DAC8455D026218AFEBEC12B3523A45B57B82C9C97E02ACFA1184DB18E48CF615DBF39C3CC6F0B4AE6BBEEA2E39D944E290302FA3DE33FA7E40296E2EF7BF1CD855BBECFAD0FD622BEE974 +310 +6FC4FDFDE545AE6E3BC234CADF83A0FB76DFB145BF3828AE3436368AC24989454F8FF70A177672C711CAA1A76107218CF948CA01F873520E7A2B118EBB161ABAD432932029628B6761590C98AB590C8C8C79798CE9F715BA696E839E9131A4BB68BE923F1747FBFF165331FFDD4D05997ACD168F12CA75348DAC04C7F509FE +310 +83898A8B421181AF492861CE81E4A2DD0A30850CFA95E05F097FF70F9E777310D584A75BA7018922E271DAB1D785671157685B7D383D3E074DDB4F1E44EF04B07BAAC75A062077C54B9752C738C9D804D402E02851F3F89605AF189738C18C021D2139EAF72513824E10DDD3047BACC8521ED7E98B257D1166903B1B08FA6F +310 +918FD7E162913FEC7D0EF4787E29C35FBA8C6E2C9C20E9AB30AE42CA2A01A090E7D010C1B5BF9CEE2A304BF3A987FB40D836979B79E18D70F257DA61E808472DD85B82761825779D6B90CAEB0F23360C6F80A7D7D77545E95B3DFF0CF96A827F9B5BCECD2C9698718417EF80FEEDEE938AFF55859D0D540B7FE09435583A6F +310 +04874A0C7E8CF234DF4879722000167F5872CEC6BF3EF7E5A8CC8121E6A359227C8A29C95212F9F735BC72A887D56B8F309FC525BFB4FD767AAEC4AEFF249CFBBD2DE1AB9969A6155C7D8DABC307E47324C1F916C7BCFF40ABA5644E9FFB9238D19ADADA49D9610F01ABBA57E4DFCBDCCBB2B93C8953206C91DE01B0E2651A +310 +6D6C31721915B31F7B7CBCE55466F6D70402788FA91D73BE4ADCBA99A35F17D6B83383704917D6833F0FA3CD45C926ADB8BC17999A4A0AFBCE2541F7B8CF4C2F5FBA611B3E626D64407CB32C122DFF4EF8674D6EDBB86C8F7C6A16D50FA685E2CCA34F8F7D1D8265BB0042E9D7F1F666F7BE5FAA5184DF58E12122A42EF02F +310 +461B3F369AC746F96528AD208512AB56844CF75B97F352DF7995024FB02855385854F3798670D01CAC771AC3E1889EC25F61A20C1F8CAB46898817F9C47E2CDA1F286BF2F358298709FE567C91133E2638C8B53975E753EA8BFD3578B5F7186B7BF084EBA3F77BAC3132A5779902EC22AF2D2E79F42D324E083386DAEE9810 +310 +D1B44A5DAA54513C7A71E52A4AB9257D975B621308FE4D2141CC3DCFE946C154A3C1DFCCA7629EC69ACFA831634ADDA93C0388D2A9A2DDAFCF957C6995BCE7FBB6DC4EBAE79E63EB08E9A25F52F67076F7093CA545CA3053B5BF576DEAD4E0017143C5F759F32B2D6F145B52B67B48909B7C7FCA6586EE385A2189C16932CB +310 +6DDFD9152AE317C430492A578ED27B72D85D40C7C93C097939E1B50F4D67F05334FE9DC362C30B7FF561B95259138ECFD9274C8D695C31B14D4EAA9F3D7FA8DDF652F8C5DEFA23284C799B7D94416DEE81E05A866302DC70192A5368EA65E1CED8BF107A1E2D3119A71E85DC1A7B0C1E93274CE04DD0F2ABC8AC7C987FDB00 +310 +2B6F81757B43502EF7A0D92E416494FAEEA4C9C090CD9557AA4D532F4996A272B94355C5A0E4974EEA9627476F30809B94D8D8D860062E02204643BEC7D0438AF800FC7E2B26955D595421E83C69E1A07E0E3122285835F014AD538188CF9737CE76D464A353FA55A001AB2526B84E985083693BC76FE211CA1D000679C889 +310 +E3C009832C87E1AC2EDD05C302557422DF0800E316FCAC86978A371F025A6C7DAA9F333E7BD160403A1A06EA75AA9888FF01504B03041400060808003D7AEE4A51464CB48900000043010000360000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F646972656374 +310 +6F72792E786D6C9D8F310EC2300C45AF127987122606C71DD918B841D4B851A43646695A717C52A85029031293A5A76FBF6FACEF7DA7264E43906840EF0FA03836E242F406C6DCEE4EA06AC22492573150D1F66C60C640E8938CB705396EEDD86DE9B5042FE27813D640EA63BB001C722AF6E55CB61E4863F58284D5D3F53D +310 +D715CE1C3985E63FD7F1B7EBDD617E9F1E504B03041400060808003D7AEE4A6760DD5E44070000541C0000340000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F6F626A656374732E786D6CDD59596FDB3810FE2B82F665F741A78FD885E3C2475C1845DAA24617 +310 +FB26D032ED70431DA0A4C4D95FBF438A944545B6951EC062132076287238F3CD3D9ABC3F46D478C22C23497C6B7AB66B1A380E931D890FB76691EFAD9169BC9F1A139624796D9F69C428C2B7265F36A793034B8A542EEDF01E15B4B9BA9AFF6564218EB169841465D9AD99850F3842362AF26487B3477B8772641FF647DBB3 +310 +CB8D3AD90343E943E3AA14311CE7C02AACC7404572E0E9FF8248CE74E2F00DDAB65EB76DFE856D8E105C6794C4598EE2F015578DEBFC335C35AEEB79FDE59D3F185B837EBF67F587BE67CD57F3A5E5DDF4DCC570B118CFEEC6A6319D6C9384560A4871BC032DBE98536FE2F02795F8128536BEC3248E7128D16CDBB0C3922E +310 +C15913130D584F88565348296B6DA1D751F833DB3A81A2B1A4A1AD84539F75EB4DB67F030859172B6519E6569EE50C742DB12FFDC19D38E5AAA617921911CA3123881A946C1962A020D85A2A4823C3BD8593323866428B8A5E9D5730ECEBBED4C6E5BEA094FBAF39159E16803FE23C139C917F303B715FBF0D7802690B12E7 +310 +23296CFE920289F1C4118BE5B39E2F1F96401A7B8A0E602D2026DFD4F31B782D782C582FCDE9C7D5F6B8E1F1A1FDF29425296639B73CE1CCD2EF94066BA605C6D7019412575D772754BE02F49F00F98A1763029A63E4D83FF695EC0CC5D93E611168C7700DCB853FAEC1BFBEFA3671AAC353C3D062E5394CC16D7F12A69A18 +310 +DADD3548A7932730FA84F5A4709B1051B0E97B12733F07433168024B10EFF7DBA3395D43308545388528480FBF13877F35E0A3A403F1A6A1A1B6F0DB4151CA619A7E76D2D507B01946C24A559A8CE7F01DFC147C67CBCDC74DC1F628C4F7D2B197784F629243326D6748035D73F96FEB4FC2233FD0648BE8E9B4B629846B0E +310 +098F1B2D6AD948B518422FC0094FC3A566C4CA3D016FA714C5382932B9DE16A7C284262CD8BE04A50BB7DEF5A7B281F2326900ADB10C927BC848CA3131A7525B5520B46B82828521262DF0506A35D816511AA02829621E6A5F9B62930F8833DC1281AAA0A60C5B79AD22BB23FB7D9141F06A25B9D08094FEAC089716DE8738 +310 +D6C62FC37B862089267140200783B3B4DEA05D50817799E70CD37D40282DA2604F28E4912042E919FA1D25A867AE47FCF29CB05D2B41DDB2944F2A1BE2804B6015482A5B95315FEA94E127C2CB4C91CF5436D076C83AD49CF6CFA40B9E295A0055C65806A41B7F38F767336B7E371B59FDBBDED01AFB73CF5AF4EFC677EE60 +310 +058FE64D3E55B4AAE73B50252F7334FFCB1F8A681B2342E101171BEDD277EF45F6BC7520343AFECD68E00EBCA1359C8D86567F359E5BE3DEC2B5EEDCC560E5790B77B5583A0C6749C1429C39BEE306CA24D3F8708E2BC5DDEB180A65CDF564A7F4D514E67F1C439B394153E27F358856B1ABA520EC1A44F9D1660845E1234F +310 +514108555FABFB348268938FB638F7C6B8ECDA3D57FC78DED877C7FE683C18740C7BE2A61F8E746D3284D079167990A42824797B4A6D20D391E5376517BBE70D4A6CD45FDF3700AFCEAB1553973393642A20113AE0608F785FD1124AAF49DC9AF50E34C93212E34CF485AFEAC5064DD786F24BCFD22D664BB220C239FA1E8B +310 +158D46C30B209AD3003AD89CCF10AED611950BC89EE51C45DE2F43AA17F2BF04198A525AF6C6D730F07C0581EA8ADA2C9433CDAF7802FB0C501EB83B2C461DD7A857ECAB8AA2556B4DEAE3EF267F061E5509FD4278D415DD8B2D7BE08DF9CF08C2D070E0F58637952AAEA1A52ECB79EF470BE856C397E01993C343A7E2B49B +310 +566A655E5988E73882D617E505EBE4B1438826CAB694403232345CA276D31B0B4AD1F3A9AE0F2E134DDFB992B8760DD4AE2486D95827415E49D16AC44217620EC8C75D6FF06C85CD55AA3F1C317F79955D77BF480C8CAEE3001576198265FCD1EAF0D64A5DABA1B2679487104A37E2D39A8347ECF0EED4CC69E4DACA7A55D3 +310 +5EABB8D73CA44630E5453CD2DEF32FF42B025543C79D391FCA42CF29D7D73CB5D930D03EF1A131ADD3FAFC61D3A0022B9D4F7FE118E1E70605B9BAE01DF40552DFD6A73941838294E8DBFAC2F1FF4417A214581BBDC104394B2881413EDE19079C40FE865145870E8545381379198C570629F516A1C3E9A7720E9BA32D55A7 +310 +C30704C36C2AFBB72284412AE4CC5B53AE1BBFC1E8944F2FC540CB7E84D64DC630D32818B935C16273021D9ABD85119818380AEA8D112738368A0F3CE397A7AAFF4FC7383A10954E72557BBAE02225D39C094AE154BC5B80BAAF7C309D7CBFE88A1D03E5F0755BE49530A78593341204F512A1140E26858F12DE23941497E5 +310 +F26CB14783A4D334FFA21D9454AD1CB18318C9D7F09E2DBF387C9ABEFE6CA4B43870755EEA98354A16EFD469514EAE26601852CC14F12A72F1CEF9C212782D15192B024660FC7E1C0DFF701649142571B9E4CCE4CB2D63F300AFAA768E1A1342EBEF7A378E981CC85701C11665D846BBEC111640B58CF0796A09B166005534 +310 +AD1980D208D7051CD3F6AF9786AC8E6A53957AD8E5F088F7000638127FFFC3DDA6BEA136B4045F00AEC4C3D317B53071180CCEA7FF02504B03041400060808003D7AEE4A4C5A99CDE2000000550100003C0000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265 +310 +736F75726365732F322F636E782E786D6C4D90CD6AC33010845FC5087AB42C8506B761ADBC444EBD0455DEB82292D6E8A7B46F5F2955A1A75D663E96D981F39777C327C664292C4C72C1060C86561BB685957C1B5FD870561089F23F8C0D417B5C589399822D52D9BBB4E24D17D75467C3BD8BE2BA61C068CD750F1B1B8CD3 +310 +292D2C990FF49AEB9269C574E7ABCE9A4B1E31518906EB896C3DF61326A2CEB8327510721EC53CCAE78B3C9EE47C3ABEBEC1D44805EF44AEF336648CB43F1D84A1BA869A48C0D4000525DA4EB537AAF1178FD77830555B41CAB176D0B1FCBD57CC7ABDE1F4407E5D05537BB28E470375B642D40F504B03041400060808003D +310 +7AEE4AFF15A4A45D000000800000003A0000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265736F75726365732F636E782E786D6C4D8B3B0E80201005AF42B6F75B59F0390B113044D83508C6E30BC6C26AF226F3B8BA6360974DA7271430F523308B2B198F9B +310 +80925DB700539227A2FCCB80A18E5640D320F996A81C9F32D6E9129A0D1EF74FCE750E6D57BC7165FBCA07504B03041400060808003D7AEE4A4FCA4AEB4301000056050000300000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F636E782E786D6CCD94C14E0321 +310 +10865F6543E271D9A5B1A9362C4D3CF8041E8C37969DAE5816363018FBF642DDB61AD3E8A1497B23C347F2FF333FC3571F8329DEC107ED6C4318AD490156B94EDBBE2111D7E51D2956827BE7F01B460A2B0768482E13C17BEFE238953A58CB6872D568BB998A12D1EB36220452282343684850AF30482A23BA0EC286761225 +310 +65D44370D12B48EF510F30BD571E244247C4AC668BB25E94ECF689CD976CB19CDFBFF02A9382B7CE9989D716C1BBF166562B978E36C9A9799501C1A3D713953D1071D4465B6D7995EE050F49AFED270EB763E6C6D168253135AA720A01CB04811C78F5050B5E65C73F7C3F3E3C1F35A453A73D28747E7BB136B0536D3848A3 +310 +2911FFEBC20EFCCBBC6BDF92E5CBCDFDA4E149D8D9ECEE931B52747F2721E549DADE5CE30738483B7FFED36241ADAED1F45ED9993C57BB1598069F37A2F804504B03041400060808003D7AEE4ADEFD15167E000000A30000000B0000006662782F636E782E786D6C4D8CD10AC22018465F45FE7B4B2BCD813ACAF23D467363 +310 +CC69D88C1E3F8D5D74F5C1E13B47B69FC5A3B74BAF290605744700B9F088FD144605791DB000D46A99625CFF6E8042B73805158396638AF9B9A1DE0D5DF695FA29CC1B3C9C05238C72CC2F82E3936DAEB8391A82EFC4304BA921D6DC8AB1AF4A995FAF6CCDEB2F504B03041400060808003D7AEE4AA6D797065F0000008200 +310 +000007000000636E782E786D6C4DCB3B0E80201045D1AD90E9FD7516806B51190811670C8A61F982B1B0BAC9C97B72CA7B1037C6D3332918DA1E04D2CAC69353902EDB8C20262D23F3F59B81A079470595414B17391D1F19B4730A5583A7ED43BBE4025D9592775E5ADFFA01504B03041400060808003D7AEE4AECFD112DA8 +310 +00000026010000130000005B436F6E74656E745F54797065735D2E786D6C7D4F4B0EC22010BD0A61DF525D18634ABB506FE00510A79458060253536F2FD89D5197F3FED3F68B9BD80362B21E25DFD40D6780DADF2C1AC9671AAA3D677DD75E9E0112CB5A4C928F44E12044D22338956A1F003333F8E814E5331A1194BE2B03 +310 +62DB343BA13D122055543278D79E6050F344ECBC6478EDCD76CE8EABAE5449AE4298AC56946789C28AAFBEABC53F3EAF09A84A1441B91F0101CD4780756578C173A578FFDDBD00504B03041400060808003D7AEE4A3DEE336E64000000790000001B0000006175746F6465736B2D64657369676E2D7061636B6167652E786D +310 +6C35CBC10E83200C00D05F21DCA56CA76D41FC96062B120735B69A7DBE268BB7777961F8D5AF396893C2ADB70FE7ADA196782C2DF776D7A97B5933C430F15651E5469C55D70F80A4992A3ADC954792C525AE70A1E4D6AD9816CC044FEFDF01FE3FDE907802504B03041400060808003D7AEE4AF9E59A9CF40000008E010000 +310 +3D0000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265736F75726365732F322F636F72652E786D6C8550CB6E833010FC15E4BB01F328101947A9115FD0536F96ED502BC1467E44F9FCDA814AEDA9A75DCDCECCEE0E3E3FD77BF690D629A34780F21264527323 +310 +945E4610FC15F6203B136C8DF1BF6820D36C952348302078B1266C07C48DF65227541B210F9039273DC8F83D362370FC4BAE2C67C14786BBE51BE337B6C81CE53B8FE0109438A44A44377555D20252D1AAA3F55B0BE9D050D85CE804FBA999E1A5AFBB7AA6EFF3504FB8486282BD5A7FB6732B999722EA4BD4C1B283A8F940 +310 +ED0975A776F8C4456212ECBC8D3F1F4BD377918E8B1DFDE31636F1BF5B50DAD7D5E1B62B1C20281EF71A105CA474627945176B4A927C03504B03041400060808003D7AEE4A09DF3B46F9000000960100003B0000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F72 +310 +65736F75726365732F636F72652E786D6C8550CB6E833010FC15E4BB01F32824328E5223BEA0A7DE2CB3A156828DFCA8FAF9B50395DA534FBB9A9D99DD1D7AF95A1FD92758A78C1E10C94B94819666567A1950F037DCA3ECC2A835C6FFA2A14C8B15069460C4E8624DD80E481AED4127549B190E5038071E65F2119B0139F9 +310 +01ABC845F091E1EEF926E45D2C90937CE7311A829A0FA99AA39BBA29B08855BCEA78FDD2627E6A386EAE7CC4FDD84CF8DAD75D3DF1D7E9548FB4486246BD5A7FB64B0BC2C31CF525E970D961D2BC91F64CBA737B7AA7456232EABC8D3F1F4BD3778859702658098E16FBF48F6BD8E6FF5D83D2BEAE0ED75DE11023F1C8E780 +310 +D122A514CB33C25853A2EC1B504B03041400060808003D7AEE4AE16FDFE9F2000000B1010000310000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F636F72652E786D6C8D50CB6EC32010FC158B3B3624F12311266ADDFA0B7AEA0DC1C64189C1E251F5F30BB12B +310 +B5B79C16CDCE30B3C3CEDFF3BDF802E7B5353DA225410518699536538F62B8E00E1567CE9CB5E10F0D1546CCD0A30C23CE2667E3B241D29A0026A3C62AD840E13D0454C87B7AF4C8CB2BCCA214312486BF958B9037314149CB95C759D0F3AF543A100114E23B425B4C5A4C0F1FB43ED1F6541F3F5995999CC5A8D566A55572 +310 +D7170D2E49DAAE26356D70F3D235F8301E5FF1713F10FC4E867AA47420E3F0C6AA2CE6CC07976EDE3EC9D73D2B5F85FF32C7453D91599BB0DF6D86ABC2234E539EC782B32A1798C6A3DD3473D9FC07504B03041400060808003D7AEE4A9AD47DCD53010000DD0200000C0000006662782F636F72652E786D6C8D52CB6E8330 +310 +10FC15E4BB79A72491719412E50B7AEACD310EB502363236CAE777798972685A21196B7667767664727A36B5D70BD349AD7214F921F284E2BA94AACA91B377BC47DE8912A3B5FDD1863CC51A91A30146945446BB7686B85656A80155BA1433C8BA4E58E4F11A2E39EAF8976898CF9C858EEEE1B78C3F5825FCC89FFA28714E +310 +96335596A026EF521844E322CE8AE46D878B435AE0F45C5CF0FE925EF1799F64C9B578BF1E920B090632255636CB746E04B3A2047E186538CC70947E44BB63941D77874F120C9D9474D6C0CEF3D0613B44EFB72709267CA3E7DAF26F3D27954DE2596F62748846606F2C50120CF9C06F0C6F9B210C86FC2CBBD5CB0A0F807A +310 +C1AD3629544AEDD652CF6A27403AF4C68F045315944781DF74E2173AAF352028A62AB0C62C5C6FCE0ED3B79E5A61706F9F9ED2A661F56A6EB2B89EEBA0CD4A0B9DEB5A9BFFB09755973083E161D26F504B03041400060808003D7AEE4A46E1175207010000B201000008000000636F72652E786D6C8D50CB6E842014FD15C2 +310 +1E151F5527C864AAF10BBAEA8E02E39051300293F6EF0BA34DDA5D57979CC73D874BCE9FCB0C1E72B3CAE80EE22483406A6E84D25307BDBBA2068233259B31EE970C02CD16D9C108434AA6CDF8F580B8D14EEA886A23E401326BA58380CFE1D141CB6F726109F32E28EC3D5919BFB3492638D9759438B5FC58F926999302D2 +310 +3CC335CA6A84CB375C9D707DAADA7792462525DE2B71442911D2D555C92D58FABCEE8B970AF56DD9A3F2D20FA819CA115D9AA22EC6FE756C8B81A4D14C89755BF8F3B124F474C0B247CC25E94E51F261CC7C084229018C9EBF20CD481A893FA5FD2AFE515A6957E4C7C2DD6121C5A1D09308C1F182613CCF1B66BC36FD0650 +310 +4B010214001400060808003D7AEE4A0000000000000000000000003700000000000000000000000000000000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F617474726962757465732E62696E504B010214001400060808003D7AEE4A0000000000000000000000 +310 +003500000000000000000000000000550000006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F76657274696365732E62696E504B010214001400060808003D7AEE4AF270F13306000000040000003600000000000000000000000000A80000006662782F3237383530 +310 +3531362D364138362D344639422D393343302D4530433546313143304643442F747269616E676C65732E62696E504B010214001400060808003D7AEE4A88E57597CD3F0000904100004200000000000000000000000000020100006662782F32373835303531362D364138362D344639422D393343302D4530433546313143 +310 +304643442F7265736F75726365732F322F305F67656E657269632E706E67504B010214001400060808003D7AEE4A51464CB4890000004301000036000000000000000000000000002F4100006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F6469726563746F72792E +310 +786D6C504B010214001400060808003D7AEE4A6760DD5E44070000541C000034000000000000000000000000000C4200006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F6F626A656374732E786D6C504B010214001400060808003D7AEE4A4C5A99CDE20000005501 +310 +00003C00000000000000000000000000A24900006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265736F75726365732F322F636E782E786D6C504B010214001400060808003D7AEE4AFF15A4A45D000000800000003A00000000000000000000000000DE4A000066 +310 +62782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265736F75726365732F636E782E786D6C504B010214001400060808003D7AEE4A4FCA4AEB43010000560500003000000000000000000000000000934B00006662782F32373835303531362D364138362D344639422D39 +310 +3343302D4530433546313143304643442F636E782E786D6C504B010214001400060808003D7AEE4ADEFD15167E000000A30000000B00000000000000000000000000244D00006662782F636E782E786D6C504B010214001400060808003D7AEE4AA6D797065F000000820000000700000000000000000000000000CB4D0000 +310 +636E782E786D6C504B010214001400060808003D7AEE4AECFD112DA80000002601000013000000000000000000000000004F4E00005B436F6E74656E745F54797065735D2E786D6C504B010214001400060808003D7AEE4A3DEE336E64000000790000001B00000000000000000000000000284F00006175746F6465736B2D +310 +64657369676E2D7061636B6167652E786D6C504B010214001400060808003D7AEE4AF9E59A9CF40000008E0100003D00000000000000000000000000C54F00006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265736F75726365732F322F636F72652E786D6C504B +310 +010214001400060808003D7AEE4A09DF3B46F9000000960100003B00000000000000000000000000145100006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F7265736F75726365732F636F72652E786D6C504B010214001400060808003D7AEE4AE16FDFE9F2000000 +310 +B10100003100000000000000000000000000665200006662782F32373835303531362D364138362D344639422D393343302D4530433546313143304643442F636F72652E786D6C504B010214001400060808003D7AEE4A9AD47DCD53010000DD0200000C00000000000000000000000000A75300006662782F636F72652E78 +310 +6D6C504B010214001400060808003D7AEE4A46E1175207010000B2010000080000000000000000000000000024550000636F72652E786D6C504B050600000000120012002D060000515600000000 + 0 +CELLSTYLEMAP + 5 +1B4 +102 +{ACAD_REACTORS +330 +1B3 +102 +} +330 +1B3 +100 +AcDbCellStyleMap + 90 + 3 +300 +CELLSTYLE + 1 +TABLEFORMAT_BEGIN + 90 + 5 +170 + 1 + 91 + 0 + 92 + 32768 + 62 + 257 + 93 + 1 +300 +CONTENTFORMAT + 1 +CONTENTFORMAT_BEGIN + 90 + 0 + 91 + 0 + 92 + 512 + 93 + 0 +300 + + 40 +0.0 +140 +1.0 + 94 + 5 + 62 + 0 +340 +193 +144 +0.25 +309 +CONTENTFORMAT_END +171 + 0 + 94 + 6 + 95 + 1 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 2 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 4 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 8 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 16 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 32 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END +309 +TABLEFORMAT_END + 1 +CELLSTYLE_BEGIN + 90 + 1 + 91 + 1 +300 +_TITLE +309 +CELLSTYLE_END +300 +CELLSTYLE + 1 +TABLEFORMAT_BEGIN + 90 + 5 +170 + 1 + 91 + 0 + 92 + 0 + 62 + 257 + 93 + 1 +300 +CONTENTFORMAT + 1 +CONTENTFORMAT_BEGIN + 90 + 0 + 91 + 0 + 92 + 512 + 93 + 0 +300 + + 40 +0.0 +140 +1.0 + 94 + 4 + 62 + 0 +340 +193 +144 +0.18 +309 +CONTENTFORMAT_END +171 + 0 + 94 + 6 + 95 + 1 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 2 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 4 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 8 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 16 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 32 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END +309 +TABLEFORMAT_END + 1 +CELLSTYLE_BEGIN + 90 + 2 + 91 + 1 +300 +_HEADER +309 +CELLSTYLE_END +300 +CELLSTYLE + 1 +TABLEFORMAT_BEGIN + 90 + 5 +170 + 1 + 91 + 0 + 92 + 0 + 62 + 257 + 93 + 1 +300 +CONTENTFORMAT + 1 +CONTENTFORMAT_BEGIN + 90 + 0 + 91 + 0 + 92 + 512 + 93 + 0 +300 + + 40 +0.0 +140 +1.0 + 94 + 4 + 62 + 0 +340 +193 +144 +0.18 +309 +CONTENTFORMAT_END +171 + 0 + 94 + 6 + 95 + 1 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 2 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 4 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 8 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 16 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 32 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 1 + 40 +0.045 +309 +GRIDFORMAT_END +309 +TABLEFORMAT_END + 1 +CELLSTYLE_BEGIN + 90 + 3 + 91 + 2 +300 +_DATA +309 +CELLSTYLE_END + 0 +CELLSTYLEMAP + 5 +1B2 +102 +{ACAD_REACTORS +330 +53 +102 +} +330 +53 +100 +AcDbCellStyleMap + 90 + 3 +300 +CELLSTYLE + 1 +TABLEFORMAT_BEGIN + 90 + 5 +170 + 1 + 91 + 0 + 92 + 32768 + 62 + 257 + 93 + 1 +300 +CONTENTFORMAT + 1 +CONTENTFORMAT_BEGIN + 90 + 0 + 91 + 0 + 92 + 4 + 93 + 0 +300 + + 40 +0.0 +140 +1.0 + 94 + 5 + 62 + 0 +340 +26 +144 +0.25 +309 +CONTENTFORMAT_END +171 + 0 + 94 + 6 + 95 + 1 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 2 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 4 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 8 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 16 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 32 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END +309 +TABLEFORMAT_END + 1 +CELLSTYLE_BEGIN + 90 + 1 + 91 + 1 +300 +_TITLE +309 +CELLSTYLE_END +300 +CELLSTYLE + 1 +TABLEFORMAT_BEGIN + 90 + 5 +170 + 1 + 91 + 0 + 92 + 0 + 62 + 257 + 93 + 1 +300 +CONTENTFORMAT + 1 +CONTENTFORMAT_BEGIN + 90 + 0 + 91 + 0 + 92 + 4 + 93 + 0 +300 + + 40 +0.0 +140 +1.0 + 94 + 5 + 62 + 0 +340 +26 +144 +0.18 +309 +CONTENTFORMAT_END +171 + 0 + 94 + 6 + 95 + 1 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 2 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 4 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 8 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 16 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 32 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END +309 +TABLEFORMAT_END + 1 +CELLSTYLE_BEGIN + 90 + 2 + 91 + 1 +300 +_HEADER +309 +CELLSTYLE_END +300 +CELLSTYLE + 1 +TABLEFORMAT_BEGIN + 90 + 5 +170 + 1 + 91 + 0 + 92 + 0 + 62 + 257 + 93 + 1 +300 +CONTENTFORMAT + 1 +CONTENTFORMAT_BEGIN + 90 + 0 + 91 + 0 + 92 + 4 + 93 + 0 +300 + + 40 +0.0 +140 +1.0 + 94 + 2 + 62 + 0 +340 +26 +144 +0.18 +309 +CONTENTFORMAT_END +171 + 0 + 94 + 6 + 95 + 1 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 2 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 4 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 8 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 16 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END + 95 + 32 +302 +GRIDFORMAT + 1 +GRIDFORMAT_BEGIN + 90 + 0 + 91 + 1 + 62 + 0 + 92 + -2 +340 +0 + 93 + 0 + 40 +0.045 +309 +GRIDFORMAT_END +309 +TABLEFORMAT_END + 1 +CELLSTYLE_BEGIN + 90 + 3 + 91 + 2 +300 +_DATA +309 +CELLSTYLE_END + 0 +DMBASEELEMENT + 5 +195 +102 +{ACAD_XDICTIONARY +360 +199 +102 +} +102 +{ACAD_REACTORS +330 +192 +102 +} +330 +192 +100 +AcMapDMBaseElement + 70 + 1 + 1 +Map Base + 70 + 1 +330 +192 +100 +AcMapDMBaseElement + 70 + 2 + 1 + + 70 + 2 + 1 + + 70 + 1 + 40 +1.797693134862314E+99 + 70 + 1 + 70 + 1 + 70 + 1 + 70 + 1 +360 +198 +100 +AcMapDMBaseElement + 70 + 1 + 0 +DMDEFAULTSTYLE + 5 +196 +102 +{ACAD_REACTORS +330 +192 +102 +} +330 +192 +100 +AcMapDMDefaultStyle + 70 + 1 + 90 + 1 + 1 +Default +100 +AcMapDMDefaultStyle + 70 + 1 +280 + 100 + 90 + 0 +1001 +AcMapDMDisplayStyleRegapp +1070 + 2 +1071 + 1 +1000 +Default +1000 + + 0 +DICTIONARY + 5 +199 +330 +195 +100 +AcDbDictionary +280 + 1 +281 + 1 + 3 +AcMapDMSurrogateStyleSets +360 +19A + 0 +DMSTYLEREFERENCE + 5 +198 +330 +195 +100 +AcMapDMStyleReference + 70 + 1 + 70 + 0 +340 +196 +330 +192 + 0 +DMSURROGATESTYLESETS + 5 +19A +102 +{ACAD_REACTORS +330 +199 +102 +} +330 +199 +100 +AcMapDMSurrogateStyleSets + 70 + 1 + 1 +Map Base + 70 + 1 + 40 +1.797693134862314E+99 + 70 + 1 + 70 + 1 + 70 + 1 + 70 + 1 +330 +198 + 70 + 0 + 0 +ENDSEC + 0 +SECTION + 2 +ACDSDATA + 70 + 2 + 71 + 8 + 0 +ACDSSCHEMA + 90 + 0 + 1 +AcDb_Thumbnail_Schema + 2 +AcDbDs::ID +280 + 10 + 91 + 8 + 2 +Thumbnail_Data +280 + 15 + 91 + 0 +101 +ACDSRECORD + 95 + 0 + 90 + 1 + 2 +AcDbDs::TreatedAsObjectData +280 + 1 +291 + 1 +101 +ACDSRECORD + 95 + 0 + 90 + 2 + 2 +AcDbDs::Legacy +280 + 1 +291 + 1 +101 +ACDSRECORD + 1 +AcDbDs::ID + 90 + 3 + 2 +AcDs:Indexable +280 + 1 +291 + 1 +101 +ACDSRECORD + 1 +AcDbDs::ID + 90 + 4 + 2 +AcDbDs::HandleAttribute +280 + 7 +282 + 1 + 0 +ACDSSCHEMA + 90 + 1 + 1 +AcDbDs::TreatedAsObjectDataSchema + 2 +AcDbDs::TreatedAsObjectData +280 + 1 + 91 + 0 + 0 +ACDSSCHEMA + 90 + 2 + 1 +AcDbDs::LegacySchema + 2 +AcDbDs::Legacy +280 + 1 + 91 + 0 + 0 +ACDSSCHEMA + 90 + 3 + 1 +AcDbDs::IndexedPropertySchema + 2 +AcDs:Indexable +280 + 1 + 91 + 0 + 0 +ACDSSCHEMA + 90 + 4 + 1 +AcDbDs::HandleAttributeSchema + 2 +AcDbDs::HandleAttribute +280 + 7 + 91 + 1 +284 + 1 + 0 +ACDSRECORD + 90 + 0 + 2 +AcDbDs::ID +280 + 10 +320 +14 + 2 +Thumbnail_Data +280 + 15 + 94 + 3608 +310 +89504E470D0A1A0A0000000D49484452000001000000005408030000005AC4190100000300504C5445000000FFFFFF0000000000000000000000000000000000000000000000000000000000330000660000990000CC0000FF0033000033330033660033990033CC0033FF0066000066330066660066990066CC0066FF0099 +310 +000099330099660099990099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF0000FF3300FF6600FF9900FFCC00FFFF3300003300333300663300993300CC3300FF3333003333333333663333993333CC3333FF3366003366333366663366993366CC3366FF3399003399333399663399993399CC3399FF33CC00 +310 +33CC3333CC6633CC9933CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF6600006600336600666600996600CC6600FF6633006633336633666633996633CC6633FF6666006666336666666666996666CC6666FF6699006699336699666699996699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066 +310 +FF3366FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF9933009933339933669933999933CC9933FF9966009966339966669966999966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC3399CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFFCC0000CC00 +310 +33CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFFCCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0000FF0033FF0066FF0099FF00CCFF00FFFF3300FF3333 +310 +FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33FFCC66FFCC99FFCCCCFFCCFFFFFF00FFFF33FFFF66FFFF99FFFFCCFFFFFF0000000D0D0D1A1A1A2828283535354343435050505D5D5D6B6B6B787878868686939393A1A1A1AEAEAEBB +310 +BBBBC9C9C9D6D6D6E4E4E4F1F1F1FFFFFF0000000000000000000000000000000000000000000000000000000000003AE5ACE9000002774944415478DAE5DC5D9283200C00608F93AB7A29EEC443DEB755BBEB5A4190FCCBCB4E9D9D917C45B490384DCA0DC39DC86CB72C0A204E8F06C0D0A7B3D7217CF0F037288056CF09 +310 +320D0F9FCD00E4AC32FCC1CC104859A51FCF0238E906989905F8014E67FF07019C77C10E0073574A377F29806B81042AA77F08004EF60118BB527BF67D0200EACD3C5D02A072E2F000573FFDA203A88DBB1B7D018DF86303A0FAE3972E40DB2A445880C6852F418049743E6E5DF88A0AD0BCF01713A063DD372440CFBAAF24 +310 +80D44369D7BA773C80CE6D8F7000BDDB1ED100BAB77D4401D87F99DFD8F50B057067D72F10C0BD4DDF380037377D6501AABD048DF8A300DCCF7988013090F310026024E72300C058CA8F30C044BF473398F2E31E6034E5C939C078C69B6F00828C376900D28D6A8A8C3FC70034099F7E0188123EBD0290E5FB3A05A0CBF7F5 +310 +094098EFEC118034DD5D1C603C618D36DDDD1F0071BABF3700F26A0F6700F4D51EBE0018AA5D3C01B0143B3902E029767203C055EBE60580ADD64D1EA0180B68C4EF0380B3D4D303006BA9A70300DE52574580B61A4EEE4A67CD11800D5D61AF7456BD04F0B22BFC95DEA601240AFD752741AC76853BFCA5785EF92E801500 +310 +648F5EEEFD01E5C0B004C0FAF023FE0285CA378BE7002813BD5500B69F7EDFB1EB3F09E23700F27FFB396FAFEC5000280980F0F03703F0F90C0237FF6DE0E7BF37B6D801C800F3FB2D3733C8360300EB8104698684F00B905E6DD7D1B4B6ED6FFD0B6C896A7B5F810980E5488279469C4B6D0FF0FAE70E80F4690780BC3613 +310 +00EF432F8072F83B8042A8FF2284B3F80F02DB314B00B5F82F018A478B00962E81F731DC2EFFAB49709918D26182F03809FE004073C0F4110743380000000049454E44AE426082D0E19D1778EC234069096939AD34FE5639AD7AC10D04400AF82D89D129D09899E7173E23E0B1CD046280FB2EC6BA7E4FB1E18680DD89500E +310 +741725D18AF8ACFBCB732BEA60AAFC8652E1BB6210849AF7626EA1F416A94160299B1A4D8F8719135096285B0E474B1B32CDE18A80DC42C58900920D5927A07ED4BAB68143D1939C664BF499F36C8512592DC9B514D03FB595070821AD2CEBE615BE804384A8935CA77B309BA147D67C0079115351406FE0340240EE07821E +310 +0F508810A422B215067568559F5A0D960A9213995B468C5E5B99E002B4C83E8FC7A56CC29E068812AAE1418B1A832DA1904BA2C54CB05933AF0685258EB20222470D5E0AE6F28F8DF592D0522DD00275DECBC2AAC179A6650DE80E924D36CE5A814C034C64824009F8A21AD4FCA8F497C652618843A6F75135B8C24030970A +310 +33C72DEC6458D28055066EFB217BC2A05A087C00B50E1E472C3941C5D37F24FF72BE60CE0404472D2585B3767311CF5A88A5BF7DEBA8E14468C1A0D3264E6990118000BD6AAA35302D23F0CFD502E2F286B8E85136C9D622B954CB8D94717DC45C2D90D21F38B3409E09B5E5BD506AFF3A18D857D92339754FA0D6CC4B04EC +310 +6C8941DE0B9A1F1A05F2129734306F91CF1690BA8D916D9E336702B5F1779CEDBC2379F283363BEB822FFB3D60D70ADE2D612E14B476AB290292EBAE2BB8C8D771EB7E6FDA1DA2FD31AAD8914E72FEA9F6C62DF900A8CDAAA6DE173E503201FC207130E403F26E689980BAE657DD60D512EA1EA9DA5F7703FA2023607F3598 +310 +29683B5B8244007B244293C7E750FA4FDD803AF8771646C43088521844A11B3075893A61C676880475A1F79D30A8D4C8729269A81C1EF7340C3B19C8AC730D2061F0C609A2F53018EFEB610CD3C627DEFEC7615D64F867582A86369940BCD8DAFB7E188489827C886C2C92ED8441796D749A4441CBB309081D813012603B11 +310 +5A903F88662E1A3FF0BDD4A12D300F9A62AE18FA16D517403937DA19A8EBAC9CACBFB134A66D7A9BF7C0D1C32038594A98F7041B6B88DCEFE959D40069A795F93038F432178E4068CE227E540BEC2780B96D2164ADBEDADDCA20A9050C39C17A34B23A317ECE9DEEF6C78FCC42E938DBD10060E1ACF6B63A035F1020D88EC1 +310 +44A81DFA28651D061EC4EF5AC29FBDF3E361A61C8680A49E9D5D383DF7321440533BF00D02ECF403782F17C360029C80A1273EA4C2D05A2BBD6B228742BD1AB690087117D0CBE04A40914E2020B453763704984A8523B905003D0523D583B25BA0CC213F10FA070818EFF8C30978CB09E6EE6AD780DE430F38DC43C3561894 +310 +7D80EA04878620ED05B40E290EA7841011BE31CB1FE4014DBF450288C163EF8E4DFD000CAA0F40B44C405D172004083D21EE04E7D380CC9D749FA277452C100022015A53ECDA09C6729C7018C8AC0DB585092798B644B65521A59A97DA635A763CDD52A5FFD35862987082E9464AD06F8A14C25A62F75979C0EB4B131A50EE +310 +24856AD7E2ED6EC8FA5F49F2FFFCF07426604A84B89B58E88875B5D17B68EC1E2A02015BEE2839DF4B2C7E74700EBFD4806F6EA9F90FF9FCFFD2F323874E0000000049454E44AE42608200323DBD6A855E83889709780758371C2B06A8DD447DF2C4C8B0545B84993F6DDFDE7EEC5A0BB911D01613A32478CAF4F87CD6F68D +310 +07432B93BBEE7D5A2104C727E48E39FE551511B96F50B9C07492FA78FD0423CDA3F3392EC361CAB3B63062E4FDAFBBF5350F733BAD932062CC5A13207F5CCE77412349CC4310C1326B2DB394FDF80300D00FCEFD04CE79B056254089C09A804AC1FF900044FE606FA85CB4955A7F98F34B5A784B8078844B3D58CD5F1E7923 +310 +06807C04238787BF4040DDACD70F01A683107FB22EABDE5560905F41FF895E40CD5A931D0250E68EEA81B473909E4195D526ED0F14079D463AEA895321D43CC0ABE701450225B536A2CE0940FF5940CFDE96B07ED35508901B7DEA5B2307944D91A4F7041289F440F632A96B1260F6499C80A126363FA5065C5724A0B909F9 +310 +973E869AE0916E30CDC0998A22946E8300BC96CBDEF257E92408648780D10D32724084809504D5C3E9CDFCB37FB7671D602CDD997483AC1B20B10F5A12604EB0C38C00CFCEB312A01A74B1105249501CA0277B2DE400DF6789216B3E618E391060EDAC099087D7939963F57403F6D002B5E99FCC42A829780FE500B64EAC1D +310 +099D4840F15A8300733AE0D56E300FF1ECBD66677583DD1038EAB97A2CD019F44EF78593D7B69AF4E7F610D8132DBE4C80D1DEC607794D90A63FB383F969B06F7AC047E1B26CB4FE9D215D075C7749C7970868D2EF81ADA3F309023AFA11ED2DFC07C767F0C1A14F6C890000000049454E44AE4260821B8C822A79F1B93860 +310 +BC17104C60288AE7BD80D334A0997672232B46FA4D203C6E2F0120EF71B91600D4E0952F5212A4AB35E0B91880751C999337325FAD011100610EF357E51F1C10F76FAB9B1FB454EE6802AB7243F3390513006963E12100ECA3C12100600800695FD9BAF0E30068BDF59AEC18ED517AFECBB0A3E4D44357A71EB8685763D59F +310 +D475DFA001B34F9D8D003C47725C75080631ECCB3289E26F00C0DE807D6B0EC791AB3D75B81B80DECCF6CAB25C7A37FCBCC15A6B6C927F44030C0F9C5EF9D0D58D65D07C14E1F1D2B63F02C03A0D780F68DE877900366E87BCB270E979FB6FF101BF0A803B847F4DD0E27F1000877DDEEB359917D8F4E8EDED082C4B8C3C3F +310 +26F488983EEEFF46E903E0E91E71E8F390BDB40432B193FC8E539DC67B9956C3F71DDC6734E0DC382E6BC02B1615643A916B2100633CC30C00CFC7E3717CDF2BBCA2F2FE482E1200D228FA4A0072AB0D98C0591E3D2A5798800500C5A89600905AAD1380C7486100988DBB0D402B50D47DC028006E4E03DC621F300EC0A80F +310 +98D78027D1E03F07C08A38203F19477878F02585FCA0FB0F9676D4FE3F6E682E5BCCC767B60000000049454E44AE426082426082 + 0 +ENDSEC + 0 +EOF diff --git a/src/ACadSharp.Tests/IO/DimensionObjectContextDataTests.cs b/src/ACadSharp.Tests/IO/DimensionObjectContextDataTests.cs new file mode 100644 index 000000000..233b78fde --- /dev/null +++ b/src/ACadSharp.Tests/IO/DimensionObjectContextDataTests.cs @@ -0,0 +1,161 @@ +using ACadSharp.Entities; +using ACadSharp.IO; +using ACadSharp.Objects; +using ACadSharp.Tables; +using CSMath; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Xunit; +using Xunit.Abstractions; + +namespace ACadSharp.Tests.IO; + +public class DimensionObjectContextDataTests : IOTestsBase +{ + private const string _fileName = "annotative/annotative_dimensions_AC1032"; + + public DimensionObjectContextDataTests(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void DwgAndDxfMatch() + { + var dwg = contextData(DwgReader.Read(path("dwg"), this.onNotification)); + var dxf = contextData(DxfReader.Read(path("dxf"), this.onNotification)); + + Assert.Equal(dxf.Count, dwg.Count); + + foreach (DimensionObjectContextData d in dwg.Values) + { + DimensionObjectContextData x = dxf[d.Handle]; + + Assert.Equal(x.GetType(), d.GetType()); + Assert.Equal(x.Scale.Name, d.Scale.Name); + Assert.Equal(x.Block.Name, d.Block.Name); + Assert.Equal(round(x.TextLocation), round(d.TextLocation)); + Assert.Equal(x.IsDefaultTextLocation, d.IsDefaultTextLocation); + Assert.Equal(x.TextRotation, d.TextRotation); + Assert.Equal(x.OverrideFlags, d.OverrideFlags); + Assert.Equal(x.TextOutsideExtensions, d.TextOutsideExtensions); + Assert.Equal(x.SuppressOutsideExtensions, d.SuppressOutsideExtensions); + Assert.Equal(x.TextInsideExtensions, d.TextInsideExtensions); + Assert.Equal(x.FlipFirstArrow, d.FlipFirstArrow); + Assert.Equal(x.FlipSecondArrow, d.FlipSecondArrow); + + switch (d) + { + case AlignedDimensionObjectContextData a: + Assert.Equal(round(((AlignedDimensionObjectContextData)x).DimensionLinePoint), round(a.DimensionLinePoint)); + break; + case AngularDimensionObjectContextData a: + Assert.Equal(round(((AngularDimensionObjectContextData)x).ArcPoint), round(a.ArcPoint)); + break; + case DiametricDimensionObjectContextData a: + Assert.Equal(round(((DiametricDimensionObjectContextData)x).FirstArcPoint), round(a.FirstArcPoint)); + Assert.Equal(round(((DiametricDimensionObjectContextData)x).DefinitionPoint), round(a.DefinitionPoint)); + break; + case OrdinateDimensionObjectContextData a: + Assert.Equal(round(((OrdinateDimensionObjectContextData)x).DefinitionPoint), round(a.DefinitionPoint)); + Assert.Equal(round(((OrdinateDimensionObjectContextData)x).LeaderEndpoint), round(a.LeaderEndpoint)); + break; + case RadialDimensionObjectContextData a: + Assert.Equal(round(((RadialDimensionObjectContextData)x).FirstArcPoint), round(a.FirstArcPoint)); + break; + } + } + } + + [Fact] + public void ReadDwg() + { + var all = contextData(DwgReader.Read(path("dwg"), this.onNotification)).Values.ToList(); + + //7 annotative dimensions, each with a 1:1 and a 1:20 representation + //the jogged radial dimension (LARGE_RADIAL_DIMENSION) is not read as an entity, so its 2 are not reached + Assert.Equal(14, all.Count); + Assert.Equal(4, all.OfType().Count()); + Assert.Equal(4, all.OfType().Count()); + Assert.Equal(2, all.OfType().Count()); + Assert.Equal(2, all.OfType().Count()); + Assert.Equal(2, all.OfType().Count()); + Assert.All(all, d => Assert.NotNull(d.Block)); + Assert.Equal(14, all.Select(d => d.Block.Name).Distinct().Count()); + + var diametric = all.OfType().First(); + Assert.Equal(new XY(240, 30), round(diametric.TextLocation)); + Assert.False(diametric.IsDefaultTextLocation); + Assert.Equal(new XYZ(224, 18, 0), round(diametric.FirstArcPoint)); + Assert.Equal(new XYZ(176, -18, 0), round(diametric.DefinitionPoint)); + + var ordinate = all.OfType().First(); + Assert.True(ordinate.IsDefaultTextLocation); + Assert.Equal(new XYZ(120, -40, 0), round(ordinate.LeaderEndpoint)); + } + + [Fact] + public void ReadOverrides() + { + //The 1:20 representation of some dimensions has overridden variables + foreach (CadDocument doc in new[] { DwgReader.Read(path("dwg"), this.onNotification), DxfReader.Read(path("dxf"), this.onNotification) }) + { + var all = contextData(doc).Values.ToList(); + + var tix = Assert.Single(all, d => d.TextInsideExtensions); + Assert.IsType(tix); + Assert.Equal(8, tix.OverrideFlags); + + var tofl = Assert.Single(all, d => d.TextOutsideExtensions); + Assert.IsType(tofl); + Assert.Equal(1, tofl.OverrideFlags); + + Assert.IsType(Assert.Single(all, d => d.OverrideFlags == 4)); + Assert.IsType(Assert.Single(all, d => d.OverrideFlags == 16)); + Assert.IsType(Assert.Single(all, d => d.FlipSecondArrow)); + } + + var dxf = contextData(DxfReader.Read(path("dxf"), this.onNotification)).Values; + Assert.Equal(TextArrowFitType.BestFit, Assert.Single(dxf, d => d.OverrideFlags == 4).DimensionTextArrowFit); + Assert.Equal(TextMovement.FreeTextPosition, Assert.Single(dxf, d => d.OverrideFlags == 16).TextMovement); + } + + private static Dictionary contextData(CadDocument doc) + { + var result = new Dictionary(); + foreach (Entity e in doc.Entities) + { + collect(e.XDictionary, result); + } + return result; + } + + private static void collect(CadDictionary dictionary, Dictionary result) + { + if (dictionary == null) + { + return; + } + + foreach (NonGraphicalObject item in dictionary) + { + if (item is DimensionObjectContextData data) + { + result[data.Handle] = data; + } + else if (item is CadDictionary sub) + { + collect(sub, result); + } + } + } + + private static string path(string ext) + { + return Path.Combine(TestVariables.SamplesFolder, $"{_fileName}.{ext}"); + } + + private static XY round(XY p) => new XY(System.Math.Round(p.X, 6), System.Math.Round(p.Y, 6)); + + private static XYZ round(XYZ p) => new XYZ(System.Math.Round(p.X, 6), System.Math.Round(p.Y, 6), System.Math.Round(p.Z, 6)); +} diff --git a/src/ACadSharp/DxfFileToken.cs b/src/ACadSharp/DxfFileToken.cs index 245abb0a4..d1457d430 100644 --- a/src/ACadSharp/DxfFileToken.cs +++ b/src/ACadSharp/DxfFileToken.cs @@ -186,7 +186,7 @@ public static class DxfFileToken public const string OrdinateDimensionObjectContextData = "ACDB_ORDDIMOBJECTCONTEXTDATA_CLASS"; - public const string RadialDimensionLargeObjectContextData = "ACDB_RADIMLGOBJECTCONTEXTDATA_CLASS"; + public const string RadialDimensionLargeObjectContextData = "ACDB_RADIMLRGOBJECTCONTEXTDATA_CLASS"; public const string RadialDimensionObjectContextData = "ACDB_RADIMOBJECTCONTEXTDATA_CLASS"; diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs index 9c04bafda..5a24d49b4 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.Objects.cs @@ -1,6 +1,7 @@ using ACadSharp.IO.Templates; using ACadSharp.Objects; using ACadSharp.Objects.Evaluations; +using ACadSharp.Tables; using System; using System.Collections.Generic; @@ -33,8 +34,71 @@ private CadTemplate readDimensionObjectContextData(DimensionObjectContextData co { var template = new CadDimensionObjectContextDataTemplate(contextData); this.readAnnotScaleObjectContextData(template); - //H 2 dimension block for this scale; the data fields are not read + + //2RD 10 Text location + contextData.TextLocation = this._objectReader.Read2RawDouble(); + //B 294 Default text location + contextData.IsDefaultTextLocation = this._objectReader.ReadBit(); + //BD 140 Text rotation + contextData.TextRotation = this._objectReader.ReadBitDouble(); + //H 2 Dimension block for this scale template.BlockHandle = this.handleReference(); + //B 293 Unknown + contextData.Unknown293 = this._objectReader.ReadBit(); + //B 298 DIMTOFL + contextData.TextOutsideExtensions = this._objectReader.ReadBit(); + //B 291 DIMSOXD + contextData.SuppressOutsideExtensions = this._objectReader.ReadBit(); + //B 70 DIMATFIT is overridden, the DXF value is not stored + this._objectReader.ReadBit(); + //B 292 DIMTIX + contextData.TextInsideExtensions = this._objectReader.ReadBit(); + //B 71 DIMTMOVE is overridden, the DXF value is not stored + this._objectReader.ReadBit(); + //RC 280 Override flags + contextData.OverrideFlags = this._objectReader.ReadByte(); + //B 295 Unknown + contextData.Unknown295 = this._objectReader.ReadBit(); + //B 296 Flip second arrow + contextData.FlipSecondArrow = this._objectReader.ReadBit(); + //B 297 Flip first arrow + contextData.FlipFirstArrow = this._objectReader.ReadBit(); + + switch (contextData) + { + case AlignedDimensionObjectContextData aligned: + //3BD 11 Dimension line point + aligned.DimensionLinePoint = this._objectReader.Read3BitDouble(); + break; + case AngularDimensionObjectContextData angular: + //3BD 11 Arc point + angular.ArcPoint = this._objectReader.Read3BitDouble(); + break; + case DiametricDimensionObjectContextData diametric: + //3BD 11 First arc point + diametric.FirstArcPoint = this._objectReader.Read3BitDouble(); + //3BD 12 Definition point + diametric.DefinitionPoint = this._objectReader.Read3BitDouble(); + break; + case OrdinateDimensionObjectContextData ordinate: + //3BD 11 Definition point + ordinate.DefinitionPoint = this._objectReader.Read3BitDouble(); + //3BD 12 Leader end point + ordinate.LeaderEndpoint = this._objectReader.Read3BitDouble(); + break; + case RadialDimensionObjectContextData radial: + //3BD 11 First arc point + radial.FirstArcPoint = this._objectReader.Read3BitDouble(); + if (radial is RadialDimensionLargeObjectContextData large) + { + //3BD 12 Override center + large.OverrideCenter = this._objectReader.Read3BitDouble(); + //3BD 13 Jog point + large.JogPoint = this._objectReader.Read3BitDouble(); + } + break; + } + return template; } diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs index 92e0f7eeb..825b2423b 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs @@ -108,6 +108,25 @@ private bool readAnnotScaleObjectContextData(CadTemplate template, DxfMap map) } } + private bool readDimensionObjectContextData(CadTemplate template, DxfMap map) + { + var tmp = template as CadDimensionObjectContextDataTemplate; + switch (this._reader.Code) + { + case 2: + tmp.BlockName = this._reader.ValueAsString; + return true; + default: + //AcDbAnnotScaleObjectContextData has no properties of its own in the map + if (!string.IsNullOrEmpty(this.currentSubclass) && !map.SubClasses.ContainsKey(this.currentSubclass)) + { + return this._reader.Code == 340 && this.readAnnotScaleObjectContextData(template, map); + } + + return this.readAnnotScaleObjectContextData(template, map); + } + } + private bool readBlock1PtParameter(CadTemplate template, DxfMap map) { CadBlock1PtParameterTemplate tmp = template as CadBlock1PtParameterTemplate; @@ -2229,6 +2248,18 @@ private CadTemplate readObject() return this.readObjectCodes(new CadAnnotScaleObjectContextDataTemplate(new BlockReferenceObjectContextData()), this.readAnnotScaleObjectContextData); case DxfFileToken.MTextAttributeObjectContextData: return this.readObjectCodes(new CadAnnotScaleObjectContextDataTemplate(new MTextAttributeObjectContextData()), this.readAnnotScaleObjectContextData); + case DxfFileToken.AlignedDimensionObjectContextData: + return this.readObjectCodes(new CadDimensionObjectContextDataTemplate(new AlignedDimensionObjectContextData()), this.readDimensionObjectContextData); + case DxfFileToken.AngularDimensionObjectContextData: + return this.readObjectCodes(new CadDimensionObjectContextDataTemplate(new AngularDimensionObjectContextData()), this.readDimensionObjectContextData); + case DxfFileToken.DiametricDimensionObjectContextData: + return this.readObjectCodes(new CadDimensionObjectContextDataTemplate(new DiametricDimensionObjectContextData()), this.readDimensionObjectContextData); + case DxfFileToken.OrdinateDimensionObjectContextData: + return this.readObjectCodes(new CadDimensionObjectContextDataTemplate(new OrdinateDimensionObjectContextData()), this.readDimensionObjectContextData); + case DxfFileToken.RadialDimensionObjectContextData: + return this.readObjectCodes(new CadDimensionObjectContextDataTemplate(new RadialDimensionObjectContextData()), this.readDimensionObjectContextData); + case DxfFileToken.RadialDimensionLargeObjectContextData: + return this.readObjectCodes(new CadDimensionObjectContextDataTemplate(new RadialDimensionLargeObjectContextData()), this.readDimensionObjectContextData); case DxfFileToken.ObjectPlaceholder: return this.readObjectCodes(new CadNonGraphicalObjectTemplate(new AcdbPlaceHolder()), this.readObjectSubclassMap); case DxfFileToken.ObjectDBColor: diff --git a/src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs b/src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs index 9d60bd88e..cfb7725bf 100644 --- a/src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadDimensionObjectContextDataTemplate.cs @@ -7,13 +7,15 @@ internal class CadDimensionObjectContextDataTemplate : CadAnnotScaleObjectContex { public ulong? BlockHandle { get; set; } + public string BlockName { get; set; } + public CadDimensionObjectContextDataTemplate(DimensionObjectContextData cadObject) : base(cadObject) { } protected override void build(CadDocumentBuilder builder) { base.build(builder); - if (builder.TryGetCadObject(this.BlockHandle, out BlockRecord block)) + if (this.getTableReference(builder, this.BlockHandle, this.BlockName, out BlockRecord block)) { ((DimensionObjectContextData)this.CadObject).Block = block; } diff --git a/src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs b/src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs index 504c3ed02..8fa0169f0 100644 --- a/src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/AlignedDimensionObjectContextData.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using CSMath; namespace ACadSharp.Objects; @@ -9,6 +10,12 @@ namespace ACadSharp.Objects; [DxfSubClass(DxfSubclassMarker.AlignedDimensionObjectContextData)] public class AlignedDimensionObjectContextData : DimensionObjectContextData { + ///

+ /// Point on the dimension line. + /// + [DxfCodeValue(11, 21, 31)] + public XYZ DimensionLinePoint { get; set; } + /// public override string ObjectName => DxfFileToken.AlignedDimensionObjectContextData; diff --git a/src/ACadSharp/Objects/AngularDimensionObjectContextData.cs b/src/ACadSharp/Objects/AngularDimensionObjectContextData.cs index 47f91a112..8b09d011f 100644 --- a/src/ACadSharp/Objects/AngularDimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/AngularDimensionObjectContextData.cs @@ -1,14 +1,21 @@ using ACadSharp.Attributes; +using CSMath; namespace ACadSharp.Objects; /// -/// Annotation scale representation of an angular dimension. +/// Annotation scale representation of an angular or arc length dimension. /// [DxfName(DxfFileToken.AngularDimensionObjectContextData)] [DxfSubClass(DxfSubclassMarker.AngularDimensionObjectContextData)] public class AngularDimensionObjectContextData : DimensionObjectContextData { + /// + /// Point on the dimension arc. + /// + [DxfCodeValue(11, 21, 31)] + public XYZ ArcPoint { get; set; } + /// public override string ObjectName => DxfFileToken.AngularDimensionObjectContextData; diff --git a/src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs b/src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs index aef5630be..5c6986f5a 100644 --- a/src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/DiametricDimensionObjectContextData.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using CSMath; namespace ACadSharp.Objects; @@ -9,6 +10,18 @@ namespace ACadSharp.Objects; [DxfSubClass(DxfSubclassMarker.DiametricDimensionObjectContextData)] public class DiametricDimensionObjectContextData : DimensionObjectContextData { + /// + /// Point on the curve where the dimension line starts. + /// + [DxfCodeValue(11, 21, 31)] + public XYZ FirstArcPoint { get; set; } + + /// + /// Point on the curve opposite to . + /// + [DxfCodeValue(12, 22, 32)] + public XYZ DefinitionPoint { get; set; } + /// public override string ObjectName => DxfFileToken.DiametricDimensionObjectContextData; diff --git a/src/ACadSharp/Objects/DimensionObjectContextData.cs b/src/ACadSharp/Objects/DimensionObjectContextData.cs index 3c84d1a2d..32c9826a4 100644 --- a/src/ACadSharp/Objects/DimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/DimensionObjectContextData.cs @@ -1,20 +1,117 @@ using ACadSharp.Attributes; +using ACadSharp.Extensions; using ACadSharp.Tables; +using CSMath; namespace ACadSharp.Objects; /// -/// Annotation scale representation of a dimension: the dimension's geometry block for one annotation scale. +/// Annotation scale representation of a dimension: the dimension's geometry block and text placement for one annotation scale. /// -/// -/// Only the block is read, the dimension data fields are not. -/// [DxfSubClass(DxfSubclassMarker.DimensionObjectContextData)] public abstract class DimensionObjectContextData : AnnotScaleObjectContextData { - /// Anonymous block holding the dimension's graphics at . + /// + /// Anonymous block holding the dimension's graphics at . + /// + [DxfCodeValue(DxfReferenceType.Name, 2)] public BlockRecord Block { get; set; } + /// + /// Arrow fit override (DIMATFIT) for this scale. + /// + /// + /// Only read from DXF, DWG files store only whether the value is overridden. + /// + [DxfCodeValue(70)] + public TextArrowFitType DimensionTextArrowFit { get; set; } + + /// + /// Flips the first arrowhead. + /// + [DxfCodeValue(297)] + public bool FlipFirstArrow { get; set; } + + /// + /// Flips the second arrowhead. + /// + [DxfCodeValue(296)] + public bool FlipSecondArrow { get; set; } + + /// + /// Indicates whether the text is at its default location. + /// + [DxfCodeValue(294)] + public bool IsDefaultTextLocation { get; set; } + + /// + /// Flags of the dimension variables overridden for this scale. + /// + /// + /// 1 = DIMTOFL, 4 = DIMATFIT, 8 = DIMTIX, 16 = DIMTMOVE. + /// + [DxfCodeValue(280)] + public byte OverrideFlags { get; set; } + /// public override string SubclassMarker => DxfSubclassMarker.DimensionObjectContextData; + + /// + /// Suppress dimension lines outside the extension lines override (DIMSOXD) for this scale. + /// + [DxfCodeValue(291)] + public bool SuppressOutsideExtensions { get; set; } + + /// + /// Text inside extension lines override (DIMTIX) for this scale. + /// + [DxfCodeValue(292)] + public bool TextInsideExtensions { get; set; } + + /// + /// Text location in the dimension's plane. + /// + [DxfCodeValue(10, 20)] + public XY TextLocation { get; set; } + + /// + /// Text movement override (DIMTMOVE) for this scale. + /// + /// + /// Only read from DXF, DWG files store only whether the value is overridden. + /// + [DxfCodeValue(71)] + public TextMovement TextMovement { get; set; } + + /// + /// Draw dimension line between extension lines override (DIMTOFL) for this scale. + /// + [DxfCodeValue(298)] + public bool TextOutsideExtensions { get; set; } + + /// + /// Text rotation, in radians. + /// + [DxfCodeValue(140)] + public double TextRotation { get; set; } + + /// + /// Unknown flag (DXF 293). + /// + [DxfCodeValue(293)] + public bool Unknown293 { get; set; } + + /// + /// Unknown flag (DXF 295). + /// + [DxfCodeValue(295)] + public bool Unknown295 { get; set; } + + /// + public override CadObject Clone() + { + DimensionObjectContextData clone = (DimensionObjectContextData)base.Clone(); + clone.Block = this.Block?.CloneTyped(); + return clone; + } } diff --git a/src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs b/src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs index 67606aa36..cef9a6a98 100644 --- a/src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/OrdinateDimensionObjectContextData.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using CSMath; namespace ACadSharp.Objects; @@ -9,6 +10,18 @@ namespace ACadSharp.Objects; [DxfSubClass(DxfSubclassMarker.OrdinateDimensionObjectContextData)] public class OrdinateDimensionObjectContextData : DimensionObjectContextData { + /// + /// Definition point. + /// + [DxfCodeValue(11, 21, 31)] + public XYZ DefinitionPoint { get; set; } + + /// + /// End point of the leader. + /// + [DxfCodeValue(12, 22, 32)] + public XYZ LeaderEndpoint { get; set; } + /// public override string ObjectName => DxfFileToken.OrdinateDimensionObjectContextData; diff --git a/src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs b/src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs index 9e54696c7..147811e76 100644 --- a/src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs +++ b/src/ACadSharp/Objects/RadialDimensionLargeObjectContextData.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using CSMath; namespace ACadSharp.Objects; @@ -7,8 +8,20 @@ namespace ACadSharp.Objects; ///
[DxfName(DxfFileToken.RadialDimensionLargeObjectContextData)] [DxfSubClass(DxfSubclassMarker.RadialDimensionLargeObjectContextData)] -public class RadialDimensionLargeObjectContextData : DimensionObjectContextData +public class RadialDimensionLargeObjectContextData : RadialDimensionObjectContextData { + /// + /// Override center point. + /// + [DxfCodeValue(12, 22, 32)] + public XYZ OverrideCenter { get; set; } + + /// + /// Jog point. + /// + [DxfCodeValue(13, 23, 33)] + public XYZ JogPoint { get; set; } + /// public override string ObjectName => DxfFileToken.RadialDimensionLargeObjectContextData; diff --git a/src/ACadSharp/Objects/RadialDimensionObjectContextData.cs b/src/ACadSharp/Objects/RadialDimensionObjectContextData.cs index f9be80959..467f865b5 100644 --- a/src/ACadSharp/Objects/RadialDimensionObjectContextData.cs +++ b/src/ACadSharp/Objects/RadialDimensionObjectContextData.cs @@ -1,4 +1,5 @@ using ACadSharp.Attributes; +using CSMath; namespace ACadSharp.Objects; @@ -9,6 +10,12 @@ namespace ACadSharp.Objects; [DxfSubClass(DxfSubclassMarker.RadialDimensionObjectContextData)] public class RadialDimensionObjectContextData : DimensionObjectContextData { + /// + /// Point on the curve where the dimension line ends. + /// + [DxfCodeValue(11, 21, 31)] + public XYZ FirstArcPoint { get; set; } + /// public override string ObjectName => DxfFileToken.RadialDimensionObjectContextData; From f2ad8c93adf8e7e58faaba5543b54352a9633d44 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:30:46 -0500 Subject: [PATCH 40/81] Keep plot style, plot view and xref block handles read from DWG The DWG reader read these handles and discarded them: - entity plot style flags and plot style handle (named plot styles), now Entity.PlotStyleFlags and Entity.PlotStyleHandle; - layer plot style handle, now set to Layer.PlotStyleName as the DXF reader already does; - layer external reference block handle, which was stored in an unused LayerControlHandle template property, now Layer.XrefBlockHandle; - plot settings plot view handle, now PlotSettings.PlotViewHandle. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp/Entities/Entity.cs | 11 +++++++++++ .../IO/DWG/DwgStreamReaders/DwgObjectReader.cs | 17 ++++++++--------- src/ACadSharp/IO/Templates/CadLayerTemplate.cs | 8 +++++--- src/ACadSharp/Objects/PlotSettings.cs | 6 ++++++ src/ACadSharp/Tables/Layer.cs | 6 ++++++ 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/ACadSharp/Entities/Entity.cs b/src/ACadSharp/Entities/Entity.cs index d0b7073bc..1e39340a2 100644 --- a/src/ACadSharp/Entities/Entity.cs +++ b/src/ACadSharp/Entities/Entity.cs @@ -76,6 +76,17 @@ public virtual LineType LineType [DxfCodeValue(370)] public LineWeightType LineWeight { get; set; } = LineWeightType.ByLayer; + /// + /// Plot style source of the entity (named plot style drawings), as stored in DWG: 0 = by layer, + /// 1 = by block, 3 = the style in . + /// + public byte PlotStyleFlags { get; set; } + + /// + /// Handle of the entity's plot style name (an entry of the ACAD_PLOTSTYLENAME dictionary) when is 3. + /// + public ulong PlotStyleHandle { get; set; } + /// [DxfCodeValue(DxfReferenceType.Handle, 347)] public Material Material diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 88239dd14..281816b2e 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -2000,10 +2000,11 @@ private void readEntityMode(CadEntityTemplate template) //R2000 +: //Plotstyle flags BB 00 = bylayer, 01 = byblock, 11 = plotstyle handle present at end of object - if (this._objectReader.Read2Bits() == 3) + template.CadObject.PlotStyleFlags = this._objectReader.Read2Bits(); + if (template.CadObject.PlotStyleFlags == 3) { //PLOTSTYLE (hard pointer) present if plotstyle flags were 11 - long plotstyleFlags = (long)this.handleReference(); + template.CadObject.PlotStyleHandle = this.handleReference(); } //R2007 +: @@ -2714,12 +2715,10 @@ private CadTemplate readLayer() var color = this._mergedReaders.ReadCmColor(); layer.Color = color.IsByBlock || color.IsByLayer ? Color.Default : color; - //TODO: This is not the Layer control handle - template.LayerControlHandle = this.handleReference(); - //Handle refs H Layer control (soft pointer) - //[Reactors(soft pointer)] - //xdicobjhandle(hard owner) - //External reference block handle(hard pointer) + //Handle refs H Layer control (soft pointer), [Reactors (soft pointer)] and xdicobjhandle + //(hard owner) are read with the common data; this one is the + //External reference block handle (hard pointer): the xref a dependent layer comes from + layer.XrefBlockHandle = this.handleReference(); //R2000+: if (this.R2000Plus) @@ -5091,7 +5090,7 @@ private void readPlotSettings(PlotSettings plot) plot.ShadePlotDPI = this._objectReader.ReadBitShort(); //6 plot view handle(hard pointer) - ulong plotViewHandle = this.handleReference(); + plot.PlotViewHandle = this.handleReference(); } //R2007 +: diff --git a/src/ACadSharp/IO/Templates/CadLayerTemplate.cs b/src/ACadSharp/IO/Templates/CadLayerTemplate.cs index 03e09a799..9139a1406 100644 --- a/src/ACadSharp/IO/Templates/CadLayerTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadLayerTemplate.cs @@ -5,8 +5,6 @@ namespace ACadSharp.IO.Templates { internal class CadLayerTemplate : CadTableEntryTemplate { - public ulong LayerControlHandle { get; set; } - public ulong PlotStyleHandle { get; set; } public ulong MaterialHandle { get; set; } @@ -28,7 +26,11 @@ protected override void build(CadDocumentBuilder builder) base.build(builder); - //this.CadObject.PlotStyleName = builder.GetCadObject(PlotStyleHandle); + // DWG keeps the layer's plot style (ACAD_PLOTSTYLENAME entry) as a handle; the DXF reader sets it directly. + if (this.PlotStyleHandle != 0) + { + this.CadObject.PlotStyleName = this.PlotStyleHandle; + } if (builder.TryGetCadObject(this.MaterialHandle, out Material material)) { diff --git a/src/ACadSharp/Objects/PlotSettings.cs b/src/ACadSharp/Objects/PlotSettings.cs index 49d56d807..418f05f52 100644 --- a/src/ACadSharp/Objects/PlotSettings.cs +++ b/src/ACadSharp/Objects/PlotSettings.cs @@ -141,6 +141,12 @@ public double NumeratorScale [DxfCodeValue(6)] public string PlotViewName { get; set; } + /// + /// Handle of the named view plotted when the plot type is View (DWG R2004+ store the view this way + /// instead of by ); 0 when none. + /// + public ulong PlotViewHandle { get; set; } + /// /// Gets the scale factor. /// diff --git a/src/ACadSharp/Tables/Layer.cs b/src/ACadSharp/Tables/Layer.cs index 4b575556b..55ec172d6 100644 --- a/src/ACadSharp/Tables/Layer.cs +++ b/src/ACadSharp/Tables/Layer.cs @@ -129,6 +129,12 @@ public bool PlotFlag [DxfCodeValue(DxfReferenceType.Unprocess, 390)] public ulong PlotStyleName { get; internal set; } = 0; + /// + /// Handle of the xref block record an xref-dependent layer comes from (0 when not dependent). + /// The layer's "XREF|" name prefix can be stale after the xref is renamed; this link is not. + /// + public ulong XrefBlockHandle { get; set; } + /// public override string SubclassMarker => DxfSubclassMarker.Layer; From be066360d5efe55e34be3fbb1d0519b43d2a1155 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:31:40 -0500 Subject: [PATCH 41/81] Read RTEXT data into UnknownEntity RTEXT (Express Tools remote text) has no class in the library and is read as an UnknownEntity, losing its data. Dispatch it through the readUnlistedType switch with a DxfFileToken constant, read the common entity data, then its insertion point, normal, rotation, height, flags, contents (a DIESEL expression or a file name) and text style handle into UnknownEntity.RText. A read failure only reports a warning. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp/DxfFileToken.cs | 2 ++ src/ACadSharp/Entities/UnknownEntity.cs | 26 +++++++++++++++++ .../DWG/DwgStreamReaders/DwgObjectReader.cs | 28 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/src/ACadSharp/DxfFileToken.cs b/src/ACadSharp/DxfFileToken.cs index c43321492..996dbef44 100644 --- a/src/ACadSharp/DxfFileToken.cs +++ b/src/ACadSharp/DxfFileToken.cs @@ -120,6 +120,8 @@ public static class DxfFileToken public const string EntityRevolvedSurface = "REVOLVEDSURFACE"; + public const string EntityRText = "RTEXT"; + public const string EntitySection = "SECTION"; public const string EntitySeqend = "SEQEND"; diff --git a/src/ACadSharp/Entities/UnknownEntity.cs b/src/ACadSharp/Entities/UnknownEntity.cs index d030367b2..29c7844c7 100644 --- a/src/ACadSharp/Entities/UnknownEntity.cs +++ b/src/ACadSharp/Entities/UnknownEntity.cs @@ -46,6 +46,32 @@ public override string SubclassMarker } } + /// + /// Remote text (Express Tools RTEXT) data, when this unknown entity is one and could be read. + /// + public RemoteText RText { get; set; } + + /// + /// Data of an Express Tools RTEXT object: text from a file or from a DIESEL expression. + /// + public class RemoteText + { + /// Insertion point. + public XYZ InsertPoint { get; set; } + /// Extrusion direction. + public XYZ Normal { get; set; } = XYZ.AxisZ; + /// Rotation, radians. + public double Rotation { get; set; } + /// Text height. + public double Height { get; set; } + /// Flags: 1 = contents is a DIESEL expression, otherwise a file name. + public short Flags { get; set; } + /// DIESEL expression or file name. + public string Contents { get; set; } + /// Handle of the text style. + public ulong StyleHandle { get; set; } + } + /// /// Dxf class linked to this entity. /// diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 88239dd14..d79559f34 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -6212,6 +6212,9 @@ private CadTemplate readUnlistedType(short classNumber) case DxfFileToken.ObjectWipeoutVariables: template = this.readWipeoutVariables(); break; + case DxfFileToken.EntityRText: + template = this.readRText(c); + break; } if (template == null && c.IsAnEntity) @@ -7325,6 +7328,31 @@ private CadTemplate readUnknownEntity(DxfClass dxfClass) return template; } + private CadTemplate readRText(DxfClass dxfClass) + { + CadTemplate template = this.readUnknownEntity(dxfClass); + UnknownEntity entity = (UnknownEntity)template.CadObject; + + try + { + var rt = new UnknownEntity.RemoteText(); + rt.InsertPoint = this._objectReader.Read3BitDouble(); + rt.Normal = this._objectReader.Read3BitDouble(); + rt.Rotation = this._objectReader.ReadBitDouble(); + rt.Height = this._objectReader.ReadBitDouble(); + rt.Flags = this._objectReader.ReadBitShort(); + rt.Contents = this._textReader.ReadVariableText(); + rt.StyleHandle = this.handleReference(); + entity.RText = rt; + } + catch (System.Exception ex) + { + this._builder.Notify($"RTEXT could not be read: {ex.Message}", NotificationType.Warning); + } + + return template; + } + private CadTemplate readUnknownNonGraphicalObject(DxfClass dxfClass) { UnknownNonGraphicalObject obj = new UnknownNonGraphicalObject(dxfClass); From 834701c363e581fa745988099ec85e21dbb9c919 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 14:10:13 +0000 Subject: [PATCH 42/81] Add native support for orphaned TABLE sections in DXF reader Some non-standard R12 (AC1009) DXF files from third-party CAD tools place TABLE records directly after the HEADER ENDSEC without the required SECTION/TABLES/ENDSEC wrapper. ACadSharp's main read loop previously skipped these bare TABLE tokens, silently producing a document with no layers. - DxfTablesSectionReader: add ReadOrphaned() that reads bare TABLE/ENDTAB blocks without expecting an ENDSEC wrapper, terminating on the next SECTION, ENDSEC, or EOF token - DxfReader.Read(): detect TABLE tokens at the top-level loop and route them to ReadOrphaned(), with a Warning notification; zero cost on conformant files since TABLE can never appear at top-level there - Add orphaned_tables_AC1009.dxf sample file reproducing the non-standard structure (bare LAYER table followed by an ENTITIES section) - Add ReadOrphanedTablesSectionTest verifying the warning is fired and the layer is correctly imported --- samples/orphaned_tables_AC1009.dxf | 52 +++++++++++++++++++ src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs | 22 ++++++++ .../DxfStreamReader/DxfTablesSectionReader.cs | 24 +++++++++ src/ACadSharp/IO/DxfReader.cs | 9 ++++ 4 files changed, 107 insertions(+) create mode 100644 samples/orphaned_tables_AC1009.dxf diff --git a/samples/orphaned_tables_AC1009.dxf b/samples/orphaned_tables_AC1009.dxf new file mode 100644 index 000000000..2b4170ce4 --- /dev/null +++ b/samples/orphaned_tables_AC1009.dxf @@ -0,0 +1,52 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1009 + 0 +ENDSEC + 0 +TABLE + 2 +LAYER + 70 + 1 + 0 +LAYER + 2 +TestLayer + 70 + 0 + 62 + 3 + 6 +CONTINUOUS + 0 +ENDTAB + 0 +SECTION + 2 +ENTITIES + 0 +LINE + 8 +TestLayer + 10 +0.0 + 20 +0.0 + 30 +0.0 + 11 +100.0 + 21 +100.0 + 31 +0.0 + 0 +ENDSEC + 0 +EOF diff --git a/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs index 958c9bd7d..4db8bbeaf 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs @@ -180,4 +180,26 @@ public void ReadTables(FileModel test) Assert.NotNull(doc.Views); Assert.NotNull(doc.VPorts); } + + [Fact] + public void ReadOrphanedTablesSectionTest() + { + string path = System.IO.Path.Combine(TestVariables.SamplesFolder, "orphaned_tables_AC1009.dxf"); + + bool notified = false; + CadDocument doc; + using (DxfReader reader = new DxfReader(path)) + { + reader.OnNotification += (_, e) => + { + if (e.NotificationType == NotificationType.Warning + && e.Message.Contains("Non-standard DXF")) + notified = true; + }; + doc = reader.Read(); + } + + Assert.True(notified, "Expected a warning notification about the orphaned TABLE section."); + Assert.True(doc.Layers.Contains("TestLayer"), "Layer 'TestLayer' should be imported from the orphaned TABLE section."); + } } \ No newline at end of file diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs index 0d6f6a85c..4d08f9c07 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs @@ -44,6 +44,30 @@ public override void Read() } } + /// + /// Reads TABLE entries that appear without a SECTION/TABLES/ENDSEC wrapper (non-standard DXF). + /// The reader must be positioned at the first bare TABLE token on entry. + /// Returns with the reader positioned at the next SECTION, ENDSEC, or EOF token. + /// + internal void ReadOrphaned() + { + while (this._reader.ValueAsString != DxfFileToken.EndSection + && this._reader.ValueAsString != DxfFileToken.BeginSection + && this._reader.ValueAsString != DxfFileToken.EndOfFile) + { + if (this._reader.ValueAsString != DxfFileToken.TableEntry) + { + this._builder.Notify( + $"Unexpected token '{this._reader.ValueAsString}' while reading orphaned TABLE entries.", + NotificationType.Warning); + break; + } + + this.readTable(); + this._reader.ReadNext(); + } + } + private void readTable() { Debug.Assert(this._reader.ValueAsString == DxfFileToken.TableEntry); diff --git a/src/ACadSharp/IO/DxfReader.cs b/src/ACadSharp/IO/DxfReader.cs index 86139e926..efa156f43 100644 --- a/src/ACadSharp/IO/DxfReader.cs +++ b/src/ACadSharp/IO/DxfReader.cs @@ -158,6 +158,15 @@ public override CadDocument Read() while (this._reader.ValueAsString != DxfFileToken.EndOfFile) { + if (this._reader.ValueAsString == DxfFileToken.TableEntry) + { + this.triggerNotification( + "Non-standard DXF: TABLE entry found outside of a TABLES section. Attempting recovery.", + NotificationType.Warning); + new DxfTablesSectionReader(this._reader, this._builder).ReadOrphaned(); + continue; + } + if (this._reader.ValueAsString != DxfFileToken.BeginSection) { this._reader.ReadNext(); From 25034f5f4c06ac7a296c836bdd85191c16644a00 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 10:57:24 +0000 Subject: [PATCH 43/81] Reuse DxfTablesSectionReader.ReadTable for orphaned TABLE entries Review feedback on DomCR/ACadSharp#1142: instead of a separate ReadOrphaned() loop, DxfReader.Read() keeps one DxfTablesSectionReader for the non-standard case and hands every bare TABLE token found at the top level to its ReadTable() method (the former private readTable). The main loop then skips the ENDTAB token like any other token outside of a section and carries on, so orphaned tables are read wherever they appear in the file, grouped or scattered, without duplicating the table loop. The warning is raised once per document, when the first orphaned TABLE is found. The sample file now contains two bare tables (LTYPE and LAYER) and the test checks that the layer resolves the line type defined in the orphaned LTYPE table. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_011ZLpLtgm9c5fRX9tRynhF7 --- samples/orphaned_tables_AC1009.dxf | 24 +++++++++++++- src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs | 2 ++ .../DxfStreamReader/DxfTablesSectionReader.cs | 31 +++++-------------- src/ACadSharp/IO/DxfReader.cs | 15 ++++++--- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/samples/orphaned_tables_AC1009.dxf b/samples/orphaned_tables_AC1009.dxf index 2b4170ce4..85735507e 100644 --- a/samples/orphaned_tables_AC1009.dxf +++ b/samples/orphaned_tables_AC1009.dxf @@ -11,6 +11,28 @@ ENDSEC 0 TABLE 2 +LTYPE + 70 + 1 + 0 +LTYPE + 2 +TestLineType + 70 + 0 + 3 +Test line type + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 LAYER 70 1 @@ -23,7 +45,7 @@ TestLayer 62 3 6 -CONTINUOUS +TestLineType 0 ENDTAB 0 diff --git a/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs index 4db8bbeaf..a78da395f 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs @@ -200,6 +200,8 @@ public void ReadOrphanedTablesSectionTest() } Assert.True(notified, "Expected a warning notification about the orphaned TABLE section."); + Assert.True(doc.LineTypes.Contains("TestLineType"), "Line type 'TestLineType' should be imported from the orphaned TABLE section."); Assert.True(doc.Layers.Contains("TestLayer"), "Layer 'TestLayer' should be imported from the orphaned TABLE section."); + Assert.Equal("TestLineType", doc.Layers["TestLayer"].LineType.Name); } } \ No newline at end of file diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs index 4d08f9c07..48444ca63 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfTablesSectionReader.cs @@ -32,7 +32,7 @@ public override void Read() { if (this._reader.ValueAsString == DxfFileToken.TableEntry) - this.readTable(); + this.ReadTable(); else throw new DxfException($"Unexpected token at the beginning of a table: {this._reader.ValueAsString}", this._reader.Position); @@ -45,30 +45,13 @@ public override void Read() } /// - /// Reads TABLE entries that appear without a SECTION/TABLES/ENDSEC wrapper (non-standard DXF). - /// The reader must be positioned at the first bare TABLE token on entry. - /// Returns with the reader positioned at the next SECTION, ENDSEC, or EOF token. + /// Reads a single table, from its TABLE token to the matching ENDTAB token. /// - internal void ReadOrphaned() - { - while (this._reader.ValueAsString != DxfFileToken.EndSection - && this._reader.ValueAsString != DxfFileToken.BeginSection - && this._reader.ValueAsString != DxfFileToken.EndOfFile) - { - if (this._reader.ValueAsString != DxfFileToken.TableEntry) - { - this._builder.Notify( - $"Unexpected token '{this._reader.ValueAsString}' while reading orphaned TABLE entries.", - NotificationType.Warning); - break; - } - - this.readTable(); - this._reader.ReadNext(); - } - } - - private void readTable() + /// + /// The reader must be positioned at the TABLE token on entry and is positioned at the ENDTAB token on return. + /// Exposed so that can recover tables found outside of the TABLES section. + /// + internal void ReadTable() { Debug.Assert(this._reader.ValueAsString == DxfFileToken.TableEntry); diff --git a/src/ACadSharp/IO/DxfReader.cs b/src/ACadSharp/IO/DxfReader.cs index efa156f43..c7239c16a 100644 --- a/src/ACadSharp/IO/DxfReader.cs +++ b/src/ACadSharp/IO/DxfReader.cs @@ -156,14 +156,21 @@ public override CadDocument Read() this._builder = new DxfDocumentBuilder(this._version, this._document, this.Configuration); this._builder.OnNotification += this.onNotificationEvent; + //Only created if a TABLE is found outside of the TABLES section (non-standard dxf) + DxfTablesSectionReader orphanTablesReader = null; + while (this._reader.ValueAsString != DxfFileToken.EndOfFile) { if (this._reader.ValueAsString == DxfFileToken.TableEntry) { - this.triggerNotification( - "Non-standard DXF: TABLE entry found outside of a TABLES section. Attempting recovery.", - NotificationType.Warning); - new DxfTablesSectionReader(this._reader, this._builder).ReadOrphaned(); + if (orphanTablesReader == null) + { + this.triggerNotification("Non-standard DXF: TABLE entry found outside of a TABLES section. Attempting recovery.", NotificationType.Warning); + orphanTablesReader = new DxfTablesSectionReader(this._reader, this._builder); + } + + //Reads up to the ENDTAB token, which the loop skips like any other token outside of a section + orphanTablesReader.ReadTable(); continue; } From 04b5a2fec022bdb1f4e86e2941f5b1ef0fd055d5 Mon Sep 17 00:00:00 2001 From: Li Yanxu Date: Tue, 13 Oct 2026 22:29:27 +0800 Subject: [PATCH 44/81] refactor: apply review feedback on the retained data of proxies Addresses the review on #1274: - `Data` moves to `IProxy`. Both `ProxyEntity` and `ProxyObject` already carried the same property, and reading it through the interface removes the type switch from the reader. - `ProxyEntity.Data` no longer carries `DxfCodeValue(311)`. The DXF reader has no handler for code 311 on an entity and the DXF writer refuses proxy entities outright, so the attribute promised a round trip that does not exist. The property is DWG only for now. - The `-1` reset in the pre-R2010 branch is gone. A file has one version, so the field cannot carry a stale value from a previous object. - The local `handleSectionOffset` is gone, the field is assigned directly. - The xml comment on the field is gone. --- src/ACadSharp/Entities/ProxyEntity.cs | 11 ++----- .../DWG/DwgStreamReaders/DwgObjectReader.cs | 32 +++---------------- src/ACadSharp/IProxy.cs | 10 ++++++ src/ACadSharp/Objects/ProxyObject.cs | 1 + 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/src/ACadSharp/Entities/ProxyEntity.cs b/src/ACadSharp/Entities/ProxyEntity.cs index 68aca8a36..e145bb86c 100644 --- a/src/ACadSharp/Entities/ProxyEntity.cs +++ b/src/ACadSharp/Entities/ProxyEntity.cs @@ -66,16 +66,11 @@ public class ProxyEntity : Entity, IProxy //An object ID(multiple entries can appear) (optional) //94 0 (indicates end of object ID section) - /// - /// Gets or sets the data of the original object as it was stored while the object is a proxy. - /// + /// /// - /// This is the payload the original application would read back, and it is the only place where - /// the custom object data survives once the object has been saved as a proxy. In the file the - /// section has no length of its own: it runs from the end of the proxy fields up to the start of - /// the handle section. + /// Only the DWG reader fills this. The DXF reader has no handler for code 311 on an entity and + /// the DXF writer does not emit proxy entities at all, so no DxfCodeValue is applied. /// - [DxfCodeValue(311)] public Stream Data { get; set; } /// diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 650c6057f..70c6cf339 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -72,13 +72,6 @@ internal partial class DwgObjectReader : DwgSectionIO /// private IDwgStreamReader _handlesReader; - /// - /// Bit position where the handle section of the current object starts, or -1 when it is unknown. - /// - /// - /// Some sections, notably the retained data of a proxy object, have no length of their own and - /// run until the handle section begins. - /// private long _handleSectionOffset = -1; private IDwgStreamReader _mergedReaders; @@ -242,11 +235,8 @@ private ObjectType getEntityType(long offset) //(the padding bits make sure the object stream ends on a byte boundary). ulong handleSize = this._crcReader.ReadModularChar(); - //Find the handles offset - ulong handleSectionOffset = (ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize; - - //Keep it, some sections run until the handle section starts - this._handleSectionOffset = (long)handleSectionOffset; + //Find the handles offset, some sections run until the handle section starts + this._handleSectionOffset = (long)((ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize); //Create a handler section reader this._objectReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); @@ -258,11 +248,11 @@ private ObjectType getEntityType(long offset) //Create a handler section reader this._handlesReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); - this._handlesReader.SetPositionInBits((long)handleSectionOffset); + this._handlesReader.SetPositionInBits(this._handleSectionOffset); //Create a text section reader this._textReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); - this._textReader.SetPositionByFlag((long)handleSectionOffset - 1); + this._textReader.SetPositionByFlag(this._handleSectionOffset - 1); this._mergedReaders = new DwgMergedReader(this._objectReader, this._textReader, this._handlesReader); } @@ -275,9 +265,6 @@ private ObjectType getEntityType(long offset) this._handlesReader = DwgStreamReaderBase.GetStreamHandler(this._version, HugeMemoryStream.Clone(this._memoryStream), this._reader.Encoding); this._textReader = this._objectReader; - //The handle section offset is not encoded before R2010, sections without a length cannot be delimited - this._handleSectionOffset = -1; - //set the initial position and get the object type this._objectInitialPos = this._objectReader.PositionInBits(); type = this._objectReader.ReadObjectType(); @@ -1207,16 +1194,7 @@ private void readCommonProxyData(IProxy proxy) int dataBytes = (int)((dataBits + 7L) / 8L); byte[] data = this._objectReader.ReadBytes(dataBytes); - Stream stream = new MemoryStream(data, writable: false); - switch (proxy) - { - case ProxyEntity entity: - entity.Data = stream; - break; - case ProxyObject proxyObject: - proxyObject.Data = stream; - break; - } + proxy.Data = new MemoryStream(data, writable: false); } } } diff --git a/src/ACadSharp/IProxy.cs b/src/ACadSharp/IProxy.cs index ea6939799..eab82aede 100644 --- a/src/ACadSharp/IProxy.cs +++ b/src/ACadSharp/IProxy.cs @@ -1,4 +1,5 @@ using ACadSharp.Classes; +using System.IO; namespace ACadSharp; @@ -29,5 +30,14 @@ public interface IProxy /// int ProxyClassId { get; } + /// + /// Gets or sets the retained data of the original object. + /// + /// + /// The custom object data survives as a proxy only here. In the file the section has no length of + /// its own: it runs from the end of the proxy fields up to the start of the handle section. + /// + Stream Data { get; set; } + ACadVersion Version { get; set; } } diff --git a/src/ACadSharp/Objects/ProxyObject.cs b/src/ACadSharp/Objects/ProxyObject.cs index a0beef042..ce26d8079 100644 --- a/src/ACadSharp/Objects/ProxyObject.cs +++ b/src/ACadSharp/Objects/ProxyObject.cs @@ -53,6 +53,7 @@ public class ProxyObject : NonGraphicalObject, IProxy [DxfCodeValue(310)] public Stream BinaryData { get; set; } + /// [DxfCodeValue(311)] public Stream Data { get; set; } From 3058b3305074c4bf62f26aa8be4c16875a988bed Mon Sep 17 00:00:00 2001 From: Li Yanxu Date: Tue, 13 Oct 2026 22:34:46 +0800 Subject: [PATCH 45/81] docs: trim comments that restate the code Removes notes that a reader can infer from the code itself and shortens the rest to the part that is not obvious: - the aside about proxy graphics in `ProxyEntity` was unrelated to this change - the `remarks` on `ProxyEntity.Data` is now one line, the missing `DxfCodeValue` is the only surprising part - the `remarks` on `IProxy.Data` described the file layout, which belongs to the reader, not to the public API - `//Find the handles offset` no longer carries an appended rationale - the note in `readCommonProxyData` is one line, and the comment above the byte rounding is gone --- src/ACadSharp/Entities/ProxyEntity.cs | 6 +----- src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs | 6 ++---- src/ACadSharp/IProxy.cs | 4 ---- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/ACadSharp/Entities/ProxyEntity.cs b/src/ACadSharp/Entities/ProxyEntity.cs index e145bb86c..b837c48c0 100644 --- a/src/ACadSharp/Entities/ProxyEntity.cs +++ b/src/ACadSharp/Entities/ProxyEntity.cs @@ -56,7 +56,6 @@ public class ProxyEntity : Entity, IProxy //92 Size of graphics data in bytes //310 Binary graphics data(multiple entries can appear) (optional) - //The graphics are read as common entity data and exposed through Entity.ProxyGeometries. //93 Size of entity data in bits //310 Binary entity data(multiple entries can appear) (optional) @@ -67,10 +66,7 @@ public class ProxyEntity : Entity, IProxy //94 0 (indicates end of object ID section) /// - /// - /// Only the DWG reader fills this. The DXF reader has no handler for code 311 on an entity and - /// the DXF writer does not emit proxy entities at all, so no DxfCodeValue is applied. - /// + /// No DxfCodeValue: the DXF reader has no handler for code 311 on an entity. public Stream Data { get; set; } /// diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 70c6cf339..9430589e3 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -235,7 +235,7 @@ private ObjectType getEntityType(long offset) //(the padding bits make sure the object stream ends on a byte boundary). ulong handleSize = this._crcReader.ReadModularChar(); - //Find the handles offset, some sections run until the handle section starts + //Find the handles offset this._handleSectionOffset = (long)((ulong)this._crcReader.PositionInBits() + sizeInBits - handleSize); //Create a handler section reader @@ -1183,14 +1183,12 @@ private void readCommonProxyData(IProxy proxy) //Common: //Databits X databits, however many there are to the handles - //The retained data of the original object has no length of its own, it extends from the - //current position up to the start of the handle section. + //The retained data has no length of its own, it runs up to the handle section if (this._handleSectionOffset > 0) { long dataBits = this._handleSectionOffset - this._objectReader.PositionInBits(); if (dataBits > 0) { - //The section is measured in bits, read the bytes that contain them int dataBytes = (int)((dataBits + 7L) / 8L); byte[] data = this._objectReader.ReadBytes(dataBytes); diff --git a/src/ACadSharp/IProxy.cs b/src/ACadSharp/IProxy.cs index eab82aede..df41357fe 100644 --- a/src/ACadSharp/IProxy.cs +++ b/src/ACadSharp/IProxy.cs @@ -33,10 +33,6 @@ public interface IProxy /// /// Gets or sets the retained data of the original object. /// - /// - /// The custom object data survives as a proxy only here. In the file the section has no length of - /// its own: it runs from the end of the proxy fields up to the start of the handle section. - /// Stream Data { get; set; } ACadVersion Version { get; set; } From 4d3a15d3d4669a719980708862aa4f247c8463ac Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 13 Oct 2026 16:54:15 +0200 Subject: [PATCH 46/81] Preserve XRecord entries in DWG/DXF Added a SortedXRecords round-trip test and updated DWG/DXF readers to retain XRecord.Entry references before resolving handles, allowing XRecord.CreateEntry to return the created entry and CadXRecordTemplate to resolve referenced CAD objects with missing-object warnings. Also added XML docs for XRecord.Entries and cleaned up test formatting. --- .../IO/WriterSingleObjectTests.cs | 39 ++++++++++++++- .../DWG/DwgStreamReaders/DwgObjectReader.cs | 9 ++-- .../DxfObjectsSectionReader.cs | 3 +- .../IO/Templates/CadXRecordTemplate.cs | 49 +++++++++++-------- src/ACadSharp/Objects/XRecrod.cs | 11 +++-- 5 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs index 3b18792ab..304a682a6 100644 --- a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs @@ -101,6 +101,7 @@ static WriterSingleObjectTests() Data.Add(new(nameof(SingleCaseGenerator.SingleMeshWithTextureCoordinates))); Data.Add(new(nameof(SingleCaseGenerator.SingleMaterial))); Data.Add(new(nameof(SingleCaseGenerator.LeaderWithArrowHead))); + Data.Add(new(nameof(SingleCaseGenerator.SortedXRecords))); } public WriterSingleObjectTests(ITestOutputHelper output) : base(output) @@ -1884,7 +1885,7 @@ public void LayerTrueColor() this.AssertRoundtrip = (doc) => { - if(this.Document.Header.Version <= ACadVersion.AC1015 || this.Format == CadFileFormat.DWG) + if (this.Document.Header.Version <= ACadVersion.AC1015 || this.Format == CadFileFormat.DWG) { //Not supported in R2000 and earlier, will be converted to index color return; @@ -2256,7 +2257,7 @@ public void SingleMaterial() this.AssertRoundtrip = (doc) => { - if(this.Format == CadFileFormat.DXF) + if (this.Format == CadFileFormat.DXF) { return; } @@ -2624,6 +2625,40 @@ public void SingleWipeout() }; } + public void SortedXRecords() + { + Layer layer = this.Document.Layers[Layer.DefaultName]; + + XRecord xrecord = new XRecord("MOREDWG_ORDER"); + xrecord.CreateEntry(300, "before"); + xrecord.CreateEntry(330, layer); + xrecord.CreateEntry(294, false); + xrecord.CreateEntry(90, 7); + + CadDictionary dictionary = new CadDictionary("MOREDWG_TEST"); + dictionary.Add(xrecord); + this.Document.RootDictionary.Add(dictionary); + + MemoryStream ms = new MemoryStream(); + DwgWriter.Write(ms, this.Document); + using MemoryStream readStream = new MemoryStream(ms.ToArray()); + CadDocument rt = DwgReader.Read(readStream); + + this.AssertRoundtrip = (doc) => + { + Assert.True(rt.RootDictionary.TryGetEntry("MOREDWG_TEST", out CadDictionary rtDictionary)); + Assert.True(rtDictionary.TryGetEntry("MOREDWG_ORDER", out XRecord result)); + + int[] codes = result.Entries.Select(e => e.Code).ToArray(); + Assert.Equal(new[] { 300, 330, 294, 90 }, codes); + + Assert.Equal("before", result.Entries.ElementAt(0).Value); + Assert.Equal(Layer.DefaultName, ((Layer)result.Entries.ElementAt(1).Value).Name); + Assert.False(System.Convert.ToBoolean(result.Entries.ElementAt(2).Value)); + Assert.Equal(7, System.Convert.ToInt32(result.Entries.ElementAt(3).Value)); + }; + } + public void SPlineCreation() { Spline spline = new Spline(); diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs index 88239dd14..06be582ee 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgObjectReader.cs @@ -6900,7 +6900,7 @@ private CadTemplate readXRecord() //numbers all follow the normal DXF convention for group codes. var code = this._objectReader.ReadShort(); var groupCode = GroupCodeValue.TransformValue(code); - + XRecord.Entry entry; switch (groupCode) { case GroupCodeValueType.String: @@ -6937,7 +6937,8 @@ private CadTemplate readXRecord() string hex = this._objectReader.ReadTextUnicode(); if (ulong.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong result)) { - template.AddHandleReference(code, result); + entry = xRecord.CreateEntry(code, result); + template.AddHandleReference(entry); } else { @@ -6953,7 +6954,9 @@ private CadTemplate readXRecord() break; case GroupCodeValueType.ObjectId: case GroupCodeValueType.ExtendedDataHandle: - template.AddHandleReference(code, this._objectReader.ReadRawULong()); + var handle = this._objectReader.ReadRawULong(); + entry = xRecord.CreateEntry(code, handle); + template.AddHandleReference(entry); break; default: this.notify($"Unidentified GroupCodeValueType {code} for XRecord [{xRecord.Handle}]", NotificationType.Warning); diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs index 92e0f7eeb..b8e5f66f5 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfObjectsSectionReader.cs @@ -3157,7 +3157,8 @@ private void readXRecordEntries(CadXRecordTemplate template) case GroupCodeValueType.Handle: case GroupCodeValueType.ObjectId: case GroupCodeValueType.ExtendedDataHandle: - template.AddHandleReference(this._reader.Code, this._reader.ValueAsHandle); + var entry = template.CadObject.CreateEntry(this._reader.Code, this._reader.ValueAsHandle); + template.AddHandleReference(entry); break; default: template.CadObject.CreateEntry(this._reader.Code, this._reader.Value); diff --git a/src/ACadSharp/IO/Templates/CadXRecordTemplate.cs b/src/ACadSharp/IO/Templates/CadXRecordTemplate.cs index da87f8a11..a0fc5cc88 100644 --- a/src/ACadSharp/IO/Templates/CadXRecordTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadXRecordTemplate.cs @@ -1,35 +1,42 @@ using ACadSharp.Objects; -using System; -namespace ACadSharp.IO.Templates +namespace ACadSharp.IO.Templates; + +internal class CadXRecordTemplate : CadTemplate { - internal class CadXRecordTemplate : CadTemplate + private readonly System.Collections.Generic.List _entries = new(); + + public CadXRecordTemplate() : base(new XRecord()) { - private readonly System.Collections.Generic.List> _entries = new(); + } - public CadXRecordTemplate() : base(new XRecord()) { } + public CadXRecordTemplate(XRecord cadObject) : base(cadObject) + { + } - public CadXRecordTemplate(XRecord cadObject) : base(cadObject) { } + public void AddHandleReference(XRecord.Entry entry) + { + _entries.Add(entry); + } - public void AddHandleReference(int code, ulong handle) - { - _entries.Add(new Tuple(code, handle)); - } + protected override void build(CadDocumentBuilder builder) + { + base.build(builder); - protected override void build(CadDocumentBuilder builder) + foreach (var entry in _entries) { - base.build(builder); + if (!(entry.Value is ulong handle)) + { + continue; + } - foreach (var entry in _entries) + if (builder.TryGetCadObject(handle, out CadObject obj)) + { + entry.Value = obj; + } + else { - if (builder.TryGetCadObject(entry.Item2, out CadObject obj)) - { - this.CadObject.CreateEntry(entry.Item1, obj); - } - else - { - builder.Notify($"XRecord reference not found {entry.Item1}|{entry.Item2}", NotificationType.Warning); - } + builder.Notify($"XRecord with handle {this.CadObject.Handle} reference with handle {handle} not found.", NotificationType.Warning); } } } diff --git a/src/ACadSharp/Objects/XRecrod.cs b/src/ACadSharp/Objects/XRecrod.cs index 5ad15721a..3d5901dd4 100644 --- a/src/ACadSharp/Objects/XRecrod.cs +++ b/src/ACadSharp/Objects/XRecrod.cs @@ -23,7 +23,9 @@ public partial class XRecord : NonGraphicalObject [DxfCodeValue(280)] public DictionaryCloningFlags CloningFlags { get; set; } - //1-369 (except 5 and 105) These values can be used by an application in any way + /// + /// Gets the collection of objects associated with this . + /// public IEnumerable Entries { get { return this._entries; } } /// @@ -74,8 +76,11 @@ public override CadObject Clone() /// /// The integer code that identifies the entry. The value should be unique within the collection. /// The value to associate with the entry. Can be any object, including . - public void CreateEntry(int code, object value) + /// The newly created object. + public Entry CreateEntry(int code, object value) { - this._entries.Add(new Entry(code, value, this)); + Entry entry = new Entry(code, value, this); + this._entries.Add(entry); + return entry; } } \ No newline at end of file From aa57a3c482d9192e768e671be989105e20cb6f86 Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 13 Oct 2026 17:02:19 +0200 Subject: [PATCH 47/81] Skip null XRecord entries Added a null-value guard when writing XRecord entries; entries with null values are skipped and a warning notification is raised. --- .../IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs index 511a53472..d8a6d988e 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs @@ -695,6 +695,13 @@ protected void writeXRecord(XRecord record) foreach (var e in record.Entries) { + if(e.Value == null) + { + //TODO: Should allow null values in XRecord entries? + this.notify($"XRecord entry with code {e.Code} has a null value", NotificationType.Warning); + continue; + } + switch (e.GroupCode) { case GroupCodeValueType.None: From 293ea94014c07218d889bce20d5541cb58a84fe6 Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 13 Oct 2026 15:04:39 +0000 Subject: [PATCH 48/81] Update project version to 3.8.13 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 76107248c..264278f0b 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.12 + 3.8.13 ../nupkg True ../ACadSharp.snk From 79662efb237de13328b37085f8e99f9cbae73a11 Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 13 Oct 2026 17:15:29 +0200 Subject: [PATCH 49/81] fix --- .../IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs index d8a6d988e..bc55b502b 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfObjectsSectionWriter.cs @@ -695,13 +695,6 @@ protected void writeXRecord(XRecord record) foreach (var e in record.Entries) { - if(e.Value == null) - { - //TODO: Should allow null values in XRecord entries? - this.notify($"XRecord entry with code {e.Code} has a null value", NotificationType.Warning); - continue; - } - switch (e.GroupCode) { case GroupCodeValueType.None: @@ -720,6 +713,10 @@ protected void writeXRecord(XRecord record) case GroupCodeValueType.ObjectId: case GroupCodeValueType.ExtendedDataHandle: var obj = e.Value as IHandledCadObject; + if(obj == null) + { + continue; + } this._writer.Write(e.Code, obj.Handle); break; default: From 6d76d57d5b1bbfd233f10b82cc64d65daec822d7 Mon Sep 17 00:00:00 2001 From: DomCR Date: Tue, 13 Oct 2026 17:36:06 +0200 Subject: [PATCH 50/81] #PR1274 v3.8.14 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 264278f0b..36c56915f 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.13 + 3.8.14 ../nupkg True ../ACadSharp.snk From f9244c2ce1742371982bdfa08513caca3bf76e5d Mon Sep 17 00:00:00 2001 From: Petr Sommer Date: Tue, 13 Oct 2026 16:48:06 +0000 Subject: [PATCH 51/81] Remove the zero handle seed regression test --- .../IO/DXF/DxfHandleSeedTests.cs | 80 ------------------- 1 file changed, 80 deletions(-) delete mode 100644 src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs diff --git a/src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs deleted file mode 100644 index 3e236ffd6..000000000 --- a/src/ACadSharp.Tests/IO/DXF/DxfHandleSeedTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using ACadSharp.Entities; -using ACadSharp.IO; -using System.IO; -using System.Linq; -using System.Text; -using Xunit; - -namespace ACadSharp.Tests.IO.DXF; - -public class DxfHandleSeedTests -{ - [Fact] - public void ReadR12WithZeroHandleSeed() - { - // R12 file without handles ($HANDLING 0) that still declares $HANDSEED 0, - // as some CAM exporters write it. Handle 0 is taken by the document itself. - string dxf = string.Join("\n", - "0", "SECTION", - "2", "HEADER", - "9", "$ACADVER", - "1", "AC1009", - "9", "$HANDLING", - "70", "0", - "9", "$HANDSEED", - "5", "0", - "0", "ENDSEC", - "0", "SECTION", - "2", "TABLES", - "0", "TABLE", - "2", "LTYPE", - "70", "1", - "0", "LTYPE", - "2", "CONTINUOUS", - "70", "0", - "3", "Solid line", - "72", "65", - "73", "0", - "40", "0.0", - "0", "ENDTAB", - "0", "TABLE", - "2", "LAYER", - "70", "2", - "0", "LAYER", - "2", "0", - "70", "0", - "62", "7", - "6", "CONTINUOUS", - "0", "LAYER", - "2", "Cutting", - "70", "0", - "62", "1", - "6", "CONTINUOUS", - "0", "ENDTAB", - "0", "ENDSEC", - "0", "SECTION", - "2", "ENTITIES", - "0", "LINE", - "8", "Cutting", - "10", "0.0", - "20", "0.0", - "30", "0.0", - "11", "100.0", - "21", "50.0", - "31", "0.0", - "0", "ENDSEC", - "0", "EOF"); - - CadDocument doc; - using (MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(dxf))) - using (DxfReader reader = new DxfReader(stream)) - { - doc = reader.Read(); - } - - Line line = Assert.IsType(doc.Entities.Single()); - Assert.Equal("Cutting", line.Layer.Name); - Assert.NotEqual(0UL, line.Handle); - Assert.True(doc.Header.HandleSeed > line.Handle); - } -} From 28b8d334f36d400fd796f3393af4a48865561c8f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 08:34:09 +0000 Subject: [PATCH 52/81] Remove the orphaned tables sample file and its test Requested in the review of DomCR/ACadSharp#1142. The reader change stays as it is. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_011ZLpLtgm9c5fRX9tRynhF7 --- samples/orphaned_tables_AC1009.dxf | 74 -------------------- src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs | 24 ------- 2 files changed, 98 deletions(-) delete mode 100644 samples/orphaned_tables_AC1009.dxf diff --git a/samples/orphaned_tables_AC1009.dxf b/samples/orphaned_tables_AC1009.dxf deleted file mode 100644 index 85735507e..000000000 --- a/samples/orphaned_tables_AC1009.dxf +++ /dev/null @@ -1,74 +0,0 @@ - 0 -SECTION - 2 -HEADER - 9 -$ACADVER - 1 -AC1009 - 0 -ENDSEC - 0 -TABLE - 2 -LTYPE - 70 - 1 - 0 -LTYPE - 2 -TestLineType - 70 - 0 - 3 -Test line type - 72 - 65 - 73 - 0 - 40 -0.0 - 0 -ENDTAB - 0 -TABLE - 2 -LAYER - 70 - 1 - 0 -LAYER - 2 -TestLayer - 70 - 0 - 62 - 3 - 6 -TestLineType - 0 -ENDTAB - 0 -SECTION - 2 -ENTITIES - 0 -LINE - 8 -TestLayer - 10 -0.0 - 20 -0.0 - 30 -0.0 - 11 -100.0 - 21 -100.0 - 31 -0.0 - 0 -ENDSEC - 0 -EOF diff --git a/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs index a78da395f..958c9bd7d 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfReaderTests.cs @@ -180,28 +180,4 @@ public void ReadTables(FileModel test) Assert.NotNull(doc.Views); Assert.NotNull(doc.VPorts); } - - [Fact] - public void ReadOrphanedTablesSectionTest() - { - string path = System.IO.Path.Combine(TestVariables.SamplesFolder, "orphaned_tables_AC1009.dxf"); - - bool notified = false; - CadDocument doc; - using (DxfReader reader = new DxfReader(path)) - { - reader.OnNotification += (_, e) => - { - if (e.NotificationType == NotificationType.Warning - && e.Message.Contains("Non-standard DXF")) - notified = true; - }; - doc = reader.Read(); - } - - Assert.True(notified, "Expected a warning notification about the orphaned TABLE section."); - Assert.True(doc.LineTypes.Contains("TestLineType"), "Line type 'TestLineType' should be imported from the orphaned TABLE section."); - Assert.True(doc.Layers.Contains("TestLayer"), "Layer 'TestLayer' should be imported from the orphaned TABLE section."); - Assert.Equal("TestLineType", doc.Layers["TestLayer"].LineType.Name); - } } \ No newline at end of file From 9db21e8631e1eb2f2895b67fc0a80c53d9a8170a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 12:01:40 +0000 Subject: [PATCH 53/81] Keep definition points of pre-R13 DIMENSION entities AC1009 DIMENSION entities carry no subclass markers, the dimension type is only known once group code 70 is read. readDimension kept currentSubclass on the placeholder's AcDbDimension after the type switch, so the codes of AcDbAlignedDimension (13/23, 14/24) and AcDbRadialDimension (15/25) were reported as unhandled and discarded. For files older than R13 set currentSubclass as soon as the type is known from code 70. DimensionLinear uses AcDbAlignedDimension, since its points are in that map and code 50 is read by readDimension itself. Pre-R13 files write AcDbDimension codes such as the text override (1) after code 70, so a value the current subclass does not know is looked up in the AcDbDimension map as well. R13 and newer are unchanged. Fixes #1246. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01UjK2J2Jzv8HFqqWhSiN1YK Claude-Session: https://claude.ai/code/session_01GXRbWLbfZCX2sEKeQ1h8sQ --- .../DXF/DxfStreamReader/DxfSectionReaderBase.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs index 2acf268f2..b29347e15 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs @@ -821,6 +821,12 @@ private bool readDimension(CadEntityTemplate template, DxfMap map, string subcla default: break; } + + //The codes that follow belong to the dimension type, + //DimensionLinear keeps its points in AcDbAlignedDimension and the code 50 is read by this method + this.currentSubclass = tmp.CadObject is DimensionLinear + ? DxfSubclassMarker.AlignedDimension + : tmp.CadObject.SubclassMarker; } return true; //Measurement - read only @@ -881,7 +887,14 @@ private bool readDimension(CadEntityTemplate template, DxfMap map, string subcla this.currentSubclass = tmp.CadObject.SubclassMarker; } - return this.tryAssignCurrentValue(template.CadObject, map); + if (this.tryAssignCurrentValue(template.CadObject, map)) + { + return true; + } + + //Pre R13 files write AcDbDimension codes like the text (1) after the code 70, mixed with the codes of the dimension type + return this._builder.Version < ACadVersion.AC1012 + && this.tryAssignCurrentValue(template.CadObject, map.SubClasses[DxfSubclassMarker.Dimension]); } } From 06fabcd6f1b2a0e46dda1884917b854694f058cc Mon Sep 17 00:00:00 2001 From: Maxence Date: Wed, 14 Oct 2026 11:00:58 +0200 Subject: [PATCH 54/81] Write MTEXT background fill and text frame codes to DXF The DXF writer did not write the background fill flags (90), color (63/421), scale (45) and transparency (441) of an MTEXT, so background masks and text frames were lost. The DXF reader now also assigns colors given by codes 63 and 421, which left MText.BackgroundColor as ByBlock. Checked with AutoCAD 2025 (Core Console): a DXF with a frame-only MTEXT (90 = 16) opens and draws the frame with the margin of code 45. Co-Authored-By: Claude Opus 5.5 --- .../IO/DXF/DxfWriterSingleObjectTests.cs | 28 +++++++++++++++++++ src/ACadSharp/DxfPropertyBase.cs | 4 +++ .../DxfSectionWriterBase.Entities.cs | 14 ++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs index 0fd724e8e..cc8729331 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs @@ -107,6 +107,34 @@ public void WriteHatchNormalizesBoundaryAngles(double startDegrees, double endDe Assert.False(ellipse.CounterClockWise); } + [Theory] + [InlineData(BackgroundFillFlags.TextFrame)] + [InlineData(BackgroundFillFlags.UseBackgroundFillColor)] + [InlineData(BackgroundFillFlags.UseBackgroundFillColor | BackgroundFillFlags.UseDrawingWindowColor)] + [InlineData(BackgroundFillFlags.UseBackgroundFillColor | BackgroundFillFlags.TextFrame)] + public void WriteMTextBackgroundAndTextFrame(BackgroundFillFlags flags) + { + CadDocument document = new CadDocument(ACadVersion.AC1032); + document.Entities.Add(new MText("Framed text") + { + Height = 2.5, + BackgroundFillFlags = flags, + BackgroundScale = 2.0, + BackgroundColor = new Color(2) + }); + + using MemoryStream output = new MemoryStream(); + DxfWriter.Write(output, document); + + using MemoryStream input = new MemoryStream(output.ToArray()); + CadDocument result = DxfReader.Read(input); + MText mtext = Assert.Single(result.Entities.OfType()); + + Assert.Equal(flags, mtext.BackgroundFillFlags); + Assert.Equal(2.0, mtext.BackgroundScale); + Assert.Equal(new Color(2), mtext.BackgroundColor); + } + protected virtual void writeDxfFile(SingleCaseGenerator data, ACadVersion version) { Assert.True(data.HasExecuted, $"The writer has failed during it's execution."); diff --git a/src/ACadSharp/DxfPropertyBase.cs b/src/ACadSharp/DxfPropertyBase.cs index a93f40c00..f73a62a47 100644 --- a/src/ACadSharp/DxfPropertyBase.cs +++ b/src/ACadSharp/DxfPropertyBase.cs @@ -212,9 +212,11 @@ public void SetValue(int code, TCadObject obj, object value) switch (code) { case 62: + case 63: this._property.SetValue(obj, new Color((short)value)); break; case 420: + case 421: byte[] b = LittleEndianConverter.Instance.GetBytes((int)value); // true color this._property.SetValue(obj, new Color(b[2], b[1], b[0])); @@ -302,6 +304,7 @@ internal void SetValue(int code, object obj, object value) switch (code) { case 62: + case 63: this._property.SetValue(obj, new Color((short)value)); break; case 90: @@ -310,6 +313,7 @@ internal void SetValue(int code, object obj, object value) this._property.SetValue(obj, new Color(b[2], b[1], b[0])); break; case 420: + case 421: b = LittleEndianConverter.Instance.GetBytes((int)value); // true color this._property.SetValue(obj, new Color(b[2], b[1], b[0])); diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs index 4155b5998..1034fe159 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs @@ -923,6 +923,20 @@ private void writeMText(MText mtext, bool writeSubclass = true) this._writer.Write(210, mtext.Normal, map); + //Background fill and text frame, R2004+ (text frame R2018+) + if (this.Version >= ACadVersion.AC1018 && mtext.BackgroundFillFlags != BackgroundFillFlags.None) + { + this._writer.Write(90, (int)mtext.BackgroundFillFlags, map); + this._writer.WriteCmColor(63, mtext.BackgroundColor, map); + if (mtext.BackgroundColor.IsTrueColor) + { + this._writer.WriteTrueColor(421, mtext.BackgroundColor, map); + } + + this._writer.Write(45, mtext.BackgroundScale, map); + this._writer.Write(441, Transparency.ToAlphaValue(mtext.BackgroundTransparency), map); + } + if (!mtext.HasColumns || this.Version < ACadVersion.AC1032) { return; From 0695e5520e624821e263d9f538104ea91b4012e4 Mon Sep 17 00:00:00 2001 From: DomCR Date: Wed, 14 Oct 2026 11:40:35 +0200 Subject: [PATCH 55/81] #PR1142 v3.8.15 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 36c56915f..c0d1f53dd 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.14 + 3.8.15 ../nupkg True ../ACadSharp.snk From 31bafd2932092d3f3fa5a4f27486b4d78042f727 Mon Sep 17 00:00:00 2001 From: DomCR Date: Wed, 14 Oct 2026 11:41:25 +0200 Subject: [PATCH 56/81] #PR1247 v3.8.16 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index c0d1f53dd..d886bbb57 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.15 + 3.8.16 ../nupkg True ../ACadSharp.snk From c30192b026962c117f2e1f1be88385f7a1d7126c Mon Sep 17 00:00:00 2001 From: simonedd Date: Wed, 14 Oct 2026 14:18:16 +0200 Subject: [PATCH 57/81] fix: resolve the DIMBLK arrow block of a dimension style --- src/ACadSharp/IO/Templates/CadDimensionStyleTemplate.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp/IO/Templates/CadDimensionStyleTemplate.cs b/src/ACadSharp/IO/Templates/CadDimensionStyleTemplate.cs index d5353f330..397f2c4f7 100644 --- a/src/ACadSharp/IO/Templates/CadDimensionStyleTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadDimensionStyleTemplate.cs @@ -64,11 +64,16 @@ protected override void build(CadDocumentBuilder builder) this.CadObject.LineTypeExt2 = linetTypeEx2; } - if (this.getTableReference(builder, this.DIMLDRBLK, this.DIMBL_Name, out BlockRecord leaderArrow)) + if (this.getTableReference(builder, this.DIMLDRBLK, null, out BlockRecord leaderArrow)) { this.CadObject.LeaderArrow = leaderArrow; } + if (this.getTableReference(builder, this.DIMBLK, this.DIMBL_Name, out BlockRecord arrowBlock)) + { + this.CadObject.ArrowBlock = arrowBlock; + } + if (this.getTableReference(builder, this.DIMBLK1, this.DIMBLK1_Name, out BlockRecord dimArrow1)) { this.CadObject.DimArrow1 = dimArrow1; From 12f836af8cd7b470ee6883a9ccc51a22dee66e24 Mon Sep 17 00:00:00 2001 From: DomCR Date: Wed, 14 Oct 2026 18:40:29 +0200 Subject: [PATCH 58/81] small bounding box tests --- .../IO/WriterSingleObjectTests.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs index 304a682a6..96957a5ee 100644 --- a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs @@ -35,6 +35,7 @@ static WriterSingleObjectTests() Data.Add(new(nameof(SingleCaseGenerator.AddViewport))); Data.Add(new(nameof(SingleCaseGenerator.SingleMLeader))); Data.Add(new(nameof(SingleCaseGenerator.SingleMLine))); + Data.Add(new(nameof(SingleCaseGenerator.DrawBoundingBox))); Data.Add(new(nameof(SingleCaseGenerator.EllipseSegments))); Data.Add(new(nameof(SingleCaseGenerator.EntityColorByLayer))); Data.Add(new(nameof(SingleCaseGenerator.EntityColorTrueColor))); @@ -1484,6 +1485,18 @@ public void DimensionWithLineType() }; } + public void DrawBoundingBox() + { + Line l = new Line(XYZ.Zero, new XYZ(10, 10, 0)); + this.drawBoundingBox(l); + + Point p = new Point(new XYZ(20, 20, 0)); + this.drawBoundingBox(p); + + Arc arc = new Arc(XYZ.Zero, 5, 0, Math.PI / 2); + this.drawBoundingBox(arc); + } + public void EllipseSegments() { XYZ center = new XYZ(5, 5, 0); @@ -2889,5 +2902,34 @@ public void XRef() Assert.Equal(this.Document.ModelSpace.Entities.Count, doc.ModelSpace.Entities.Count); }; } + + private void drawBoundingBox(Entity entity) + { + this.Document.Entities.Add(entity); + + var box = entity.GetBoundingBox(); + if (box.Extent == BoundingBoxExtent.Infinite) + { + return; + } + + var corners = new List + { + box.Min, + new XYZ(box.Max.X, box.Min.Y, box.Min.Z), + new XYZ(box.Max.X, box.Max.Y, box.Min.Z), + new XYZ(box.Min.X, box.Max.Y, box.Min.Z), + new XYZ(box.Min.X, box.Min.Y, box.Max.Z), + new XYZ(box.Max.X, box.Min.Y, box.Max.Z), + box.Max, + new XYZ(box.Min.X, box.Max.Y, box.Max.Z) + }; + + var pline = new Polyline3D(corners); + + pline.IsClosed = true; + pline.Color = Color.Green; + this.Document.Entities.Add(pline); + } } } \ No newline at end of file From 088bc6a390c4458a8aca055822a56a8393d6cb2c Mon Sep 17 00:00:00 2001 From: Thiago Mizuta <267745193+thiagomizutamcaa@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:55:52 -0300 Subject: [PATCH 59/81] SeqendCollection: only unregister the seqend when the last item is removed Remove raised OnSeqendRemoved on every removal, so the document unregistered the Seqend while items remained (handle 0; DwgWriter then failed with duplicate key 0 for two such inserts). Also return false when nothing was removed. Co-Authored-By: Claude Opus 5.5 --- src/ACadSharp.Tests/Entities/SeqendTests.cs | 49 +++++++++++++++++++++ src/ACadSharp/SeqendCollection.cs | 6 ++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/ACadSharp.Tests/Entities/SeqendTests.cs b/src/ACadSharp.Tests/Entities/SeqendTests.cs index b913b9cb2..a597ef199 100644 --- a/src/ACadSharp.Tests/Entities/SeqendTests.cs +++ b/src/ACadSharp.Tests/Entities/SeqendTests.cs @@ -87,4 +87,53 @@ public void OnSeqendRemovedTest() Assert.False(doc.TryGetCadObject(holder.Handle, out Seqend inner)); Assert.Null(inner); } + + [Fact] + public void RemoveOneOfManyKeepsSeqendTest() + { + CadDocument doc = new CadDocument(); + + Polyline2D polyline = new Polyline2D(); + Vertex2D first = new Vertex2D(); + polyline.Vertices.Add(first); + polyline.Vertices.Add(new Vertex2D()); + + doc.Entities.Add(polyline); + ulong handle = polyline.Vertices.Seqend.Handle; + + Assert.True(polyline.Vertices.Remove(first)); + + Assert.NotEmpty(polyline.Vertices); + Assert.Equal(handle, polyline.Vertices.Seqend.Handle); + Assert.True(doc.TryGetCadObject(handle, out Seqend inner)); + Assert.NotNull(inner); + } + + [Fact] + public void RemoveLastUnregistersSeqendTest() + { + CadDocument doc = new CadDocument(); + + Polyline2D polyline = new Polyline2D(); + Vertex2D vertex = new Vertex2D(); + polyline.Vertices.Add(vertex); + Seqend holder = polyline.Vertices.Seqend; + + doc.Entities.Add(polyline); + + Assert.True(polyline.Vertices.Remove(vertex)); + + Assert.Null(polyline.Vertices.Seqend); + Assert.True(holder.Handle == 0); + } + + [Fact] + public void RemoveMissingItemReturnsFalseTest() + { + Polyline2D polyline = new Polyline2D(); + polyline.Vertices.Add(new Vertex2D()); + + Assert.False(polyline.Vertices.Remove(new Vertex2D())); + Assert.NotNull(polyline.Vertices.Seqend); + } } diff --git a/src/ACadSharp/SeqendCollection.cs b/src/ACadSharp/SeqendCollection.cs index 4412f0957..f175edd65 100644 --- a/src/ACadSharp/SeqendCollection.cs +++ b/src/ACadSharp/SeqendCollection.cs @@ -62,7 +62,11 @@ public override void Add(T item) /// public override bool Remove(T item) { - if(base.Remove(item)) + if (!base.Remove(item)) + return false; + + //The seqend only leaves with the last entry + if (!this._entries.Any()) { this.OnSeqendRemoved?.Invoke(this, new CollectionChangedEventArgs(this._seqend)); } From 71cade6bac56960b49163c3cabf1f0385f71fc58 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 09:45:48 +0200 Subject: [PATCH 60/81] #PR1300 v3.8.17 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index d886bbb57..f7f5ec6cf 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.16 + 3.8.17 ../nupkg True ../ACadSharp.snk From f37b66258eb462178c14531f31e1af8f012ef735 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 09:55:32 +0200 Subject: [PATCH 61/81] #PR1267 v3.8.18 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index f7f5ec6cf..3bcfd28b0 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.17 + 3.8.18 ../nupkg True ../ACadSharp.snk From 9655d553e7f998c4cd4df0ede8fe025ee4afba4b Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 09:58:13 +0200 Subject: [PATCH 62/81] #PR1298 v3.8.19 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 3bcfd28b0..557322e72 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.18 + 3.8.19 ../nupkg True ../ACadSharp.snk From 8451dd136f479a9d1308b8d7a44a75ce091d9620 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 10:35:06 +0200 Subject: [PATCH 63/81] Push version changes to PR source repositories Run version pushes with Bash, validation, and auth Preserve author metadata and target the configured branch. --- .github/workflows/auto-version.yml | 10 +++++---- push-changes.sh | 36 ++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml index 609deb314..f0ba073b6 100644 --- a/.github/workflows/auto-version.yml +++ b/.github/workflows/auto-version.yml @@ -29,8 +29,10 @@ jobs: file: ./src/ACadSharp/ACadSharp.csproj version: ${{env.VERSION_FORMAT}} - name: Push changes - run: ./push-changes.sh - shell: bash + run: bash push-changes.sh env: - VERSION: ${{steps.update.outputs.newVersion}} - BRANCH: ${{env.CURRENT_BRANCH}} + BRANCH: ${{ github.head_ref || github.ref_name }} + VERSION: ${{ env.VERSION }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + PUSH_TOKEN: ${{ secrets.PUSH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/push-changes.sh b/push-changes.sh index ea17880dc..b4c863d83 100644 --- a/push-changes.sh +++ b/push-changes.sh @@ -1,16 +1,38 @@ -echo Prepare commit -#Get commit details -author=`git log -1 --format="%an"` -email=`git log -1 --format="%ae"` +#!/usr/bin/env bash +set -e + +echo "Prepare commit" + +if [ -z "$BRANCH" ] || [ -z "$VERSION" ]; then + echo "BRANCH or VERSION not set: skipping push" + exit 0 +fi + +# Target repo: the fork for contributor PRs, otherwise this repo +TARGET_REPO="${HEAD_REPO:-$GITHUB_REPOSITORY}" +TOKEN="${PUSH_TOKEN:-$GITHUB_TOKEN}" + +if [ -z "$TOKEN" ]; then + echo "No token available: skipping push" + exit 0 +fi + +# Get commit details +author=$(git log -1 --format="%an") +email=$(git log -1 --format="%ae") git config --local user.email "$email" -git config --local user.name "$author" +git config --local user.name "$author" + git add . if git diff-index --quiet HEAD; then echo "Nothing changed" exit 0 fi -echo "Pushing changes $BRANCH" +echo "Pushing changes to $TARGET_REPO:$BRANCH" git commit -m "Update project version to $VERSION" -git push origin $BRANCH \ No newline at end of file + +git remote remove target 2>/dev/null || true +git remote add target "https://x-access-token:${TOKEN}@github.com/${TARGET_REPO}.git" +git push target "HEAD:$BRANCH" \ No newline at end of file From c949cc7c0673ca985fa129787e5d618a674bc416 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 10:40:07 +0200 Subject: [PATCH 64/81] mark errors --- push-changes.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/push-changes.sh b/push-changes.sh index b4c863d83..ac7407660 100644 --- a/push-changes.sh +++ b/push-changes.sh @@ -5,7 +5,7 @@ echo "Prepare commit" if [ -z "$BRANCH" ] || [ -z "$VERSION" ]; then echo "BRANCH or VERSION not set: skipping push" - exit 0 + exit 1 fi # Target repo: the fork for contributor PRs, otherwise this repo @@ -14,7 +14,7 @@ TOKEN="${PUSH_TOKEN:-$GITHUB_TOKEN}" if [ -z "$TOKEN" ]; then echo "No token available: skipping push" - exit 0 + exit 1 fi # Get commit details @@ -27,7 +27,7 @@ git config --local user.name "$author" git add . if git diff-index --quiet HEAD; then echo "Nothing changed" - exit 0 + exit 1 fi echo "Pushing changes to $TARGET_REPO:$BRANCH" From 9cbad91276568f0c6f7cabd9d18d6c88491d442f Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 10:43:58 +0200 Subject: [PATCH 65/81] fix --- .github/workflows/auto-version.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml index f0ba073b6..6695adb7e 100644 --- a/.github/workflows/auto-version.yml +++ b/.github/workflows/auto-version.yml @@ -30,9 +30,10 @@ jobs: version: ${{env.VERSION_FORMAT}} - name: Push changes run: bash push-changes.sh + shell: bash env: - BRANCH: ${{ github.head_ref || github.ref_name }} - VERSION: ${{ env.VERSION }} - HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} - PUSH_TOKEN: ${{ secrets.PUSH_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: ${{github.head_ref || github.ref_name}} + VERSION: ${{steps.update.outputs.newVersion}} + HEAD_REPO: ${{github.event.pull_request.head.repo.full_name}} + PUSH_TOKEN: ${{secrets.PUSH_TOKEN}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} From 2112f5bc250a6758462e59c4f96daa9783611a12 Mon Sep 17 00:00:00 2001 From: simonedd Date: Thu, 15 Oct 2026 10:32:04 +0200 Subject: [PATCH 66/81] fix(dxf): read and write the flip arrow flags of a dimension --- .../IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs | 8 ++++++-- .../DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs | 6 ++++++ src/ACadSharp/IO/Templates/CadDimensionTemplate.cs | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs index 4cdada779..128459deb 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs @@ -841,10 +841,14 @@ private bool readDimension(CadEntityTemplate template, DxfMap map, string subcla //Measurement - read only case 42: return true; - //Undocumented codes - case 73: case 74: + ((Dimension)tmp.CadObject).FlipArrow1 = this._reader.ValueAsBool; + return true; case 75: + ((Dimension)tmp.CadObject).FlipArrow2 = this._reader.ValueAsBool; + return true; + //Undocumented codes + case 73: case 90: case 361: return true; diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs index 4155b5998..08c458403 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs @@ -308,6 +308,12 @@ private void writeDimension(Dimension dim) this._writer.Write(72, (short)dim.LineSpacingStyle, map); this._writer.Write(41, dim.LineSpacingFactor, map); + if (this.Version >= ACadVersion.AC1021) + { + this._writer.Write(74, dim.FlipArrow1, map); + this._writer.Write(75, dim.FlipArrow2, map); + } + if (!string.IsNullOrEmpty(dim.Text)) { this._writer.Write(1, dim.Text, map); diff --git a/src/ACadSharp/IO/Templates/CadDimensionTemplate.cs b/src/ACadSharp/IO/Templates/CadDimensionTemplate.cs index ec29347db..5e24e8a4c 100644 --- a/src/ACadSharp/IO/Templates/CadDimensionTemplate.cs +++ b/src/ACadSharp/IO/Templates/CadDimensionTemplate.cs @@ -98,6 +98,8 @@ public void SetDimensionObject(Dimension dimension) dimension.Text = source.Text; dimension.TextRotation = source.TextRotation; dimension.HorizontalDirection = source.HorizontalDirection; + dimension.FlipArrow1 = source.FlipArrow1; + dimension.FlipArrow2 = source.FlipArrow2; dimension.Flags = source.Flags; From 7374a164f7182013af03febaaaa345f95495dcd8 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 08:44:32 +0000 Subject: [PATCH 67/81] Update project version to 3.8.20 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 557322e72..18431a5d7 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.19 + 3.8.20 ../nupkg True ../ACadSharp.snk From 6b58e7ea95c08e6c135936a2f7bef19e76f8e66b Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 10:52:52 +0200 Subject: [PATCH 68/81] rollback --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 18431a5d7..557322e72 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.20 + 3.8.19 ../nupkg True ../ACadSharp.snk From 7065430f25ed3e34b63d012ac23e76bb788a9f22 Mon Sep 17 00:00:00 2001 From: ilCosmico Date: Thu, 15 Oct 2026 11:17:17 +0200 Subject: [PATCH 69/81] Read every page of a multi-page blob in the Prototype1b section --- src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs index 19ba9f601..8e2d2a5f9 100644 --- a/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs +++ b/src/ACadSharp/IO/DWG/DwgStreamReaders/DwgPrototype1bReader.cs @@ -242,6 +242,7 @@ private void readDataFields(CadFileDataStorage storage) { var segmentIndex = this._reader.ReadUInt(); var size = this._reader.ReadUInt(); + var nextPagePosition = this._reader.Position; this._reader.Position = (long)this._fileSegmentIndex.Entries[(int)segmentIndex].Offset; @@ -256,6 +257,8 @@ private void readDataFields(CadFileDataStorage storage) blob.Data = this._reader.ReadBytes((int)blob.PageDataSize); value.BlobReference.Blobs.Add(blob); + + this._reader.Position = nextPagePosition; } } From 26e91ff34806a79ae965e5bcad3573aa661aba5b Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 11:44:31 +0200 Subject: [PATCH 70/81] checkout --- .github/workflows/auto-version.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml index 6695adb7e..3131b0ee5 100644 --- a/.github/workflows/auto-version.yml +++ b/.github/workflows/auto-version.yml @@ -20,8 +20,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - submodules: true - ref: ${{env.CURRENT_BRANCH}} + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + ref: ${{ github.head_ref || github.ref_name }} + fetch-depth: 0 + persist-credentials: false - name: Update version id: update uses: vers-one/dotnet-project-version-updater@v1.7 From 72c115059dc586ae0714b3964862a444fa2b74c4 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 11:47:19 +0200 Subject: [PATCH 71/81] #PR1305 v3.8.20 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 557322e72..18431a5d7 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.19 + 3.8.20 ../nupkg True ../ACadSharp.snk From 060fb5865a4b29c9e23296a6dece7daa73e12513 Mon Sep 17 00:00:00 2001 From: simonedd Date: Thu, 15 Oct 2026 11:15:51 +0200 Subject: [PATCH 72/81] fix: keep the length of the VPORT view direction --- src/ACadSharp/Tables/VPort.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/Tables/VPort.cs b/src/ACadSharp/Tables/VPort.cs index 657db3989..df3788680 100644 --- a/src/ACadSharp/Tables/VPort.cs +++ b/src/ACadSharp/Tables/VPort.cs @@ -75,7 +75,7 @@ public XYZ Direction get { return this._direction; } set { - this._direction = value.Normalize(); + this._direction = value; } } From 3889e1da308fb77d5750912f877bdf965002c421 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 12:07:47 +0200 Subject: [PATCH 73/81] #PR1306 v3.8.21 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 18431a5d7..250971626 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.20 + 3.8.21 ../nupkg True ../ACadSharp.snk From f921817e11e1ef42491111113d8239ac0aeba2df Mon Sep 17 00:00:00 2001 From: simonedd Date: Thu, 15 Oct 2026 12:43:55 +0200 Subject: [PATCH 74/81] refactor(dxf): let the class map assign the flip arrow codes --- .../IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs index 128459deb..a6cc9dfb7 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamReader/DxfSectionReaderBase.cs @@ -841,12 +841,6 @@ private bool readDimension(CadEntityTemplate template, DxfMap map, string subcla //Measurement - read only case 42: return true; - case 74: - ((Dimension)tmp.CadObject).FlipArrow1 = this._reader.ValueAsBool; - return true; - case 75: - ((Dimension)tmp.CadObject).FlipArrow2 = this._reader.ValueAsBool; - return true; //Undocumented codes case 73: case 90: From 9bd47f48766a371c441e8d1ab80733e21f62b711 Mon Sep 17 00:00:00 2001 From: DomCR Date: Thu, 15 Oct 2026 15:04:32 +0200 Subject: [PATCH 75/81] #PR1304 v3.8.22 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 250971626..722eb7161 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.21 + 3.8.22 ../nupkg True ../ACadSharp.snk From 814ef67edb91aa7209a2655e9129be9382354799 Mon Sep 17 00:00:00 2001 From: Thiago Mizuta <267745193+thiagomizutamcaa@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:51:16 -0300 Subject: [PATCH 76/81] DimensionStyle: round-trip check for the main arrow block (DIMBLK) The fix itself came with #1298. This adds the DimensionStyleArrowBlock case to WriterSingleObjectTests: a dimension style with a custom ArrowBlock is written and read back (DWG and DXF, every version), and the arrow block must come back by name while the leader arrow stays null. The case fails when ArrowBlock is not resolved from DIMBLK. Co-Authored-By: Claude Opus 5.5 --- .../IO/WriterSingleObjectTests.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs index 96957a5ee..cb74f2984 100644 --- a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs @@ -88,6 +88,7 @@ static WriterSingleObjectTests() Data.Add(new(nameof(SingleCaseGenerator.DimensionRadius))); Data.Add(new(nameof(SingleCaseGenerator.Dimensions))); Data.Add(new(nameof(SingleCaseGenerator.DimensionWithLineType))); + Data.Add(new(nameof(SingleCaseGenerator.DimensionStyleArrowBlock))); Data.Add(new(nameof(SingleCaseGenerator.GeoData))); Data.Add(new(nameof(SingleCaseGenerator.LineTypeInBlock))); Data.Add(new(nameof(SingleCaseGenerator.XData))); @@ -1485,6 +1486,35 @@ public void DimensionWithLineType() }; } + public void DimensionStyleArrowBlock() + { + BlockRecord tick = new BlockRecord("CUSTOM_TICK"); + tick.Entities.Add(new Line(new XYZ(-0.5, -0.5, 0), new XYZ(0.5, 0.5, 0))); + this.Document.BlockRecords.Add(tick); + + DimensionStyle style = new DimensionStyle("arrow_block_style"); + style.ArrowBlock = tick; + + DimensionLinear dim = new DimensionLinear + { + FirstPoint = new XYZ(0, 0, 0), + SecondPoint = new XYZ(10, 0, 0), + Offset = 0.5, + Style = style, + }; + + this.Document.Entities.Add(dim); + + this.AssertRoundtrip = (doc) => + { + DimensionStyle result = doc.GetCadObject(style.Handle); + + Assert.NotNull(result); + Assert.Equal(tick.Name, result.ArrowBlock?.Name); + Assert.Null(result.LeaderArrow); + }; + } + public void DrawBoundingBox() { Line l = new Line(XYZ.Zero, new XYZ(10, 10, 0)); From 70848614497ae968fb18e34f63065401d80779b3 Mon Sep 17 00:00:00 2001 From: Maxence Date: Thu, 15 Oct 2026 18:13:26 +0200 Subject: [PATCH 77/81] Apply review: write MTEXT background values only when filled or framed Codes 63, 421, 45 and 441 are now written only when UseBackgroundFillColor or TextFrame is set, as the DWG writer does. The DXF-only test cases are replaced by a roundtrip case in WriterSingleObjectTests, with one MTEXT per flag combination, checked for DXF and DWG. Co-Authored-By: Claude Opus 5.5 --- .../IO/DXF/DxfWriterSingleObjectTests.cs | 28 ---------- .../IO/WriterSingleObjectTests.cs | 56 +++++++++++++++++++ .../DxfSectionWriterBase.Entities.cs | 18 ++++-- 3 files changed, 68 insertions(+), 34 deletions(-) diff --git a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs index cc8729331..0fd724e8e 100644 --- a/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/DXF/DxfWriterSingleObjectTests.cs @@ -107,34 +107,6 @@ public void WriteHatchNormalizesBoundaryAngles(double startDegrees, double endDe Assert.False(ellipse.CounterClockWise); } - [Theory] - [InlineData(BackgroundFillFlags.TextFrame)] - [InlineData(BackgroundFillFlags.UseBackgroundFillColor)] - [InlineData(BackgroundFillFlags.UseBackgroundFillColor | BackgroundFillFlags.UseDrawingWindowColor)] - [InlineData(BackgroundFillFlags.UseBackgroundFillColor | BackgroundFillFlags.TextFrame)] - public void WriteMTextBackgroundAndTextFrame(BackgroundFillFlags flags) - { - CadDocument document = new CadDocument(ACadVersion.AC1032); - document.Entities.Add(new MText("Framed text") - { - Height = 2.5, - BackgroundFillFlags = flags, - BackgroundScale = 2.0, - BackgroundColor = new Color(2) - }); - - using MemoryStream output = new MemoryStream(); - DxfWriter.Write(output, document); - - using MemoryStream input = new MemoryStream(output.ToArray()); - CadDocument result = DxfReader.Read(input); - MText mtext = Assert.Single(result.Entities.OfType()); - - Assert.Equal(flags, mtext.BackgroundFillFlags); - Assert.Equal(2.0, mtext.BackgroundScale); - Assert.Equal(new Color(2), mtext.BackgroundColor); - } - protected virtual void writeDxfFile(SingleCaseGenerator data, ACadVersion version) { Assert.True(data.HasExecuted, $"The writer has failed during it's execution."); diff --git a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs index 304a682a6..1cdd7e936 100644 --- a/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs +++ b/src/ACadSharp.Tests/IO/WriterSingleObjectTests.cs @@ -50,6 +50,7 @@ static WriterSingleObjectTests() Data.Add(new(nameof(SingleCaseGenerator.SingleLongMText))); Data.Add(new(nameof(SingleCaseGenerator.SingleMTextRotation))); Data.Add(new(nameof(SingleCaseGenerator.SingleMTextSpecialCharacter))); + Data.Add(new(nameof(SingleCaseGenerator.MTextBackgroundFillAndTextFrame))); Data.Add(new(nameof(SingleCaseGenerator.TextWithChineseCharacters))); Data.Add(new(nameof(SingleCaseGenerator.CreateGroup))); Data.Add(new(nameof(SingleCaseGenerator.SingleMTextMultiline))); @@ -2501,6 +2502,61 @@ public void SingleMTextSpecialCharacter() }; } + public void MTextBackgroundFillAndTextFrame() + { + List mtexts = new List(); + foreach (BackgroundFillFlags flags in new[] + { + BackgroundFillFlags.TextFrame, + BackgroundFillFlags.UseBackgroundFillColor, + BackgroundFillFlags.UseBackgroundFillColor | BackgroundFillFlags.UseDrawingWindowColor, + BackgroundFillFlags.UseBackgroundFillColor | BackgroundFillFlags.TextFrame, + }) + { + MText mtext = new MText($"Background {flags}") + { + InsertPoint = new XYZ(0, -10 * mtexts.Count, 0), + Height = 2.5, + BackgroundFillFlags = flags, + BackgroundScale = 2.0, + BackgroundColor = flags.HasFlag(BackgroundFillFlags.TextFrame) ? new Color(255, 128, 0) : new Color(2), + }; + + mtexts.Add(mtext); + this.Document.Entities.Add(mtext); + } + + this.AssertRoundtrip = (doc) => + { + foreach (MText mtext in mtexts) + { + MText result = doc.GetCadObject(mtext.Handle); + + Assert.NotNull(result); + + if (doc.Header.Version < ACadVersion.AC1018) + { + //Background fill is not supported before R2004 + Assert.Equal(BackgroundFillFlags.None, result.BackgroundFillFlags); + continue; + } + + Assert.Equal(mtext.BackgroundFillFlags, result.BackgroundFillFlags); + + if (this.Format == CadFileFormat.DWG + && doc.Header.Version < ACadVersion.AC1032 + && !mtext.BackgroundFillFlags.HasFlag(BackgroundFillFlags.UseBackgroundFillColor)) + { + //The DWG format stores the values of a text frame only in R2018+ + continue; + } + + Assert.Equal(mtext.BackgroundScale, result.BackgroundScale); + Assert.Equal(mtext.BackgroundColor, result.BackgroundColor); + } + }; + } + public void SinglePdfUnderlay() { var definition = new PdfUnderlayDefinition(); diff --git a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs index 1034fe159..3c60302c0 100644 --- a/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs +++ b/src/ACadSharp/IO/DXF/DxfStreamWriter/DxfSectionWriterBase.Entities.cs @@ -927,14 +927,20 @@ private void writeMText(MText mtext, bool writeSubclass = true) if (this.Version >= ACadVersion.AC1018 && mtext.BackgroundFillFlags != BackgroundFillFlags.None) { this._writer.Write(90, (int)mtext.BackgroundFillFlags, map); - this._writer.WriteCmColor(63, mtext.BackgroundColor, map); - if (mtext.BackgroundColor.IsTrueColor) + + //Color, scale and transparency only when the background is filled or the text is framed + if ((mtext.BackgroundFillFlags & (BackgroundFillFlags.UseBackgroundFillColor | BackgroundFillFlags.TextFrame)) + != BackgroundFillFlags.None) { - this._writer.WriteTrueColor(421, mtext.BackgroundColor, map); - } + this._writer.WriteCmColor(63, mtext.BackgroundColor, map); + if (mtext.BackgroundColor.IsTrueColor) + { + this._writer.WriteTrueColor(421, mtext.BackgroundColor, map); + } - this._writer.Write(45, mtext.BackgroundScale, map); - this._writer.Write(441, Transparency.ToAlphaValue(mtext.BackgroundTransparency), map); + this._writer.Write(45, mtext.BackgroundScale, map); + this._writer.Write(441, Transparency.ToAlphaValue(mtext.BackgroundTransparency), map); + } } if (!mtext.HasColumns || this.Version < ACadVersion.AC1032) From beb9f01025b0bb19e277e51505ec01b8ab14f3d5 Mon Sep 17 00:00:00 2001 From: DomCR Date: Fri, 16 Oct 2026 07:53:10 +0200 Subject: [PATCH 78/81] #PR1296 v3.8.23 --- src/ACadSharp/ACadSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ACadSharp/ACadSharp.csproj b/src/ACadSharp/ACadSharp.csproj index 722eb7161..5fcbe30af 100644 --- a/src/ACadSharp/ACadSharp.csproj +++ b/src/ACadSharp/ACadSharp.csproj @@ -17,7 +17,7 @@ true README.md - 3.8.22 + 3.8.23 ../nupkg True ../ACadSharp.snk From 2f8b0997ed9318eb9551c45cdabd63c92e90ff31 Mon Sep 17 00:00:00 2001 From: DomCR Date: Fri, 16 Oct 2026 08:03:13 +0200 Subject: [PATCH 79/81] test auto version fix --- push-changes.sh | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/push-changes.sh b/push-changes.sh index ac7407660..8b258e3e3 100644 --- a/push-changes.sh +++ b/push-changes.sh @@ -4,19 +4,32 @@ set -e echo "Prepare commit" if [ -z "$BRANCH" ] || [ -z "$VERSION" ]; then - echo "BRANCH or VERSION not set: skipping push" + echo "BRANCH or VERSION not set" exit 1 fi # Target repo: the fork for contributor PRs, otherwise this repo TARGET_REPO="${HEAD_REPO:-$GITHUB_REPOSITORY}" -TOKEN="${PUSH_TOKEN:-$GITHUB_TOKEN}" + +# Fork pushes require the PAT; GITHUB_TOKEN can never push to forks +if [ "$TARGET_REPO" != "$GITHUB_REPOSITORY" ]; then + if [ -z "$PUSH_TOKEN" ]; then + echo "PUSH_TOKEN is empty: secrets are not available (use pull_request_target and pass secrets.PUSH_TOKEN)" + exit 1 + fi + TOKEN="$PUSH_TOKEN" +else + TOKEN="${PUSH_TOKEN:-$GITHUB_TOKEN}" +fi if [ -z "$TOKEN" ]; then - echo "No token available: skipping push" + echo "No token available" exit 1 fi +# Remove the GITHUB_TOKEN auth header persisted by actions/checkout +git config --local --unset-all http.https://github.com/.extraheader || true + # Get commit details author=$(git log -1 --format="%an") email=$(git log -1 --format="%ae") @@ -27,7 +40,7 @@ git config --local user.name "$author" git add . if git diff-index --quiet HEAD; then echo "Nothing changed" - exit 1 + exit 0 fi echo "Pushing changes to $TARGET_REPO:$BRANCH" From bd2273cbb81558739e2439a37f29c30f2a6e910e Mon Sep 17 00:00:00 2001 From: jarrett-meyer Date: Tue, 6 Oct 2026 15:22:03 -0500 Subject: [PATCH 80/81] Update CSUtilities to dgr-fixes-matrix4 (GetArbitraryAxis threshold 1/64) Co-Authored-By: Claude Opus 5.5 --- src/CSUtilities | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CSUtilities b/src/CSUtilities index b1f53ee2c..8ecb6e157 160000 --- a/src/CSUtilities +++ b/src/CSUtilities @@ -1 +1 @@ -Subproject commit b1f53ee2c68143173100d031adcdb275f524aea9 +Subproject commit 8ecb6e1573a4192b4844fdbf4f1cd9117f63359c From 4114870b6f871c2972728d980a0483232a067526 Mon Sep 17 00:00:00 2001 From: jarrett-meyer <240749296+jarrett-meyer@users.noreply.github.com> Date: Thu, 15 Oct 2026 08:06:08 -0500 Subject: [PATCH 81/81] Point CSUtilities at the DGR-Engineering fork (dgr-fixes) The GetArbitraryAxis threshold fix lives on DGR-Engineering/CSUtilities dgr-fixes, so a fresh clone with --recurse-submodules builds the same code DGR ships. Co-Authored-By: Claude Opus 5.5 --- .gitmodules | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 4c98b82c6..31720f075 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "src/CSUtilities"] path = src/CSUtilities - url = https://github.com/DomCR/CSUtilities.git + url = https://github.com/DGR-Engineering/CSUtilities.git + branch = dgr-fixes