Barvy: zachovat ACI index a ByLayer po celé cestě DXF → model → DXF
Šablona i všech 9 log jsou zapsané výhradně indexovanými barvami AutoCADu (group 62) – true color v nich není ani jednou. Kreslicí rozhraní přitom neslo jen `int rgb`, takže se index i ByLayer ztratily už při čtení a export zapisoval všechno jako natvrdo RGB (group 420). Výkres v CADu pak přestal reagovat na tabulku barev a barva hladiny se rozpadla na kopie na entitách. `IProfileRenderer.Layer(name, rgb, mm)` se mění na `Stav(in KresliciStav)`. Stav nese barvu i s původem (`Barva`: ACI index / true color / ByLayer / ByBlock), typ čáry, tloušťku a celý popis hladiny (`HladinaStav`) – hladina se ve výstupu staví z tabulky hladin, ne z první entity, která na ní přistála. Rámeček `!!NOPRINT` proto zůstane netisknutelný. Paleta ACI je vlastní (`AciPaleta`), ne z ACadSharp: ta se od AutoCADu liší u 33 indexů, mimo jiné u 8 (použitý v šabloně) a u posledního indexu většiny desítek odstínů, kde vrací barvu ze sousední skupiny – 29 a 39 tam dokonce dostanou tutéž barvu. Rozsah 10–249 se dopočítává, ne vypisuje. Co ověřují nové testy (93 zelených): - paleta proti AutoCADu i proti ACadSharp, se seznamem známých odchylek, - rozřešení ByLayer/ByBlock, tloušťky a typu čáry, - kolečko šablona → export → načtení: žádná entita není true color, indexy odpovídají zdroji, ByLayer zůstane ByLayer, hladiny mají barvu i příznak tisku ze zdrojové tabulky. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
using Rozpisky.Rendering;
|
||||
using Xunit;
|
||||
using AcColor = ACadSharp.Color;
|
||||
|
||||
namespace Rozpisky.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Paleta indexovaných barev AutoCADu. Rozhoduje o tom, jak vypadá náhled proti CADu –
|
||||
/// v exportu se index zapisuje číslem, takže tahle tabulka do souboru nikdy neproteče.
|
||||
/// </summary>
|
||||
public class AciPaletaTesty
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(1, 0xFF0000)] [InlineData(2, 0xFFFF00)] [InlineData(3, 0x00FF00)]
|
||||
[InlineData(4, 0x00FFFF)] [InlineData(5, 0x0000FF)] [InlineData(6, 0xFF00FF)]
|
||||
[InlineData(7, 0xFFFFFF)] [InlineData(8, 0x414141)] [InlineData(9, 0x808080)]
|
||||
[InlineData(255, 0xFFFFFF)] [InlineData(250, 0x333333)]
|
||||
public void ZakladniBarvySedle(int index, int rgb) => Assert.Equal(rgb, AciPaleta.Rgb(index));
|
||||
|
||||
[Theory]
|
||||
// Odstínová část: první čtveřice ze skupiny červené a modrozelené, ověřená proti AutoCADu.
|
||||
[InlineData(10, 0xFF0000)] [InlineData(11, 0xFF7F7F)] [InlineData(12, 0xA50000)]
|
||||
[InlineData(13, 0xA55252)] [InlineData(20, 0xFF3F00)] [InlineData(21, 0xFF9F7F)]
|
||||
[InlineData(22, 0xA52900)] [InlineData(136, 0x004C4C)] [InlineData(18, 0x260000)]
|
||||
public void OdstinySeDopocitavajiSpravne(int index, int rgb) => Assert.Equal(rgb, AciPaleta.Rgb(index));
|
||||
|
||||
[Fact]
|
||||
public void MimoRozsahJeCerna()
|
||||
{
|
||||
Assert.Equal(0x000000, AciPaleta.Rgb(-1));
|
||||
Assert.Equal(0x000000, AciPaleta.Rgb(256));
|
||||
Assert.Equal(0x000000, AciPaleta.Rgb(0)); // 0 = ByBlock, samostatně se nekreslí
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KazdyOdstinMaVlastniBarvu()
|
||||
{
|
||||
// 24 skupin po 10 – žádné dvě sousední skupiny nesmí vyjít stejně. Přesně tímhle se
|
||||
// ACadSharp plete (29 a 39 tam mají tutéž barvu), a proto máme paletu vlastní.
|
||||
for (int skupina = 0; skupina + 1 < 24; skupina++)
|
||||
for (int pozice = 0; pozice < 10; pozice++)
|
||||
Assert.NotEqual(AciPaleta.Rgb(10 + skupina * 10 + pozice),
|
||||
AciPaleta.Rgb(20 + skupina * 10 + pozice));
|
||||
}
|
||||
|
||||
/// <summary>Indexy, kde se <c>ACadSharp.Color.GetIndexRGB</c> od AutoCADu liší o víc než
|
||||
/// zaokrouhlení: dva stupně šedi (8, 9), šedý klín na konci palety (250–254) a poslední
|
||||
/// index většiny desítek odstínů (29, 119, 139, …), kde ACadSharp bere barvu ze sousední
|
||||
/// skupiny – proto tam vycházejí i barvy mimo jas dané pozice.</summary>
|
||||
private static readonly int[] ZnameOdchylky =
|
||||
{
|
||||
8, 9, 29, 91, 109, 119, 129, 139, 145, 147, 149, 157, 159, 167, 169, 177, 179,
|
||||
187, 189, 192, 197, 199, 203, 209, 219, 229, 239, 249, 250, 251, 252, 253, 254,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void ShodujeSeSAcadSharpKromeZnamychOdchylek()
|
||||
{
|
||||
// Pojistka proti tichému rozejití při upgradu balíčku. Jinde než v seznamu se smí lišit
|
||||
// nanejvýš o 1 na kanál (jiné zaokrouhlení při dopočtu odstínu) – to okem nepoznat.
|
||||
var zname = new HashSet<int>(ZnameOdchylky);
|
||||
var novaOdchylka = new List<int>();
|
||||
var zmizelaOdchylka = new List<int>();
|
||||
|
||||
for (int i = 0; i <= 255; i++)
|
||||
{
|
||||
var acad = AcColor.GetIndexRGB((byte)i);
|
||||
int nas = AciPaleta.Rgb(i);
|
||||
int rozdil = Math.Max(Math.Abs(acad[0] - ((nas >> 16) & 0xFF)),
|
||||
Math.Max(Math.Abs(acad[1] - ((nas >> 8) & 0xFF)),
|
||||
Math.Abs(acad[2] - (nas & 0xFF))));
|
||||
if (rozdil > 1 && !zname.Contains(i)) novaOdchylka.Add(i);
|
||||
if (rozdil <= 1 && zname.Contains(i)) zmizelaOdchylka.Add(i);
|
||||
}
|
||||
|
||||
Assert.Empty(novaOdchylka); // ACadSharp se rozešel s AutoCADem na dalším indexu
|
||||
Assert.Empty(zmizelaOdchylka); // ACadSharp se opravil – seznam zúžit
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NejblizsiIndexNajdePresnouShoduIPriblizek()
|
||||
{
|
||||
Assert.Equal(1, AciPaleta.NejblizsiIndex(0xFF0000));
|
||||
Assert.Equal(5, AciPaleta.NejblizsiIndex(0x0000FF));
|
||||
Assert.Equal(0xFF0000, AciPaleta.Rgb(AciPaleta.NejblizsiIndex(0xFE0101)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Rozpisky.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace Rozpisky.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Rozřešení stavu kresby: co se má nakreslit, když barvu neurčí entita, ale hladina.
|
||||
/// Chyba tady se projeví špatnou barvou v náhledu, ne pádem – proto explicitní testy.
|
||||
/// </summary>
|
||||
public class BarvaTesty
|
||||
{
|
||||
private static KresliciStav Stav(Barva entita, Barva hladina)
|
||||
=> new(HladinaStav.Vychozi with { Barva = hladina }, entita);
|
||||
|
||||
[Fact]
|
||||
public void VlastniBarvaEntityPrebijiHladinu()
|
||||
{
|
||||
Assert.Equal(0x0000FF, Stav(Barva.Aci(5), Barva.Aci(1)).RgbProKresbu());
|
||||
Assert.Equal(0x123456, Stav(Barva.Prima(0x123456), Barva.Aci(1)).RgbProKresbu());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ByLayerSiVezmeBarvuHladiny()
|
||||
=> Assert.Equal(0xFF0000, Stav(Barva.ZHladiny, Barva.Aci(1)).RgbProKresbu());
|
||||
|
||||
[Fact]
|
||||
public void ByBlockMimoBlokSeChovaJakoByLayer()
|
||||
=> Assert.Equal(0xFF0000, Stav(Barva.ZBloku, Barva.Aci(1)).RgbProKresbu());
|
||||
|
||||
[Fact]
|
||||
public void ByLayerNadHladinouBezBarvyDaCernou()
|
||||
=> Assert.Equal(0x000000, Stav(Barva.ZHladiny, Barva.ZHladiny).RgbProKresbu());
|
||||
|
||||
[Fact]
|
||||
public void TloustkaPadaZEntityNaHladinuAPakNaVychozi()
|
||||
{
|
||||
var h = HladinaStav.Vychozi with { TloustkaMm = 0.5 };
|
||||
Assert.Equal(0.35, new KresliciStav(h, Barva.ZHladiny, TloustkaMm: 0.35).TloustkaKresby(), 3);
|
||||
Assert.Equal(0.5, new KresliciStav(h, Barva.ZHladiny).TloustkaKresby(), 3);
|
||||
Assert.Equal(KresliciStav.VychoziTloustkaMm,
|
||||
new KresliciStav(HladinaStav.Vychozi, Barva.ZHladiny).TloustkaKresby(), 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypCaryPadaZEntityNaHladinuAPakNaContinuous()
|
||||
{
|
||||
var h = HladinaStav.Vychozi with { TypCary = "DASHED" };
|
||||
Assert.Equal("CENTER", new KresliciStav(h, Barva.ZHladiny, TypCary: "CENTER").TypCaryKresby());
|
||||
Assert.Equal("DASHED", new KresliciStav(h, Barva.ZHladiny).TypCaryKresby());
|
||||
Assert.Equal("Continuous",
|
||||
new KresliciStav(h with { TypCary = "" }, Barva.ZHladiny).TypCaryKresby());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexNeseIRgbProKresbu()
|
||||
{
|
||||
var b = Barva.Aci(5);
|
||||
Assert.Equal(BarvaDruh.Index, b.Druh);
|
||||
Assert.Equal(5, b.Index);
|
||||
Assert.Equal(0x0000FF, b.Rgb);
|
||||
Assert.True(b.JeVlastni);
|
||||
Assert.False(Barva.ZHladiny.JeVlastni);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.IO;
|
||||
using ACadSharp.Entities;
|
||||
using ACadSharp.IO;
|
||||
using Rozpisky.Dxf;
|
||||
using Rozpisky.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace Rozpisky.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Cesta barev tam a zpět: <c>Rozpiska.dxf</c> → <see cref="DxfProfileRenderer"/> → soubor →
|
||||
/// zpátky. Šablona i všech 9 log jsou zapsané výhradně indexovanými barvami AutoCADu
|
||||
/// (group 62); dřív z nich export dělal natvrdo RGB (group 420), takže výkres v CADu přestal
|
||||
/// reagovat na tabulku barev a ByLayer se rozpadlo na kopie barvy hladiny.
|
||||
/// </summary>
|
||||
public class DxfBarvyRoundTripTesty : IDisposable
|
||||
{
|
||||
private readonly string _soubor = Path.Combine(Path.GetTempPath(), "rozpisky-barvy-" + Guid.NewGuid().ToString("N") + ".dxf");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { File.Delete(_soubor); } catch (IOException) { }
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>Přehraje šablonu do DXF rendereru, zapíše a načte zpět.</summary>
|
||||
private ACadSharp.CadDocument Kolecko(bool sRameckem = true)
|
||||
{
|
||||
var tpl = new DxfTemplate(TestData.Sablona);
|
||||
var dxf = new DxfProfileRenderer();
|
||||
tpl.Replay(dxf, values: null, includeNoPrint: sRameckem);
|
||||
DxfWriter.Write(_soubor, dxf.Document, binary: true);
|
||||
return DxfReader.Read(_soubor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZadnaEntitaNevyjdeJakoTrueColor()
|
||||
{
|
||||
var doc = Kolecko();
|
||||
var prohresky = doc.Entities.OfType<Entity>().Where(e => e.Color.IsTrueColor).ToList();
|
||||
Assert.Empty(prohresky);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PouziteIndexyOdpovidajiZdroji()
|
||||
{
|
||||
// Indexy, které kreslítko dostalo ze šablony, musí být tytéž, jaké skončí v souboru.
|
||||
var zaznam = new RecordingRenderer();
|
||||
new DxfTemplate(TestData.Sablona).Replay(zaznam, values: null, includeNoPrint: true);
|
||||
var zdrojove = zaznam.Stavy.Where(s => s.Barva.Druh == BarvaDruh.Index)
|
||||
.Select(s => (int)s.Barva.Index).ToHashSet();
|
||||
|
||||
var doc = Kolecko();
|
||||
var vysledne = doc.Entities.OfType<Entity>()
|
||||
.Where(e => !e.Color.IsByLayer && !e.Color.IsByBlock)
|
||||
.Select(e => (int)e.Color.Index).ToHashSet();
|
||||
|
||||
Assert.NotEmpty(zdrojove);
|
||||
Assert.Equal(zdrojove, vysledne);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ByLayerZustaneByLayer()
|
||||
{
|
||||
// Šablona má 41 entit s barvou 256 (ByLayer). Když se rozbalí na konkrétní RGB,
|
||||
// přestane jít barva výkresu měnit přes hladinu – přesně to uživatel hlásí.
|
||||
var zaznam = new RecordingRenderer();
|
||||
new DxfTemplate(TestData.Sablona).Replay(zaznam, values: null, includeNoPrint: true);
|
||||
int zdrojovych = zaznam.Stavy.Count(s => s.Barva.Druh == BarvaDruh.ByLayer);
|
||||
Assert.True(zdrojovych > 0, "šablona žádnou ByLayer entitu nemá – test ztratil smysl");
|
||||
|
||||
var doc = Kolecko();
|
||||
Assert.Contains(doc.Entities.OfType<Entity>(), e => e.Color.IsByLayer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HladinyMajiVlastnostiZTabulkyNeZPrvniEntity()
|
||||
{
|
||||
var zdroj = ACadSharp.IO.DxfReader.Read(TestData.Sablona);
|
||||
var doc = Kolecko();
|
||||
|
||||
foreach (var vrstva in doc.Layers)
|
||||
{
|
||||
if (!zdroj.Layers.TryGetValue(vrstva.Name, out var puvodni)) continue;
|
||||
Assert.Equal(puvodni.Color.Index, vrstva.Color.Index);
|
||||
Assert.Equal(puvodni.PlotFlag, vrstva.PlotFlag);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RamecekNoPrintZustaneNetisknutelny()
|
||||
{
|
||||
// Rámeček A4 se do exportu kreslí jako reference, ale tisknout se nesmí.
|
||||
var doc = Kolecko();
|
||||
Assert.True(doc.Layers.TryGetValue(DxfTemplate.HladinaNoPrint, out var vrstva),
|
||||
"hladina !!NOPRINT v exportu chybí");
|
||||
Assert.False(vrstva!.PlotFlag);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class ObliqueTesty
|
||||
public void DxfExport_ZachovaObliqueNaTextu()
|
||||
{
|
||||
var dxf = new DxfProfileRenderer();
|
||||
dxf.Layer("0", 0x000000, 0.25);
|
||||
dxf.Stav(new KresliciStav(HladinaStav.Vychozi, Barva.Aci(7), TloustkaMm: 0.25));
|
||||
dxf.Text(10, 20, "Zkouška", 2.5, style: new TextStyle("Arial", ObliqueDeg: 3));
|
||||
|
||||
string path = Path.Combine(Path.GetTempPath(), $"rozpisky-oblique-{Guid.NewGuid():N}.dxf");
|
||||
|
||||
@@ -84,7 +84,7 @@ public class ObloukyTesty
|
||||
var contour = new Contour(new[] { arc, line }, Closed: true);
|
||||
|
||||
var g = new DxfProfileRenderer();
|
||||
g.Layer("0", 0x000000);
|
||||
g.Stav(KresliciStav.Vychozi);
|
||||
g.Fill(new[] { contour });
|
||||
|
||||
// Export → dočasný DXF → import zpět a porovnání bboxu (sagitta ≤ 0,02 mm).
|
||||
@@ -118,7 +118,7 @@ public class ObloukyTesty
|
||||
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.Stav(KresliciStav.Vychozi);
|
||||
g.Stroke(contour);
|
||||
|
||||
var arc = Assert.Single(g.Document.Entities.OfType<Arc>());
|
||||
|
||||
@@ -26,7 +26,12 @@ public sealed class RecordingRenderer : IProfileRenderer
|
||||
public int LineCount { get; private set; }
|
||||
public int CircleCount { get; private set; }
|
||||
|
||||
public void Layer(string name, int rgb, double lineWeightMm = 0.25) { }
|
||||
/// <summary>Stavy kresby v pořadí, jak přišly – testy tak můžou tvrdit i o barvě a hladině.</summary>
|
||||
public List<KresliciStav> Stavy { get; } = new();
|
||||
/// <summary>Stav platný pro naposledy zaznamenanou entitu.</summary>
|
||||
public KresliciStav PosledniStav => Stavy.Count > 0 ? Stavy[^1] : KresliciStav.Vychozi;
|
||||
|
||||
public void Stav(in KresliciStav s) => Stavy.Add(s);
|
||||
|
||||
public void Line(double x1, double y1, double x2, double y2) => LineCount++;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SkiaRenderTesty
|
||||
var edge = Edge.Arc(5, 0, 5, 0, 0, 0, r: 5, a0: 0, sweep: 2 * Math.PI);
|
||||
var contour = new Contour(new[] { edge }, Closed: true);
|
||||
|
||||
int filled = PocetVyplnenych(g => { g.Layer("0", 0); g.Fill(new[] { contour }); });
|
||||
int filled = PocetVyplnenych(g => { g.Stav(KresliciStav.Vychozi); g.Fill(new[] { contour }); });
|
||||
|
||||
// Plocha kruhu r=5 mm × (10 px/mm)² = π·2500 ≈ 7854 px; tolerantně > 5000.
|
||||
Assert.True(filled > 5000, $"plná kružnice se nevykreslila (vyplněno {filled} px)");
|
||||
@@ -47,7 +47,7 @@ public class SkiaRenderTesty
|
||||
{
|
||||
// Kontrolní: běžný oblouk (< 360°) přes Stroke musí něco nakreslit.
|
||||
var edge = Edge.Arc(5, 0, 0, 5, 0, 0, r: 5, a0: 0, sweep: Math.PI / 2);
|
||||
int filled = PocetVyplnenych(g => { g.Layer("0", 0); g.Stroke(new Contour(new[] { edge }, Closed: false)); });
|
||||
int filled = PocetVyplnenych(g => { g.Stav(KresliciStav.Vychozi); g.Stroke(new Contour(new[] { edge }, Closed: false)); });
|
||||
Assert.True(filled > 50, $"oblouk se nevykreslil (vyplněno {filled} px)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ public static class TestData
|
||||
/// <summary>Plná cesta k logu ve zdrojové složce <c>Rozpisky\Podklady\loga</c>.</summary>
|
||||
public static string Logo(string fileName) => Path.Combine(RepoRoot, "Rozpisky", "Podklady", "loga", fileName);
|
||||
|
||||
/// <summary>Plná cesta k DXF šabloně rozpisky.</summary>
|
||||
public static string Sablona => Path.Combine(RepoRoot, "Rozpisky", "Podklady", "Rozpiska.dxf");
|
||||
|
||||
/// <summary>Všech 9 log dodávaných s programem.</summary>
|
||||
public static readonly string[] VsechnaLoga =
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user