mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-14 03:25:07 -05:00
Geometry, nesting and import code now call PortableMath instead of System.Math for Sin, Cos, Tan, Atan, Atan2, Asin and Acos (Core, Engine, IO, Data; 269 calls in 64 files). A test fails if those projects call the runtime's trig again. With the runtime's trig, Windows and Linux disagreed in the last bit at non-quarter angles and the same job could nest differently: the learned-angle nests (a cross fixture) placed 23 parts in Strip on Windows and 28 on Linux. Now every Engine.Tests pin gives the same layout on both (3600 passing on Linux and on DesktopPC), Linux layouts are unchanged, and the learned-angle rows are re-pinned to the shared layouts, which pass the overlap and work-area checks, and run on every platform again. Fixes Project Memory 518f3b58.
118 lines
4.8 KiB
C#
118 lines
4.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using OpenNest.Engine.Jobs.Adapters;
|
|
using OpenNest.Engine.Jobs.CommonLines;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Math;
|
|
|
|
namespace OpenNest.Engine.Jobs.Placement;
|
|
|
|
/// <summary>A proposed fill: new parts and the common-line groups among them (empty when none).</summary>
|
|
public sealed record PlateFillProposal(List<Part> Parts, IReadOnlyList<CommonLineGroup> CommonLines);
|
|
|
|
public static partial class PlateFillService
|
|
{
|
|
/// <summary>
|
|
/// <see cref="FillItem(string, Plate, NestItem, Box, IProgress{NestProgress}, CancellationToken)"/>
|
|
/// at a common-line usage level. With the level off, a drawing that does not allow common lines,
|
|
/// or no positive kerf, it is exactly that fill. Otherwise the area is also filled with kerf-gap
|
|
/// pairs of the drawing (<see cref="CommonLinePair"/>), each expanded into its two parts and a
|
|
/// group, and the pair fill replaces the plain one when it places more parts, or (at
|
|
/// <see cref="CommonLineUsage.Whenever"/>) the same number and the strategy's own ranking does
|
|
/// not prefer the plain fill. Ties at <see cref="CommonLineUsage.WhenItAddsParts"/> keep the
|
|
/// plain fill. The proposal is not committed: the caller adds the parts, then the groups.
|
|
/// </summary>
|
|
public static PlateFillProposal FillItem(
|
|
string strategy,
|
|
Plate plate,
|
|
NestItem item,
|
|
Box workArea,
|
|
CommonLineUsage usage,
|
|
double kerf,
|
|
IProgress<NestProgress> progress,
|
|
CancellationToken token
|
|
)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(item);
|
|
var plain = FillItem(strategy, plate, item, workArea, progress, token);
|
|
var none = new PlateFillProposal(plain, Array.Empty<CommonLineGroup>());
|
|
if (usage == CommonLineUsage.Off || item.Drawing?.AllowCommonLine != true
|
|
|| !double.IsFinite(kerf) || kerf <= 0 || !(kerf < plate.PartSpacing)
|
|
|| item.Quantity is > 0 and < 2 || token.IsCancellationRequested)
|
|
return none;
|
|
var constraints = item.Drawing.Constraints;
|
|
// The second copy turns half a turn, so a fixed rotation must allow its 180° equivalent.
|
|
if (item.StepAngle > 0 && constraints?.Allow180Equivalent != true)
|
|
return none;
|
|
|
|
CommonLinePair pair;
|
|
try
|
|
{
|
|
pair = CommonLinePair.Build(PartGeometrySnapshot.FromProgram(item.Drawing.Program), kerf);
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
return none;
|
|
}
|
|
if (pair == null)
|
|
return none;
|
|
|
|
var proxy = new Drawing(item.Drawing.Name, DrawingJobMapper.ToProgram(pair.Geometry))
|
|
{
|
|
Priority = item.Drawing.Priority,
|
|
Constraints = constraints,
|
|
};
|
|
var proxyItem = new NestItem
|
|
{
|
|
Drawing = proxy,
|
|
Priority = item.Priority,
|
|
Quantity = item.Quantity > 0 ? item.Quantity / 2 : item.Quantity,
|
|
StepAngle = item.StepAngle,
|
|
RotationStart = item.RotationStart,
|
|
RotationEnd = item.RotationEnd,
|
|
};
|
|
// Previews show the plain fill; the pair fill is compared once both are done.
|
|
var pairs = FillItem(strategy, plate, proxyItem, workArea, null, token);
|
|
if (pairs.Count == 0 || token.IsCancellationRequested)
|
|
return none;
|
|
|
|
var parts = new List<Part>(2 * pairs.Count);
|
|
var groups = new List<CommonLineGroup>(pairs.Count);
|
|
foreach (var placed in pairs)
|
|
{
|
|
var rotation = placed.Rotation;
|
|
var first = Place(item.Drawing, rotation, placed.Location);
|
|
var sine = PortableMath.Sin(rotation);
|
|
var cosine = PortableMath.Cos(rotation);
|
|
var second = Place(item.Drawing, rotation + CommonLinePair.SecondRotation,
|
|
new Vector(placed.Location.X + pair.SecondX * cosine - pair.SecondY * sine,
|
|
placed.Location.Y + pair.SecondX * sine + pair.SecondY * cosine));
|
|
parts.Add(first);
|
|
parts.Add(second);
|
|
groups.Add(new CommonLineGroup(new[] { first, second }, kerf));
|
|
}
|
|
|
|
if (parts.Count < plain.Count)
|
|
return none;
|
|
if (parts.Count == plain.Count)
|
|
{
|
|
if (usage != CommonLineUsage.Whenever
|
|
|| RequireFiller(strategy, plate).IsBetterFill(plain, parts, workArea))
|
|
return none;
|
|
}
|
|
return new PlateFillProposal(parts, groups);
|
|
}
|
|
|
|
// The proxy's frame is the drawing's own: rotate about the program origin, then move.
|
|
private static Part Place(Drawing drawing, double rotation, Vector location)
|
|
{
|
|
var part = new Part(drawing);
|
|
part.Rotate(rotation);
|
|
part.Location = location;
|
|
part.UpdateBounds();
|
|
return part;
|
|
}
|
|
}
|