Files
marekandClaude Opus 5 001afc2d81 Šrafy: rozvinout vzor na čáry místo plné výplně
Každá šrafa se kreslila jako plná výplň, bez ohledu na vzor. Logo md_sfdi.dxf
má 3× ANSI31 (šikmé čáry) – vycházel z něj plný blok. Nový SrafaVzor rozvine
definiční čáry vzoru na úsečky: rodina rovnoběžek podle vektoru Offset,
ořezaná hranicemi šrafy sudo-lichou paritou (takže díry ve vnořených smyčkách
vyjdou správně) a čárkováním z DashLengths. Proti zamrznutí náhledu při vadném
měřítku ve zdroji je strop 20 000 úseček na šrafu.

Rozhraní: Fill(loops) dostává druhý parametr Vypln. Rastrové renderery kreslí
rozvinuté čáry (Skia vzory neumí), DXF export je ignoruje a zapíše zpátky HATCH
s původním jménem vzoru, měřítkem a úhlem – v CADu tak zůstane editovatelný.
Když se vzor rozvinout nepodaří, plocha zůstane prázdná; zaplácnout ji načerno
je horší než ji nevyplnit.

Ověřeno i vizuálně: z 9 dodávaných log se změnilo jen md_sfdi, ostatních 8 je
po renderu do PNG pixel po pixelu shodných s předchozím stavem.

105 testů zelených.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 14:36:51 +02:00

120 lines
3.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Rozpisky.Dxf;
using Rozpisky.Rendering;
using Xunit;
namespace Rozpisky.Tests;
/// <summary>
/// Šrafy se vzorem. Logo <c>md_sfdi.dxf</c> má 3× vzor ANSI31 (šikmé čáry); dřív se každá
/// šrafa kreslila jako plná výplň, takže z něj byl v náhledu, PNG, PDF i DXF černý blok.
/// </summary>
public class SrafaVzorTesty
{
private static RecordingRenderer Prehraj(string logo)
{
var g = new RecordingRenderer();
new DxfTemplate(TestData.Logo(logo)).Replay(g);
return g;
}
[Fact]
public void Ansi31SeRozvineNaCary()
{
var g = Prehraj("md_sfdi.dxf");
var vzorovane = g.Vyplne.Where(v => !v.JeSolid).ToList();
Assert.Equal(3, vzorovane.Count);
foreach (var v in vzorovane)
{
Assert.Equal("ANSI31", v.Vzor);
Assert.NotNull(v.Cary);
Assert.NotEmpty(v.Cary!);
}
}
[Fact]
public void CaryLeziUvnitrObrysuSrafy()
{
var g = Prehraj("md_sfdi.dxf");
for (int i = 0; i < g.Vyplne.Count; i++)
{
if (g.Vyplne[i].JeSolid) continue;
var (minX, minY, maxX, maxY) = Bbox(g.Fills[i]);
foreach (var u in g.Vyplne[i].Cary!)
{
Assert.InRange(u.X1, minX - 0.01, maxX + 0.01);
Assert.InRange(u.Y1, minY - 0.01, maxY + 0.01);
Assert.InRange(u.X2, minX - 0.01, maxX + 0.01);
Assert.InRange(u.Y2, minY - 0.01, maxY + 0.01);
}
}
}
[Fact]
public void CaryAnsi31JsouRovnobezneA45Stupnu()
{
var g = Prehraj("md_sfdi.dxf");
var cary = g.Vyplne.Where(v => !v.JeSolid).SelectMany(v => v.Cary!).ToList();
Assert.NotEmpty(cary);
foreach (var u in cary)
{
double dx = u.X2 - u.X1, dy = u.Y2 - u.Y1;
if (Math.Abs(dx) + Math.Abs(dy) < 1e-9) continue;
// ANSI31 je 45° (v tomhle logu 45,007° tak to má zapsané zdroj).
double uhel = Math.Atan2(dy, dx) * 180 / Math.PI;
Assert.InRange(uhel, 44.9, 45.1);
}
}
[Fact]
public void PlnaSrafaZustavaPlna()
{
// Ostatní loga mají výhradně SOLID nesmí se z nich stát čárová šrafa.
var g = Prehraj("exprojekt.dxf");
Assert.NotEmpty(g.Vyplne);
Assert.All(g.Vyplne, v =>
{
Assert.True(v.JeSolid);
Assert.Null(v.Cary);
});
}
[Fact]
public void VychoziVyplnJePlna()
{
// Pojistka: `default(Vypln)` musí být plná výplň, jinak by volání Fill(loops) bez
// druhého parametru přestalo cokoli kreslit.
Assert.True(default(Vypln).JeSolid);
}
[Fact]
public void BezDefiniceVzoruSeNevrationZadneCary()
{
var ctverec = new[]
{
new Contour(new[]
{
Edge.Line(0, 0, 10, 0), Edge.Line(10, 0, 10, 10),
Edge.Line(10, 10, 0, 10), Edge.Line(0, 10, 0, 0),
}, Closed: true),
};
Assert.Empty(SrafaVzor.Cary(null, ctverec));
}
private static (double MinX, double MinY, double MaxX, double MaxY) Bbox(
IReadOnlyList<IReadOnlyList<(double X, double Y)>> smycky)
{
double minX = double.MaxValue, minY = double.MaxValue, maxX = double.MinValue, maxY = double.MinValue;
foreach (var s in smycky)
foreach (var (x, y) in s)
{
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
}
return (minX, minY, maxX, maxY);
}
}