├── version.txt ├── src ├── Examples │ ├── .gitignore │ ├── README.md │ ├── Examples.csproj │ ├── TestExtensions.cs │ └── TestExamples.cs ├── IxMilia.Dxf.ReferenceCollector │ ├── .gitignore │ ├── Properties │ │ └── launchSettings.json │ ├── IxMilia.Dxf.ReferenceCollector.csproj │ ├── README.md │ └── Program.cs ├── IxMilia.Dxf.Test │ ├── diamond-bin.dxf │ ├── FileTests.cs │ ├── IxMilia.Dxf.Test.csproj │ ├── TestCodePairBufferReader.cs │ ├── CollectionTests.cs │ ├── CodePairWriterTests.cs │ ├── TextWriterTests.cs │ ├── MiscFileTests.cs │ ├── StreamWithNoLengthOrPosition.cs │ ├── CodePairReaderTests.cs │ ├── EntityExtensionTests.cs │ ├── BoundingBoxTests.cs │ ├── BlockTests.cs │ └── DxbTests.cs ├── IxMilia.Dxf │ ├── DxfVersion.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── IDxfItem.cs │ ├── DxfShadowType.cs │ ├── DxfShadowMode.cs │ ├── IDxfCodePairReader.cs │ ├── Tables │ │ ├── DxfTableType.cs │ │ ├── DxfTableItems.cs │ │ ├── DxfViewMode.cs │ │ ├── DxfLineTypeElement.cs │ │ └── DxfDimStyle.cs │ ├── Entities │ │ ├── DxfAngularTwoLineDimension.cs │ │ ├── DxfLwPolylineVertex.cs │ │ ├── DxfOle2Frame.cs │ │ ├── DxfOleFrame.cs │ │ ├── DxfControlPoint.cs │ │ ├── DxfWipeout.cs │ │ ├── DxfUnderlay.cs │ │ ├── DxfHelix.cs │ │ ├── DxfProxyEntity.cs │ │ ├── DxfEntitySection.cs │ │ ├── DxfHatch.PatternDefinitionLine.cs │ │ ├── DxfMLine.cs │ │ ├── DxfSpline.cs │ │ ├── DxfImage.cs │ │ ├── DxfVertex.cs │ │ ├── DxfDimensionBase.cs │ │ ├── DxfInsert.cs │ │ ├── DxfArc.cs │ │ └── DxfEntityExtensions.cs │ ├── Objects │ │ ├── DxfImageDefinitionReactor.cs │ │ ├── DxfRenderGlobal.cs │ │ ├── DxfRasterVariables.cs │ │ ├── DxfVbaProject.cs │ │ ├── DxfVisualStyle.cs │ │ ├── DxfSpatialIndex.cs │ │ ├── DxfAcadProxyObject.cs │ │ ├── DxfSortentsTable.cs │ │ ├── DxfGeoData.cs │ │ ├── DxfXRecordObject.cs │ │ ├── DxfField.cs │ │ ├── DxfPlotSettings.cs │ │ ├── DxfLightList.cs │ │ ├── DxfMLineStyle.cs │ │ └── DxfTransformationMatrix.cs │ ├── Extensions │ │ ├── EnumerableExtensions.cs │ │ └── StreamExtensions.cs │ ├── IDxfItemInternal.cs │ ├── DxfReadException.cs │ ├── DxbItemType.cs │ ├── DxfHelpers.cs │ ├── DxfEncodingHelper.cs │ ├── BinaryHelpers.cs │ ├── IDxfHasXData.cs │ ├── IxMilia.Dxf.csproj │ ├── DxfHandle.cs │ ├── DxfCodePairBufferReader.cs │ ├── Sections │ │ ├── DxfClassesSection.cs │ │ ├── DxfSectionType.cs │ │ ├── DxfBlocksSection.cs │ │ ├── DxfSection.cs │ │ ├── DxfObjectsSection.cs │ │ ├── DxfHeader.cs │ │ └── DxfThumbnailImageSection.cs │ ├── DxfPoint.cs │ ├── Collections │ │ ├── DxfPointerList.cs │ │ ├── ListWithPredicates`1.cs │ │ └── DictionaryWithPredicate.cs │ ├── DxfBoundingBox.cs │ ├── DxfCodePairGroup.cs │ ├── DxfLineWeight.cs │ ├── DxfCodePair.ExpectedType.cs │ ├── DxfReader.cs │ └── Blocks │ │ └── DxfBlock.DxfEndBlock.cs ├── test-notebook.dib ├── IxMilia.Dxf.Generator │ ├── IxMilia.Dxf.Generator.csproj │ └── Program.cs └── IxMilia.Dxf.Integration.Test │ ├── IxMilia.Dxf.Integration.Test.csproj │ ├── AutoCadExistsFactAttribute.cs │ ├── ODAConverterExistsFactAttribute.cs │ └── CompatTestsBase.cs ├── .gitattributes ├── global.json ├── .gitignore ├── Directory.Build.targets ├── generate-code.sh ├── generate-code.cmd ├── .vscode ├── launch.json └── tasks.json ├── Directory.Build.props ├── .github └── workflows │ └── ci.yml ├── LICENSE.txt ├── IxMilia.Dxf.sln └── CHANGELOG.md /version.txt: -------------------------------------------------------------------------------- 1 | 0.8.5 2 | -------------------------------------------------------------------------------- /src/Examples/.gitignore: -------------------------------------------------------------------------------- 1 | SavedExamples/ 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/IxMilia.Dxf.Test/diamond-bin.dxf binary 2 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.ReferenceCollector/.gitignore: -------------------------------------------------------------------------------- 1 | dxf-reference-R*.html 2 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestMinor" 5 | } 6 | } -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/diamond-bin.dxf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ixmilia/dxf/HEAD/src/IxMilia.Dxf.Test/diamond-bin.dxf -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfVersion.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | public enum DxfVersion 4 | { 5 | R2010 = 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("IxMilia.Dxf.Test")] 4 | 5 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/IDxfItem.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | public interface IDxfItem 4 | { 5 | IDxfItem? Owner { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore VS files 2 | *.suo 3 | *.user 4 | .vs/ 5 | 6 | # ignore artifacts 7 | artifacts/ 8 | 9 | # ignore generated code 10 | Generated/ 11 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfShadowType.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | public enum DxfShadowType 4 | { 5 | RayTraced = 0, 6 | ShadowMaps = 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(Version) 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.ReferenceCollector/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "IxMilia.Dxf.ReferenceCollector": { 4 | "commandName": "Project", 5 | "commandLineArgs": "2015" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/test-notebook.dib: -------------------------------------------------------------------------------- 1 | #!csharp 2 | 3 | #r "../artifacts/bin/IxMilia.Dxf/Debug/netstandard2.0/IxMilia.Dxf.dll" 4 | 5 | #!csharp 6 | 7 | using IxMilia.Dxf; 8 | 9 | var file = DxfFile.Load(@"path/to/file.dxf"); 10 | -------------------------------------------------------------------------------- /generate-code.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | _SCRIPT_DIR="$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )" 4 | 5 | cd "$_SCRIPT_DIR/src/IxMilia.Dxf.Generator" 6 | dotnet run -- "$_SCRIPT_DIR/src/IxMilia.Dxf/Generated" 7 | cd - 8 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfShadowMode.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | public enum DxfShadowMode 4 | { 5 | CastsAndReceivesShadows = 0, 6 | CastsShadows = 1, 7 | ReceivesShadows = 2, 8 | IgnoresShadows = 3 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Examples/README.md: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | This project is created as a bunch of unit tests, but the tests themselves don't actually assert anything. They exist 5 | primarily to give short, self-contained, individually runnable examples of how to use the library. 6 | -------------------------------------------------------------------------------- /generate-code.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | set thisdir=%~dp0 5 | 6 | pushd "%thisdir%src\IxMilia.Dxf.Generator" 7 | dotnet run -- "%thisdir%src\IxMilia.Dxf\Generated" 8 | if errorlevel 1 goto error 9 | popd 10 | 11 | exit /b 0 12 | 13 | :error 14 | echo Error running generator. 15 | popd 16 | exit /b 1 17 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/IDxfCodePairReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace IxMilia.Dxf 5 | { 6 | internal interface IDxfCodePairReader 7 | { 8 | IEnumerable GetCodePairs(); 9 | void SetReaderEncoding(Encoding encoding); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Tables/DxfTableType.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Tables 2 | { 3 | public enum DxfTableType 4 | { 5 | AppId, 6 | BlockRecord, 7 | DimStyle, 8 | Layer, 9 | LType, 10 | Style, 11 | Ucs, 12 | View, 13 | ViewPort 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfAngularTwoLineDimension.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Entities 2 | { 3 | public partial class DxfAngularTwoLineDimension 4 | { 5 | public DxfPoint SecondExtensionLineP1 6 | { 7 | get => DefinitionPoint1; 8 | set => DefinitionPoint1 = value; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfImageDefinitionReactor.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Objects 2 | { 3 | public partial class DxfImageDefinitionReactor 4 | { 5 | public IDxfItem AssociatedImage 6 | { 7 | get { return Owner; } 8 | set { ((IDxfItemInternal)this).SetOwner(value); } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.ReferenceCollector/IxMilia.Dxf.ReferenceCollector.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | false 7 | 12 8 | enable 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfRenderGlobal.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Objects 2 | { 3 | public enum DxfRenderProcedure 4 | { 5 | View = 0, 6 | Crop = 1, 7 | Selection = 2 8 | } 9 | 10 | public enum DxfRenderDestination 11 | { 12 | RenderWindow = 0, 13 | Viewport = 1 14 | } 15 | 16 | public partial class DxfRenderGlobal 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Tables/DxfTableItems.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | public partial class DxfLayer 4 | { 5 | public DxfLayer(string name, DxfColor color) 6 | : this(name) 7 | { 8 | Color = color; 9 | } 10 | } 11 | 12 | public partial class DxfViewPort 13 | { 14 | public const string ActiveViewPortName = "*ACTIVE"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace IxMilia.Dxf.Extensions 5 | { 6 | internal static class EnumerableExtensions 7 | { 8 | public static IEnumerable WhereNotNull(this IEnumerable source) 9 | { 10 | return source.Where(item => item is not null).Cast(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfLwPolylineVertex.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Entities 2 | { 3 | public class DxfLwPolylineVertex 4 | { 5 | public double X { get; set; } 6 | public double Y { get; set; } 7 | public int Identifier { get; set; } 8 | public double StartingWidth { get; set; } 9 | public double EndingWidth { get; set; } 10 | public double Bulge { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/IDxfItemInternal.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IxMilia.Dxf 4 | { 5 | internal interface IDxfItemInternal : IDxfItem 6 | { 7 | DxfHandle Handle { get; set; } 8 | DxfHandle OwnerHandle { get; set; } 9 | void SetOwner(IDxfItem owner); 10 | IEnumerable GetPointers(); 11 | IEnumerable GetChildItems(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfRasterVariables.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Objects 2 | { 3 | public enum DxfRasterImageUnits 4 | { 5 | None = 0, 6 | Millimeter = 1, 7 | Centimeter = 2, 8 | Meter = 3, 9 | Kilometer = 4, 10 | Inch = 5, 11 | Foot = 6, 12 | Yard = 7, 13 | Mile = 8 14 | } 15 | 16 | public partial class DxfRasterVariables 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfVbaProject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf.Objects 4 | { 5 | public partial class DxfVbaProject 6 | { 7 | public byte[] Data { get; set; } = Array.Empty(); 8 | 9 | protected override DxfObject PostParse() 10 | { 11 | Data = BinaryHelpers.CombineBytes(_hexData); 12 | _hexData.Clear(); 13 | return this; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfOle2Frame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf.Entities 4 | { 5 | public partial class DxfOle2Frame 6 | { 7 | public byte[] Data { get; set; } = Array.Empty(); 8 | 9 | protected override DxfEntity PostParse() 10 | { 11 | Data = BinaryHelpers.CombineBytes(_binaryDataStrings); 12 | _binaryDataStrings.Clear(); 13 | return this; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfOleFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf.Entities 4 | { 5 | public partial class DxfOleFrame 6 | { 7 | public byte[] Data { get; set; } = Array.Empty(); 8 | 9 | protected override DxfEntity PostParse() 10 | { 11 | Data = BinaryHelpers.CombineBytes(_binaryDataStrings); 12 | _binaryDataStrings.Clear(); 13 | return this; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.ReferenceCollector/README.md: -------------------------------------------------------------------------------- 1 | IxMilia.Dxf.ReferenceCollector 2 | ============================== 3 | 4 | Download the AutoCAD DXF reference as a single, self-contained HTML file. 5 | 6 | Usage: 7 | 8 | ``` bash 9 | dotnet run -- [dxf-version] 10 | ``` 11 | 12 | Valid values for `[dxf-version]` are: 13 | 14 | * 2015 15 | * 2016 16 | * 2017 17 | * 2018 18 | * 2019 19 | * 2020 20 | 21 | The result is placed at `.\dxf-reference-R[dxf-version].html`, e.g., `.\dxf-reference-R2020.html`. 22 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfControlPoint.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Entities 2 | { 3 | public struct DxfControlPoint 4 | { 5 | public DxfPoint Point { get; set; } 6 | public double Weight { get; set; } 7 | 8 | public DxfControlPoint(DxfPoint point, double weight) 9 | { 10 | Point = point; 11 | Weight = weight; 12 | } 13 | 14 | public DxfControlPoint(DxfPoint point) 15 | : this(point, 1.0) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch generator", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build-generator", 9 | "program": "${workspaceFolder}/artifacts/bin/IxMilia.Dxf.Generator/Debug/net8.0/IxMilia.Dxf.Generator.dll", 10 | "args": [ 11 | "${workspaceFolder}/src/IxMilia.Dxf/Generated" 12 | ], 13 | "cwd": "${workspaceFolder}", 14 | "console": "internalConsole" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfReadException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf 4 | { 5 | public class DxfReadException : Exception 6 | { 7 | public int Offset { get; private set; } 8 | 9 | public DxfReadException(string message, int offset) 10 | : base(message) 11 | { 12 | Offset = offset; 13 | } 14 | 15 | public DxfReadException(string message, DxfCodePair pair) 16 | : base(message) 17 | { 18 | Offset = pair == null ? -1 : pair.Offset; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Generator/IxMilia.Dxf.Generator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 12 7 | enable 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxbItemType.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | internal enum DxbItemType 4 | { 5 | Line = 1, 6 | Point = 2, 7 | Circle = 3, 8 | Arc = 8, 9 | Trace = 9, 10 | Solid = 11, 11 | Seqend = 17, 12 | Polyline = 19, 13 | Vertex = 20, 14 | Line3D = 21, 15 | Face = 22, 16 | ScaleFactor = 128, 17 | NewLayer = 129, 18 | LineExtension = 130, 19 | TraceExtension = 131, 20 | BlockBase = 132, 21 | Bulge = 133, 22 | Width = 134, 23 | NumberMode = 135, 24 | NewColor = 136, 25 | LineExtension3D = 137 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfHelpers.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | internal static class DxfHelpers 4 | { 5 | public static void SetFlag(ref int flags, int mask) 6 | { 7 | flags |= mask; 8 | } 9 | 10 | public static void ClearFlag(ref int flags, int mask) 11 | { 12 | flags &= ~mask; 13 | } 14 | 15 | public static bool GetFlag(int flags, int mask) 16 | { 17 | return (flags & mask) != 0; 18 | } 19 | 20 | public static void SetFlag(bool value, ref int flags, int mask) 21 | { 22 | if (value) SetFlag(ref flags, mask); 23 | else ClearFlag(ref flags, mask); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Examples/Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 12 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfEncodingHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace IxMilia.Dxf 4 | { 5 | internal class DxfEncodingHelper 6 | { 7 | private static readonly Regex DxfCodePagePattern = new Regex(@"^ANSI_(\d+)$", RegexOptions.IgnoreCase); 8 | 9 | public static bool TryParseEncoding(string encodingName, out int codePage) 10 | { 11 | var match = DxfCodePagePattern.Match(encodingName); 12 | if (match.Success && 13 | match.Groups.Count >= 2 && 14 | int.TryParse(match.Groups[1].Value, out codePage)) 15 | { 16 | return true; 17 | } 18 | 19 | codePage = 0; 20 | return false; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Examples/TestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.CompilerServices; 3 | using IxMilia.Dxf; 4 | 5 | namespace Examples 6 | { 7 | internal static class TestExtensions 8 | { 9 | internal static void SaveExample(this DxfFile file, [CallerFilePath] string? testFilePath = null, [CallerMemberName] string? testName = null) 10 | { 11 | var testDirectory = Path.GetDirectoryName(testFilePath)!; 12 | var fullTestDirectory = Path.Combine(testDirectory, "SavedExamples"); 13 | Directory.CreateDirectory(fullTestDirectory); 14 | 15 | var fileName = $"{testName}.dxf"; 16 | var fullOutputPath = Path.Combine(fullTestDirectory, fileName); 17 | file.Save(fullOutputPath); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)version.txt').Trim()) 5 | $(MSBuildThisFileDirectory)artifacts 6 | $(ArtifactsDir)\packages 7 | $(ArtifactsDir)\bin\$(MSBuildProjectName) 8 | $(ArtifactsDir)\obj\$(MSBuildProjectName) 9 | $(ArtifactsPackagesDir)\$(Configuration) 10 | embedded 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/FileTests.cs: -------------------------------------------------------------------------------- 1 | using IxMilia.Dxf.Entities; 2 | using Xunit; 3 | 4 | namespace IxMilia.Dxf.Test 5 | { 6 | public class FileTests : AbstractDxfTests 7 | { 8 | [Fact] 9 | public void EmptyFileBoundingBoxTest() 10 | { 11 | Assert.Equal(new DxfBoundingBox(DxfPoint.Origin, DxfVector.Zero), new DxfFile().GetBoundingBox()); 12 | } 13 | 14 | [Fact] 15 | public void FileBoundingBoxTest() 16 | { 17 | var file = new DxfFile(); 18 | var line = new DxfLine(new DxfPoint(0.0, 1.0, 0.0), new DxfPoint(1.0, 0.0, 0.0)); 19 | file.Entities.Add(line); 20 | Assert.Equal(new DxfBoundingBox(DxfPoint.Origin, new DxfVector(1.0, 1.0, 0.0)), file.GetBoundingBox()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfWipeout.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Entities 2 | { 3 | public partial class DxfWipeout 4 | { 5 | /// The path to the image. 6 | /// The bottom left corner of the location to display the image in the drawing. 7 | /// The width of the image in pixels. 8 | /// The height of the image in pixels. 9 | /// The display size of the image in drawing units. 10 | public DxfWipeout(string imagePath, DxfPoint location, int imageWidth, int imageHeight, DxfVector displaySize) 11 | : base(imagePath, location, imageWidth, imageHeight, displaySize) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Integration.Test/IxMilia.Dxf.Integration.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exe 6 | net8.0 7 | 12 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "type": "shell", 7 | "command": "dotnet build", 8 | "options": { 9 | "cwd": "${workspaceFolder}" 10 | }, 11 | "dependsOn": [ 12 | "generate" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "generate", 18 | "type": "shell", 19 | "command": "pwsh ./generate-code.ps1", 20 | "options": { 21 | "cwd": "${workspaceFolder}" 22 | } 23 | }, 24 | { 25 | "label": "build-generator", 26 | "type": "shell", 27 | "command": "dotnet build", 28 | "options": { 29 | "cwd": "${workspaceFolder}/src/IxMilia.Dxf.Generator" 30 | }, 31 | "problemMatcher": "$msCompile" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/IxMilia.Dxf.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exe 6 | net8.0 7 | 12 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfUnderlay.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using IxMilia.Dxf.Collections; 4 | 5 | namespace IxMilia.Dxf.Entities 6 | { 7 | public partial class DxfUnderlay 8 | { 9 | public IList BoundaryPoints { get; } = new List(); 10 | 11 | protected override DxfEntity PostParse() 12 | { 13 | Debug.Assert(_pointX.Count == _pointY.Count); 14 | for (int i = 0; i < _pointX.Count; i++) 15 | { 16 | BoundaryPoints.Add(new DxfPoint(_pointX[i], _pointY[i], 0.0)); 17 | } 18 | 19 | _pointX.Clear(); 20 | _pointY.Clear(); 21 | 22 | return this; 23 | } 24 | 25 | protected override IEnumerable GetExtentsPoints() 26 | { 27 | return BoundaryPoints; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/TestCodePairBufferReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace IxMilia.Dxf.Test 6 | { 7 | public class TestCodePairBufferReader : IDxfCodePairReader 8 | { 9 | public List Pairs { get; } 10 | 11 | public TestCodePairBufferReader(IEnumerable<(int code, object value)> pairs) 12 | : this(pairs.Select(cp => new DxfCodePair(cp.code, cp.value))) 13 | { 14 | } 15 | 16 | public TestCodePairBufferReader(IEnumerable pairs) 17 | { 18 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 19 | Pairs = pairs.ToList(); 20 | } 21 | 22 | public IEnumerable GetCodePairs() => Pairs; 23 | 24 | public void SetReaderEncoding(Encoding _encoding) 25 | { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.ReferenceCollector/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace IxMilia.Dxf.ReferenceCollector 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | if (args.Length == 0) 11 | { 12 | Console.WriteLine("Required arguments: The DXF spec version to download, e.g., '2018'."); 13 | Environment.Exit(1); 14 | } 15 | 16 | var dxfVersion = args[0]; 17 | var linkVirtualRoot = $"http://help.autodesk.com/cloudhelp/{dxfVersion}/ENU/AutoCAD-DXF/"; // don't crawl above this path 18 | var startPageUrl = linkVirtualRoot + "files/index.htm"; 19 | var resultFile = Path.Combine(Environment.CurrentDirectory, $"dxf-reference-R{dxfVersion}.html"); 20 | var collector = new WebPageCollector(startPageUrl, linkVirtualRoot, resultFile); 21 | collector.Run().Wait(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfHelix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IxMilia.Dxf.Entities 4 | { 5 | public partial class DxfHelix 6 | { 7 | protected override IEnumerable GetExtentsPoints() 8 | { 9 | var radiusX = new DxfVector(Radius, 0.0, 0.0); 10 | var radiusY = new DxfVector(0.0, Radius, 0.0); 11 | 12 | // base of the helix 13 | yield return StartPoint + radiusX; 14 | yield return StartPoint - radiusX; 15 | yield return StartPoint + radiusY; 16 | yield return StartPoint - radiusY; 17 | 18 | // other side of the helix 19 | var endPoint = StartPoint + AxisVector.Normalize() * NumberOfTurns * TurnHeight; 20 | yield return endPoint + radiusX; 21 | yield return endPoint - radiusX; 22 | yield return endPoint + radiusY; 23 | yield return endPoint - radiusY; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, windows-latest] 12 | configuration: [Debug, Release] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-dotnet@v4 16 | - name: Build and test 17 | shell: pwsh 18 | run: | 19 | $shellExt = if ($IsWindows) { "cmd" } else { "sh" } 20 | & ./build-and-test.$shellExt --configuration ${{ matrix.configuration }} 21 | 22 | nuget-publish: 23 | if: startsWith(github.ref, 'refs/tags/v') 24 | needs: [build] 25 | runs-on: windows-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-dotnet@v4 29 | - run: .\build-and-test.cmd -c Release -notest 30 | - run: dotnet nuget push .\artifacts\packages\Release\*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --no-symbols 31 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfVisualStyle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf.Objects 4 | { 5 | public enum DxfFaceLightingModel 6 | { 7 | Invisible = 0, 8 | Visible = 1, 9 | Phong = 2, 10 | Gooch = 3 11 | } 12 | 13 | public enum DxfFaceLightingQuality 14 | { 15 | None = 0, 16 | PerFace = 1, 17 | PerVertex = 2 18 | } 19 | 20 | public enum DxfFaceColorMode 21 | { 22 | NoColor = 0, 23 | ObjectColor = 1, 24 | BackgroundColor = 2, 25 | CustomColor = 3, 26 | MonoColor = 4, 27 | Tinted = 5, 28 | Desaturated = 6 29 | } 30 | 31 | [Flags] 32 | public enum DxfFaceModifier 33 | { 34 | None = 0, 35 | Opacity = 1, 36 | Specular = 2 37 | } 38 | 39 | public enum DxfEdgeStyleModel 40 | { 41 | NoEdges = 0, 42 | IsoLines = 1, 43 | FacetEdges = 2 44 | } 45 | 46 | public partial class DxfVisualStyle 47 | { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/BinaryHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace IxMilia.Dxf 5 | { 6 | internal static class BinaryHelpers 7 | { 8 | public static byte[] CombineBytes(IEnumerable data) 9 | { 10 | var result = new List(); 11 | foreach (var d in data) 12 | { 13 | result.AddRange(d); 14 | } 15 | 16 | return result.ToArray(); 17 | } 18 | 19 | public static IEnumerable ChunkBytes(byte[] data, int chunkSize) 20 | { 21 | if (data == null) 22 | { 23 | yield break; 24 | } 25 | 26 | var pos = 0; 27 | while (pos < data.Length) 28 | { 29 | var slice = data.Skip(pos).Take(chunkSize).ToArray(); 30 | pos += slice.Length; 31 | yield return slice; 32 | } 33 | } 34 | 35 | public static IEnumerable ChunkBytes(byte[] data) 36 | { 37 | return ChunkBytes(data, 128); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) IxMilia. 4 | All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using IxMilia.Dxf.Collections; 3 | using Xunit; 4 | 5 | namespace IxMilia.Dxf.Test 6 | { 7 | public class CollectionTests 8 | { 9 | [Fact] 10 | public void MinimumCountIsZeroTest() 11 | { 12 | var list = new ListWithPredicates(null, 0); 13 | list.Add(0); 14 | list.Clear(); 15 | } 16 | 17 | [Fact] 18 | public void MinimumCountIsNonZeroTest() 19 | { 20 | Assert.Throws(() => new ListWithPredicates(null, 3)); 21 | var list = new ListWithPredicates(null, 3, 1, 2, 3); 22 | Assert.Throws(() => list.RemoveAt(0)); 23 | list.Add(4); 24 | list.Add(5); 25 | list.RemoveAt(0); 26 | } 27 | 28 | [Fact] 29 | public void ListWithPredicateTest() 30 | { 31 | var list = new ListWithPredicates(i => i > 5, 0); 32 | list.Add(6); 33 | list.Add(7); 34 | Assert.Throws(() => list.Add(5)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfProxyEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf.Entities 4 | { 5 | public partial class DxfProxyEntity 6 | { 7 | public byte[] GraphicsData { get; set; } = Array.Empty(); 8 | 9 | public byte[] EntityData { get; set; } = Array.Empty(); 10 | 11 | public int ObjectDrawingFormatVersion 12 | { 13 | // lower word 14 | get { return (int)(_objectDrawingFormat & 0xFFFF); } 15 | set { _objectDrawingFormat |= (uint)value & 0xFFFF; } 16 | } 17 | 18 | public int ObjectMaintenanceReleaseVersion 19 | { 20 | // upper word 21 | get { return (int)(_objectDrawingFormat >> 4); } 22 | set { _objectDrawingFormat = (uint)(value << 4) + _objectDrawingFormat & 0xFFFF; } 23 | } 24 | 25 | protected override DxfEntity PostParse() 26 | { 27 | GraphicsData = BinaryHelpers.CombineBytes(_graphicsDataBytes); 28 | _graphicsDataBytes.Clear(); 29 | 30 | EntityData = BinaryHelpers.CombineBytes(_entityDataBytes); 31 | _entityDataBytes.Clear(); 32 | 33 | return this; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/IDxfHasXData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IxMilia.Dxf 4 | { 5 | public interface IDxfHasXData 6 | { 7 | IList ExtensionDataGroups { get; } 8 | IDictionary XData { get; } 9 | } 10 | 11 | internal static class DxfXDataHelper 12 | { 13 | public static bool TrySetExtensionData(this THasXData hasXData, DxfCodePair pair, DxfCodePairBufferReader buffer) 14 | where THasXData : IDxfHasXData 15 | { 16 | if (pair.Code == DxfCodePairGroup.GroupCodeNumber && pair.StringValue.StartsWith("{")) 17 | { 18 | buffer.Advance(); 19 | var groupName = DxfCodePairGroup.GetGroupName(pair.StringValue); 20 | hasXData.ExtensionDataGroups.Add(DxfCodePairGroup.FromBuffer(buffer, groupName)); 21 | return true; 22 | } 23 | else if (pair.Code >= 1000) 24 | { 25 | buffer.Advance(); 26 | DxfXData.PopulateFromBuffer(buffer, hasXData.XData, pair.StringValue); 27 | return true; 28 | } 29 | 30 | return false; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfSpatialIndex.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace IxMilia.Dxf.Objects 4 | { 5 | public partial class DxfSpatialIndex 6 | { 7 | private string LastSubclassMarker = string.Empty; 8 | 9 | internal override bool TrySetPair(DxfCodePair pair) 10 | { 11 | switch (pair.Code) 12 | { 13 | case 100: 14 | LastSubclassMarker = pair.StringValue; 15 | break; 16 | case 40: 17 | switch (LastSubclassMarker) 18 | { 19 | case "": 20 | case "AcDbIndex": 21 | this.Timestamp = DateDouble(pair.DoubleValue); 22 | break; 23 | case "AcDbSpatialIndex": 24 | this.Values.Add(pair.DoubleValue); 25 | break; 26 | default: 27 | Debug.Assert(false, "Unexpected extra values for code 40"); 28 | break; 29 | } 30 | break; 31 | default: 32 | return base.TrySetPair(pair); 33 | } 34 | 35 | return true; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/CodePairWriterTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace IxMilia.Dxf.Test 4 | { 5 | public class CodePairWriterTests : AbstractDxfTests 6 | { 7 | [Fact] 8 | public void WriteStringInBinary() 9 | { 10 | var actual = WriteToBinaryWriter( 11 | (1, "A") 12 | ); 13 | var expected = new byte[] 14 | { 15 | 0x01, 0x00, // code 1, string 16 | (byte)'A', 17 | 0x00 // \0 18 | }; 19 | Assert.Equal(expected, actual); 20 | } 21 | 22 | [Fact] 23 | public void WriteBinaryChunkInBinary() 24 | { 25 | var actual = WriteToBinaryWriter( 26 | (310, new byte[] { 0x01, 0x02 }) 27 | ); 28 | var expected = new byte[] 29 | { 30 | 0x36, 0x01, // code 310, binary 31 | 0x02, // length 32 | 0x01, 0x02 // data 33 | }; 34 | Assert.Equal(expected, actual); 35 | } 36 | 37 | [Fact] 38 | public void WriteBinaryChunkInText() 39 | { 40 | var actual = WriteToTextWriter( 41 | (310, new byte[] { 0x01, 0x02 }) 42 | ); 43 | var expected = "310\r\n0102\r\n"; 44 | Assert.Equal(expected, actual); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/TextWriterTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Xunit; 3 | 4 | namespace IxMilia.Dxf.Test 5 | { 6 | public class TextWriterTests : AbstractDxfTests 7 | { 8 | protected static string WriteStringAsText(string value, DxfAcadVersion version) 9 | { 10 | using (var ms = new MemoryStream()) 11 | { 12 | var writer = new DxfWriter(ms, asText: true, version: version); 13 | writer.CreateInternalWriters(); 14 | writer.WriteStringWithEncoding(value); 15 | writer.Flush(); 16 | ms.Seek(0, SeekOrigin.Begin); 17 | using (var reader = new StreamReader(ms)) 18 | { 19 | return reader.ReadToEnd(); 20 | } 21 | } 22 | } 23 | 24 | [Fact] 25 | public void WriteUnicodeToAsciiStreamTest() 26 | { 27 | var actual = WriteStringAsText("Repère pièce", DxfAcadVersion.R2004); 28 | var expected = "Rep\\U+00E8re pi\\U+00E8ce\r\n"; 29 | Assert.Equal(expected, actual); 30 | } 31 | 32 | [Fact] 33 | public void WriteUnicodeToUtf8StreamTest() 34 | { 35 | var actual = WriteStringAsText("Repère pièce", DxfAcadVersion.R2007); 36 | var expected = "Repère pièce\r\n"; 37 | Assert.Equal(expected, actual); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Tables/DxfViewMode.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | public struct DxfViewMode 4 | { 5 | private int Flags; 6 | 7 | public DxfViewMode(int flags) 8 | : this() 9 | { 10 | Flags = flags; 11 | } 12 | 13 | public static implicit operator int (DxfViewMode mode) 14 | { 15 | return mode.Flags; 16 | } 17 | 18 | public static implicit operator DxfViewMode(int flags) 19 | { 20 | return new DxfViewMode(flags); 21 | } 22 | 23 | public bool PerspectiveViewActive 24 | { 25 | get { return DxfHelpers.GetFlag(Flags, 1); } 26 | set { DxfHelpers.SetFlag(value, ref Flags, 1); } 27 | } 28 | 29 | public bool FrontClippingOn 30 | { 31 | get { return DxfHelpers.GetFlag(Flags, 2); } 32 | set { DxfHelpers.SetFlag(value, ref Flags, 2); } 33 | } 34 | 35 | public bool BackClippingOn 36 | { 37 | get { return DxfHelpers.GetFlag(Flags, 4); } 38 | set { DxfHelpers.SetFlag(value, ref Flags, 4); } 39 | } 40 | 41 | public bool UcsFollowModeOn 42 | { 43 | get { return DxfHelpers.GetFlag(Flags, 8); } 44 | set { DxfHelpers.SetFlag(value, ref Flags, 8); } 45 | } 46 | 47 | public bool FrontClippingAtEye 48 | { 49 | get { return !DxfHelpers.GetFlag(Flags, 16); } 50 | set { DxfHelpers.SetFlag(!value, ref Flags, 16); } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/IxMilia.Dxf.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A portable .NET library for reading and writing DXF and DXB files. AutoCAD versions 1.0 through R2018 are supported. 5 | Copyright 2020 6 | IxMilia.Dxf 7 | IxMilia 8 | net8.0;netstandard2.0 9 | 10.0 10 | IxMilia.Dxf 11 | IxMilia.Dxf 12 | AutoCAD;CAD;DXB;DXF 13 | https://github.com/ixmilia/dxf 14 | MIT 15 | true 16 | $(NoWarn);1573;1591 17 | 12 18 | enable 19 | 20 | 21 | true 22 | true 23 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 24 | 25 | 26 | 27 | $(NoWarn);nullable 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/MiscFileTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Xunit; 4 | 5 | namespace IxMilia.Dxf.Test 6 | { 7 | public class MiscFileTests : AbstractDxfTests 8 | { 9 | [Fact] 10 | public void ReadEmptyFileTest() 11 | { 12 | using (var ms = new MemoryStream()) 13 | { 14 | var _ = DxfFile.Load(ms); 15 | } 16 | } 17 | 18 | [Fact] 19 | public void ExceptionOnNonDxfFileTest() 20 | { 21 | using (var ms = new MemoryStream()) 22 | { 23 | // made to look like the start of an R14 DWG 24 | var dwgBytes = new[] 25 | { 26 | 'A', 'C', '1', '0', '1', '4', 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 28 | }; 29 | foreach (var b in dwgBytes) 30 | { 31 | ms.WriteByte((byte)b); 32 | } 33 | ms.Flush(); 34 | ms.Seek(0, SeekOrigin.Begin); 35 | try 36 | { 37 | DxfFile.Load(ms); 38 | throw new Exception("Previous call should have thrown."); 39 | } 40 | catch (DxfReadException ex) 41 | { 42 | if (ex.Message != "Not a valid DXF file header: `AC1014`.") 43 | { 44 | throw new Exception("Improper exception", ex); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfEntitySection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using IxMilia.Dxf.Collections; 5 | 6 | namespace IxMilia.Dxf.Entities 7 | { 8 | public partial class DxfEntitySection 9 | { 10 | public IList Vertices { get; } = new List(); 11 | public IList BackLineVertices { get; } = new List(); 12 | 13 | protected override DxfEntity PostParse() 14 | { 15 | Debug.Assert(_vertexCount == _vertexX.Count && _vertexCount == _vertexY.Count && _vertexCount == _vertexZ.Count); 16 | for (int i = 0; i < _vertexCount; i++) 17 | { 18 | Vertices.Add(new DxfPoint(_vertexX[i], _vertexY[i], _vertexZ[i])); 19 | } 20 | 21 | _vertexX.Clear(); 22 | _vertexY.Clear(); 23 | _vertexZ.Clear(); 24 | 25 | Debug.Assert(_backLineVertexCount == _backLineVertexX.Count && _backLineVertexCount == _backLineVertexY.Count && _backLineVertexCount == _backLineVertexZ.Count); 26 | for (int i = 0; i < _backLineVertexCount; i++) 27 | { 28 | BackLineVertices.Add(new DxfPoint(_backLineVertexX[i], _backLineVertexY[i], _backLineVertexZ[i])); 29 | } 30 | 31 | _backLineVertexX.Clear(); 32 | _backLineVertexY.Clear(); 33 | _backLineVertexZ.Clear(); 34 | 35 | return this; 36 | } 37 | 38 | protected override IEnumerable GetExtentsPoints() 39 | { 40 | return Vertices.Concat(BackLineVertices); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace IxMilia.Dxf 5 | { 6 | public struct DxfHandle : IEquatable 7 | { 8 | public ulong Value { get; } 9 | 10 | public DxfHandle(ulong value) 11 | { 12 | Value = value; 13 | } 14 | 15 | public static explicit operator ulong(DxfHandle handle) => handle.Value; 16 | 17 | public static explicit operator DxfHandle(ulong value) => new DxfHandle(value); 18 | 19 | public override string ToString() 20 | { 21 | return Value.ToString("X", CultureInfo.InvariantCulture); 22 | } 23 | 24 | public static bool TryParse(string s, out DxfHandle result) 25 | { 26 | var parseResult = ulong.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var handle); 27 | result = new DxfHandle(handle); 28 | return parseResult; 29 | } 30 | 31 | public override bool Equals(object? obj) 32 | { 33 | return obj is DxfHandle handle && Equals(handle); 34 | } 35 | 36 | public bool Equals(DxfHandle other) 37 | { 38 | return Value == other.Value; 39 | } 40 | 41 | public override int GetHashCode() 42 | { 43 | return Value.GetHashCode(); 44 | } 45 | 46 | public static bool operator ==(DxfHandle a, DxfHandle b) 47 | { 48 | return a.Value == b.Value; 49 | } 50 | 51 | public static bool operator !=(DxfHandle a, DxfHandle b) 52 | { 53 | return !(a == b); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfCodePairBufferReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace IxMilia.Dxf 6 | { 7 | internal class DxfBufferReader 8 | { 9 | private readonly IEnumerator enumerator; 10 | private readonly Func isIgnorable; 11 | 12 | public DxfBufferReader(IEnumerable pairs, Func isIgnorable) 13 | { 14 | this.enumerator = pairs.GetEnumerator(); 15 | this.isIgnorable = isIgnorable; 16 | Advance(); 17 | } 18 | 19 | public bool ItemsRemain { get; private set; } 20 | 21 | public T Peek() 22 | { 23 | if (!this.ItemsRemain) 24 | { 25 | throw new IndexOutOfRangeException("No more items."); 26 | } 27 | 28 | return enumerator.Current; 29 | } 30 | 31 | public void Advance() 32 | { 33 | ItemsRemain = enumerator.MoveNext(); 34 | while (ItemsRemain && isIgnorable(Peek())) 35 | { 36 | ItemsRemain = enumerator.MoveNext(); 37 | } 38 | } 39 | } 40 | 41 | internal class DxfCodePairBufferReader : DxfBufferReader 42 | { 43 | private IDxfCodePairReader reader; 44 | 45 | public DxfCodePairBufferReader(IDxfCodePairReader reader) 46 | : base(reader.GetCodePairs(), (pair) => pair.Code == 999) 47 | { 48 | this.reader = reader; 49 | } 50 | 51 | public void SetReaderEncoding(Encoding encoding) 52 | { 53 | reader.SetReaderEncoding(encoding); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfAcadProxyObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using IxMilia.Dxf.Collections; 4 | 5 | namespace IxMilia.Dxf.Objects 6 | { 7 | public partial class DxfAcadProxyObject 8 | { 9 | public byte[] ObjectData { get; set; } = Array.Empty(); 10 | 11 | public IList ObjectIds { get; } = new ListNonNull(); 12 | 13 | public uint DrawingVersion 14 | { 15 | get { return _objectDrawingFormat | 0x0000FFFF; } 16 | set { _objectDrawingFormat |= value & 0x0000FFFF; } 17 | } 18 | 19 | public uint MaintenenceReleaseVersion 20 | { 21 | get { return (_objectDrawingFormat | 0xFFFF0000) >> 16; } 22 | set { _objectDrawingFormat |= (value & 0xFFFF0000) << 16; } 23 | } 24 | 25 | protected override DxfObject PostParse() 26 | { 27 | foreach (var a in _objectIdsA) 28 | { 29 | ObjectIds.Add(a); 30 | } 31 | 32 | foreach (var b in _objectIdsB) 33 | { 34 | ObjectIds.Add(b); 35 | } 36 | 37 | foreach (var c in _objectIdsC) 38 | { 39 | ObjectIds.Add(c); 40 | } 41 | 42 | foreach (var d in _objectIdsD) 43 | { 44 | ObjectIds.Add(d); 45 | } 46 | 47 | _objectIdsA.Clear(); 48 | _objectIdsB.Clear(); 49 | _objectIdsC.Clear(); 50 | _objectIdsD.Clear(); 51 | 52 | ObjectData = BinaryHelpers.CombineBytes(_binaryObjectBytes); 53 | _binaryObjectBytes.Clear(); 54 | 55 | return this; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Extensions/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace IxMilia.Dxf.Extensions 6 | { 7 | internal static class StreamExtensions 8 | { 9 | public static string? ReadLine(this Stream stream, Encoding encoding, out int bytesRead) 10 | { 11 | // read line char-by-char 12 | bytesRead = 0; 13 | var bytes = new List(); 14 | var b = stream.ReadByte(); 15 | if (b < 0) 16 | { 17 | return null; 18 | } 19 | 20 | bytesRead++; 21 | 22 | while (b > 0 && b != '\n') 23 | { 24 | bytes.Add((byte)b); 25 | b = stream.ReadByte(); 26 | bytesRead++; 27 | } 28 | 29 | var byteArray = bytes.ToArray(); 30 | var result = GetString(encoding, byteArray); 31 | if (result.Length > 0 && result[result.Length - 1] == '\r') 32 | { 33 | result = result.Substring(0, result.Length - 1); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | private static string GetString(Encoding encoding, byte[] bytes) 40 | { 41 | if (encoding == null) 42 | { 43 | var sb = new StringBuilder(bytes.Length); 44 | foreach (var b in bytes) 45 | { 46 | sb.Append((char)b); 47 | } 48 | 49 | return sb.ToString(); 50 | } 51 | else 52 | { 53 | return encoding.GetString(bytes, 0, bytes.Length); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Generator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace IxMilia.Dxf.Generator 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | if (args.Length != 1) 11 | { 12 | Console.WriteLine("Usage: requires one argument specifying the location of the root project."); 13 | Environment.Exit(1); 14 | } 15 | 16 | var projectDir = args[0]; 17 | var entityDir = Path.Combine(projectDir, "Entities"); 18 | var objectDir = Path.Combine(projectDir, "Objects"); 19 | var sectionDir = Path.Combine(projectDir, "Sections"); 20 | var tableDir = Path.Combine(projectDir, "Tables"); 21 | 22 | CleanDirectory(entityDir); 23 | CleanDirectory(objectDir); 24 | CleanDirectory(sectionDir); 25 | CleanDirectory(tableDir); 26 | 27 | Console.WriteLine($"Generating entities into: {entityDir}"); 28 | Console.WriteLine($"Generating objects into: {objectDir}"); 29 | Console.WriteLine($"Generating sections into: {sectionDir}"); 30 | Console.WriteLine($"Generating tables into: {tableDir}"); 31 | 32 | new EntityGenerator(entityDir).Run(); 33 | new ObjectGenerator(objectDir).Run(); 34 | new SectionGenerator(sectionDir).Run(); 35 | new TableGenerator(tableDir).Run(); 36 | } 37 | 38 | private static void CleanDirectory(string directoryPath) 39 | { 40 | if (Directory.Exists(directoryPath)) 41 | { 42 | Directory.Delete(directoryPath, true); 43 | } 44 | 45 | Directory.CreateDirectory(directoryPath); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfHatch.PatternDefinitionLine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IxMilia.Dxf.Entities 4 | { 5 | public partial class DxfHatch 6 | { 7 | public class PatternDefinitionLine 8 | { 9 | public double Angle { get; set; } = 0.0; 10 | public DxfPoint BasePoint { get; set; } = DxfPoint.Origin; 11 | public DxfVector Offset { get; set; } = DxfVector.Zero; 12 | public List DashLengths { get; } = new List(); 13 | 14 | internal bool TrySetPair(DxfCodePair pair) 15 | { 16 | switch (pair.Code) 17 | { 18 | case 53: 19 | Angle = pair.DoubleValue; 20 | break; 21 | case 43: 22 | BasePoint = BasePoint.WithUpdatedX(pair.DoubleValue); 23 | break; 24 | case 44: 25 | BasePoint = BasePoint.WithUpdatedY(pair.DoubleValue); 26 | break; 27 | case 45: 28 | Offset = Offset.WithUpdatedX(pair.DoubleValue); 29 | break; 30 | case 46: 31 | Offset = Offset.WithUpdatedY(pair.DoubleValue); 32 | break; 33 | case 79: 34 | var _dashLengthCount = pair.ShortValue; 35 | break; 36 | case 49: 37 | DashLengths.Add(pair.DoubleValue); 38 | break; 39 | default: 40 | return false; 41 | } 42 | 43 | return true; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/StreamWithNoLengthOrPosition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace IxMilia.Dxf.Test 5 | { 6 | internal class StreamWithNoLengthOrPosition : Stream 7 | { 8 | public Stream BaseStream { get; } 9 | 10 | public StreamWithNoLengthOrPosition(Stream baseStream) 11 | { 12 | BaseStream = baseStream; 13 | } 14 | 15 | public override bool CanRead => true; 16 | 17 | public override bool CanSeek => false; 18 | 19 | public override bool CanWrite => false; 20 | 21 | public override long Length 22 | { 23 | get 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | 29 | public override long Position 30 | { 31 | get 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | 36 | set 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | } 41 | 42 | public override void Flush() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | public override int Read(byte[] buffer, int offset, int count) 48 | { 49 | return BaseStream.Read(buffer, offset, count); 50 | } 51 | 52 | public override long Seek(long offset, SeekOrigin origin) 53 | { 54 | throw new NotImplementedException(); 55 | } 56 | 57 | public override void SetLength(long value) 58 | { 59 | throw new NotImplementedException(); 60 | } 61 | 62 | public override void Write(byte[] buffer, int offset, int count) 63 | { 64 | throw new NotImplementedException(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Sections/DxfClassesSection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using IxMilia.Dxf.Collections; 4 | 5 | namespace IxMilia.Dxf.Sections 6 | { 7 | internal class DxfClassesSection : DxfSection 8 | { 9 | public IList Classes { get; } 10 | 11 | public DxfClassesSection() 12 | { 13 | Classes = new ListNonNull(); 14 | } 15 | 16 | public override DxfSectionType Type 17 | { 18 | get { return DxfSectionType.Classes; } 19 | } 20 | 21 | protected internal override IEnumerable GetSpecificPairs(DxfAcadVersion version, bool outputHandles, HashSet writtenItems) 22 | { 23 | return this.Classes.SelectMany(e => e.GetValuePairs(version, outputHandles)); 24 | } 25 | 26 | protected internal override void Clear() 27 | { 28 | Classes.Clear(); 29 | } 30 | 31 | internal static DxfClassesSection ClassesSectionFromBuffer(DxfCodePairBufferReader buffer, DxfAcadVersion version) 32 | { 33 | var section = new DxfClassesSection(); 34 | section.Clear(); 35 | while (buffer.ItemsRemain) 36 | { 37 | var pair = buffer.Peek(); 38 | if (DxfCodePair.IsSectionEnd(pair)) 39 | { 40 | // done reading classes 41 | buffer.Advance(); // swallow (0, ENDSEC) 42 | break; 43 | } 44 | 45 | if (pair.Code != 0) 46 | { 47 | throw new DxfReadException("Expected new class.", pair); 48 | } 49 | 50 | var cls = DxfClass.FromBuffer(buffer, version); 51 | if (cls != null) 52 | { 53 | section.Classes.Add(cls); 54 | } 55 | } 56 | 57 | return section; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/CodePairReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Xunit; 3 | 4 | namespace IxMilia.Dxf.Test 5 | { 6 | public class CodePairReaderTests : AbstractDxfTests 7 | { 8 | [Fact] 9 | public void ReadStringInBinary() 10 | { 11 | var buffer = new byte[] 12 | { 13 | 0x01, 0x00, // code 1, string 14 | (byte)'A', 15 | 0x00 // \0 16 | }; 17 | using (var stream = new MemoryStream(buffer)) 18 | using (var binReader = new BinaryReader(stream)) 19 | { 20 | var reader = new DxfBinaryReader(binReader, isPostR13File: true); 21 | var pair = reader.GetCodePair(); 22 | Assert.NotNull(pair); 23 | Assert.Equal(1, pair.Code); 24 | Assert.Equal("A", pair.StringValue); 25 | } 26 | } 27 | 28 | [Fact] 29 | public void ReadBinaryChunkInBinary() 30 | { 31 | var buffer = new byte[] 32 | { 33 | 0x36, 0x01, // code 310, binary 34 | 0x02, // length 35 | 0x01, 0x02 // data 36 | }; 37 | using (var stream = new MemoryStream(buffer)) 38 | using (var binReader = new BinaryReader(stream)) 39 | { 40 | var reader = new DxfBinaryReader(binReader, isPostR13File: true); 41 | var pair = reader.GetCodePair(); 42 | Assert.NotNull(pair); 43 | Assert.Equal(310, pair.Code); 44 | Assert.Equal(new byte[] { 0x01, 0x02 }, pair.BinaryValue); 45 | } 46 | } 47 | 48 | [Fact] 49 | public void ReadBinaryChunkInText() 50 | { 51 | var reader = TextReaderFromLines( 52 | "310", 53 | "0102"); 54 | var pair = reader.GetCodePair(); 55 | Assert.NotNull(pair); 56 | Assert.Equal(310, pair.Code); 57 | Assert.Equal(new byte[] { 0x01, 0x02 }, pair.BinaryValue); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfMLine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using IxMilia.Dxf.Collections; 4 | 5 | namespace IxMilia.Dxf.Entities 6 | { 7 | public partial class DxfMLine 8 | { 9 | public IList Vertices { get; } = new List(); 10 | public IList SegmentDirections { get; } = new List(); 11 | public IList MiterDirections { get; } = new List(); 12 | 13 | protected override DxfEntity PostParse() 14 | { 15 | Debug.Assert(_vertexCount == _vertexX.Count && _vertexCount == _vertexY.Count && _vertexCount == _vertexZ.Count); 16 | for (int i = 0; i < _vertexCount; i++) 17 | { 18 | Vertices.Add(new DxfPoint(_vertexX[i], _vertexY[i], _vertexZ[i])); 19 | } 20 | 21 | _vertexX.Clear(); 22 | _vertexY.Clear(); 23 | _vertexZ.Clear(); 24 | 25 | Debug.Assert(_vertexCount == _segmentDirectionX.Count && _vertexCount == _segmentDirectionY.Count && _vertexCount == _segmentDirectionZ.Count); 26 | for (int i = 0; i < _vertexCount; i++) 27 | { 28 | Vertices.Add(new DxfPoint(_segmentDirectionX[i], _segmentDirectionY[i], _segmentDirectionZ[i])); 29 | } 30 | 31 | _segmentDirectionX.Clear(); 32 | _segmentDirectionY.Clear(); 33 | _segmentDirectionZ.Clear(); 34 | 35 | Debug.Assert(_vertexCount == _miterDirectionX.Count && _vertexCount == _miterDirectionY.Count && _vertexCount == _miterDirectionZ.Count); 36 | for (int i = 0; i < _vertexCount; i++) 37 | { 38 | Vertices.Add(new DxfPoint(_miterDirectionX[i], _miterDirectionY[i], _miterDirectionZ[i])); 39 | } 40 | 41 | _miterDirectionX.Clear(); 42 | _miterDirectionY.Clear(); 43 | _miterDirectionZ.Clear(); 44 | 45 | return this; 46 | } 47 | 48 | protected override IEnumerable GetExtentsPoints() 49 | { 50 | return Vertices; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfSpline.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | 4 | namespace IxMilia.Dxf.Entities 5 | { 6 | public partial class DxfSpline 7 | { 8 | public int NumberOfKnots 9 | { 10 | get { return KnotValues.Count; } 11 | } 12 | 13 | public int NumberOfControlPoints 14 | { 15 | get { return ControlPoints.Count; } 16 | } 17 | 18 | public int NumberOfFitPoints 19 | { 20 | get { return FitPoints.Count; } 21 | } 22 | 23 | public IList ControlPoints { get; } = new List(); 24 | 25 | public IList FitPoints { get; } = new List(); 26 | 27 | protected override DxfEntity PostParse() 28 | { 29 | Debug.Assert((_controlPointX.Count == _controlPointY.Count) && (_controlPointX.Count == _controlPointZ.Count)); 30 | for (int i = 0; i < _controlPointX.Count; i++) 31 | { 32 | var weight = _controlPointX.Count == _weights.Count ? _weights[i] : 1.0; 33 | ControlPoints.Add(new DxfControlPoint(new DxfPoint(_controlPointX[i], _controlPointY[i], _controlPointZ[i]), weight)); 34 | } 35 | 36 | _controlPointX.Clear(); 37 | _controlPointY.Clear(); 38 | _controlPointZ.Clear(); 39 | 40 | Debug.Assert((_fitPointX.Count == _fitPointY.Count) && (_fitPointX.Count == _fitPointZ.Count)); 41 | for (int i = 0; i < _fitPointX.Count; i++) 42 | { 43 | FitPoints.Add(new DxfPoint(_fitPointX[i], _fitPointY[i], _fitPointZ[i])); 44 | } 45 | 46 | _fitPointX.Clear(); 47 | _fitPointY.Clear(); 48 | _fitPointZ.Clear(); 49 | 50 | return this; 51 | } 52 | 53 | protected override IEnumerable GetExtentsPoints() 54 | { 55 | // TODO: this doesn't account for the actual body of the curve; including `ControlPoints` would guarantee 56 | // that everything is contained, but at the cost of making the bounding box too big 57 | return FitPoints; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfSortentsTable.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Objects 2 | { 3 | public partial class DxfSortentsTable 4 | { 5 | internal override DxfObject PopulateFromBuffer(DxfCodePairBufferReader buffer) 6 | { 7 | var isReadyForSortHandles = false; 8 | while (buffer.ItemsRemain) 9 | { 10 | var pair = buffer.Peek(); 11 | if (pair.Code == 0) 12 | { 13 | break; 14 | } 15 | 16 | while (this.TrySetExtensionData(pair, buffer)) 17 | { 18 | pair = buffer.Peek(); 19 | } 20 | 21 | if (pair.Code == 0) 22 | { 23 | break; 24 | } 25 | 26 | switch (pair.Code) 27 | { 28 | case 5: 29 | if (isReadyForSortHandles) 30 | { 31 | SortItemsPointers.Pointers.Add(new DxfPointer(DxfCommonConverters.HandleString(pair.StringValue))); 32 | } 33 | else 34 | { 35 | ((IDxfItemInternal)this).Handle = DxfCommonConverters.HandleString(pair.StringValue); 36 | isReadyForSortHandles = true; 37 | } 38 | break; 39 | case 100: 40 | isReadyForSortHandles = true; 41 | break; 42 | case 330: 43 | ((IDxfItemInternal)this).OwnerHandle = DxfCommonConverters.HandleString(pair.StringValue); 44 | isReadyForSortHandles = true; 45 | break; 46 | case 331: 47 | EntitiesPointers.Pointers.Add(new DxfPointer(DxfCommonConverters.HandleString(pair.StringValue))); 48 | isReadyForSortHandles = true; 49 | break; 50 | default: 51 | if (!base.TrySetPair(pair)) 52 | { 53 | InternalExcessCodePairs.Add(pair); 54 | } 55 | break; 56 | } 57 | 58 | buffer.Advance(); 59 | } 60 | 61 | return PostParse(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfImage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using IxMilia.Dxf.Objects; 5 | 6 | namespace IxMilia.Dxf.Entities 7 | { 8 | public partial class DxfImage 9 | { 10 | public IList ClippingVertices { get; } = new List(); 11 | 12 | /// The path to the image. 13 | /// The bottom left corner of the location to display the image in the drawing. 14 | /// The width of the image in pixels. 15 | /// The height of the image in pixels. 16 | /// The display size of the image in drawing units. 17 | public DxfImage(string imagePath, DxfPoint location, int imageWidth, int imageHeight, DxfVector displaySize) 18 | : this() 19 | { 20 | ImageDefinition = new DxfImageDefinition() 21 | { 22 | FilePath = imagePath, 23 | ImageWidth = imageWidth, 24 | ImageHeight = imageHeight 25 | }; 26 | ImageSize = new DxfVector(imageWidth, imageHeight, 0.0); 27 | Location = location; 28 | UVector = new DxfVector(displaySize.X / imageWidth, 0.0, 0.0); 29 | VVector = new DxfVector(0.0, displaySize.Y / imageHeight, 0.0); 30 | } 31 | 32 | internal override void AddObjectsToOutput(List objects) 33 | { 34 | if (ImageDefinition is not null) 35 | { 36 | objects.Add(ImageDefinition); 37 | } 38 | 39 | if (ImageDefinitionReactor is not null) 40 | { 41 | objects.Add(ImageDefinitionReactor); 42 | } 43 | } 44 | 45 | protected override DxfEntity PostParse() 46 | { 47 | Debug.Assert((ClippingVertexCount == _clippingVerticesX.Count) && (ClippingVertexCount == _clippingVerticesY.Count)); 48 | for (var i = 0; i < this.ClippingVertexCount; i++) 49 | { 50 | var x = this._clippingVerticesX[i]; 51 | var y = this._clippingVerticesY[i]; 52 | this.ClippingVertices.Add(new DxfPoint(x, y, 0.0)); 53 | } 54 | 55 | _clippingVerticesX.Clear(); 56 | _clippingVerticesY.Clear(); 57 | return this; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/EntityExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using IxMilia.Dxf.Entities; 2 | using Xunit; 3 | 4 | namespace IxMilia.Dxf.Test 5 | { 6 | public class EntityExtensionTests 7 | { 8 | [Fact] 9 | public void ShouldContainStartAndEndAngle() 10 | { 11 | var radius = 1.0; // negative radius only for DxfVertex.Bulge, but not for arcs (?) 12 | for (var start = 0; start <= 360; start += 10) 13 | { 14 | for (var end = 0; end <= 360; end += 10) 15 | { 16 | var arc = new DxfArc(DxfPoint.Origin, radius, start, end); 17 | Assert.True(arc.ContainsAngle(start), $"{start}°=>{end}° arc contains {start}°"); 18 | Assert.True(arc.ContainsAngle(end), $"{start}°=>{end}° arc contains {end}°"); 19 | } 20 | } 21 | } 22 | 23 | [Theory] 24 | // simple 90° arcs 25 | [InlineData(0, 90, 20)] 26 | [InlineData(90, 180, 110)] 27 | [InlineData(180, 270, 200)] 28 | [InlineData(270, 360, 290)] 29 | // large outer 270° arcs 30 | [InlineData(90, 0, 0)] 31 | [InlineData(90, 0, 95)] 32 | [InlineData(90, 0, 355)] 33 | [InlineData(90, 0, 360)] 34 | [InlineData(180, 90, 190)] 35 | [InlineData(180, 90, 355)] // before boundary 36 | [InlineData(180, 90, 5)] // after boundary 37 | [InlineData(180, 90, 85)] 38 | public void ShouldContainAngle(double startAngle, double endAngle, double angle) 39 | { 40 | var radius = 1.0; // negative radius only for DxfVertex.Bulge, but not for arcs (?) 41 | var arc = new DxfArc(DxfPoint.Origin, radius, startAngle, endAngle); 42 | Assert.True(arc.ContainsAngle(angle)); 43 | } 44 | 45 | [Theory] 46 | // simple 90° arcs 47 | [InlineData(0, 90, 359.9)] 48 | [InlineData(90, 180, 0)] 49 | [InlineData(180, 270, 360)] 50 | [InlineData(270, 360, 80)] 51 | // large outer 270° arcs cross boundary 52 | [InlineData(90, 0, 45)] 53 | [InlineData(180, 90, 91)] 54 | [InlineData(180, 90, 179)] 55 | public void ShouldNotContainAngle(double startAngle, double endAngle, double angle) 56 | { 57 | var radius = 1.0; 58 | var arc = new DxfArc(DxfPoint.Origin, radius, startAngle, endAngle); 59 | Assert.False(arc.ContainsAngle(angle)); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfVertex.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IxMilia.Dxf.Entities 4 | { 5 | public partial class DxfVertex 6 | { 7 | protected override void AddValuePairs(List pairs, DxfAcadVersion version, bool outputHandles, bool writeXData) 8 | { 9 | base.AddValuePairs(pairs, version, outputHandles, writeXData: false); 10 | var subclassMarker = Is3DPolylineVertex || Is3DPolygonMesh ? "AcDb3dPolylineVertex" : "AcDb2dVertex"; 11 | pairs.Add(new DxfCodePair(100, "AcDbVertex")); 12 | pairs.Add(new DxfCodePair(100, subclassMarker)); 13 | pairs.Add(new DxfCodePair(10, Location.X)); 14 | pairs.Add(new DxfCodePair(20, Location.Y)); 15 | pairs.Add(new DxfCodePair(30, Location.Z)); 16 | if (StartingWidth != 0.0) 17 | { 18 | pairs.Add(new DxfCodePair(40, StartingWidth)); 19 | } 20 | 21 | if (EndingWidth != 0.0) 22 | { 23 | pairs.Add(new DxfCodePair(41, EndingWidth)); 24 | } 25 | 26 | if (Bulge != 0.0) 27 | { 28 | pairs.Add(new DxfCodePair(42, Bulge)); 29 | } 30 | 31 | pairs.Add(new DxfCodePair(70, (short)Flags)); 32 | pairs.Add(new DxfCodePair(50, CurveFitTangentDirection)); 33 | if (version >= DxfAcadVersion.R13) 34 | { 35 | if (PolyfaceMeshVertexIndex1 != 0) 36 | { 37 | pairs.Add(new DxfCodePair(71, (short)PolyfaceMeshVertexIndex1)); 38 | } 39 | 40 | if (PolyfaceMeshVertexIndex2 != 0) 41 | { 42 | pairs.Add(new DxfCodePair(72, (short)PolyfaceMeshVertexIndex2)); 43 | } 44 | 45 | if (PolyfaceMeshVertexIndex3 != 0) 46 | { 47 | pairs.Add(new DxfCodePair(73, (short)PolyfaceMeshVertexIndex3)); 48 | } 49 | 50 | if (PolyfaceMeshVertexIndex4 != 0) 51 | { 52 | pairs.Add(new DxfCodePair(74, (short)PolyfaceMeshVertexIndex4)); 53 | } 54 | } 55 | 56 | if (version >= DxfAcadVersion.R2010 && Identifier != 0) 57 | { 58 | pairs.Add(new DxfCodePair(91, Identifier)); 59 | } 60 | 61 | if (writeXData) 62 | { 63 | DxfXData.AddValuePairs(XData, pairs, version, outputHandles); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Integration.Test/AutoCadExistsFactAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Xunit; 4 | 5 | namespace IxMilia.Dxf.Integration.Test 6 | { 7 | public class AutoCadExistsFactAttribute : FactAttribute 8 | { 9 | private const string _converterExe = "accoreconsole.exe"; 10 | private static bool _pathResolved = false; 11 | private static string? _converterPath = null; 12 | 13 | public AutoCadExistsFactAttribute() 14 | { 15 | if (GetPathToAutoCad(throwOnError: false) == null) 16 | { 17 | Skip = $"Unable to locate '{_converterExe}', test will be skipped"; 18 | } 19 | } 20 | 21 | public static string? GetPathToAutoCad() 22 | { 23 | return GetPathToAutoCad(throwOnError: true); 24 | } 25 | 26 | private static string? GetPathToAutoCad(bool throwOnError) 27 | { 28 | if (!_pathResolved) 29 | { 30 | _pathResolved = true; 31 | var programFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)") ?? 32 | Environment.GetEnvironmentVariable("ProgramFiles"); 33 | if (programFiles?.EndsWith(" (x86)") == true) 34 | { 35 | // hack when running in VS 36 | programFiles = programFiles.Substring(0, programFiles.Length - 6); 37 | } 38 | 39 | if (programFiles is not null) 40 | { 41 | var autodesk = Path.Combine(programFiles, "Autodesk"); 42 | if (Directory.Exists(autodesk)) 43 | { 44 | foreach (var candidateDir in Directory.EnumerateDirectories(autodesk, "AutoCAD*")) 45 | { 46 | var candidatePath = Path.Combine(candidateDir, _converterExe); 47 | if (File.Exists(candidatePath)) 48 | { 49 | _converterPath = candidatePath; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | if (_converterPath == null && throwOnError) 57 | { 58 | throw new Exception($"Unable to locate '{_converterExe}'. Please mark this test with the {nameof(AutoCadExistsFactAttribute)} attribute."); 59 | } 60 | else 61 | { 62 | return _converterPath; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfDimensionBase.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf.Entities 2 | { 3 | public partial class DxfDimensionBase : DxfEntity 4 | { 5 | public bool IsBlockReferenceReferencedByThisBlockOnly { get; set; } = false; 6 | 7 | public bool IsOrdinateXType { get; set; } = false; 8 | 9 | public bool IsAtUserDefinedLocation { get; set; } = false; 10 | 11 | protected override void CopyManualValues(DxfEntity other) 12 | { 13 | if (other is DxfDimensionBase otherDim) 14 | { 15 | IsBlockReferenceReferencedByThisBlockOnly = otherDim.IsBlockReferenceReferencedByThisBlockOnly; 16 | IsOrdinateXType = otherDim.IsOrdinateXType; 17 | IsAtUserDefinedLocation = otherDim.IsAtUserDefinedLocation; 18 | } 19 | } 20 | 21 | private DxfDimensionType HandleDimensionType(short value) 22 | { 23 | // reader 24 | IsBlockReferenceReferencedByThisBlockOnly = (value & 32) == 32; 25 | IsOrdinateXType = (value & 64) == 64; 26 | IsAtUserDefinedLocation = (value & 128) == 128; 27 | return (DxfDimensionType)(value & 0x0F); // only the lower 4 bits matter 28 | } 29 | 30 | private short HandleDimensionType(DxfDimensionType dimensionType) 31 | { 32 | // writer 33 | var value = (short)dimensionType; 34 | if (IsBlockReferenceReferencedByThisBlockOnly) 35 | { 36 | value |= 32; 37 | } 38 | 39 | if (IsOrdinateXType) 40 | { 41 | value |= 64; 42 | } 43 | 44 | if (IsAtUserDefinedLocation) 45 | { 46 | value |= 128; 47 | } 48 | 49 | return value; 50 | } 51 | } 52 | 53 | public partial class DxfAlignedDimension 54 | { 55 | public bool IsBaselineAndContinue { get; set; } 56 | 57 | protected override void AppliedCodePair(DxfCodePair pair) 58 | { 59 | if (pair.Code == 12 || pair.Code == 22 || pair.Code == 32) 60 | { 61 | IsBaselineAndContinue = true; 62 | } 63 | } 64 | } 65 | 66 | public partial class DxfRotatedDimension 67 | { 68 | public bool IsBaselineAndContinue { get; set; } 69 | 70 | protected override void AppliedCodePair(DxfCodePair pair) 71 | { 72 | if (pair.Code == 12 || pair.Code == 22 || pair.Code == 32) 73 | { 74 | IsBaselineAndContinue = true; 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Integration.Test/ODAConverterExistsFactAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Xunit; 4 | 5 | namespace IxMilia.Dxf.Integration.Test 6 | { 7 | public class ODAConverterExistsFactAttribute : FactAttribute 8 | { 9 | private const string _converterExe = "ODAFileConverter.exe"; 10 | private static bool _pathResolved = false; 11 | private static string? _converterPath = null; 12 | 13 | public ODAConverterExistsFactAttribute() 14 | { 15 | if (GetPathToFileConverter(throwOnError: false) == null) 16 | { 17 | Skip = $"Unable to locate '{_converterExe}'. Please install from 'https://www.opendesign.com/guestfiles/oda_file_converter' to enable this test."; 18 | } 19 | } 20 | 21 | public static string? GetPathToFileConverter() 22 | { 23 | return GetPathToFileConverter(throwOnError: true); 24 | } 25 | 26 | private static string? GetPathToFileConverter(bool throwOnError) 27 | { 28 | if (!_pathResolved) 29 | { 30 | _pathResolved = true; 31 | var environmentVariables = new[] 32 | { 33 | "ProgramFiles(x86)", 34 | "ProgramFiles", 35 | "ProgramW6432", 36 | }; 37 | foreach (var environmentVariable in environmentVariables) 38 | { 39 | var programFiles = Environment.GetEnvironmentVariable(environmentVariable); 40 | if (programFiles is not null) 41 | { 42 | var oda = Path.Combine(programFiles, "ODA"); 43 | if (Directory.Exists(oda)) 44 | { 45 | foreach (var candidateDir in Directory.EnumerateDirectories(oda, "ODA*")) 46 | { 47 | var candidatePath = Path.Combine(candidateDir, _converterExe); 48 | if (File.Exists(candidatePath)) 49 | { 50 | _converterPath = candidatePath; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | if (_converterPath == null && throwOnError) 59 | { 60 | throw new Exception($"Unable to locate '{_converterExe}'. Please mark this test with the {nameof(ODAConverterExistsFactAttribute)} attribute."); 61 | } 62 | else 63 | { 64 | return _converterPath; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfGeoData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | 5 | namespace IxMilia.Dxf.Objects 6 | { 7 | public partial class DxfGeoData 8 | { 9 | public struct DxfGeoMeshPoint 10 | { 11 | public DxfPoint SourcePoint { get; set; } 12 | public DxfPoint DestinationPoint { get; set; } 13 | } 14 | 15 | public IList GeoMeshPoints { get; } = new List(); 16 | public IList FaceIndices { get; } = new List(); 17 | 18 | public IDxfItem HostBlock 19 | { 20 | get { return Owner; } 21 | set { ((IDxfItemInternal)this).SetOwner(value); } 22 | } 23 | 24 | protected override DxfObject PostParse() 25 | { 26 | // geo mesh points 27 | Debug.Assert(_geoMeshPointCount == _sourceMeshXPoints.Count); 28 | Debug.Assert(_geoMeshPointCount == _sourceMeshYPoints.Count); 29 | Debug.Assert(_geoMeshPointCount == _destinationMeshXPoints.Count); 30 | Debug.Assert(_geoMeshPointCount == _destinationMeshYPoints.Count); 31 | var limit = new[] { _geoMeshPointCount, _sourceMeshXPoints.Count, _sourceMeshYPoints.Count, _destinationMeshXPoints.Count, _destinationMeshYPoints.Count }.Min(); 32 | GeoMeshPoints.Clear(); 33 | for (int i = 0; i < limit; i++) 34 | { 35 | GeoMeshPoints.Add(new DxfGeoMeshPoint() 36 | { 37 | SourcePoint = new DxfPoint(_sourceMeshXPoints[i], _sourceMeshYPoints[i], 0.0), 38 | DestinationPoint = new DxfPoint(_destinationMeshXPoints[i], _destinationMeshYPoints[i], 0.0) 39 | }); 40 | } 41 | 42 | _sourceMeshXPoints.Clear(); 43 | _sourceMeshYPoints.Clear(); 44 | _destinationMeshXPoints.Clear(); 45 | _destinationMeshYPoints.Clear(); 46 | 47 | // face index points 48 | Debug.Assert(_facesCount == _facePointIndexX.Count); 49 | Debug.Assert(_facesCount == _facePointIndexY.Count); 50 | Debug.Assert(_facesCount == _facePointIndexZ.Count); 51 | limit = new[] { _facesCount, _facePointIndexX.Count, _facePointIndexY.Count, _facePointIndexZ.Count }.Min(); 52 | FaceIndices.Clear(); 53 | for (int i = 0; i < limit; i++) 54 | { 55 | FaceIndices.Add(new DxfPoint(_facePointIndexX[i], _facePointIndexY[i], _facePointIndexZ[i])); 56 | } 57 | 58 | _facePointIndexX.Clear(); 59 | _facePointIndexY.Clear(); 60 | _facePointIndexZ.Clear(); 61 | 62 | return this; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfInsert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using IxMilia.Dxf.Collections; 5 | 6 | namespace IxMilia.Dxf.Entities 7 | { 8 | public partial class DxfInsert : IDxfItemInternal 9 | { 10 | private DxfPointerList _attributes = new DxfPointerList(); 11 | private DxfPointer _seqendPointer = new DxfPointer(new DxfSeqend()); 12 | internal Func>? GetEntities { get; set; } 13 | 14 | public IEnumerable Entities => GetEntities?.Invoke() ?? []; 15 | 16 | public IList Attributes { get { return _attributes; } } 17 | 18 | public DxfSeqend? Seqend 19 | { 20 | get { return _seqendPointer.Item as DxfSeqend; } 21 | set { _seqendPointer.Item = value; } 22 | } 23 | 24 | IEnumerable IDxfItemInternal.GetPointers() 25 | { 26 | foreach (var att in _attributes.Pointers) 27 | { 28 | yield return att; 29 | } 30 | 31 | yield return _seqendPointer; 32 | } 33 | 34 | protected override void AddTrailingCodePairs(List pairs, DxfAcadVersion version, bool outputHandles) 35 | { 36 | foreach (var attribute in Attributes) 37 | { 38 | pairs.AddRange(attribute.GetValuePairs(version, outputHandles)); 39 | } 40 | 41 | if (Attributes.Any() && Seqend != null) 42 | { 43 | pairs.AddRange(Seqend.GetValuePairs(version, outputHandles)); 44 | } 45 | } 46 | 47 | protected override IEnumerable GetExtentsPoints() 48 | { 49 | if (GetEntities != null) 50 | { 51 | var boundingBoxes = GetEntities().Select(e => e.GetBoundingBox()).Where(bb => bb.HasValue).Select(bb => bb.GetValueOrDefault()).ToList(); 52 | if (boundingBoxes.Count == 0) 53 | { 54 | yield break; 55 | } 56 | 57 | var boundingBox = DxfBoundingBox.FromEnumerable(boundingBoxes); 58 | var minX = boundingBox.MinimumPoint.X * XScaleFactor + Location.X; 59 | var minY = boundingBox.MinimumPoint.Y * YScaleFactor + Location.Y; 60 | var minZ = boundingBox.MinimumPoint.Z * ZScaleFactor + Location.Z; 61 | var maxX = boundingBox.MaximumPoint.X * XScaleFactor + Location.X; 62 | var maxY = boundingBox.MaximumPoint.Y * YScaleFactor + Location.Y; 63 | var maxZ = boundingBox.MaximumPoint.Z * ZScaleFactor + Location.Z; 64 | var minP = new DxfPoint(minX, minY, minZ); 65 | var maxP = new DxfPoint(maxX, maxY, maxZ); 66 | yield return minP; 67 | yield return maxP; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf 4 | { 5 | public struct DxfPoint : IEquatable 6 | { 7 | public double X { get; } 8 | public double Y { get; } 9 | public double Z { get; } 10 | 11 | public DxfPoint(double x, double y, double z) 12 | : this() 13 | { 14 | this.X = x; 15 | this.Y = y; 16 | this.Z = z; 17 | } 18 | 19 | public static implicit operator DxfVector(DxfPoint point) 20 | { 21 | return new DxfVector(point.X, point.Y, point.Z); 22 | } 23 | 24 | public static bool operator ==(DxfPoint p1, DxfPoint p2) 25 | { 26 | return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z; 27 | } 28 | 29 | public static bool operator !=(DxfPoint p1, DxfPoint p2) 30 | { 31 | return !(p1 == p2); 32 | } 33 | 34 | public static DxfPoint operator +(DxfPoint p1, DxfVector p2) 35 | { 36 | return new DxfPoint(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z); 37 | } 38 | 39 | public static DxfVector operator -(DxfPoint p1, DxfVector p2) 40 | { 41 | return new DxfVector(p1.X - p2.X, p1.Y - p2.Y, p1.Z - p2.Z); 42 | } 43 | 44 | public static DxfPoint operator *(DxfPoint p, double scalar) 45 | { 46 | return new DxfPoint(p.X * scalar, p.Y * scalar, p.Z * scalar); 47 | } 48 | 49 | public static DxfPoint operator /(DxfPoint p, double scalar) 50 | { 51 | return new DxfPoint(p.X / scalar, p.Y / scalar, p.Z / scalar); 52 | } 53 | 54 | public override bool Equals(object? obj) 55 | { 56 | return obj is DxfPoint && this == (DxfPoint)obj; 57 | } 58 | 59 | public bool Equals(DxfPoint other) 60 | { 61 | return this == other; 62 | } 63 | 64 | public override int GetHashCode() 65 | { 66 | return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); 67 | } 68 | 69 | public override string ToString() 70 | { 71 | return $"({DxfWriter.DoubleAsString(X)},{DxfWriter.DoubleAsString(Y)},{DxfWriter.DoubleAsString(Z)})"; 72 | } 73 | 74 | public static DxfPoint Origin 75 | { 76 | get { return new DxfPoint(0, 0, 0); } 77 | } 78 | 79 | // the following methods are only used to allow setting individual x/y/z values in the auto-generated readers 80 | 81 | internal DxfPoint WithUpdatedX(double x) 82 | { 83 | return new DxfPoint(x, this.Y, this.Z); 84 | } 85 | 86 | internal DxfPoint WithUpdatedY(double y) 87 | { 88 | return new DxfPoint(this.X, y, this.Z); 89 | } 90 | 91 | internal DxfPoint WithUpdatedZ(double z) 92 | { 93 | return new DxfPoint(this.X, this.Y, z); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfXRecordObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using IxMilia.Dxf.Collections; 4 | 5 | namespace IxMilia.Dxf.Objects 6 | { 7 | public partial class DxfXRecordObject 8 | { 9 | public IList DataPairs { get; } = new ListNonNull(); 10 | 11 | protected override void AddValuePairs(List pairs, DxfAcadVersion version, bool outputHandles, bool writeXData) 12 | { 13 | base.AddValuePairs(pairs, version, outputHandles, writeXData: false); 14 | pairs.Add(new DxfCodePair(100, "AcDbXrecord")); 15 | pairs.Add(new DxfCodePair(280, (short)(this.DuplicateRecordHandling))); 16 | pairs.AddRange(DataPairs); 17 | 18 | if (writeXData) 19 | { 20 | DxfXData.AddValuePairs(XData, pairs, version, outputHandles); 21 | } 22 | } 23 | 24 | internal override DxfObject PopulateFromBuffer(DxfCodePairBufferReader buffer) 25 | { 26 | bool readingData = false; 27 | while (buffer.ItemsRemain) 28 | { 29 | var pair = buffer.Peek(); 30 | if (pair.Code == 0) 31 | { 32 | break; 33 | } 34 | 35 | if (readingData) 36 | { 37 | DataPairs.Add(pair); 38 | } 39 | else 40 | { 41 | if (pair.Code == 280) 42 | { 43 | DuplicateRecordHandling = (DxfDictionaryDuplicateRecordHandling)pair.ShortValue; 44 | buffer.Advance(); 45 | readingData = true; 46 | continue; 47 | } 48 | 49 | if (base.TrySetPair(pair)) 50 | { 51 | buffer.Advance(); 52 | continue; 53 | } 54 | 55 | if (this.TrySetExtensionData(pair, buffer)) 56 | { 57 | continue; 58 | } 59 | 60 | if (pair.Code == 100) 61 | { 62 | Debug.Assert(pair.StringValue == "AcDbXrecord"); 63 | buffer.Advance(); 64 | continue; 65 | } 66 | else if (pair.Code == 5 || pair.Code == 105) 67 | { 68 | // these codes aren't allowed here 69 | InternalExcessCodePairs.Add(pair); 70 | } 71 | else 72 | { 73 | DataPairs.Add(pair); 74 | readingData = true; 75 | } 76 | } 77 | 78 | buffer.Advance(); 79 | } 80 | 81 | return PostParse(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace IxMilia.Dxf.Objects 6 | { 7 | public partial class DxfField 8 | { 9 | private byte[] BinaryData { get; set; } = Array.Empty(); 10 | 11 | public string FormatString { get; set; } = string.Empty; 12 | 13 | public object? Value 14 | { 15 | get 16 | { 17 | switch (_valueTypeCode) 18 | { 19 | case 91: 20 | return _longValue; 21 | case 140: 22 | return _doubleValue; 23 | case 330: 24 | return _idValue; 25 | case 310: 26 | return BinaryData; 27 | default: 28 | return null; 29 | } 30 | } 31 | set 32 | { 33 | if (value == null) 34 | { 35 | _binaryData = new List(); 36 | _valueTypeCode = 310; 37 | } 38 | else if (value.GetType() == typeof(int)) 39 | { 40 | _longValue = (int)value; 41 | _valueTypeCode = 91; 42 | } 43 | else if (value.GetType() == typeof(double)) 44 | { 45 | _doubleValue = (double)value; 46 | _valueTypeCode = 140; 47 | } 48 | else if (value.GetType() == typeof(DxfHandle)) 49 | { 50 | _idValue = (DxfHandle)value; 51 | _valueTypeCode = 330; 52 | } 53 | else if (value.GetType() == typeof(byte[])) 54 | { 55 | BinaryData = (byte[])value; 56 | _valueTypeCode = 310; 57 | } 58 | else 59 | { 60 | Debug.Assert(false, "Unexpected field value."); 61 | } 62 | } 63 | } 64 | 65 | protected override DxfObject PostParse() 66 | { 67 | // code 90 is shared between `_childFieldCount` and `_valueTypeCode` so they're teased apart here 68 | if (_childFieldCount_valueTypeCode.Count == 2) 69 | { 70 | _childFieldCount = _childFieldCount_valueTypeCode[0]; 71 | _valueTypeCode = _childFieldCount_valueTypeCode[1]; 72 | _childFieldCount_valueTypeCode.Clear(); 73 | } 74 | 75 | // rebuild format string 76 | FormatString = _formatStringCode4 ?? (_formatStringCode301 + _formatStringOverflow); 77 | _formatStringOverflow = string.Empty; 78 | 79 | BinaryData = BinaryHelpers.CombineBytes(_binaryData); 80 | _binaryData.Clear(); 81 | 82 | return this; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Sections/DxfSectionType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf.Sections 4 | { 5 | internal enum DxfSectionType 6 | { 7 | None, 8 | Header, 9 | Classes, 10 | Tables, 11 | Blocks, 12 | Entities, 13 | Objects, 14 | Thumbnail 15 | } 16 | 17 | internal static class DxfSectionTypeHelper 18 | { 19 | public static string? ToSectionName(this DxfSectionType section) 20 | { 21 | string? name = null; 22 | switch (section) 23 | { 24 | case DxfSectionType.None: 25 | throw new NotImplementedException("Section type NONE not supported."); 26 | case DxfSectionType.Header: 27 | name = DxfSection.HeaderSectionText; 28 | break; 29 | case DxfSectionType.Classes: 30 | name = DxfSection.ClassesSectionText; 31 | break; 32 | case DxfSectionType.Tables: 33 | name = DxfSection.TablesSectionText; 34 | break; 35 | case DxfSectionType.Blocks: 36 | name = DxfSection.BlocksSectionText; 37 | break; 38 | case DxfSectionType.Entities: 39 | name = DxfSection.EntitiesSectionText; 40 | break; 41 | case DxfSectionType.Objects: 42 | name = DxfSection.ObjectsSectionText; 43 | break; 44 | case DxfSectionType.Thumbnail: 45 | name = DxfSection.ThumbnailImageSectionText; 46 | break; 47 | } 48 | 49 | return name; 50 | } 51 | 52 | public static DxfSectionType ToDxfSection(this string sectionName) 53 | { 54 | var sec = DxfSectionType.None; 55 | switch (sectionName.ToUpperInvariant()) 56 | { 57 | case DxfSection.HeaderSectionText: 58 | sec = DxfSectionType.Header; 59 | break; 60 | case DxfSection.ClassesSectionText: 61 | sec = DxfSectionType.Classes; 62 | break; 63 | case DxfSection.TablesSectionText: 64 | sec = DxfSectionType.Tables; 65 | break; 66 | case DxfSection.BlocksSectionText: 67 | sec = DxfSectionType.Blocks; 68 | break; 69 | case DxfSection.EntitiesSectionText: 70 | sec = DxfSectionType.Entities; 71 | break; 72 | case DxfSection.ObjectsSectionText: 73 | sec = DxfSectionType.Objects; 74 | break; 75 | case DxfSection.ThumbnailImageSectionText: 76 | sec = DxfSectionType.Thumbnail; 77 | break; 78 | } 79 | return sec; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfPlotSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf.Objects 4 | { 5 | public enum DxfPlotPaperUnits 6 | { 7 | Inches = 0, 8 | Millimeters = 1, 9 | Pixels = 2 10 | } 11 | 12 | public enum DxfPlotRotation 13 | { 14 | NoRotation = 0, 15 | CounterClockwise90Degrees = 1, 16 | UpsideDown = 2, 17 | Clockwise90Degrees = 3 18 | } 19 | 20 | public enum DxfPlotType 21 | { 22 | LastScreenDisplay = 0, 23 | DrawingExtents = 1, 24 | DrawingLimits = 2, 25 | SpecificView = 3, 26 | SpecificWindow = 4, 27 | LayoutInformation = 5 28 | } 29 | 30 | public enum DxfStandardScale 31 | { 32 | ScaledToFit = 0, 33 | Scale_1_128_inch_to_1_foot = 1, 34 | Scale_1_64_inch_to_1_foot = 2, 35 | Scale_1_32_inch_to_1_foot = 3, 36 | Scale_1_16_inch_to_1_foot = 4, 37 | Scale_3_32_inch_to_1_foot = 5, 38 | Scale_1_8_inch_to_1_foot = 6, 39 | Scale_3_16_inch_to_1_foot = 7, 40 | Scale_1_4_inch_to_1_foot = 8, 41 | Scale_3_8_inch_to_1_foot = 9, 42 | Scale_1_2_inch_to_1_foot = 10, 43 | Scale_3_4_inch_to_1_foot = 11, 44 | Scale_1_inch_to_1_foot = 12, 45 | Scale_3_inches_to_1_foot = 13, 46 | Scale_6_inches_to_1_foot = 14, 47 | Scale_1_foot_to_1_foot = 15, 48 | Scale_1_to_1 = 16, 49 | Scale_1_to_2 = 17, 50 | Scale_1_to_4 = 18, 51 | Scale_1_to_8 = 19, 52 | Scale_1_to_10 = 20, 53 | Scale_1_to_16 = 21, 54 | Scale_1_to_20 = 22, 55 | Scale_1_to_30 = 23, 56 | Scale_1_to_40 = 24, 57 | Scale_1_to_50 = 25, 58 | Scale_1_to_100 = 26, 59 | Scale_2_to_1 = 27, 60 | Scale_4_to_1 = 28, 61 | Scale_8_to_1 = 29, 62 | Scale_10_to_1 = 30, 63 | Scale_100_to_1 = 31, 64 | Scale_1000_to_1 = 32 65 | } 66 | 67 | public enum DxfShadePlotMode 68 | { 69 | AsDisplayed = 0, 70 | Wireframe = 1, 71 | Hidden = 2, 72 | Rendered = 3 73 | } 74 | 75 | public enum DxfShadePlotResolutionLevel 76 | { 77 | Draft = 0, 78 | Preview = 1, 79 | Normal = 2, 80 | Presentation = 3, 81 | Maximum = 4, 82 | Custom = 5 83 | } 84 | 85 | public partial class DxfPlotSettings 86 | { 87 | public virtual string PlotViewName 88 | { 89 | get => _plotViewName; 90 | set 91 | { 92 | if (string.IsNullOrEmpty(value)) 93 | { 94 | throw new InvalidOperationException($"{nameof(PlotViewName)} must be non-empty."); 95 | } 96 | 97 | _plotViewName = value; 98 | } 99 | } 100 | 101 | public DxfPlotSettings(string plotViewName) 102 | : this() 103 | { 104 | PlotViewName = plotViewName; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Collections/DxfPointerList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using IxMilia.Dxf.Extensions; 6 | 7 | namespace IxMilia.Dxf.Collections 8 | { 9 | internal class DxfPointerList : IList where TItem : class, IDxfItem 10 | { 11 | private List _items = new List(); 12 | 13 | public int MinimumCount { get; } 14 | 15 | public DxfPointerList() 16 | : this(0) 17 | { 18 | } 19 | 20 | public DxfPointerList(int minimum) 21 | { 22 | MinimumCount = minimum; 23 | } 24 | 25 | public void ValidateCount() 26 | { 27 | if (Count < MinimumCount) 28 | { 29 | throw new InvalidOperationException($"This collection must contain at least {MinimumCount} items at all times."); 30 | } 31 | } 32 | 33 | internal IList Pointers => _items; 34 | 35 | public TItem this[int index] 36 | { 37 | get { return (TItem)_items[index].Item!; } 38 | set { _items[index].Item = value; } 39 | } 40 | 41 | public int Count => _items.Count; 42 | 43 | public bool IsReadOnly => false; 44 | 45 | public void Add(TItem? item) => _items.Add(new DxfPointer(item)); 46 | 47 | public void Clear() 48 | { 49 | _items.Clear(); 50 | ValidateCount(); 51 | } 52 | 53 | public bool Contains(TItem? item) => GetItems().Contains(item); 54 | 55 | public void CopyTo(TItem[] array, int arrayIndex) => GetItems().ToList().CopyTo(array, arrayIndex); 56 | 57 | public IEnumerator GetEnumerator() => GetItems().GetEnumerator(); 58 | 59 | public int IndexOf(TItem? item) 60 | { 61 | for (int i = 0; i < _items.Count; i++) 62 | { 63 | if (item?.Equals(_items[i].Item) ?? false) 64 | { 65 | return i; 66 | } 67 | } 68 | 69 | return -1; 70 | } 71 | 72 | public void Insert(int index, TItem? item) => _items.Insert(index, new DxfPointer(item)); 73 | 74 | public bool Remove(TItem? item) 75 | { 76 | for (int i = 0; i < _items.Count; i++) 77 | { 78 | if (item?.Equals(_items[i].Item) ?? false) 79 | { 80 | _items.RemoveAt(i); 81 | ValidateCount(); 82 | return true; 83 | } 84 | } 85 | 86 | return false; 87 | } 88 | 89 | public void RemoveAt(int index) 90 | { 91 | _items.RemoveAt(index); 92 | ValidateCount(); 93 | } 94 | 95 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 96 | 97 | private IEnumerable GetItems() => _items.Select(i => i.Item as TItem).WhereNotNull(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfBoundingBox.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace IxMilia.Dxf 5 | { 6 | public struct DxfBoundingBox 7 | { 8 | public DxfPoint MinimumPoint { get; } 9 | public DxfVector Size { get; } 10 | 11 | public DxfPoint MaximumPoint => MinimumPoint + Size; 12 | 13 | public DxfBoundingBox(DxfPoint minimumPoint, DxfVector size) 14 | { 15 | MinimumPoint = minimumPoint; 16 | Size = size; 17 | } 18 | 19 | public DxfBoundingBox WithPoint(DxfPoint point) 20 | { 21 | var min = new DxfPoint(MinimumPoint.X, MinimumPoint.Y, MinimumPoint.Z); // copy the values so we don't accidentally update this 22 | var max = MaximumPoint; 23 | UpdateMinMax(ref min, ref max, point); 24 | return FromMinMax(min, max); 25 | } 26 | 27 | public DxfBoundingBox Combine(DxfBoundingBox other) 28 | { 29 | return FromPoints(new[] { MinimumPoint, MaximumPoint, other.MinimumPoint, other.MaximumPoint }).GetValueOrDefault(); 30 | } 31 | 32 | public static DxfBoundingBox FromMinMax(DxfPoint minimum, DxfPoint maximum) 33 | { 34 | var size = maximum - minimum; 35 | return new DxfBoundingBox(minimum, size); 36 | } 37 | 38 | public static DxfBoundingBox? FromPoints(IEnumerable points) 39 | { 40 | if (!points.Any()) 41 | { 42 | return null; 43 | } 44 | 45 | var cur = points.First(); 46 | var min = new DxfPoint(cur.X, cur.Y, cur.Z); // copy the values so we don't accidentally update the underlying value 47 | var max = new DxfPoint(cur.X, cur.Y, cur.Z); 48 | foreach (var point in points) 49 | { 50 | UpdateMinMax(ref min, ref max, point); 51 | } 52 | 53 | var size = max - min; 54 | return new DxfBoundingBox(min, size); 55 | } 56 | 57 | private static void UpdateMinMax(ref DxfPoint min, ref DxfPoint max, DxfPoint point) 58 | { 59 | // min 60 | if (point.X < min.X) 61 | { 62 | min = new DxfPoint(point.X, min.Y, min.Z); 63 | } 64 | if (point.Y < min.Y) 65 | { 66 | min = new DxfPoint(min.X, point.Y, min.Z); 67 | } 68 | if (point.Z < min.Z) 69 | { 70 | min = new DxfPoint(min.X, min.Y, point.Z); 71 | } 72 | 73 | // max 74 | if (point.X > max.X) 75 | { 76 | max = new DxfPoint(point.X, max.Y, max.Z); 77 | } 78 | if (point.Y > max.Y) 79 | { 80 | max = new DxfPoint(max.X, point.Y, max.Z); 81 | } 82 | if (point.Z > max.Z) 83 | { 84 | max = new DxfPoint(max.X, max.Y, point.Z); 85 | } 86 | } 87 | 88 | public static DxfBoundingBox FromEnumerable(IEnumerable boundingBoxes) 89 | { 90 | var result = boundingBoxes.Aggregate((a, b) => a.Combine(b)); 91 | return result; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Sections/DxfBlocksSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using IxMilia.Dxf.Blocks; 5 | using IxMilia.Dxf.Collections; 6 | using IxMilia.Dxf.Objects; 7 | 8 | namespace IxMilia.Dxf.Sections 9 | { 10 | internal class DxfBlocksSection : DxfSection 11 | { 12 | public IList Blocks { get; } 13 | 14 | public List AdditionalObjects { get; } 15 | 16 | public DxfBlocksSection() 17 | { 18 | Blocks = new ListNonNull(); 19 | AdditionalObjects = new List(); 20 | Normalize(); 21 | } 22 | 23 | internal void Normalize() 24 | { 25 | foreach (var name in new[] { "*MODEL_SPACE", "*PAPER_SPACE" }) 26 | { 27 | if (!Blocks.Any(b => string.Compare(b.Name, name, StringComparison.OrdinalIgnoreCase) == 0)) 28 | { 29 | Blocks.Add(new DxfBlock(name)); 30 | } 31 | } 32 | } 33 | 34 | public override DxfSectionType Type 35 | { 36 | get { return DxfSectionType.Blocks; } 37 | } 38 | 39 | protected internal override IEnumerable GetSpecificPairs(DxfAcadVersion version, bool outputHandles, HashSet writtenItems) 40 | { 41 | AdditionalObjects.Clear(); 42 | foreach (var block in Blocks.Where(b => b != null)) 43 | { 44 | if (writtenItems.Add(block)) 45 | { 46 | foreach (var pair in block.GetValuePairs(version, outputHandles)) 47 | { 48 | yield return pair; 49 | } 50 | 51 | foreach (var entity in block.Entities) 52 | { 53 | entity.AddObjectsToOutput(AdditionalObjects); 54 | } 55 | } 56 | } 57 | } 58 | 59 | protected internal override void Clear() 60 | { 61 | Blocks.Clear(); 62 | } 63 | 64 | internal static DxfBlocksSection BlocksSectionFromBuffer(DxfCodePairBufferReader buffer) 65 | { 66 | var section = new DxfBlocksSection(); 67 | section.Clear(); 68 | while (buffer.ItemsRemain) 69 | { 70 | var pair = buffer.Peek(); 71 | if (DxfCodePair.IsSectionStart(pair)) 72 | { 73 | // done reading blocks, onto the next section 74 | break; 75 | } 76 | else if (DxfCodePair.IsSectionEnd(pair)) 77 | { 78 | // done reading blocks 79 | buffer.Advance(); // swallow (0, ENDSEC) 80 | break; 81 | } 82 | 83 | if (pair.Code != 0) 84 | { 85 | throw new DxfReadException("Expected new block.", pair); 86 | } 87 | 88 | buffer.Advance(); // swallow (0, CLASS) 89 | var block = DxfBlock.FromBuffer(buffer); 90 | if (block != null) 91 | { 92 | section.Blocks.Add(block); 93 | } 94 | } 95 | 96 | return section; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfArc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace IxMilia.Dxf.Entities 5 | { 6 | public partial class DxfArc 7 | { 8 | public static bool TryCreateFromVertices(double x1, double y1, double bulge, double x2, double y2, [NotNullWhen(returnValue: true)] out DxfArc? arc) 9 | { 10 | // To the best of my knowledge, 3D Arcs are not defined via z but a normal vector. 11 | // Thus, simply ignore non-zero z values. 12 | 13 | if (Math.Abs(bulge) < 1e-10) 14 | { 15 | //throw new ArgumentException("arc bulge too small: " + bulge); 16 | arc = null; 17 | return false; 18 | } 19 | 20 | var deltaX = x2 - x1; 21 | var deltaY = y2 - y1; 22 | var deltaLength = Math.Sqrt(deltaX * deltaX + deltaY * deltaY); 23 | if (deltaLength < 1e-10) 24 | { 25 | //throw new ArgumentException("arc startpoint == endpoint"); 26 | arc = null; 27 | return false; 28 | } 29 | 30 | var alpha = 4.0 * Math.Atan(bulge); 31 | var radius = deltaLength / (2.0 * Math.Abs(Math.Sin(alpha * 0.5))); 32 | var deltaNormX = deltaX / deltaLength; 33 | var deltaNormY = deltaY / deltaLength; 34 | int bulgeSign = Math.Sign(bulge); 35 | 36 | // 2D only solution (z=0) 37 | var normalX = -deltaNormY * bulgeSign; 38 | var normalY = +deltaNormX * bulgeSign; 39 | 40 | var centerX = (x1 + x2) * 0.5 + normalX * Math.Cos(alpha * 0.5) * radius; 41 | var centerY = (y1 + y2) * 0.5 + normalY * Math.Cos(alpha * 0.5) * radius; 42 | 43 | // bulge<0 indicates CW arc, but DxfArc is CCW always 44 | double startAngleDeg; 45 | double endAngleDeg; 46 | if (bulge > 0) 47 | { 48 | startAngleDeg = NormalizedAngleDegree(x1, y1, centerX, centerY); 49 | endAngleDeg = NormalizedAngleDegree(x2, y2, centerX, centerY); 50 | } 51 | else 52 | { 53 | endAngleDeg = NormalizedAngleDegree(x1, y1, centerX, centerY); 54 | startAngleDeg = NormalizedAngleDegree(x2, y2, centerX, centerY); 55 | } 56 | 57 | arc = new DxfArc(new DxfPoint(centerX, centerY, 0), radius, startAngleDeg, endAngleDeg); 58 | return true; 59 | } 60 | 61 | public static bool TryCreateFromVertices(DxfVertex v1, DxfVertex v2, [NotNullWhen(returnValue: true)] out DxfArc? arc) 62 | { 63 | return TryCreateFromVertices(v1.Location.X, v1.Location.Y, v1.Bulge, v2.Location.X, v2.Location.Y, out arc); 64 | } 65 | 66 | public static bool TryCreateFromVertices(DxfLwPolylineVertex v1, DxfLwPolylineVertex v2, [NotNullWhen(returnValue: true)] out DxfArc? arc) 67 | { 68 | return TryCreateFromVertices(v1.X, v1.Y, v1.Bulge, v2.X, v2.Y, out arc); 69 | } 70 | 71 | private static double NormalizedAngleDegree(double pX, double pY, double centerX, double centerY) 72 | { 73 | var dx = pX - centerX; 74 | var dy = pY - centerY; 75 | var angle = Math.Atan2(dy, dx); 76 | if (angle < 0) 77 | { 78 | angle += 2 * Math.PI; 79 | } 80 | 81 | return angle * 180 / Math.PI; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfLightList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IxMilia.Dxf.Entities; 3 | 4 | namespace IxMilia.Dxf.Objects 5 | { 6 | public partial class DxfLightList 7 | { 8 | internal override DxfObject PopulateFromBuffer(DxfCodePairBufferReader buffer) 9 | { 10 | bool readVersionNumber = false; 11 | while (buffer.ItemsRemain) 12 | { 13 | var pair = buffer.Peek(); 14 | if (pair.Code == 0) 15 | { 16 | break; 17 | } 18 | 19 | while (this.TrySetExtensionData(pair, buffer)) 20 | { 21 | pair = buffer.Peek(); 22 | } 23 | 24 | if (pair.Code == 0) 25 | { 26 | break; 27 | } 28 | 29 | switch (pair.Code) 30 | { 31 | case 5: 32 | if (readVersionNumber) 33 | { 34 | // pointer to a new light 35 | LightsPointers.Pointers.Add(new DxfPointer(DxfCommonConverters.HandleString(pair.StringValue))); 36 | } 37 | else 38 | { 39 | // might still be the handle 40 | if (!base.TrySetPair(pair)) 41 | { 42 | InternalExcessCodePairs.Add(pair); 43 | } 44 | } 45 | break; 46 | case 1: 47 | // don't worry about the name; it'll be read from the light entity directly 48 | break; 49 | case 90: 50 | if (readVersionNumber) 51 | { 52 | // count of lights is ignored since it's implicitly set by reading the values 53 | } 54 | else 55 | { 56 | Version = pair.IntegerValue; 57 | readVersionNumber = true; 58 | } 59 | break; 60 | default: 61 | if (!base.TrySetPair(pair)) 62 | { 63 | InternalExcessCodePairs.Add(pair); 64 | } 65 | break; 66 | } 67 | 68 | buffer.Advance(); 69 | } 70 | 71 | return PostParse(); 72 | } 73 | 74 | protected override void AddValuePairs(List pairs, DxfAcadVersion version, bool outputHandles, bool writeXData) 75 | { 76 | base.AddValuePairs(pairs, version, outputHandles, writeXData: false); 77 | pairs.Add(new DxfCodePair(100, "AcDbLightList")); 78 | pairs.Add(new DxfCodePair(90, (this.Version))); 79 | pairs.Add(new DxfCodePair(90, Lights.Count)); 80 | foreach (var item in LightsPointers.Pointers) 81 | { 82 | pairs.Add(new DxfCodePair(5, HandleString(item.Handle))); 83 | pairs.Add(new DxfCodePair(1, ((DxfLight)item.Item!).Name)); 84 | } 85 | 86 | if (writeXData) 87 | { 88 | DxfXData.AddValuePairs(XData, pairs, version, outputHandles); 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Entities/DxfEntityExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace IxMilia.Dxf.Entities 5 | { 6 | public static class DxfEntityExtensions 7 | { 8 | /// 9 | /// Gets a point corresponding to a specified angle of the given circle. 10 | /// 11 | /// The angle in degrees. 12 | public static DxfPoint GetPointFromAngle(this DxfCircle circle, double angle) 13 | { 14 | var sin = Math.Sin(angle * Math.PI / 180.0); 15 | var cos = Math.Cos(angle * Math.PI / 180.0); 16 | return new DxfPoint(cos, sin, 0.0) * circle.Radius + circle.Center; 17 | } 18 | 19 | public static bool ContainsAngle(this DxfArc arc, double angle) 20 | { 21 | // normalize all angles 22 | var start = NormalizeAngleDegree(arc.StartAngle); 23 | var end = NormalizeAngleDegree(arc.EndAngle); 24 | angle = NormalizeAngleDegree(angle); 25 | 26 | // simple case: CCW arc from small to large angle. 27 | if (start <= end) 28 | { 29 | return start <= angle && angle <= end; 30 | } 31 | 32 | // if end < start consider two areas 33 | return (start <= angle && angle < 360) // start ccw ... 360° 34 | || (0 <= angle && angle <= end); // overflow: 0° ... end 35 | } 36 | 37 | private static double NormalizeAngleDegree(double angle) 38 | { 39 | if (angle >= 360) 40 | { 41 | angle -= 360; 42 | } 43 | else if (angle < 0) 44 | { 45 | angle += 360; 46 | } 47 | return angle; 48 | } 49 | 50 | /// 51 | /// Gets a point corresponding to a specified angle of the given ellipse. 52 | /// 53 | /// The angle in radians. 54 | public static DxfPoint GetPointFromAngle(this DxfEllipse ellipse, double angle) 55 | { 56 | var sin = Math.Sin(angle); 57 | var cos = Math.Cos(angle); 58 | var majorAxisLength = ellipse.MajorAxis.Length; 59 | var minorAxisLength = majorAxisLength * ellipse.MinorAxisRatio; 60 | return new DxfPoint(cos * majorAxisLength, sin * minorAxisLength, 0.0) + ellipse.Center; 61 | } 62 | 63 | public static bool ContainsAngle(this DxfEllipse ellipse, double angle) 64 | { 65 | var start = ellipse.StartParameter; 66 | var end = ellipse.EndParameter; 67 | 68 | // normalize angles such that start is always less than end 69 | if (start > end) 70 | { 71 | // ellipses specify angles in radians 72 | start -= Math.PI * 2.0; 73 | } 74 | 75 | return start <= angle && end >= angle; 76 | } 77 | 78 | public static DxfVector MinorAxis(this DxfEllipse ellipse) 79 | { 80 | var minor = ellipse.Normal.Cross(ellipse.MajorAxis); 81 | var minorUnit = minor / minor.Length; 82 | return minorUnit * ellipse.MajorAxis.Length * ellipse.MinorAxisRatio; 83 | } 84 | 85 | public static bool AppearsClosed(this DxfSpline spline) 86 | { 87 | return spline.IsClosed || 88 | (spline.ControlPoints.Any() && spline.ControlPoints.First().Point == spline.ControlPoints.Last().Point); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfCodePairGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using IxMilia.Dxf.Collections; 5 | 6 | namespace IxMilia.Dxf 7 | { 8 | public interface IDxfCodePairOrGroup 9 | { 10 | bool IsCodePair { get; } 11 | } 12 | 13 | public class DxfCodePairGroup : IDxfCodePairOrGroup 14 | { 15 | internal const int GroupCodeNumber = 102; 16 | 17 | public string GroupName { get; set; } 18 | 19 | public IList Items { get; } 20 | 21 | public bool IsCodePair { get { return false; } } 22 | 23 | public DxfCodePairGroup(string groupName, IEnumerable items) 24 | { 25 | GroupName = groupName; 26 | Items = items == null ? (IList)(new ListNonNull()) : items.ToList(); 27 | } 28 | 29 | internal void AddValuePairs(List pairs, DxfAcadVersion version, bool outputHandles) 30 | { 31 | if (Items.Count == 0) 32 | { 33 | return; 34 | } 35 | 36 | pairs.Add(new DxfCodePair(GroupCodeNumber, "{" + GroupName)); 37 | foreach (var item in Items) 38 | { 39 | if (item.IsCodePair) 40 | { 41 | pairs.Add((DxfCodePair)item); 42 | } 43 | else 44 | { 45 | ((DxfCodePairGroup)item).AddValuePairs(pairs, version, outputHandles); 46 | } 47 | } 48 | 49 | pairs.Add(new DxfCodePair(GroupCodeNumber, "}")); 50 | } 51 | 52 | internal static DxfCodePairGroup FromBuffer(DxfCodePairBufferReader buffer, string groupName) 53 | { 54 | var items = new List(); 55 | while (buffer.ItemsRemain) 56 | { 57 | var pair = buffer.Peek(); 58 | if (pair.Code == 0) 59 | { 60 | Debug.Assert(false, "Unexpected end of section/file while parsing group."); 61 | break; 62 | } 63 | 64 | buffer.Advance(); 65 | if (pair.Code == GroupCodeNumber) 66 | { 67 | if (pair.StringValue == "}") 68 | { 69 | // end of group 70 | break; 71 | } 72 | else if (pair.StringValue.StartsWith("{")) 73 | { 74 | // nested group 75 | var subGroupName = GetGroupName(pair.StringValue); 76 | var subGroup = FromBuffer(buffer, subGroupName); 77 | items.Add(subGroup); 78 | } 79 | else 80 | { 81 | throw new DxfReadException("Unexpected code pair group text", pair); 82 | } 83 | } 84 | else 85 | { 86 | items.Add(pair); 87 | } 88 | } 89 | 90 | return new DxfCodePairGroup(groupName, items); 91 | } 92 | 93 | internal static string GetGroupName(string controlString) 94 | { 95 | Debug.Assert(controlString.StartsWith("{")); 96 | return controlString.Substring(1); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfLineWeight.cs: -------------------------------------------------------------------------------- 1 | namespace IxMilia.Dxf 2 | { 3 | public enum DxfLineWeightType : short 4 | { 5 | Standard = -3, 6 | ByLayer = -2, 7 | ByBlock = -1, 8 | Custom = 0 9 | } 10 | 11 | public class DxfLineWeight 12 | { 13 | /// 14 | /// A non-zero value indicates 1/100th of a mm. 15 | /// 16 | public short Value { get; set; } 17 | 18 | /// 19 | /// When the line weight type is , the property represents the width of the line in 1/100th mm increments. 20 | /// 21 | public DxfLineWeightType LineWeightType 22 | { 23 | get 24 | { 25 | switch (Value) 26 | { 27 | case -3: 28 | return DxfLineWeightType.Standard; 29 | case -2: 30 | return DxfLineWeightType.ByLayer; 31 | case -1: 32 | return DxfLineWeightType.ByBlock; 33 | default: 34 | return DxfLineWeightType.Custom; 35 | } 36 | } 37 | } 38 | 39 | public void SetStandard() 40 | { 41 | Value = (short)DxfLineWeightType.Standard; 42 | } 43 | 44 | public void SetByLayer() 45 | { 46 | Value = (short)DxfLineWeightType.ByLayer; 47 | } 48 | 49 | public void SetByBlock() 50 | { 51 | Value = (short)DxfLineWeightType.ByBlock; 52 | } 53 | 54 | public DxfLineWeight() 55 | : this((short)DxfLineWeightType.ByLayer) 56 | { 57 | } 58 | 59 | public static DxfLineWeight Standard => new DxfLineWeight((short)DxfLineWeightType.Standard); 60 | public static DxfLineWeight ByLayer => new DxfLineWeight((short)DxfLineWeightType.ByLayer); 61 | public static DxfLineWeight ByBlock => new DxfLineWeight((short)DxfLineWeightType.ByBlock); 62 | 63 | private DxfLineWeight(short value) 64 | { 65 | Value = value; 66 | } 67 | 68 | public static bool operator ==(DxfLineWeight? a, DxfLineWeight? b) 69 | { 70 | if (ReferenceEquals(a, b)) 71 | { 72 | return true; 73 | } 74 | 75 | if (a is null || b is null) 76 | { 77 | return false; 78 | } 79 | 80 | return a.Value == b.Value; 81 | } 82 | 83 | public static bool operator !=(DxfLineWeight? a, DxfLineWeight? b) 84 | { 85 | return !(a == b); 86 | } 87 | 88 | public override bool Equals(object? obj) 89 | { 90 | if (obj is DxfLineWeight) 91 | { 92 | return this == (DxfLineWeight)obj; 93 | } 94 | else 95 | { 96 | return false; 97 | } 98 | } 99 | 100 | public override int GetHashCode() 101 | { 102 | return Value.GetHashCode(); 103 | } 104 | 105 | internal static DxfLineWeight FromRawValue(short value) 106 | { 107 | return new DxfLineWeight(value); 108 | } 109 | 110 | internal static short GetRawValue(DxfLineWeight lineWeight) 111 | { 112 | return lineWeight?.Value ?? 0; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Examples/TestExamples.cs: -------------------------------------------------------------------------------- 1 | using IxMilia.Dxf; 2 | using IxMilia.Dxf.Blocks; 3 | using IxMilia.Dxf.Entities; 4 | using Xunit; 5 | 6 | namespace Examples 7 | { 8 | public class TestExamples 9 | { 10 | [Fact] 11 | public void InsertBlockR12() 12 | { 13 | var file = new DxfFile(); 14 | file.Header.Version = DxfAcadVersion.R12; // this example has only been tested on R12 drawings 15 | 16 | // create a block with a line from (0,0) to (1,1) 17 | var block = new DxfBlock("my-block"); 18 | block.Entities.Add(new DxfLine(new DxfPoint(0.0, 0.0, 0.0), new DxfPoint(1.0, 1.0, 0.0))); 19 | file.Blocks.Add(block); 20 | 21 | // insert a copy of the block at location (3,3); the result is a line from (3,3) to (4,4) 22 | var insert = new DxfInsert(); 23 | insert.Name = "my-block"; // this is the name of the block that we're going to insert 24 | insert.Location = new DxfPoint(3.0, 3.0, 0.0); 25 | file.Entities.Add(insert); 26 | 27 | file.SaveExample(); 28 | } 29 | 30 | [Fact] 31 | public void AddRadiusDimensionForACircle() 32 | { 33 | // this test has only been verified against LibreCAD 2.1.3 34 | var file = new DxfFile(); 35 | file.Header.Version = DxfAcadVersion.R12; 36 | 37 | // add a circle at (0,0) with radius 1 38 | var circle = new DxfCircle(new DxfPoint(0.0, 0.0, 0.0), 1.0); 39 | file.Entities.Add(circle); 40 | 41 | // add a radius dimension that corresponds to the circle 42 | var dim = new DxfRadialDimension(); 43 | dim.DefinitionPoint1 = circle.Center; 44 | dim.DefinitionPoint2 = circle.Center + new DxfVector(circle.Radius, 0.0, 0.0); 45 | file.Entities.Add(dim); 46 | 47 | file.SaveExample(); 48 | } 49 | 50 | [Fact] 51 | public void AddACustomLineType() 52 | { 53 | var file = new DxfFile(); 54 | file.Header.Version = DxfAcadVersion.R12; 55 | 56 | // create a custom line type and add it to the file 57 | // this line type will have a dash of length 0.5 followed by a gap of length 0.25 58 | var dashed = new DxfLineType("dashed"); 59 | dashed.Elements.Add(new DxfLineTypeElement() { DashDotSpaceLength = 0.5 }); 60 | dashed.Elements.Add(new DxfLineTypeElement() { DashDotSpaceLength = 0.25 }); 61 | file.LineTypes.Add(dashed); 62 | 63 | // now add a line using this line type 64 | var line = new DxfLine(new DxfPoint(0.0, 0.0, 0.0), new DxfPoint(10.0, 10.0, 0.0)); 65 | line.LineTypeName = dashed.Name; 66 | file.Entities.Add(line); 67 | 68 | file.SaveExample(); 69 | } 70 | 71 | [Fact] 72 | public void SpecifyFontForTextEntity() 73 | { 74 | var file = new DxfFile(); 75 | file.Header.Version = DxfAcadVersion.R14; 76 | 77 | // add a text style with a specific font 78 | var textStyle = new DxfStyle("MY_TEXT_STYLE"); // this name is what will assign the font to TEXT entities 79 | textStyle.PrimaryFontFileName = "Times New Roman"; 80 | file.Styles.Add(textStyle); 81 | 82 | // add a text entity with the appropriate style name 83 | var text = new DxfText(new DxfPoint(10, 10, 0), 2.0, "This is text."); 84 | text.TextStyleName = textStyle.Name; // either assign the name from the text style, or you can maually specify "MY_TEXT_STYLE" from above 85 | file.Entities.Add(text); 86 | 87 | file.SaveExample(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Collections/ListWithPredicates`1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace IxMilia.Dxf.Collections 6 | { 7 | internal class ListNonNull : ListWithPredicates 8 | { 9 | public ListNonNull() 10 | : base(item => item != null, 0) 11 | { 12 | } 13 | } 14 | 15 | internal class ListNonNullWithMinimum : ListWithPredicates 16 | { 17 | public ListNonNullWithMinimum(int minimum) 18 | : base(item => item != null, minimum, false) 19 | { 20 | } 21 | } 22 | 23 | internal class ListWithPredicates : IList 24 | { 25 | private List _items = new List(); 26 | public Func? ItemPredicate { get; } 27 | public int MinimumCount { get; } 28 | 29 | public ListWithPredicates(Func? itemPredicate, int minimumCount, params T[] initialItems) 30 | : this(itemPredicate, minimumCount, true, initialItems) 31 | { 32 | } 33 | 34 | public ListWithPredicates(Func? itemPredicate, int minimumCount, bool validateInitialCount, params T[] initialItems) 35 | { 36 | ItemPredicate = itemPredicate; 37 | MinimumCount = minimumCount; 38 | foreach (var item in initialItems) 39 | { 40 | Add(item); 41 | } 42 | 43 | if (validateInitialCount) 44 | { 45 | ValidateCount(); 46 | } 47 | } 48 | 49 | private void ValidatePredicate(T item) 50 | { 51 | if (ItemPredicate != null && !ItemPredicate(item)) 52 | { 53 | throw new InvalidOperationException("Item does not meet the criteria to be added to this collection."); 54 | } 55 | } 56 | 57 | public void ValidateCount() 58 | { 59 | if (Count < MinimumCount) 60 | { 61 | throw new InvalidOperationException($"This collection must contain at least {MinimumCount} items at all times."); 62 | } 63 | } 64 | 65 | public T this[int index] 66 | { 67 | get { return _items[index]; } 68 | set 69 | { 70 | ValidatePredicate(value); 71 | _items[index] = value; 72 | } 73 | } 74 | 75 | public int Count => _items.Count; 76 | public bool IsReadOnly => false; 77 | 78 | public void Add(T item) 79 | { 80 | ValidatePredicate(item); 81 | _items.Add(item); 82 | } 83 | 84 | public void Clear() 85 | { 86 | _items.Clear(); 87 | ValidateCount(); 88 | } 89 | 90 | public bool Contains(T item) => _items.Contains(item); 91 | public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex); 92 | public IEnumerator GetEnumerator() => _items.GetEnumerator(); 93 | public int IndexOf(T item) => _items.IndexOf(item); 94 | 95 | public void Insert(int index, T item) 96 | { 97 | ValidatePredicate(item); 98 | _items.Insert(index, item); 99 | } 100 | 101 | public bool Remove(T item) 102 | { 103 | var result = _items.Remove(item); 104 | ValidateCount(); 105 | return result; 106 | } 107 | 108 | public void RemoveAt(int index) 109 | { 110 | _items.RemoveAt(index); 111 | ValidateCount(); 112 | } 113 | 114 | IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_items).GetEnumerator(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfMLineStyle.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IxMilia.Dxf.Collections; 3 | 4 | namespace IxMilia.Dxf.Objects 5 | { 6 | public partial class DxfMLineStyle 7 | { 8 | public class DxfMLineStyleElement 9 | { 10 | public double Offset { get; set; } 11 | public DxfColor? Color { get; set; } 12 | public string? LineType { get; set; } 13 | } 14 | 15 | public IList Elements { get; } = new ListNonNull(); 16 | 17 | internal override DxfObject PopulateFromBuffer(DxfCodePairBufferReader buffer) 18 | { 19 | var readElementCount = false; 20 | var elementCount = 0; 21 | while (buffer.ItemsRemain) 22 | { 23 | var pair = buffer.Peek(); 24 | if (pair.Code == 0) 25 | { 26 | break; 27 | } 28 | 29 | while (this.TrySetExtensionData(pair, buffer)) 30 | { 31 | pair = buffer.Peek(); 32 | } 33 | 34 | if (pair.Code == 0) 35 | { 36 | break; 37 | } 38 | 39 | switch (pair.Code) 40 | { 41 | case 2: 42 | this.StyleName = (pair.StringValue); 43 | break; 44 | case 3: 45 | this.Description = (pair.StringValue); 46 | break; 47 | case 51: 48 | this.StartAngle = (pair.DoubleValue); 49 | break; 50 | case 52: 51 | this.EndAngle = (pair.DoubleValue); 52 | break; 53 | case 70: 54 | this._flags = (pair.ShortValue); 55 | break; 56 | case 71: 57 | elementCount = (pair.ShortValue); 58 | readElementCount = true; 59 | break; 60 | case 49: 61 | // found a new element value 62 | Elements.Add(new DxfMLineStyleElement() { Offset = pair.DoubleValue }); 63 | break; 64 | case 62: 65 | if (readElementCount) 66 | { 67 | // update the last element 68 | if (Elements.Count > 0) 69 | { 70 | Elements[Elements.Count - 1].Color = FromRawValue(pair.ShortValue); 71 | } 72 | } 73 | else 74 | { 75 | this.FillColor = FromRawValue(pair.ShortValue); 76 | } 77 | break; 78 | case 6: 79 | // update the last element 80 | if (Elements.Count > 0) 81 | { 82 | Elements[Elements.Count - 1].LineType = pair.StringValue; 83 | } 84 | break; 85 | default: 86 | if (!base.TrySetPair(pair)) 87 | { 88 | InternalExcessCodePairs.Add(pair); 89 | } 90 | break; 91 | } 92 | 93 | buffer.Advance(); 94 | } 95 | 96 | return this; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Collections/DictionaryWithPredicate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | namespace IxMilia.Dxf.Collections 7 | { 8 | internal class DictionaryWithPredicate : IDictionary where TKey : notnull 9 | { 10 | private Dictionary _dict = new Dictionary(); 11 | 12 | public Func ItemPredicate { get; } 13 | 14 | public DictionaryWithPredicate(Func itemPredicate) 15 | { 16 | if (itemPredicate == null) 17 | { 18 | throw new ArgumentNullException(nameof(itemPredicate)); 19 | } 20 | 21 | ItemPredicate = itemPredicate; 22 | } 23 | 24 | private void ValidatePredicate(TKey key, TValue value) 25 | { 26 | if (!ItemPredicate(key, value)) 27 | { 28 | throw new InvalidOperationException("Item does not meet the criteria to be added to this collection."); 29 | } 30 | } 31 | 32 | public TValue this[TKey key] 33 | { 34 | get => ((IDictionary)_dict)[key]; 35 | set 36 | { 37 | ValidatePredicate(key, value); 38 | ((IDictionary)_dict)[key] = value; 39 | } 40 | } 41 | 42 | public ICollection Keys => ((IDictionary)_dict).Keys; 43 | 44 | public ICollection Values => ((IDictionary)_dict).Values; 45 | 46 | public int Count => ((ICollection>)_dict).Count; 47 | 48 | public bool IsReadOnly => ((ICollection>)_dict).IsReadOnly; 49 | 50 | public void Add(TKey key, TValue value) 51 | { 52 | ValidatePredicate(key, value); 53 | ((IDictionary)_dict).Add(key, value); 54 | } 55 | 56 | public void Add(KeyValuePair item) 57 | { 58 | ValidatePredicate(item.Key, item.Value); 59 | ((ICollection>)_dict).Add(item); 60 | } 61 | 62 | public void Clear() 63 | { 64 | ((ICollection>)_dict).Clear(); 65 | } 66 | 67 | public bool Contains(KeyValuePair item) 68 | { 69 | return ((ICollection>)_dict).Contains(item); 70 | } 71 | 72 | public bool ContainsKey(TKey key) 73 | { 74 | return ((IDictionary)_dict).ContainsKey(key); 75 | } 76 | 77 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 78 | { 79 | ((ICollection>)_dict).CopyTo(array, arrayIndex); 80 | } 81 | 82 | public IEnumerator> GetEnumerator() 83 | { 84 | return ((IEnumerable>)_dict).GetEnumerator(); 85 | } 86 | 87 | public bool Remove(TKey key) 88 | { 89 | return ((IDictionary)_dict).Remove(key); 90 | } 91 | 92 | public bool Remove(KeyValuePair item) 93 | { 94 | return ((ICollection>)_dict).Remove(item); 95 | } 96 | 97 | public bool TryGetValue(TKey key, [NotNullWhen(returnValue: true)] out TValue? value) 98 | { 99 | return ((IDictionary)_dict).TryGetValue(key, out value!); 100 | } 101 | 102 | IEnumerator IEnumerable.GetEnumerator() 103 | { 104 | return ((IEnumerable)_dict).GetEnumerator(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /IxMilia.Dxf.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32001.329 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IxMilia.Dxf", "src\IxMilia.Dxf\IxMilia.Dxf.csproj", "{C067E97D-3B96-462A-B09D-33FAD60DE761}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IxMilia.Dxf.Test", "src\IxMilia.Dxf.Test\IxMilia.Dxf.Test.csproj", "{B98F9CB2-3E1E-4072-99FB-B93B1C7E1A19}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IxMilia.Dxf.Generator", "src\IxMilia.Dxf.Generator\IxMilia.Dxf.Generator.csproj", "{34D31EC8-883B-4C37-AF98-A4BD5D0E2D3B}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IxMilia.Dxf.ReferenceCollector", "src\IxMilia.Dxf.ReferenceCollector\IxMilia.Dxf.ReferenceCollector.csproj", "{8AA16B3F-4FA0-4132-846B-6640699FF12F}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{712CA821-79DE-43D7-8E44-BFFA7A4EFC2C}" 15 | ProjectSection(SolutionItems) = preProject 16 | build-and-test.cmd = build-and-test.cmd 17 | build-and-test.sh = build-and-test.sh 18 | README.md = README.md 19 | EndProjectSection 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IxMilia.Dxf.Integration.Test", "src\IxMilia.Dxf.Integration.Test\IxMilia.Dxf.Integration.Test.csproj", "{02FE5CB3-53AF-428F-8936-463112151FC6}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples", "src\Examples\Examples.csproj", "{1E0308A7-479B-42CA-9437-548677532A85}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {C067E97D-3B96-462A-B09D-33FAD60DE761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {C067E97D-3B96-462A-B09D-33FAD60DE761}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {C067E97D-3B96-462A-B09D-33FAD60DE761}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {C067E97D-3B96-462A-B09D-33FAD60DE761}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {B98F9CB2-3E1E-4072-99FB-B93B1C7E1A19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {B98F9CB2-3E1E-4072-99FB-B93B1C7E1A19}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {B98F9CB2-3E1E-4072-99FB-B93B1C7E1A19}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {B98F9CB2-3E1E-4072-99FB-B93B1C7E1A19}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {34D31EC8-883B-4C37-AF98-A4BD5D0E2D3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {34D31EC8-883B-4C37-AF98-A4BD5D0E2D3B}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {34D31EC8-883B-4C37-AF98-A4BD5D0E2D3B}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {34D31EC8-883B-4C37-AF98-A4BD5D0E2D3B}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {8AA16B3F-4FA0-4132-846B-6640699FF12F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {8AA16B3F-4FA0-4132-846B-6640699FF12F}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {8AA16B3F-4FA0-4132-846B-6640699FF12F}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {8AA16B3F-4FA0-4132-846B-6640699FF12F}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {02FE5CB3-53AF-428F-8936-463112151FC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {02FE5CB3-53AF-428F-8936-463112151FC6}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {02FE5CB3-53AF-428F-8936-463112151FC6}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {02FE5CB3-53AF-428F-8936-463112151FC6}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {1E0308A7-479B-42CA-9437-548677532A85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {1E0308A7-479B-42CA-9437-548677532A85}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {1E0308A7-479B-42CA-9437-548677532A85}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {1E0308A7-479B-42CA-9437-548677532A85}.Release|Any CPU.Build.0 = Release|Any CPU 55 | EndGlobalSection 56 | GlobalSection(SolutionProperties) = preSolution 57 | HideSolutionNode = FALSE 58 | EndGlobalSection 59 | GlobalSection(ExtensibilityGlobals) = postSolution 60 | SolutionGuid = {C7B8DA5D-38A0-4647-A3E8-76B5E7CBC849} 61 | EndGlobalSection 62 | EndGlobal 63 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/BoundingBoxTests.cs: -------------------------------------------------------------------------------- 1 | using IxMilia.Dxf.Blocks; 2 | using IxMilia.Dxf.Entities; 3 | using Xunit; 4 | 5 | namespace IxMilia.Dxf.Test 6 | { 7 | public class BoundingBoxTests 8 | { 9 | [Fact] 10 | public void ArcBoundingBox() 11 | { 12 | var radius = 1.0; 13 | var startAngle = 90; 14 | var endAngle = 180; 15 | 16 | var arc = new DxfArc(DxfPoint.Origin, radius, startAngle, endAngle); 17 | var bbNullable = arc.GetBoundingBox(); 18 | Assert.NotNull(bbNullable); 19 | var bb = bbNullable.Value; 20 | 21 | var expectedMin = new DxfPoint(-1, 0, 0); 22 | var expectedMax = new DxfPoint(0, 1, 0); 23 | Assert.Equal(expectedMin.X, bb.MinimumPoint.X, 10); 24 | Assert.Equal(expectedMin.Y, bb.MinimumPoint.Y, 10); 25 | Assert.Equal(expectedMin.X, bb.MinimumPoint.X, 10); 26 | Assert.Equal(expectedMax.Y, bb.MaximumPoint.Y, 10); 27 | } 28 | 29 | [Fact] 30 | public void UnclosedPolyLineVertexBulgeIsRespected() 31 | { 32 | // Data from https://github.com/ixmilia/dxf/issues/102 33 | var vertices = new[] 34 | { 35 | new DxfVertex(new DxfPoint(0, +0.5, 0)), 36 | new DxfVertex(new DxfPoint(-1.5, +0.5, 0)) {Bulge = 1}, 37 | new DxfVertex(new DxfPoint(-1.5, -0.5, 0)), 38 | new DxfVertex(new DxfPoint(0, -0.5, 0)) {Bulge = 1} 39 | }; 40 | var poly = new DxfPolyline(vertices) {IsClosed = false}; 41 | var expectedMin = new DxfPoint(-2.0, -0.5, 0); 42 | var expectedMax = new DxfPoint(0, +0.5, 0); 43 | var expectedSize = new DxfVector(2.0, 1.0, 0); 44 | 45 | var bbNullable = poly.GetBoundingBox(); 46 | Assert.NotNull(bbNullable); 47 | var bb = bbNullable.Value; 48 | Assert.Equal(expectedMin, bb.MinimumPoint); 49 | Assert.Equal(expectedMax, bb.MaximumPoint); 50 | Assert.Equal(expectedSize, bb.Size); 51 | } 52 | 53 | [Fact] 54 | public void ClosedPolyLineVertexBulgeIsRespected() 55 | { 56 | // Data from https://github.com/ixmilia/dxf/issues/102 57 | var vertices = new[] 58 | { 59 | new DxfVertex(new DxfPoint(0, +0.5, 0)), 60 | new DxfVertex(new DxfPoint(-1.5, +0.5, 0)) {Bulge = 1}, 61 | new DxfVertex(new DxfPoint(-1.5, -0.5, 0)), 62 | new DxfVertex(new DxfPoint(0, -0.5, 0)) {Bulge = 1} 63 | }; 64 | var poly = new DxfPolyline(vertices) {IsClosed = true}; 65 | var expectedMin = new DxfPoint(-2.0, -0.5, 0); 66 | var expectedMax = new DxfPoint(0.5, +0.5, 0); 67 | var expectedSize = new DxfVector(2.5, 1.0, 0); 68 | 69 | var bbNullable = poly.GetBoundingBox(); 70 | Assert.NotNull(bbNullable); 71 | var bb = bbNullable.Value; 72 | Assert.Equal(expectedMin, bb.MinimumPoint); 73 | Assert.Equal(expectedMax, bb.MaximumPoint); 74 | Assert.Equal(expectedSize, bb.Size); 75 | } 76 | 77 | [Fact] 78 | public void InsertBoundingBox() 79 | { 80 | var line = new DxfLine(new DxfPoint(1.0, 1.0, 0.0), new DxfPoint(2.0, 3.0, 0.0)); 81 | var offset = new DxfVector(2.0, 2.0, 0.0); 82 | 83 | var block = new DxfBlock("some-block"); 84 | block.Entities.Add(line); 85 | 86 | var insert = new DxfInsert(); 87 | insert.Name = "some-block"; 88 | insert.Location = offset; 89 | insert.XScaleFactor = 2.0; 90 | 91 | var file = new DxfFile(); 92 | file.Blocks.Add(block); 93 | file.Entities.Add(insert); 94 | 95 | var boundingBox = file.GetBoundingBox(); 96 | Assert.Equal(new DxfPoint(4.0, 3.0, 0.0), boundingBox.MinimumPoint); 97 | Assert.Equal(new DxfPoint(6.0, 5.0, 0.0), boundingBox.MaximumPoint); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/BlockTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using IxMilia.Dxf.Blocks; 3 | using IxMilia.Dxf.Entities; 4 | using IxMilia.Dxf.Objects; 5 | using Xunit; 6 | 7 | namespace IxMilia.Dxf.Test 8 | { 9 | public class BlockTests : AbstractDxfTests 10 | { 11 | // test is inspired by this page: https://ezdxf.readthedocs.io/en/stable/dxfinternals/block_management.html 12 | [Fact] 13 | public void FileLayoutWithBlockSavedAsR12() 14 | { 15 | var file = new DxfFile(); 16 | file.Header.Version = DxfAcadVersion.R12; 17 | 18 | var block = new DxfBlock("my-block"); 19 | block.Entities.Add(new DxfLine(new DxfPoint(0.0, 0.0, 0.0), new DxfPoint(1.0, 1.0, 0.0))); 20 | file.Blocks.Add(block); 21 | 22 | var insert = new DxfInsert(); 23 | insert.Name = "my-block"; 24 | insert.Location = new DxfPoint(3.0, 3.0, 0.0); 25 | file.Entities.Add(insert); 26 | 27 | var actualCodePairs = file.GetCodePairs().ToList(); 28 | AssertContains(actualCodePairs, 29 | (0, "BLOCK"), 30 | // no handle 31 | (8, "0"), 32 | (2, "my-block"), 33 | (70, 0), 34 | (10, 0.0), 35 | (20, 0.0), 36 | (30, 0.0), 37 | (3, "my-block"), 38 | (1, ""), 39 | (0, "LINE"), 40 | (5, "#") 41 | ); 42 | AssertContains(actualCodePairs, 43 | (0, "ENDBLK"), 44 | (5, "#"), // only endblk gets a handle 45 | (8, "0") 46 | ); 47 | AssertContains(actualCodePairs, 48 | (0, "INSERT"), 49 | (5, "#"), 50 | (8, "0"), 51 | (2, "my-block"), 52 | (10, 3.0), 53 | (20, 3.0), 54 | (30, 0.0), 55 | (0, "ENDSEC") // ensures nothing else was written 56 | ); 57 | } 58 | 59 | [Fact] 60 | public void PointerResolutionInAnImageInABlockWhenReading() 61 | { 62 | var drawing = Parse( 63 | // blocks 64 | (0, "SECTION"), 65 | (2, "BLOCKS"), 66 | (0, "BLOCK"), 67 | (0, "IMAGE"), 68 | (340, "FFFF0340"), // points to the IMAGEDEF below 69 | (360, "FFFF0360"), // points to the IMAGEDEF_REACTOR below 70 | (0, "ENDSEC"), 71 | // objects 72 | (0, "SECTION"), 73 | (2, "OBJECTS"), 74 | (0, "IMAGEDEF"), 75 | (5, "FFFF0340"), // from IMAGE.340 above 76 | (1, "image-def-file-path"), 77 | (0, "IMAGEDEF_REACTOR"), 78 | (5, "FFFF0360"), // from IMAGE.360 above 79 | (0, "ENDSEC"), 80 | (0, "EOF") 81 | ); 82 | var block = drawing.Blocks.Single(); 83 | var imageEntity = (DxfImage)block.Entities.Single(); 84 | var imageDef = imageEntity.ImageDefinition; 85 | Assert.NotNull(imageDef); 86 | Assert.Equal("image-def-file-path", imageDef.FilePath); 87 | } 88 | 89 | [Fact] 90 | public void PointerAssignmentInAnImageInABlockWhenWriting() 91 | { 92 | var file = new DxfFile(); 93 | var block = new DxfBlock("my-block"); 94 | var image = new DxfImage(); 95 | image.ImageDefinition = new DxfImageDefinition(); 96 | image.ImageDefinition.FilePath = "image-def-file-path"; 97 | block.Entities.Add(image); 98 | file.Blocks.Add(block); 99 | 100 | // don't care about the code pairs, but this forces handles to be assigned to pointers 101 | var _actualCodePairs = file.GetCodePairs().ToList(); 102 | 103 | Assert.NotEqual(0u, ((IDxfItemInternal)block).Handle.Value); 104 | Assert.NotEqual(0u, ((IDxfItemInternal)image.ImageDefinition).Handle.Value); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | ## 0.8.4 (13 June 2024) 5 | 6 | - Improve handling of empty boundaries in `HATCH` entities. 7 | - Add support for `DefinedHeight` (code 46) to `MTEXT` entities. 8 | - Support more varieties of `MENTALRAYRENDERSETTINGS` object. 9 | - Include additional entities in `BLOCK` objects. 10 | - Fix handling of front clipping plane in `SPATIAL_FILTER` objects. 11 | - Improve BOM detection and handling for text files. 12 | 13 | ## 0.8.3 (3 June 2022) 14 | 15 | - Improved handle resolution inside `BLOCK` objects. 16 | - Improve `POLYLINE`/`LWPOLYLINE` entity splitting. 17 | - Fix writing `MTEXT` entities for R14. 18 | 19 | ## 0.8.2 (10 January 2022) 20 | 21 | - Improve XDATA reading. 22 | - Improve `HATCH` writing. 23 | - Improve compatibility with R12 when writing `BLOCK` and `INSERT` entities. 24 | 25 | ## 0.8.1 (13 July 2021) 26 | 27 | - Expand object handles to 64 bits. 28 | - Improve `DIMENSION` entity writing; better interop with AutoCAD. 29 | - Fix writing `TEXT` alignment. 30 | - Allow XDATA after every entity and object. 31 | - Ensure all items have a valid handle after save. 32 | 33 | ## 0.8.0 (19 January 2021) 34 | 35 | - Only build `netstandard2.0` TFM. 36 | - Add bounding box for `LWPOLYLINE`. 37 | - Improve thumbnail detection. 38 | - Honor code page header setting. 39 | 40 | ## 0.7.5 (13 November 2020) 41 | 42 | - Improve table handling. 43 | - Enable XData for all entities and objects. 44 | - Associate `INSERT` entities by block name. 45 | - Improve handling of binary data. 46 | 47 | ## 0.7.4 (26 June 2020) 48 | 49 | - Relicense as MIT. 50 | - Basic support for `HATCH` entities. 51 | - Improve `DIMENSION` support. 52 | - Allow reading incomplete `LEADER` points. 53 | 54 | ## 0.7.3 (13 February 2020) 55 | 56 | - Fix bug in writing spline weights. 57 | - Fix ARC bounding box calculation. 58 | - Numerous compatibility updates including various encoding and unicode support. 59 | - Support both pre- and post-R13 binary files. 60 | 61 | ## 0.7.2 (23 October 2018) 62 | 63 | - Enable reading files with a long `$ACADMAINTVER` value. 64 | - Add direct file system support to the platforms that allow it. 65 | 66 | ## 0.7.1 (16 October 2018) 67 | 68 | - Enable writing entities with embedded objects. 69 | - Improve writing `VERTEX` and `POLYLINE` entities. 70 | - Add `net35` framework support (thanks to @nzain). 71 | 72 | ## 0.7 (29 January 2018) 73 | 74 | - Embed commit hash in produced binaries. 75 | - Convert `DxfPoint` and `DxfVector` to structs. 76 | - Add simple bounding box computations to file and entities. 77 | 78 | ## 0.6 (11 December 2017) 79 | 80 | - Improve `SEQEND` creation. 81 | - Improve handle assignment on file write. 82 | - Improve reading XDATA code pairs. 83 | - Fix issue with object minimum versions. 84 | - Add support for R2018 (AC1032) files. 85 | 86 | ## 0.5.1 (26 July 2017) 87 | 88 | - Improve reading XData points. 89 | 90 | ## 0.5 (28 April 2017) 91 | 92 | - Doc comment support 93 | - Enforce collection restrictions: 94 | - Minimum number of children (e.g., `POLYLINE` vertices). 95 | - Disallow `null`. 96 | - etc. 97 | - Directly get/set the active view port from the drawing. 98 | - Ignore case on block and table item names. 99 | - Improved `LTYPE` support. 100 | - Convert to .NET Core, including cross-platform. 101 | 102 | ## 0.4 (6 July 2016) 103 | 104 | - Make file contents more closely match those produced by AutoCAD. 105 | - Proper support for item handles, including binding. 106 | - Include support for pre-R9 versions. 107 | - Improved XRECORD handling. 108 | 109 | ## 0.3.1 (25 November 2015) 110 | 111 | - Better handle layer colors. 112 | 113 | ## 0.3 (6 November 2015) 114 | 115 | - Added support for binary DXB files. 116 | - Improved back compat with R10-R13. 117 | - Non-ASCII control characters in strings. 118 | - XDATA. 119 | - Culture invariant. 120 | - Objects. 121 | - More entities: 122 | - `HELIX` 123 | - `LIGHT` 124 | - `MLEADER` 125 | - `MLINE` 126 | - `SECTION` 127 | - `UNDERLAY` 128 | 129 | ## 0.2 (13 March 2015) 130 | 131 | - Support ASCII and binary files, R10 through R2013. 132 | - Simple entity support. 133 | - Blocks. 134 | - Tables (including layers, view ports, etc.) 135 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Objects/DxfTransformationMatrix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IxMilia.Dxf.Objects 4 | { 5 | public class DxfTransformationMatrix 6 | { 7 | public double M11 { get; set; } 8 | public double M12 { get; set; } 9 | public double M13 { get; set; } 10 | public double M14 { get; set; } 11 | public double M21 { get; set; } 12 | public double M22 { get; set; } 13 | public double M23 { get; set; } 14 | public double M24 { get; set; } 15 | public double M31 { get; set; } 16 | public double M32 { get; set; } 17 | public double M33 { get; set; } 18 | public double M34 { get; set; } 19 | public double M41 { get; set; } 20 | public double M42 { get; set; } 21 | public double M43 { get; set; } 22 | public double M44 { get; set; } 23 | 24 | public DxfTransformationMatrix( 25 | double m11, double m12, double m13, double m14, 26 | double m21, double m22, double m23, double m24, 27 | double m31, double m32, double m33, double m34, 28 | double m41, double m42, double m43, double m44) 29 | { 30 | M11 = m11; 31 | M12 = m12; 32 | M13 = m13; 33 | M14 = m14; 34 | M21 = m21; 35 | M22 = m22; 36 | M23 = m23; 37 | M24 = m24; 38 | M31 = m31; 39 | M32 = m32; 40 | M33 = m33; 41 | M34 = m34; 42 | M41 = m41; 43 | M42 = m42; 44 | M43 = m43; 45 | M44 = m44; 46 | } 47 | 48 | internal DxfTransformationMatrix(params double[] values) 49 | : this( 50 | GetIndexOrDefault(values, 0), GetIndexOrDefault(values, 1), GetIndexOrDefault(values, 2), GetIndexOrDefault(values, 3), 51 | GetIndexOrDefault(values, 4), GetIndexOrDefault(values, 5), GetIndexOrDefault(values, 6), GetIndexOrDefault(values, 7), 52 | GetIndexOrDefault(values, 8), GetIndexOrDefault(values, 9), GetIndexOrDefault(values, 10), GetIndexOrDefault(values, 11), 53 | GetIndexOrDefault(values, 12), GetIndexOrDefault(values, 13), GetIndexOrDefault(values, 14), GetIndexOrDefault(values, 15)) 54 | { 55 | } 56 | 57 | private static double GetIndexOrDefault(double[] values, int index) 58 | { 59 | return values.Length > index 60 | ? values[index] 61 | : default(double); 62 | } 63 | 64 | internal IEnumerable GetValues() 65 | { 66 | yield return M11; 67 | yield return M12; 68 | yield return M13; 69 | yield return M14; 70 | yield return M21; 71 | yield return M22; 72 | yield return M23; 73 | yield return M24; 74 | yield return M31; 75 | yield return M32; 76 | yield return M33; 77 | yield return M34; 78 | yield return M41; 79 | yield return M42; 80 | yield return M43; 81 | yield return M44; 82 | } 83 | 84 | internal IEnumerable Get4x3ValuesRowMajor() 85 | { 86 | // similar to GetValues() but it returns the 4x3 matrix values 87 | yield return M11; 88 | yield return M21; 89 | yield return M31; 90 | yield return M12; 91 | yield return M22; 92 | yield return M32; 93 | yield return M13; 94 | yield return M23; 95 | yield return M33; 96 | yield return M14; 97 | yield return M24; 98 | yield return M34; 99 | } 100 | 101 | public static DxfTransformationMatrix Identity 102 | { 103 | get 104 | { 105 | return new DxfTransformationMatrix( 106 | 1.0, 0.0, 0.0, 0.0, 107 | 0.0, 1.0, 0.0, 0.0, 108 | 0.0, 0.0, 1.0, 0.0, 109 | 0.0, 0.0, 0.0, 1.0); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfCodePair.ExpectedType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IxMilia.Dxf 4 | { 5 | public partial class DxfCodePair 6 | { 7 | public int Offset { get; internal set; } 8 | 9 | public static Type ExpectedType(int code) 10 | { 11 | Type expected = typeof(string); 12 | Func between = (lower, upper) => code >= lower && code <= upper; 13 | 14 | // official code types 15 | if (between(0, 9)) 16 | expected = typeof(string); 17 | else if (between(10, 39)) 18 | expected = typeof(double); 19 | else if (between(40, 59)) 20 | expected = typeof(double); 21 | else if (between(60, 79)) 22 | expected = typeof(short); 23 | else if (between(90, 99)) 24 | expected = typeof(int); 25 | else if (between(100, 102)) 26 | expected = typeof(string); 27 | else if (code == 105) 28 | expected = typeof(string); 29 | else if (between(110, 119)) 30 | expected = typeof(double); 31 | else if (between(120, 129)) 32 | expected = typeof(double); 33 | else if (between(130, 139)) 34 | expected = typeof(double); 35 | else if (between(140, 149)) 36 | expected = typeof(double); 37 | else if (between(160, 169)) 38 | expected = typeof(long); 39 | else if (between(170, 179)) 40 | expected = typeof(short); 41 | else if (between(210, 239)) 42 | expected = typeof(double); 43 | else if (between(270, 279)) 44 | expected = typeof(short); 45 | else if (between(280, 289)) 46 | expected = typeof(short); 47 | else if (between(290, 299)) 48 | expected = typeof(bool); 49 | else if (between(300, 309)) 50 | expected = typeof(string); 51 | else if (between(310, 319)) 52 | expected = typeof(byte[]); 53 | else if (between(320, 329)) 54 | expected = typeof(string); 55 | else if (between(330, 369)) 56 | expected = typeof(string); 57 | else if (between(370, 379)) 58 | expected = typeof(short); 59 | else if (between(380, 389)) 60 | expected = typeof(short); 61 | else if (between(390, 399)) 62 | expected = typeof(string); 63 | else if (between(400, 409)) 64 | expected = typeof(short); 65 | else if (between(410, 419)) 66 | expected = typeof(string); 67 | else if (between(420, 429)) 68 | expected = typeof(int); 69 | else if (between(430, 439)) 70 | expected = typeof(string); 71 | else if (between(440, 449)) 72 | expected = typeof(int); 73 | else if (between(450, 459)) 74 | expected = typeof(int); 75 | else if (between(460, 469)) 76 | expected = typeof(double); 77 | else if (between(470, 479)) 78 | expected = typeof(string); 79 | else if (between(480, 481)) 80 | expected = typeof(string); 81 | else if (code == 999) 82 | expected = typeof(string); 83 | else if (between(1000, 1009)) 84 | expected = typeof(string); 85 | else if (between(1010, 1059)) 86 | expected = typeof(double); 87 | else if (between(1060, 1070)) 88 | expected = typeof(short); 89 | else if (code == 1071) 90 | expected = typeof(int); 91 | 92 | // unofficial app-specific types 93 | else if (code == 250) // used in POLYLINEs by CLO 94 | expected = typeof(short); 95 | 96 | else 97 | expected = typeof(string); // unsupported code, assume string so the value can be swallowed 98 | 99 | return expected; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Sections/DxfSection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | 5 | namespace IxMilia.Dxf.Sections 6 | { 7 | internal abstract class DxfSection 8 | { 9 | internal const string HeaderSectionText = "HEADER"; 10 | internal const string ClassesSectionText = "CLASSES"; 11 | internal const string TablesSectionText = "TABLES"; 12 | internal const string BlocksSectionText = "BLOCKS"; 13 | internal const string EntitiesSectionText = "ENTITIES"; 14 | internal const string ObjectsSectionText = "OBJECTS"; 15 | internal const string ThumbnailImageSectionText = "THUMBNAILIMAGE"; 16 | 17 | internal const string SectionText = "SECTION"; 18 | internal const string EndSectionText = "ENDSEC"; 19 | 20 | internal const string TableText = "TABLE"; 21 | internal const string EndTableText = "ENDTAB"; 22 | 23 | public abstract DxfSectionType Type { get; } 24 | 25 | protected DxfSection() 26 | { 27 | } 28 | 29 | public override string ToString() 30 | { 31 | return Type.ToSectionName() ?? string.Empty; 32 | } 33 | 34 | protected internal abstract IEnumerable GetSpecificPairs(DxfAcadVersion version, bool outputHandles, HashSet writtenItems); 35 | protected internal abstract void Clear(); 36 | 37 | internal IEnumerable GetValuePairs(DxfAcadVersion version, bool outputHandles, HashSet writtenItems) 38 | { 39 | yield return new DxfCodePair(0, SectionText); 40 | yield return new DxfCodePair(2, this.Type.ToSectionName() ?? string.Empty); 41 | foreach (var pair in GetSpecificPairs(version, outputHandles, writtenItems).ToList()) 42 | yield return pair; 43 | yield return new DxfCodePair(0, EndSectionText); 44 | } 45 | 46 | internal static DxfSection? FromBuffer(DxfCodePairBufferReader buffer, DxfFile parentFile, DxfAcadVersion version) 47 | { 48 | Debug.Assert(buffer.ItemsRemain); 49 | var sectionType = buffer.Peek(); 50 | buffer.Advance(); 51 | if (sectionType.Code != 2) 52 | { 53 | throw new DxfReadException($"Expected code 2, got {sectionType.Code}", sectionType); 54 | } 55 | 56 | DxfSection? section; 57 | switch (sectionType.StringValue) 58 | { 59 | case BlocksSectionText: 60 | section = DxfBlocksSection.BlocksSectionFromBuffer(buffer); 61 | break; 62 | case ClassesSectionText: 63 | section = DxfClassesSection.ClassesSectionFromBuffer(buffer, version); 64 | break; 65 | case EntitiesSectionText: 66 | section = DxfEntitiesSection.EntitiesSectionFromBuffer(buffer, parentFile); 67 | break; 68 | case HeaderSectionText: 69 | section = DxfHeaderSection.HeaderSectionFromBuffer(buffer); 70 | break; 71 | case ObjectsSectionText: 72 | section = DxfObjectsSection.ObjectsSectionFromBuffer(buffer); 73 | break; 74 | case TablesSectionText: 75 | section = DxfTablesSection.TablesSectionFromBuffer(buffer); 76 | break; 77 | case ThumbnailImageSectionText: 78 | section = DxfThumbnailImageSection.ThumbnailImageSectionFromBuffer(buffer); 79 | break; 80 | default: 81 | SwallowSection(buffer); 82 | section = null; 83 | break; 84 | } 85 | 86 | return section; 87 | } 88 | 89 | internal static void SwallowSection(DxfCodePairBufferReader buffer) 90 | { 91 | while (buffer.ItemsRemain) 92 | { 93 | var pair = buffer.Peek(); 94 | buffer.Advance(); 95 | if (DxfCodePair.IsSectionEnd(pair)) 96 | break; 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/DxfReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace IxMilia.Dxf 6 | { 7 | internal static class DxfReader 8 | { 9 | internal const string UnicodeMarker = @"\U+"; 10 | internal const int UnicodeValueLength = 4; 11 | 12 | internal static string TransformUnicodeCharacters(string str) 13 | { 14 | var sb = new StringBuilder(); 15 | int startIndex = 0; 16 | int currentIndex; 17 | while ((currentIndex = str.IndexOf(UnicodeMarker, startIndex)) >= 0) 18 | { 19 | var prefix = str.Substring(startIndex, currentIndex - startIndex); 20 | sb.Append(prefix); 21 | var unicode = str.Substring(currentIndex + UnicodeMarker.Length, UnicodeValueLength); 22 | if (int.TryParse(unicode, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var code)) 23 | { 24 | var c = char.ConvertFromUtf32(code); 25 | sb.Append(c); 26 | } 27 | else 28 | { 29 | // invalid unicode value 30 | sb.Append('?'); 31 | } 32 | 33 | startIndex = currentIndex + UnicodeMarker.Length + UnicodeValueLength; 34 | } 35 | 36 | sb.Append(str.Substring(startIndex)); 37 | return sb.ToString(); 38 | } 39 | 40 | internal static string TransformControlCharacters(string str) 41 | { 42 | var start = str.IndexOf('^'); 43 | if (start < 0) 44 | { 45 | return str; 46 | } 47 | else 48 | { 49 | var sb = new StringBuilder(); 50 | sb.Append(str.Substring(0, start)); 51 | for (int i = start; i < str.Length; i++) 52 | { 53 | if (str[i] == '^' && i < str.Length - 1) 54 | { 55 | var controlCharacter = str[i + 1]; 56 | sb.Append(TransformControlCharacter(controlCharacter)); 57 | i++; 58 | } 59 | else 60 | { 61 | sb.Append(str[i]); 62 | } 63 | } 64 | 65 | return sb.ToString(); 66 | } 67 | } 68 | 69 | private static char TransformControlCharacter(char c) 70 | { 71 | switch (c) 72 | { 73 | case '@': return (char)0x00; 74 | case 'A': return (char)0x01; 75 | case 'B': return (char)0x02; 76 | case 'C': return (char)0x03; 77 | case 'D': return (char)0x04; 78 | case 'E': return (char)0x05; 79 | case 'F': return (char)0x06; 80 | case 'G': return (char)0x07; 81 | case 'H': return (char)0x08; 82 | case 'I': return (char)0x09; 83 | case 'J': return (char)0x0A; 84 | case 'K': return (char)0x0B; 85 | case 'L': return (char)0x0C; 86 | case 'M': return (char)0x0D; 87 | case 'N': return (char)0x0E; 88 | case 'O': return (char)0x0F; 89 | case 'P': return (char)0x10; 90 | case 'Q': return (char)0x11; 91 | case 'R': return (char)0x12; 92 | case 'S': return (char)0x13; 93 | case 'T': return (char)0x14; 94 | case 'U': return (char)0x15; 95 | case 'V': return (char)0x16; 96 | case 'W': return (char)0x17; 97 | case 'X': return (char)0x18; 98 | case 'Y': return (char)0x19; 99 | case 'Z': return (char)0x1A; 100 | case '[': return (char)0x1B; 101 | case '\\': return (char)0x1C; 102 | case ']': return (char)0x1D; 103 | case '^': return (char)0x1E; 104 | case '_': return (char)0x1F; 105 | case ' ': return '^'; 106 | default: 107 | throw new ArgumentOutOfRangeException(nameof(c), $"Unexpected ASCII control character: {c}"); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Tables/DxfLineTypeElement.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using IxMilia.Dxf.Extensions; 4 | 5 | namespace IxMilia.Dxf 6 | { 7 | public class DxfLineTypeElement : IDxfItemInternal 8 | { 9 | public double DashDotSpaceLength { get; set; } 10 | public int ComplexFlags { get; set; } 11 | public int ShapeNumber { get; set; } 12 | public IList ScaleValues { get; } = new List(); 13 | 14 | /// 15 | /// The rotation angle in radians. 16 | /// 17 | public double RotationAngle { get; set; } 18 | 19 | public IList Offsets { get; } = new List(); 20 | public string TextString { get; set; } 21 | 22 | public DxfStyle? Style { get { return StylePointer.Item as DxfStyle; } set { StylePointer.Item = value; } } 23 | 24 | public bool IsRotationAbsolute 25 | { 26 | get { return DxfHelpers.GetFlag(ComplexFlags, 1); } 27 | set 28 | { 29 | var flags = ComplexFlags; 30 | DxfHelpers.SetFlag(value, ref flags, 1); 31 | ComplexFlags = flags; 32 | } 33 | } 34 | 35 | public bool IsEmbeddedElementAString 36 | { 37 | get { return DxfHelpers.GetFlag(ComplexFlags, 2); } 38 | set 39 | { 40 | var flags = ComplexFlags; 41 | DxfHelpers.SetFlag(value, ref flags, 2); 42 | ComplexFlags = flags; 43 | } 44 | } 45 | 46 | public bool IsEmbeddedElementAShape 47 | { 48 | get { return DxfHelpers.GetFlag(ComplexFlags, 4); } 49 | set 50 | { 51 | var flags = ComplexFlags; 52 | DxfHelpers.SetFlag(value, ref flags, 4); 53 | ComplexFlags = flags; 54 | } 55 | } 56 | 57 | public DxfLineTypeElement() 58 | { 59 | DashDotSpaceLength = 0.0; 60 | ComplexFlags = 0; 61 | ShapeNumber = 0; 62 | RotationAngle = 0.0; 63 | TextString = string.Empty; 64 | } 65 | 66 | internal DxfPointer StylePointer { get; } = new DxfPointer(); 67 | 68 | internal void AddValuePairs(List pairs, DxfAcadVersion version) 69 | { 70 | pairs.Add(new DxfCodePair(49, DashDotSpaceLength)); 71 | if (version >= DxfAcadVersion.R13) 72 | { 73 | pairs.Add(new DxfCodePair(74, (short)ComplexFlags)); 74 | if (ComplexFlags != 0) 75 | { 76 | var value = IsEmbeddedElementAString ? 0 : ShapeNumber; 77 | pairs.Add(new DxfCodePair(75, (short)value)); 78 | if (StylePointer.Handle.Value != 0) 79 | { 80 | pairs.Add(new DxfCodePair(340, DxfCommonConverters.HandleString(StylePointer.Handle))); 81 | } 82 | } 83 | } 84 | 85 | pairs.AddRange(ScaleValues.Select(s => new DxfCodePair(46, s))); 86 | if (IsEmbeddedElementAShape || IsEmbeddedElementAString) 87 | { 88 | pairs.Add(new DxfCodePair(50, RotationAngle)); 89 | } 90 | 91 | foreach (var offset in Offsets) 92 | { 93 | pairs.Add(new DxfCodePair(44, offset.X)); 94 | pairs.Add(new DxfCodePair(45, offset.Y)); 95 | } 96 | 97 | if (IsEmbeddedElementAString) 98 | { 99 | pairs.Add(new DxfCodePair(9, TextString)); 100 | } 101 | } 102 | 103 | DxfHandle IDxfItemInternal.Handle { get; set; } 104 | DxfHandle IDxfItemInternal.OwnerHandle { get; set; } 105 | public IDxfItem? Owner { get; private set; } 106 | 107 | void IDxfItemInternal.SetOwner(IDxfItem owner) 108 | { 109 | Owner = owner; 110 | } 111 | 112 | IEnumerable IDxfItemInternal.GetPointers() 113 | { 114 | yield return StylePointer; 115 | } 116 | 117 | IEnumerable IDxfItemInternal.GetChildItems() 118 | { 119 | return ((IDxfItemInternal)this).GetPointers().Select(p => p.Item as IDxfItemInternal).WhereNotNull(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Integration.Test/CompatTestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using IxMilia.Dxf.Entities; 7 | using IxMilia.Dxf.Test; 8 | using Xunit; 9 | 10 | namespace IxMilia.Dxf.Integration.Test 11 | { 12 | public abstract class CompatTestsBase : AbstractDxfTests 13 | { 14 | public static readonly string MinimumFileText = @" 15 | 0 16 | SECTION 17 | 2 18 | ENTITIES 19 | 0 20 | LINE 21 | 8 22 | 0 23 | 10 24 | 0.0 25 | 20 26 | 0.0 27 | 30 28 | 0.0 29 | 11 30 | 10.0 31 | 21 32 | 10.0 33 | 31 34 | 0.0 35 | 0 36 | ENDSEC 37 | 0 38 | EOF 39 | ".Trim(); 40 | 41 | public class ManageTemporaryDirectory : IDisposable 42 | { 43 | public string DirectoryPath { get; } 44 | 45 | public ManageTemporaryDirectory() 46 | { 47 | DirectoryPath = Path.Combine( 48 | Path.GetTempPath(), 49 | Guid.NewGuid().ToString() 50 | ); 51 | if (Directory.Exists(DirectoryPath)) 52 | { 53 | Directory.Delete(DirectoryPath, true); 54 | } 55 | 56 | Directory.CreateDirectory(DirectoryPath); 57 | } 58 | 59 | public void Dispose() 60 | { 61 | if (Directory.Exists(DirectoryPath)) 62 | { 63 | Directory.Delete(DirectoryPath, true); 64 | } 65 | } 66 | } 67 | 68 | protected void RoundTripDimensionWithXData(Func roundTripper) 69 | { 70 | var dim = new DxfAlignedDimension(); 71 | dim.XData.Add("ACAD", 72 | new DxfXDataApplicationItemCollection( 73 | new DxfXDataString("DSTYLE"), 74 | new DxfXDataItemList( 75 | new DxfXDataItem[] 76 | { 77 | new DxfXDataInteger(271), 78 | new DxfXDataInteger(9), 79 | }) 80 | )); 81 | var file = new DxfFile(); 82 | file.Header.Version = DxfAcadVersion.R14; 83 | file.Entities.Add(dim); 84 | 85 | // perform round trip 86 | var result = roundTripper(file); 87 | 88 | // verify 89 | var roundTrippedDim = (DxfAlignedDimension)result.Entities.Single(); 90 | var xdataPair = roundTrippedDim.XData.Single(); 91 | Assert.Equal("ACAD", xdataPair.Key); 92 | 93 | var styleItems = xdataPair.Value; 94 | Assert.Equal("DSTYLE", ((DxfXDataString)styleItems.First()).Value); 95 | var dataItems = (DxfXDataItemList)styleItems.Last(); 96 | Assert.Single(dataItems.Items.OfType().Where(i => i.Value == 271)); 97 | } 98 | 99 | protected void WaitForProcess(string fileName, string arguments) 100 | { 101 | var proc = new Process() 102 | { 103 | StartInfo = new ProcessStartInfo() 104 | { 105 | FileName = fileName, 106 | Arguments = arguments, 107 | CreateNoWindow = true, 108 | RedirectStandardOutput = true, 109 | RedirectStandardError = true, 110 | StandardOutputEncoding = Encoding.Unicode, 111 | StandardErrorEncoding = Encoding.Unicode, 112 | UseShellExecute = false, 113 | }, 114 | }; 115 | var stdout = new StringBuilder(); 116 | var stderr = new StringBuilder(); 117 | proc.OutputDataReceived += (_, args) => stdout.AppendLine(args.Data); 118 | proc.ErrorDataReceived += (_, args) => stderr.AppendLine(args.Data); 119 | proc.Start(); 120 | proc.BeginOutputReadLine(); 121 | proc.BeginErrorReadLine(); 122 | var exited = proc.WaitForExit((int)TimeSpan.FromSeconds(30).TotalMilliseconds); 123 | proc.CancelOutputRead(); 124 | proc.CancelErrorRead(); 125 | if (!exited) 126 | { 127 | proc.Kill(); 128 | } 129 | 130 | var message = $"STDOUT:\n{stdout}\nSTDERR:\n{stderr}"; 131 | Assert.True(exited && proc.ExitCode == 0, message); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf.Test/DxbTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using IxMilia.Dxf.Entities; 4 | using Xunit; 5 | 6 | namespace IxMilia.Dxf.Test 7 | { 8 | public class DxbTests : AbstractDxfTests 9 | { 10 | public static byte[] DxbSentinel => (DxbReader.BinarySentinel + "\r\n").Select(c => (byte)c).Concat(new byte[] { 0x1A, 0x00 }).ToArray(); 11 | 12 | [Fact] 13 | public void ReadDxbTest() 14 | { 15 | var data = DxbSentinel.Concat(new byte[] 16 | { 17 | // color 18 | 136, // type specifier for new color 19 | 0x01, 0x00, // color index 1 20 | 21 | // line 22 | 0x01, // type specifier 23 | 0x01, 0x00, // P1.X = 0x0001 24 | 0x02, 0x00, // P1.Y = 0x0002 25 | 0x03, 0x00, // P1.Z = 0x0003 26 | 0x04, 0x00, // P2.X = 0x0004 27 | 0x05, 0x00, // P2.Y = 0x0005 28 | 0x06, 0x00, // P2.Z = 0x0006 29 | 30 | 0x0 // null terminator 31 | }).ToArray(); 32 | using (var stream = new MemoryStream(data)) 33 | { 34 | var file = DxfFile.Load(stream); 35 | var line = (DxfLine)file.Entities.Single(); 36 | Assert.Equal(1, line.Color.RawValue); 37 | Assert.Equal(new DxfPoint(1, 2, 3), line.P1); 38 | Assert.Equal(new DxfPoint(4, 5, 6), line.P2); 39 | } 40 | } 41 | 42 | [Fact] 43 | public void ReadDxbNoLengthOrPositionStreamTest() 44 | { 45 | var data = DxbSentinel.Concat(new byte[] 46 | { 47 | // color 48 | 136, // type specifier for new color 49 | 0x01, 0x00, // color index 1 50 | 51 | // line 52 | 0x01, // type specifier 53 | 0x01, 0x00, // P1.X = 0x0001 54 | 0x02, 0x00, // P1.Y = 0x0002 55 | 0x03, 0x00, // P1.Z = 0x0003 56 | 0x04, 0x00, // P2.X = 0x0004 57 | 0x05, 0x00, // P2.Y = 0x0005 58 | 0x06, 0x00, // P2.Z = 0x0006 59 | 60 | 0x0 // null terminator 61 | }).ToArray(); 62 | using (var ms = new MemoryStream(data)) 63 | using (var stream = new StreamWithNoLengthOrPosition(ms)) 64 | { 65 | var file = DxfFile.Load(stream); 66 | var line = (DxfLine)file.Entities.Single(); 67 | Assert.Equal(1, line.Color.RawValue); 68 | Assert.Equal(new DxfPoint(1, 2, 3), line.P1); 69 | Assert.Equal(new DxfPoint(4, 5, 6), line.P2); 70 | } 71 | } 72 | 73 | [Fact] 74 | public void ReadDxbPolylineTest() 75 | { 76 | var data = DxbSentinel.Concat(new byte[] 77 | { 78 | 19, // polyline 79 | 0x00, 0x00, // is closed = false 80 | 81 | 20, // vertex 82 | 0x01, 0x00, // x 83 | 0x02, 0x00, // y 84 | 85 | 20, // vertex 86 | 0x03, 0x00, // x 87 | 0x04, 0x00, // y 88 | 89 | 17, // seqend 90 | 91 | 0x00 // null terminator 92 | }).ToArray(); 93 | using (var stream = new MemoryStream(data)) 94 | { 95 | var file = DxfFile.Load(stream); 96 | var poly = (DxfPolyline)file.Entities.Single(); 97 | Assert.Equal(2, poly.Vertices.Count); 98 | Assert.Equal(new DxfPoint(1.0, 2.0, 0.0), poly.Vertices[0].Location); 99 | Assert.Equal(new DxfPoint(3.0, 4.0, 0.0), poly.Vertices[1].Location); 100 | } 101 | } 102 | 103 | [Fact] 104 | public void WriteDxbTest() 105 | { 106 | // write file 107 | var file = new DxfFile(); 108 | file.Entities.Add(new DxfLine(new DxfPoint(1, 2, 3), new DxfPoint(4, 5, 6))); 109 | var stream = new MemoryStream(); 110 | file.SaveDxb(stream); 111 | stream.Seek(0, SeekOrigin.Begin); 112 | 113 | // read it back in 114 | var dxb = DxfFile.Load(stream); 115 | var line = (DxfLine)dxb.Entities.Single(); 116 | Assert.Equal(new DxfPoint(1, 2, 3), line.P1); 117 | Assert.Equal(new DxfPoint(4, 5, 6), line.P2); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Blocks/DxfBlock.DxfEndBlock.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using IxMilia.Dxf.Collections; 5 | using IxMilia.Dxf.Extensions; 6 | 7 | namespace IxMilia.Dxf.Blocks 8 | { 9 | public partial class DxfBlock 10 | { 11 | private class DxfEndBlock : IDxfItemInternal 12 | { 13 | #region IDxfItem and IDxfItemInternal 14 | DxfHandle IDxfItemInternal.Handle { get; set; } 15 | DxfHandle IDxfItemInternal.OwnerHandle { get; set; } 16 | public IDxfItem Owner { get; private set; } 17 | 18 | void IDxfItemInternal.SetOwner(IDxfItem owner) 19 | { 20 | Owner = owner; 21 | } 22 | 23 | IEnumerable IDxfItemInternal.GetPointers() 24 | { 25 | yield break; 26 | } 27 | IEnumerable IDxfItemInternal.GetChildItems() 28 | { 29 | return ((IDxfItemInternal)this).GetPointers().Select(p => p.Item as IDxfItemInternal).WhereNotNull(); 30 | } 31 | #endregion 32 | 33 | public DxfBlock Parent => (DxfBlock)Owner; 34 | public IList ExtensionDataGroups { get; } 35 | 36 | public DxfEndBlock(DxfBlock parent) 37 | { 38 | Owner = parent; 39 | ExtensionDataGroups = new ListNonNull(); 40 | } 41 | 42 | public void ApplyCodePairs(DxfCodePairBufferReader buffer) 43 | { 44 | var pair = buffer.Peek(); 45 | buffer.Advance(); 46 | switch (pair.Code) 47 | { 48 | case 5: 49 | ((IDxfItemInternal)this).Handle = DxfCommonConverters.HandleString(pair.StringValue); 50 | break; 51 | case 8: 52 | // just a re-iteration of the layer 53 | break; 54 | case 67: 55 | // just a re-iteration of the paper space setting 56 | break; 57 | case 100: 58 | Debug.Assert(pair.StringValue == AcDbEntityText || pair.StringValue == AcDbBlockEndText); 59 | break; 60 | case DxfCodePairGroup.GroupCodeNumber: 61 | var groupName = DxfCodePairGroup.GetGroupName(pair.StringValue); 62 | ExtensionDataGroups.Add(DxfCodePairGroup.FromBuffer(buffer, groupName)); 63 | break; 64 | } 65 | } 66 | 67 | public IEnumerable GetValuePairs(DxfAcadVersion version, bool outputHandles) 68 | { 69 | var list = new List(); 70 | list.Add(new DxfCodePair(0, EndBlockText)); 71 | if (outputHandles && ((IDxfItemInternal)this).Handle.Value != 0) 72 | { 73 | list.Add(new DxfCodePair(5, DxfCommonConverters.HandleString(((IDxfItemInternal)this).Handle))); 74 | } 75 | 76 | DxfXData.AddValuePairs(Parent.XData, list, version, outputHandles); 77 | 78 | if (version >= DxfAcadVersion.R14) 79 | { 80 | foreach (var group in ExtensionDataGroups) 81 | { 82 | group.AddValuePairs(list, version, outputHandles); 83 | } 84 | } 85 | 86 | if (version >= DxfAcadVersion.R2000) 87 | { 88 | list.Add(new DxfCodePair(330, DxfCommonConverters.HandleString(((IDxfItemInternal)Parent).OwnerHandle))); 89 | } 90 | 91 | if (version >= DxfAcadVersion.R13) 92 | { 93 | list.Add(new DxfCodePair(100, AcDbEntityText)); 94 | } 95 | 96 | if (Parent.IsInPaperSpace) 97 | { 98 | list.Add(new DxfCodePair(67, DxfCommonConverters.BoolShort(Parent.IsInPaperSpace))); 99 | } 100 | 101 | list.Add(new DxfCodePair(8, Parent.Layer)); 102 | 103 | if (version >= DxfAcadVersion.R13) 104 | { 105 | list.Add(new DxfCodePair(100, AcDbBlockEndText)); 106 | } 107 | return list; 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Tables/DxfDimStyle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace IxMilia.Dxf 6 | { 7 | public partial class DxfDimStyle 8 | { 9 | public const string XDataStyleName = "DSTYLE"; 10 | 11 | public bool TryGetStyleFromXDataDifference(DxfXDataApplicationItemCollection xdataItemCollection, [NotNullWhen(returnValue: true)] out DxfDimStyle? style) 12 | { 13 | // style data is encoded as 14 | // 1001 DSTYLE 15 | // 1002 { 16 | // ... style overrides 17 | // 1002 } 18 | 19 | style = null; 20 | if (xdataItemCollection == null) 21 | { 22 | return false; 23 | } 24 | 25 | for (int i = 0; i < xdataItemCollection.Count - 1; i++) 26 | { 27 | if (xdataItemCollection[i] is DxfXDataString xdataString && xdataString.Value == XDataStyleName && 28 | xdataItemCollection[i + 1] is DxfXDataItemList itemList) 29 | { 30 | if (itemList.Items.Count % 2 != 0) 31 | { 32 | // must be an even number 33 | return false; 34 | } 35 | 36 | var codePairs = new List(); 37 | for (int j = 0; j < itemList.Items.Count; j += 2) 38 | { 39 | if (!(itemList.Items[j] is DxfXDataInteger codeItem)) 40 | { 41 | // must alternate between int/ 42 | return false; 43 | } 44 | 45 | DxfCodePair pair; 46 | var valueItem = itemList.Items[j + 1]; 47 | var code = codeItem.Value; 48 | switch (DxfCodePair.ExpectedType(code).Name) 49 | { 50 | case nameof(Boolean): 51 | pair = new DxfCodePair(code, true); 52 | break; 53 | case nameof(Double) when valueItem is DxfXDataDistance dist: 54 | pair = new DxfCodePair(code, dist.Value); 55 | break; 56 | case nameof(Double) when valueItem is DxfXDataReal real: 57 | pair = new DxfCodePair(code, real.Value); 58 | break; 59 | case nameof(Double) when valueItem is DxfXDataScaleFactor scale: 60 | pair = new DxfCodePair(code, scale.Value); 61 | break; 62 | case nameof(Int16) when valueItem is DxfXDataInteger i32: 63 | pair = new DxfCodePair(code, i32.Value); 64 | break; 65 | case nameof(Int32) when valueItem is DxfXDataLong i32: 66 | pair = new DxfCodePair(code, i32.Value); 67 | break; 68 | case nameof(Int64) when valueItem is DxfXDataLong i32: 69 | pair = new DxfCodePair(code, i32.Value); 70 | break; 71 | case nameof(String) when valueItem is DxfXDataString str: 72 | pair = new DxfCodePair(code, str.Value); 73 | break; 74 | default: 75 | // unexpected code pair type 76 | return false; 77 | } 78 | 79 | codePairs.Add(pair); 80 | } 81 | 82 | if (codePairs.Count == 0) 83 | { 84 | // no difference to apply 85 | return false; 86 | } 87 | 88 | // if we made it this far, there is a difference that should be applied 89 | style = Clone(); 90 | foreach (var pair in codePairs) 91 | { 92 | style.ApplyCodePair(pair); 93 | } 94 | 95 | return true; 96 | } 97 | } 98 | 99 | return false; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Sections/DxfObjectsSection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using IxMilia.Dxf.Collections; 4 | using IxMilia.Dxf.Objects; 5 | 6 | namespace IxMilia.Dxf.Sections 7 | { 8 | internal class DxfObjectsSection : DxfSection 9 | { 10 | public IList Objects { get; } 11 | 12 | public DxfObjectsSection() 13 | { 14 | Objects = new ListNonNull(); 15 | } 16 | 17 | public override DxfSectionType Type { get { return DxfSectionType.Objects; } } 18 | 19 | protected internal override IEnumerable GetSpecificPairs(DxfAcadVersion version, bool outputHandles, HashSet writtenItems) 20 | { 21 | foreach (var obj in Objects) 22 | { 23 | if (writtenItems.Add(obj)) 24 | { 25 | foreach (var pair in obj.GetValuePairs(version, outputHandles, writtenItems)) 26 | { 27 | yield return pair; 28 | } 29 | } 30 | } 31 | } 32 | 33 | protected internal override void Clear() 34 | { 35 | Objects.Clear(); 36 | } 37 | 38 | internal void Normalize() 39 | { 40 | if (Objects.FirstOrDefault()?.ObjectType != DxfObjectType.Dictionary) 41 | { 42 | // first object must be a dictionary 43 | Objects.Insert(0, new DxfDictionary()); 44 | } 45 | 46 | // now ensure that dictionary contains the expected values 47 | var dict = (DxfDictionary)Objects.First(); 48 | if (!dict.ContainsKey("ACAD_GROUP") || !(dict["ACAD_GROUP"] is DxfDictionary)) 49 | { 50 | dict["ACAD_GROUP"] = new DxfDictionary(); 51 | } 52 | } 53 | 54 | internal static DxfObjectsSection ObjectsSectionFromBuffer(DxfCodePairBufferReader buffer) 55 | { 56 | var objects = new List(); 57 | objects.Clear(); 58 | while (buffer.ItemsRemain) 59 | { 60 | var pair = buffer.Peek(); 61 | if (DxfCodePair.IsSectionEnd(pair)) 62 | { 63 | // done reading objects 64 | buffer.Advance(); // swallow (0, ENDSEC) 65 | break; 66 | } 67 | 68 | if (pair.Code != 0) 69 | { 70 | throw new DxfReadException("Expected new object.", pair); 71 | } 72 | 73 | var obj = DxfObject.FromBuffer(buffer); 74 | if (obj != null) 75 | { 76 | objects.Add(obj); 77 | } 78 | } 79 | 80 | var section = new DxfObjectsSection(); 81 | foreach (var obj in objects) 82 | { 83 | section.Objects.Add(obj); 84 | } 85 | 86 | return section; 87 | } 88 | 89 | private static List GatherObjects(IEnumerable objects) 90 | { 91 | var buffer = new DxfBufferReader(objects, o => o == null); 92 | var result = new List(); 93 | var defaultObjectHandles = new HashSet(); 94 | while (buffer.ItemsRemain) 95 | { 96 | var obj = buffer.Peek(); 97 | buffer.Advance(); 98 | switch (obj.ObjectType) 99 | { 100 | case DxfObjectType.DictionaryWithDefault: 101 | var dict = (DxfDictionaryWithDefault)obj; 102 | if (dict.DefaultObjectPointer.Handle.Value != 0) 103 | { 104 | defaultObjectHandles.Add(dict.DefaultObjectPointer.Handle); 105 | } 106 | break; 107 | default: 108 | break; 109 | } 110 | 111 | result.Add(obj); 112 | } 113 | 114 | // trim default objects from the resultant list because they shouldn't be directly accessible 115 | for (int i = result.Count - 1; i >= 0; i--) 116 | { 117 | if (defaultObjectHandles.Contains(((IDxfItemInternal)result[i]).Handle)) 118 | { 119 | result.RemoveAt(i); 120 | } 121 | } 122 | 123 | return result; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Sections/DxfHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace IxMilia.Dxf 5 | { 6 | public partial class DxfHeader 7 | { 8 | public bool IsViewportScaledToFit 9 | { 10 | get { return ViewportViewScaleFactor == 0.0; } 11 | set { ViewportViewScaleFactor = value ? 0.0 : 1.0; } 12 | } 13 | 14 | public object this[string variableName] 15 | { 16 | get { return GetValue(variableName); } 17 | set { SetValue(variableName, value); } 18 | } 19 | 20 | public bool IsRestrictedVersion { get; set; } = false; 21 | 22 | private void SetManualDefaults() 23 | { 24 | IsRestrictedVersion = false; 25 | } 26 | 27 | private DxfAcadVersion VersionConverter(string str) 28 | { 29 | if (str.EndsWith("S")) 30 | { 31 | IsRestrictedVersion = true; 32 | str = str.Substring(0, str.Length - 1); 33 | } 34 | else 35 | { 36 | IsRestrictedVersion = false; 37 | } 38 | 39 | return DxfAcadVersionStrings.StringToVersion(str); 40 | } 41 | 42 | private string VersionConverter(DxfAcadVersion version) 43 | { 44 | var str = DxfAcadVersionStrings.VersionToString(version); 45 | if (IsRestrictedVersion) 46 | { 47 | str += "S"; 48 | } 49 | 50 | return str; 51 | } 52 | 53 | private static string StringShort(short s) 54 | { 55 | return DxfCommonConverters.StringShort(s); 56 | } 57 | 58 | private static short StringShort(string s) 59 | { 60 | return DxfCommonConverters.StringShort(s); 61 | } 62 | 63 | private static bool BoolShort(short? s) 64 | { 65 | return DxfCommonConverters.BoolShort(s ?? 0); 66 | } 67 | 68 | private static short BoolShort(bool? b) 69 | { 70 | return DxfCommonConverters.BoolShort(b ?? false); 71 | } 72 | 73 | private static string GuidString(Guid g) 74 | { 75 | return DxfCommonConverters.GuidString(g); 76 | } 77 | 78 | private static Guid GuidString(string s) 79 | { 80 | return DxfCommonConverters.GuidString(s); 81 | } 82 | 83 | private static DxfHandle HandleString(string s) 84 | { 85 | return DxfCommonConverters.HandleString(s); 86 | } 87 | 88 | private static string HandleString(DxfHandle handle) 89 | { 90 | return DxfCommonConverters.HandleString(handle); 91 | } 92 | 93 | private static DateTime DateDouble(double date) 94 | { 95 | return DxfCommonConverters.DateDouble(date); 96 | } 97 | 98 | private static double DateDouble(DateTime date) 99 | { 100 | return DxfCommonConverters.DateDouble(date); 101 | } 102 | 103 | private static TimeSpan TimeSpanDouble(double d) 104 | { 105 | return TimeSpan.FromDays(d); 106 | } 107 | 108 | private static double TimeSpanDouble(TimeSpan t) 109 | { 110 | return t.TotalDays; 111 | } 112 | 113 | private static string DefaultIfNullOrEmpty(string? value, string defaultValue) 114 | { 115 | return DxfCommonConverters.DefaultIfNullOrEmpty(value, defaultValue); 116 | } 117 | 118 | private static double EnsurePositiveOrDefault(double value, double defaultValue) 119 | { 120 | return DxfCommonConverters.EnsurePositiveOrDefault(value, defaultValue); 121 | } 122 | 123 | private static void EnsureCode(DxfCodePair pair, int code) 124 | { 125 | if (pair.Code != code) 126 | { 127 | Debug.Assert(false, string.Format("Expected code {0}, got {1}", code, pair.Code)); 128 | } 129 | } 130 | 131 | private static DxfPoint UpdatePoint(DxfCodePair pair, DxfPoint point) 132 | { 133 | switch (pair.Code) 134 | { 135 | case 10: 136 | return point.WithUpdatedX(pair.DoubleValue); 137 | case 20: 138 | return point.WithUpdatedY(pair.DoubleValue); 139 | case 30: 140 | return point.WithUpdatedZ(pair.DoubleValue); 141 | default: 142 | return point; 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/IxMilia.Dxf/Sections/DxfThumbnailImageSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace IxMilia.Dxf.Sections 6 | { 7 | internal class DxfThumbnailImageSection : DxfSection 8 | { 9 | public override DxfSectionType Type 10 | { 11 | get { return DxfSectionType.Thumbnail; } 12 | } 13 | 14 | protected internal override IEnumerable GetSpecificPairs(DxfAcadVersion version, bool outputHandles, HashSet writtenItems) 15 | { 16 | yield return new DxfCodePair(90, RawData.Length); 17 | 18 | // write lines in 128-byte chunks (expands to 256 hex bytes) 19 | foreach (var chunk in BinaryHelpers.ChunkBytes(RawData)) 20 | { 21 | yield return new DxfCodePair(310, chunk); 22 | } 23 | } 24 | 25 | public byte[] RawData { get; set; } = Array.Empty(); 26 | 27 | public byte[] GetThumbnailBitmap() 28 | { 29 | var result = new byte[RawData.Length + BITMAPFILEHEADER.Length]; 30 | 31 | // populate the bitmap header 32 | Array.Copy(BITMAPFILEHEADER, 0, result, 0, BITMAPFILEHEADER.Length); 33 | 34 | // write the file length 35 | var lengthBytes = BitConverter.GetBytes(result.Length); 36 | Array.Copy(lengthBytes, 0, result, BITMAPFILELENGTHOFFSET, lengthBytes.Length); 37 | 38 | // write the image data offset if it can be safely calculated 39 | if (RawData.Length >= 36) 40 | { 41 | var dibHeaderSize = BitConverter.ToInt32(RawData, 0); 42 | var paletteColorCount = BitConverter.ToInt32(RawData, 32); // in all valid headers, the palette count is at this location 43 | var paletteSize = paletteColorCount * 4; // always 4 values; BGRA 44 | var imageDataOffset = BITMAPFILEHEADER.Length + dibHeaderSize + paletteSize; 45 | var imageDataOffsetBytes = BitConverter.GetBytes(imageDataOffset); 46 | Array.Copy(imageDataOffsetBytes, 0, result, IMAGEDATAOFFSETOFFSET, imageDataOffsetBytes.Length); 47 | } 48 | 49 | // copy the raw data 50 | Array.Copy(RawData, 0, result, BITMAPFILEHEADER.Length, RawData.Length); 51 | 52 | return result; 53 | } 54 | 55 | public void SetThumbnailBitmap(byte[] thumbnail) 56 | { 57 | // strip off bitmap header 58 | Debug.Assert(thumbnail != null); 59 | Debug.Assert(thumbnail.Length > BITMAPFILEHEADER.Length); 60 | Debug.Assert(thumbnail[0] == 'B'); 61 | Debug.Assert(thumbnail[1] == 'M'); 62 | RawData = new byte[thumbnail.Length - BITMAPFILEHEADER.Length]; 63 | Array.Copy(thumbnail, BITMAPFILEHEADER.Length, RawData, 0, RawData.Length); 64 | } 65 | 66 | // BITMAPFILEHEADER structure 67 | internal static byte[] BITMAPFILEHEADER 68 | { 69 | get 70 | { 71 | return new byte[] 72 | { 73 | (byte)'B', (byte)'M', // magic number 74 | 0x00, 0x00, 0x00, 0x00, // file length (calculated later) 75 | 0x00, 0x00, // reserved 76 | 0x00, 0x00, // reserved 77 | 0x36, 0x04, 0x00, 0x00 // bit offset; assumed to be 1078 which means BITMAPFILEHEADER.Length + BITMAPINFOHEADER.Length + 256 element palette * 4 78 | }; 79 | } 80 | } 81 | 82 | private const int BITMAPFILELENGTHOFFSET = 2; 83 | private const int IMAGEDATAOFFSETOFFSET = 10; 84 | 85 | protected internal override void Clear() 86 | { 87 | } 88 | 89 | internal static DxfThumbnailImageSection? ThumbnailImageSectionFromBuffer(DxfCodePairBufferReader buffer) 90 | { 91 | if (buffer.ItemsRemain) 92 | { 93 | var lengthPair = buffer.Peek(); 94 | buffer.Advance(); 95 | 96 | if (lengthPair.Code != 90) 97 | { 98 | return null; 99 | } 100 | 101 | var length = lengthPair.IntegerValue; 102 | var rawData = new List(); 103 | while (buffer.ItemsRemain) 104 | { 105 | var pair = buffer.Peek(); 106 | buffer.Advance(); 107 | 108 | if (DxfCodePair.IsSectionEnd(pair)) 109 | { 110 | break; 111 | } 112 | 113 | Debug.Assert(pair.Code == 310); 114 | rawData.AddRange(pair.BinaryValue); 115 | } 116 | 117 | var section = new DxfThumbnailImageSection(); 118 | section.Clear(); 119 | section.RawData = rawData.ToArray(); 120 | return section; 121 | } 122 | 123 | return null; 124 | } 125 | } 126 | } 127 | --------------------------------------------------------------------------------