├── 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 |
--------------------------------------------------------------------------------