Š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>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user