Files
OpenNest/docs/plans/2026-03-07-net8-migration.md

1025 lines
32 KiB
Markdown

# .NET 8 Migration Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Convert OpenNest from .NET Framework 4.8 to .NET 8, replacing netDxf with ACadSharp.
**Architecture:** Three projects (OpenNest.Core, OpenNest.Engine, OpenNest) converted from legacy csproj to SDK-style targeting `net8.0-windows`. The netDxf NuGet dependency is replaced with ACadSharp 3.1.32 in the WinForms project. The 3 IO files (DxfImporter, DxfExporter, Extensions) are rewritten against the ACadSharp API.
**Tech Stack:** .NET 8, WinForms, ACadSharp 3.1.32, System.Drawing.Common, CSMath
---
## Reference Material
- ACadSharp usage examples: `C:\Users\aisaacs\Desktop\Projects\Geometry\Geometry.Dxf\Extensions.cs` and `DxfImport.cs`
- ACadSharp key types:
- Reading: `ACadSharp.IO.DxfReader``.Read()` returns `CadDocument`
- Writing: `ACadSharp.IO.DxfWriter`
- Entities: `ACadSharp.Entities.{Line, Arc, Circle, Polyline, LwPolyline, Spline, Ellipse}`
- Vectors: `CSMath.XY`, `CSMath.XYZ`
- Layers: `ACadSharp.Tables.Layer`
- Colors: `ACadSharp.Color` (ACI index-based)
- Document: `ACadSharp.CadDocument`
- netDxf → ACadSharp type mapping:
- `netDxf.Vector2``CSMath.XY`
- `netDxf.Vector3``CSMath.XYZ`
- `netDxf.DxfDocument``ACadSharp.CadDocument`
- `netDxf.DxfDocument.Load(path)``new DxfReader(path).Read()`
- `netDxf.DxfDocument.Save(stream)``new DxfWriter(stream, doc).Write()`
- `netDxf.Tables.Layer``ACadSharp.Tables.Layer`
- `netDxf.AciColor``ACadSharp.Color`
- `netDxf.Linetype``ACadSharp.Tables.LineType`
- `doc.Entities.Lines``doc.Entities.OfType<ACadSharp.Entities.Line>()`
- `doc.Entities.Arcs``doc.Entities.OfType<ACadSharp.Entities.Arc>()`
- `spline.PolygonalVertexes(n)` → manual control point interpolation
- `polyline.Vertexes``polyline.Vertices`
- `ellipse.ToPolyline2D(n)` → manual parametric calculation
---
### Task 1: Convert OpenNest.Core to SDK-style .NET 8
**Files:**
- Replace: `OpenNest.Core/OpenNest.Core.csproj`
- Delete: `OpenNest.Core/Properties/AssemblyInfo.cs`
**Step 1: Replace the csproj with SDK-style**
Replace `OpenNest.Core/OpenNest.Core.csproj` with:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>OpenNest</RootNamespace>
<AssemblyName>OpenNest.Core</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
</ItemGroup>
</Project>
```
Notes:
- `net8.0-windows` needed because `System.Drawing.Common` requires Windows on .NET 8
- `GenerateAssemblyInfo` false to keep existing AssemblyInfo.cs (we'll remove it in step 2 and re-enable)
- SDK-style auto-includes all `.cs` files, so no `<Compile>` items needed
- `System.Drawing.Common` NuGet replaces the Framework's built-in `System.Drawing`
**Step 2: Delete AssemblyInfo.cs and enable auto-generated assembly info**
Delete `OpenNest.Core/Properties/AssemblyInfo.cs`.
Then update the csproj to remove `GenerateAssemblyInfo`:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>OpenNest</RootNamespace>
<AssemblyName>OpenNest.Core</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
</ItemGroup>
</Project>
```
**Step 3: Build to verify**
Run: `dotnet build OpenNest.Core/OpenNest.Core.csproj`
Expected: Build succeeds with 0 errors.
**Step 4: Commit**
```
feat: convert OpenNest.Core to .NET 8 SDK-style project
```
---
### Task 2: Convert OpenNest.Engine to SDK-style .NET 8
**Files:**
- Replace: `OpenNest.Engine/OpenNest.Engine.csproj`
- Delete: `OpenNest.Engine/Properties/AssemblyInfo.cs`
**Step 1: Replace the csproj with SDK-style**
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>OpenNest</RootNamespace>
<AssemblyName>OpenNest.Engine</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
</ItemGroup>
</Project>
```
**Step 2: Delete AssemblyInfo.cs**
Delete `OpenNest.Engine/Properties/AssemblyInfo.cs`.
**Step 3: Build to verify**
Run: `dotnet build OpenNest.Engine/OpenNest.Engine.csproj`
Expected: Build succeeds.
**Step 4: Commit**
```
feat: convert OpenNest.Engine to .NET 8 SDK-style project
```
---
### Task 3: Convert OpenNest (WinForms) to SDK-style .NET 8
**Files:**
- Replace: `OpenNest/OpenNest.csproj`
- Delete: `OpenNest/Properties/AssemblyInfo.cs`
- Delete: `OpenNest/packages.config`
- Keep: `OpenNest/Properties/Settings.settings`, `Settings.Designer.cs`, `Resources.resx`, `Resources.Designer.cs`
- Keep: `OpenNest/app.config`
**Step 1: Replace the csproj with SDK-style**
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>OpenNest</RootNamespace>
<AssemblyName>OpenNest</AssemblyName>
<UseWindowsForms>true</UseWindowsForms>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ApplicationHighDpiMode>DpiUnaware</ApplicationHighDpiMode>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
<ProjectReference Include="..\OpenNest.Engine\OpenNest.Engine.csproj" />
<PackageReference Include="ACadSharp" Version="3.1.32" />
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>
```
Notes:
- `UseWindowsForms` enables WinForms support on .NET 8
- `ApplicationHighDpiMode` replaces the manual `SetProcessDPIAware()` P/Invoke
- ACadSharp replaces netDxf package reference
- `GenerateAssemblyInfo` false initially — we'll clean up AssemblyInfo and re-enable after
**Step 2: Delete AssemblyInfo.cs and packages.config**
Delete `OpenNest/Properties/AssemblyInfo.cs` and `OpenNest/packages.config`.
Then remove `GenerateAssemblyInfo` from the csproj.
**Step 3: Build to check what breaks**
Run: `dotnet build OpenNest/OpenNest.csproj`
Expected: Build fails — the 3 IO files reference `netDxf` types. This is expected and will be fixed in Tasks 4-6.
**Step 4: Commit (with build errors expected)**
```
feat: convert OpenNest WinForms project to .NET 8 SDK-style
Build errors expected in IO files — netDxf references not yet migrated to ACadSharp.
```
---
### Task 4: Rewrite Extensions.cs for ACadSharp
**Files:**
- Modify: `OpenNest/IO/Extensions.cs`
- Reference: `C:\Users\aisaacs\Desktop\Projects\Geometry\Geometry.Dxf\Extensions.cs`
**Step 1: Rewrite Extensions.cs**
Replace the full file. Key changes:
- `netDxf.Vector2``CSMath.XY`
- `netDxf.Vector3``CSMath.XYZ`
- `netDxf.Entities.*``ACadSharp.Entities.*`
- `netDxf.Tables.Layer``ACadSharp.Tables.Layer`
- `layer.Color.ToColor()``System.Drawing.Color.FromArgb(layer.Color.GetColorIndex()...)` or equivalent
- `spline.PolygonalVertexes(n)` → manual control point iteration
- `polyline.Vertexes``polyline.Vertices`
- `ellipse.ToPolyline2D(n)` → manual parametric calculation
- Polyline2D closed check: use `Flags` property
- `arc.StartAngle`/`arc.EndAngle` — ACadSharp stores arc angles in **radians** already (netDxf used degrees)
```csharp
using System.Collections.Generic;
using System.Linq;
using ACadSharp.Entities;
using CSMath;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.IO
{
internal static class Extensions
{
public static Vector ToOpenNest(this XY v)
{
return new Vector(v.X, v.Y);
}
public static Vector ToOpenNest(this XYZ v)
{
return new Vector(v.X, v.Y);
}
public static Arc ToOpenNest(this ACadSharp.Entities.Arc arc)
{
// ACadSharp stores angles in radians already
return new Arc(
arc.Center.X, arc.Center.Y, arc.Radius,
arc.StartAngle,
arc.EndAngle)
{
Layer = arc.Layer.ToOpenNest()
};
}
public static Circle ToOpenNest(this ACadSharp.Entities.Circle circle)
{
return new Circle(
circle.Center.X, circle.Center.Y,
circle.Radius)
{
Layer = circle.Layer.ToOpenNest()
};
}
public static Line ToOpenNest(this ACadSharp.Entities.Line line)
{
return new Line(
line.StartPoint.X, line.StartPoint.Y,
line.EndPoint.X, line.EndPoint.Y)
{
Layer = line.Layer.ToOpenNest()
};
}
public static List<Line> ToOpenNest(this Spline spline, int precision = 200)
{
var lines = new List<Line>();
var controlPoints = spline.ControlPoints
.Select(p => new Vector(p.X, p.Y)).ToList();
if (controlPoints.Count < 2)
return lines;
var lastPoint = controlPoints[0];
for (int i = 1; i < controlPoints.Count; i++)
{
var nextPoint = controlPoints[i];
lines.Add(new Line(lastPoint, nextPoint)
{ Layer = spline.Layer.ToOpenNest() });
lastPoint = nextPoint;
}
if (spline.IsClosed)
lines.Add(new Line(lastPoint, controlPoints[0])
{ Layer = spline.Layer.ToOpenNest() });
return lines;
}
public static List<Line> ToOpenNest(this Polyline polyline)
{
var lines = new List<Line>();
var vertices = polyline.Vertices.ToList();
if (vertices.Count == 0)
return lines;
var lastPoint = vertices[0].Location.ToOpenNest();
for (int i = 1; i < vertices.Count; i++)
{
var nextPoint = vertices[i].Location.ToOpenNest();
lines.Add(new Line(lastPoint, nextPoint)
{ Layer = polyline.Layer.ToOpenNest() });
lastPoint = nextPoint;
}
if ((polyline.Flags & PolylineFlags.ClosedPolylineOrClosedPolygonMeshInM) != 0)
lines.Add(new Line(lastPoint, vertices[0].Location.ToOpenNest())
{ Layer = polyline.Layer.ToOpenNest() });
return lines;
}
public static List<Line> ToOpenNest(this LwPolyline polyline)
{
var lines = new List<Line>();
var vertices = polyline.Vertices.ToList();
if (vertices.Count == 0)
return lines;
var lastPoint = new Vector(vertices[0].Location.X, vertices[0].Location.Y);
for (int i = 1; i < vertices.Count; i++)
{
var nextPoint = new Vector(vertices[i].Location.X, vertices[i].Location.Y);
lines.Add(new Line(lastPoint, nextPoint)
{ Layer = polyline.Layer.ToOpenNest() });
lastPoint = nextPoint;
}
if ((polyline.Flags & LwPolylineFlags.Closed) != 0)
lines.Add(new Line(lastPoint,
new Vector(vertices[0].Location.X, vertices[0].Location.Y))
{ Layer = polyline.Layer.ToOpenNest() });
return lines;
}
public static List<Line> ToOpenNest(this Ellipse ellipse, int precision = 200)
{
var lines = new List<Line>();
var points = new List<Vector>();
var startParam = ellipse.StartParameter;
var endParam = ellipse.EndParameter;
var paramRange = endParam - startParam;
var radius = System.Math.Sqrt(
ellipse.EndPoint.X * ellipse.EndPoint.X +
ellipse.EndPoint.Y * ellipse.EndPoint.Y);
for (int i = 0; i <= precision; i++)
{
var t = i / (double)precision;
var angle = startParam + paramRange * t;
var x = ellipse.Center.X + radius * System.Math.Cos(angle);
var y = ellipse.Center.Y + radius * System.Math.Sin(angle);
points.Add(new Vector(x, y));
}
for (int i = 1; i < points.Count; i++)
{
lines.Add(new Line(points[i - 1], points[i])
{ Layer = ellipse.Layer.ToOpenNest() });
}
if (points.Count > 1 &&
System.Math.Abs(paramRange - 2 * System.Math.PI) < 0.0001)
{
lines.Add(new Line(points[points.Count - 1], points[0])
{ Layer = ellipse.Layer.ToOpenNest() });
}
return lines;
}
public static Layer ToOpenNest(this ACadSharp.Tables.Layer layer)
{
return new Layer(layer.Name)
{
Color = System.Drawing.Color.FromArgb(
layer.Color.R, layer.Color.G, layer.Color.B),
IsVisible = layer.IsOn
};
}
public static XY ToAcad(this Vector v)
{
return new XY(v.X, v.Y);
}
public static XYZ ToAcadXYZ(this Vector v)
{
return new XYZ(v.X, v.Y, 0);
}
}
}
```
**Step 2: Commit**
```
refactor: rewrite IO Extensions for ACadSharp
```
---
### Task 5: Rewrite DxfImporter.cs for ACadSharp
**Files:**
- Modify: `OpenNest/IO/DxfImporter.cs`
**Step 1: Rewrite DxfImporter.cs**
Key changes:
- `DxfDocument.Load(path)``new DxfReader(path).Read()` returning `CadDocument`
- `doc.Entities.Lines``doc.Entities.OfType<ACadSharp.Entities.Line>()`
- Same pattern for Arcs, Circles, Splines, etc.
- `doc.Entities.Polylines2D``doc.Entities.OfType<LwPolyline>()` + `doc.Entities.OfType<Polyline>()`
- `doc.Entities.Polylines3D` → handled via `Polyline` type
```csharp
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using ACadSharp;
using ACadSharp.IO;
using OpenNest.Geometry;
namespace OpenNest.IO
{
public class DxfImporter
{
public int SplinePrecision { get; set; }
public DxfImporter()
{
}
private List<Entity> GetGeometry(CadDocument doc)
{
var entities = new List<Entity>();
var lines = new List<Line>();
var arcs = new List<Arc>();
foreach (var entity in doc.Entities)
{
switch (entity)
{
case ACadSharp.Entities.Spline spline:
lines.AddRange(spline.ToOpenNest(SplinePrecision));
break;
case ACadSharp.Entities.LwPolyline lwPolyline:
lines.AddRange(lwPolyline.ToOpenNest());
break;
case ACadSharp.Entities.Polyline polyline:
lines.AddRange(polyline.ToOpenNest());
break;
case ACadSharp.Entities.Ellipse ellipse:
lines.AddRange(ellipse.ToOpenNest(SplinePrecision));
break;
case ACadSharp.Entities.Line line:
lines.Add(line.ToOpenNest());
break;
case ACadSharp.Entities.Arc arc:
arcs.Add(arc.ToOpenNest());
break;
case ACadSharp.Entities.Circle circle:
entities.Add(circle.ToOpenNest());
break;
}
}
Helper.Optimize(lines);
Helper.Optimize(arcs);
entities.AddRange(lines);
entities.AddRange(arcs);
return entities;
}
public bool GetGeometry(Stream stream, out List<Entity> geometry)
{
bool success = false;
try
{
using var reader = new DxfReader(stream);
var doc = reader.Read();
geometry = GetGeometry(doc);
success = true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
geometry = new List<Entity>();
}
return success;
}
public bool GetGeometry(string path, out List<Entity> geometry)
{
bool success = false;
try
{
using var reader = new DxfReader(path);
var doc = reader.Read();
geometry = GetGeometry(doc);
success = true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
geometry = new List<Entity>();
}
return success;
}
}
}
```
**Step 2: Commit**
```
refactor: rewrite DxfImporter for ACadSharp
```
---
### Task 6: Rewrite DxfExporter.cs for ACadSharp
**Files:**
- Modify: `OpenNest/IO/DxfExporter.cs`
**Step 1: Rewrite DxfExporter.cs**
Key changes:
- `new DxfDocument()``new CadDocument()`
- `doc.Entities.Add(entity)``doc.Entities.Add(entity)` (same pattern)
- `doc.Save(stream)``using var writer = new DxfWriter(stream); writer.Write(doc);`
- `new netDxf.Entities.Line(pt1, pt2)``new ACadSharp.Entities.Line { StartPoint = xyz1, EndPoint = xyz2 }`
- `new netDxf.Vector2(x, y)``new XYZ(x, y, 0)` (ACadSharp Lines use XYZ)
- `AciColor.Red``new Color(1)` (ACI color index 1 = red)
- `AciColor.Blue``new Color(5)` (ACI color index 5 = blue)
- `AciColor.Cyan``new Color(4)` (ACI color index 4 = cyan)
- `Linetype.Dashed` → look up or create dashed linetype
- `Vector2 curpos``XYZ curpos` (track as 3D)
- Arc angles: netDxf used degrees, ACadSharp uses radians — remove degree conversions
```csharp
using System;
using System.Diagnostics;
using System.IO;
using ACadSharp;
using ACadSharp.Entities;
using ACadSharp.IO;
using ACadSharp.Tables;
using CSMath;
using OpenNest.CNC;
using OpenNest.Math;
namespace OpenNest.IO
{
using AcadLine = ACadSharp.Entities.Line;
using AcadArc = ACadSharp.Entities.Arc;
using AcadCircle = ACadSharp.Entities.Circle;
public class DxfExporter
{
private CadDocument doc;
private XYZ curpos;
private Mode mode;
private readonly Layer cutLayer;
private readonly Layer rapidLayer;
private readonly Layer plateLayer;
public DxfExporter()
{
doc = new CadDocument();
cutLayer = new Layer("Cut");
cutLayer.Color = new Color(1); // Red
rapidLayer = new Layer("Rapid");
rapidLayer.Color = new Color(5); // Blue
plateLayer = new Layer("Plate");
plateLayer.Color = new Color(4); // Cyan
}
public void ExportProgram(Program program, Stream stream)
{
doc = new CadDocument();
EnsureLayers();
AddProgram(program);
using var writer = new DxfWriter(stream);
writer.Write(doc);
}
public bool ExportProgram(Program program, string path)
{
Stream stream = null;
bool success = false;
try
{
stream = File.Create(path);
ExportProgram(program, stream);
success = true;
}
catch
{
Debug.Fail("DxfExporter.ExportProgram failed to write program to file: " + path);
}
finally
{
if (stream != null)
stream.Close();
}
return success;
}
public void ExportPlate(Plate plate, Stream stream)
{
doc = new CadDocument();
EnsureLayers();
AddPlateOutline(plate);
foreach (var part in plate.Parts)
{
var endpt = part.Location.ToAcadXYZ();
var line = new AcadLine();
line.StartPoint = curpos;
line.EndPoint = endpt;
line.Layer = rapidLayer;
doc.Entities.Add(line);
curpos = endpt;
AddProgram(part.Program);
}
using var writer = new DxfWriter(stream);
writer.Write(doc);
}
public bool ExportPlate(Plate plate, string path)
{
Stream stream = null;
bool success = false;
try
{
stream = File.Create(path);
ExportPlate(plate, stream);
success = true;
}
catch
{
Debug.Fail("DxfExporter.ExportPlate failed to write plate to file: " + path);
}
finally
{
if (stream != null)
stream.Close();
}
return success;
}
private void EnsureLayers()
{
if (!doc.Layers.Contains(cutLayer.Name))
doc.Layers.Add(cutLayer);
if (!doc.Layers.Contains(rapidLayer.Name))
doc.Layers.Add(rapidLayer);
if (!doc.Layers.Contains(plateLayer.Name))
doc.Layers.Add(plateLayer);
}
private void AddPlateOutline(Plate plate)
{
XYZ pt1, pt2, pt3, pt4;
switch (plate.Quadrant)
{
case 1:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, plate.Size.Height, 0);
pt3 = new XYZ(plate.Size.Width, plate.Size.Height, 0);
pt4 = new XYZ(plate.Size.Width, 0, 0);
break;
case 2:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, plate.Size.Height, 0);
pt3 = new XYZ(-plate.Size.Width, plate.Size.Height, 0);
pt4 = new XYZ(-plate.Size.Width, 0, 0);
break;
case 3:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, -plate.Size.Height, 0);
pt3 = new XYZ(-plate.Size.Width, -plate.Size.Height, 0);
pt4 = new XYZ(-plate.Size.Width, 0, 0);
break;
case 4:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, -plate.Size.Height, 0);
pt3 = new XYZ(plate.Size.Width, -plate.Size.Height, 0);
pt4 = new XYZ(plate.Size.Width, 0, 0);
break;
default:
return;
}
AddLine(pt1, pt2, plateLayer);
AddLine(pt2, pt3, plateLayer);
AddLine(pt3, pt4, plateLayer);
AddLine(pt4, pt1, plateLayer);
// Inner margin lines
var m1 = new XYZ(pt1.X + plate.EdgeSpacing.Left, pt1.Y + plate.EdgeSpacing.Bottom, 0);
var m2 = new XYZ(m1.X, pt2.Y - plate.EdgeSpacing.Top, 0);
var m3 = new XYZ(pt3.X - plate.EdgeSpacing.Right, m2.Y, 0);
var m4 = new XYZ(m3.X, m1.Y, 0);
AddLine(m1, m2, plateLayer);
AddLine(m2, m3, plateLayer);
AddLine(m3, m4, plateLayer);
AddLine(m4, m1, plateLayer);
}
private void AddLine(XYZ start, XYZ end, Layer layer)
{
var line = new AcadLine();
line.StartPoint = start;
line.EndPoint = end;
line.Layer = layer;
doc.Entities.Add(line);
}
private void AddProgram(Program program)
{
mode = program.Mode;
for (int i = 0; i < program.Length; ++i)
{
var code = program[i];
switch (code.Type)
{
case CodeType.ArcMove:
var arc = (ArcMove)code;
AddArcMove(arc);
break;
case CodeType.LinearMove:
var line = (LinearMove)code;
AddLinearMove(line);
break;
case CodeType.RapidMove:
var rapid = (RapidMove)code;
AddRapidMove(rapid);
break;
case CodeType.SubProgramCall:
var tmpmode = mode;
var subpgm = (CNC.SubProgramCall)code;
AddProgram(subpgm.Program);
mode = tmpmode;
break;
}
}
}
private void AddLinearMove(LinearMove line)
{
var pt = line.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
pt = new XYZ(pt.X + curpos.X, pt.Y + curpos.Y, 0);
AddLine(curpos, pt, cutLayer);
curpos = pt;
}
private void AddRapidMove(RapidMove rapid)
{
var pt = rapid.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
pt = new XYZ(pt.X + curpos.X, pt.Y + curpos.Y, 0);
AddLine(curpos, pt, rapidLayer);
curpos = pt;
}
private void AddArcMove(ArcMove arc)
{
var center = arc.CenterPoint.ToAcadXYZ();
var endpt = arc.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
{
endpt = new XYZ(endpt.X + curpos.X, endpt.Y + curpos.Y, 0);
center = new XYZ(center.X + curpos.X, center.Y + curpos.Y, 0);
}
// ACadSharp uses radians — no conversion needed
var startAngle = System.Math.Atan2(
curpos.Y - center.Y,
curpos.X - center.X);
var endAngle = System.Math.Atan2(
endpt.Y - center.Y,
endpt.X - center.X);
if (arc.Rotation == OpenNest.RotationType.CW)
Generic.Swap(ref startAngle, ref endAngle);
var dx = endpt.X - center.X;
var dy = endpt.Y - center.Y;
var radius = System.Math.Sqrt(dx * dx + dy * dy);
if (startAngle.IsEqualTo(endAngle))
{
var circle = new AcadCircle();
circle.Center = center;
circle.Radius = radius;
circle.Layer = cutLayer;
doc.Entities.Add(circle);
}
else
{
var arc2 = new AcadArc();
arc2.Center = center;
arc2.Radius = radius;
arc2.StartAngle = startAngle;
arc2.EndAngle = endAngle;
arc2.Layer = cutLayer;
doc.Entities.Add(arc2);
}
curpos = endpt;
}
}
}
```
**Step 2: Commit**
```
refactor: rewrite DxfExporter for ACadSharp
```
---
### Task 7: Update MainApp.cs for .NET 8
**Files:**
- Modify: `OpenNest/MainApp.cs`
**Step 1: Simplify MainApp.cs**
.NET 8 WinForms handles DPI awareness via the `ApplicationHighDpiMode` project property. Remove the manual `SetProcessDPIAware()` P/Invoke.
```csharp
using System;
using System.Windows.Forms;
using OpenNest.Forms;
namespace OpenNest
{
internal static class MainApp
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
```
**Step 2: Commit**
```
refactor: simplify MainApp for .NET 8 DPI handling
```
---
### Task 8: Update solution file and clean up
**Files:**
- Modify: `OpenNest.sln`
- Delete: `packages/` directory (if present on disk)
- Verify: `.gitignore` already ignores `packages/`
**Step 1: Update solution file**
Replace `OpenNest.sln` with a clean version. The project GUIDs stay the same but the solution format is updated:
```
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.0.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest", "OpenNest\OpenNest.csproj", "{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest.Core", "OpenNest.Core\OpenNest.Core.csproj", "{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest.Engine", "OpenNest.Engine\OpenNest.Engine.csproj", "{0083B9CC-54AD-4085-A30D-56BC6834B71A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Release|Any CPU.Build.0 = Release|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Release|Any CPU.Build.0 = Release|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
```
**Step 2: Full solution build**
Run: `dotnet build OpenNest.sln`
Expected: Build succeeds with 0 errors.
**Step 3: Fix any remaining build errors**
If there are build errors, fix them. Common issues:
- `System.IO.Compression` — now built into .NET 8, no reference needed
- Missing `using` statements for new namespaces
- API differences in `System.Drawing.Common` on .NET 8
- `LayoutViewGL.cs` exists on disk but was never in the old csproj — SDK-style will auto-include it. Exclude or delete it if it causes errors.
**Step 4: Commit**
```
feat: complete .NET 8 migration — update solution and clean up
```
---
### Task 9: Update CLAUDE.md
**Files:**
- Modify: `CLAUDE.md`
**Step 1: Update build instructions and dependency info**
Update the Build section to reflect `dotnet build` instead of `msbuild`, remove references to `packages/` folder and `nuget restore`, update netDxf references to ACadSharp, and note .NET 8 target.
**Step 2: Commit**
```
docs: update CLAUDE.md for .NET 8 migration
```