Baseline: současný stav projektu Rozpisky
První commit — WPF aplikace pro generování výkresových rohových razítek (.NET 10, SkiaSharp, ACadSharp, ClosedXML) včetně xUnit testů, DXF šablony, log a dokumentace. Build výstupy (bin/, obj/, .vs/) jsou ignorovány. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
using System.IO;
|
||||
using ACadSharp.Entities;
|
||||
using ACadSharp.IO;
|
||||
using Rozpisky.Dxf;
|
||||
using Rozpisky.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace Rozpisky.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Analytické oblouky end-to-end: model <see cref="Edge"/>/<see cref="Contour"/>, jemnost vzorkování
|
||||
/// (sagitta) a round-trip přes DXF export (<see cref="DxfProfileRenderer"/>) + import
|
||||
/// (<see cref="DxfTemplate"/>). Hlídá zejména znaménka (CCW/CW) a převod bulge → oblouk.
|
||||
/// </summary>
|
||||
public class ObloukyTesty
|
||||
{
|
||||
private static (double W, double H, double MinX, double MinY) Bbox(IEnumerable<(double X, double Y)> pts)
|
||||
{
|
||||
double ax = double.MaxValue, ay = double.MaxValue, bx = double.MinValue, by = double.MinValue;
|
||||
foreach (var (x, y) in pts) { ax = Math.Min(ax, x); ay = Math.Min(ay, y); bx = Math.Max(bx, x); by = Math.Max(by, y); }
|
||||
return (bx - ax, by - ay, ax, ay);
|
||||
}
|
||||
|
||||
private static List<(double X, double Y)> Zplosti(Contour c, double tol = 0.005)
|
||||
{
|
||||
var pts = new List<(double X, double Y)>();
|
||||
foreach (var e in c.Edges) e.AppendPoints(pts, tol);
|
||||
if (c.Edges.Count > 0) pts.Add((c.Edges[^1].EX, c.Edges[^1].EY));
|
||||
return pts;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Edge_PointAt_A_Reversed_SediKoncoveBody()
|
||||
{
|
||||
// Čtvrtkružnice jednotkového kruhu proti směru hodin z (1,0) do (0,1).
|
||||
var e = Edge.Arc(1, 0, 0, 1, 0, 0, r: 1, a0: 0, sweep: Math.PI / 2);
|
||||
var s = e.PointAt(e.A0);
|
||||
var en = e.PointAt(e.A0 + e.Sweep);
|
||||
Assert.Equal(1, s.X, 6); Assert.Equal(0, s.Y, 6);
|
||||
Assert.Equal(0, en.X, 6); Assert.Equal(1, en.Y, 6);
|
||||
|
||||
var r = e.Reversed();
|
||||
Assert.Equal(e.EX, r.SX, 9); Assert.Equal(e.SX, r.EX, 9);
|
||||
Assert.Equal(Math.PI / 2, r.A0, 9);
|
||||
Assert.Equal(-Math.PI / 2, r.Sweep, 9);
|
||||
// Otočená hrana má stejný geometrický počátek jako původní konec.
|
||||
var rs = r.PointAt(r.A0);
|
||||
Assert.Equal(0, rs.X, 6); Assert.Equal(1, rs.Y, 6);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(10.0, 0.02)]
|
||||
[InlineData(3.825, 0.02)]
|
||||
[InlineData(50.0, 0.05)]
|
||||
public void ArcSegments_DodrziSagittu(double r, double tol)
|
||||
{
|
||||
double sweep = 2 * Math.PI;
|
||||
int n = ContourGeometry.ArcSegments(sweep, r, tol);
|
||||
double delta = sweep / n;
|
||||
double sagitta = r * (1 - Math.Cos(delta / 2));
|
||||
Assert.True(sagitta <= tol + 1e-9, $"sagitta {sagitta} > tol {tol} (n={n})");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalytickyKruh_ZplosteniDaSpravnyBbox()
|
||||
{
|
||||
// Jediná Arc hrana přes celý kruh (r=3,825) → bbox = průměr, ostrý na tětivovou toleranci.
|
||||
var e = Edge.Arc(3.825, 0, 3.825, 0, 0, 0, r: 3.825, a0: 0, sweep: 2 * Math.PI);
|
||||
var (w, h, _, _) = Bbox(Zplosti(new Contour(new[] { e }, Closed: true)));
|
||||
Assert.InRange(w, 7.64, 7.65);
|
||||
Assert.InRange(h, 7.64, 7.65);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1.0)] // CCW horní půlkruh
|
||||
[InlineData(-1.0)] // CW dolní půlkruh – hlídá normalizaci úhlů v exportu i importu
|
||||
public void RoundTrip_HatchOblouk_ZachovaGeometrii(double smer)
|
||||
{
|
||||
double r = 5;
|
||||
double sweep = smer * Math.PI;
|
||||
// Půldisk: oblouk (5,0)→(-5,0) + úsečka zpět.
|
||||
var arc = Edge.Arc(5, 0, -5, 0, 0, 0, r, a0: 0, sweep: sweep);
|
||||
var line = Edge.Line(-5, 0, 5, 0);
|
||||
var contour = new Contour(new[] { arc, line }, Closed: true);
|
||||
|
||||
var g = new DxfProfileRenderer();
|
||||
g.Layer("0", 0x000000);
|
||||
g.Fill(new[] { contour });
|
||||
|
||||
// Export → dočasný DXF → import zpět a porovnání bboxu (sagitta ≤ 0,02 mm).
|
||||
string path = Path.Combine(Path.GetTempPath(), $"rozpisky_arc_{Guid.NewGuid():N}.dxf");
|
||||
try
|
||||
{
|
||||
DxfWriter.Write(path, g.Document, binary: false);
|
||||
var rec = new RecordingRenderer();
|
||||
new DxfTemplate(path).Replay(rec);
|
||||
|
||||
var loop = Assert.Single(Assert.Single(rec.FillContours));
|
||||
var (w, h, minX, minY) = Bbox(Zplosti(loop, 0.02));
|
||||
Assert.InRange(w, 9.9, 10.05); // šířka = 2r
|
||||
Assert.InRange(h, 4.95, 5.05); // výška = r
|
||||
Assert.InRange(minX, -5.05, -4.95);
|
||||
if (smer > 0) Assert.InRange(minY, -0.05, 0.05); // horní půlkruh: y ∈ [0, 5]
|
||||
else Assert.InRange(minY, -5.05, -4.95); // dolní půlkruh: y ∈ [-5, 0]
|
||||
}
|
||||
finally { if (File.Exists(path)) File.Delete(path); }
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1.0)]
|
||||
[InlineData(-1.0)]
|
||||
public void StandaloneArc_Export_DaEntituArcSpravnymUhlem(double smer)
|
||||
{
|
||||
double r = 4, a0 = 0.3, sweep = smer * 1.2;
|
||||
var edge = Edge.Arc(0, 0, 0, 0, cx: 1, cy: 2, r: r, a0: a0, sweep: sweep);
|
||||
// dopočti skutečné koncové body
|
||||
var s = edge.PointAt(a0); var e = edge.PointAt(a0 + sweep);
|
||||
var contour = new Contour(new[] { Edge.Arc(s.X, s.Y, e.X, e.Y, 1, 2, r, a0, sweep) }, Closed: false);
|
||||
|
||||
var g = new DxfProfileRenderer();
|
||||
g.Layer("0", 0);
|
||||
g.Stroke(contour);
|
||||
|
||||
var arc = Assert.Single(g.Document.Entities.OfType<Arc>());
|
||||
Assert.Equal(1, arc.Center.X, 6);
|
||||
Assert.Equal(2, arc.Center.Y, 6);
|
||||
Assert.Equal(r, arc.Radius, 6);
|
||||
// ARC entita je vždy CCW start→end; pro CW rozsah se meze prohodí, ale koncové BODY musí sedět.
|
||||
var pStart = (arc.Center.X + r * Math.Cos(arc.StartAngle), arc.Center.Y + r * Math.Sin(arc.StartAngle));
|
||||
var pEnd = (arc.Center.X + r * Math.Cos(arc.EndAngle), arc.Center.Y + r * Math.Sin(arc.EndAngle));
|
||||
// množina koncových bodů oblouku se musí shodovat (v jednom z pořadí).
|
||||
bool ok = (Blizko(pStart, s) && Blizko(pEnd, e)) || (Blizko(pStart, e) && Blizko(pEnd, s));
|
||||
Assert.True(ok, $"koncové body oblouku nesedí: start={pStart} end={pEnd} vs s={s} e={e}");
|
||||
|
||||
static bool Blizko((double X, double Y) a, (double X, double Y) b)
|
||||
=> Math.Abs(a.X - b.X) < 1e-6 && Math.Abs(a.Y - b.Y) < 1e-6;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user