Oprava: exportovaná šrafa šla ven bez definice vzoru a ve špatném měřítku
Nahlásil uživatel: šrafa v exportovaném DXF nedrží měřítko, takže na první pohled ani nevypadá jako šrafa, a v každém výkresu se chová jinak. Dvě příčiny: 1. Do výstupu šlo jen *jméno* vzoru (ANSI31), bez definičních čar — ověřeno na exportovaném souboru: definicnichCar=0. DXF přitom nese definici vzoru uvnitř entity HATCH; bez ní si ji cílový CAD dosadí ze svých .pat, ve svých jednotkách a měřítku. Odtud „v každém výkresu jinak". Vypln proto nese VzorCara (úhel, vztažný bod, rozteč, čárkování) a export ji zapisuje. 2. Vzor se neškáloval spolu s geometrií. Logo se do rámečku rozpisky vejde jen zmenšené (typicky ~0,1×), ale rozteč vzoru zůstávala původní — v CADu z toho byla buď plná plocha, nebo pár čar přes celé logo. TransformRenderer teď mapuje i definici: vztažný bod celou transformací, rozteč jako vektor (bez posunu), délky čárek a měřítko × měřítko transformace. Měřítko a úhel se do HATCH zapisují neutrální (1 a 0) a efektivní hodnoty nese definice. ACadSharp totiž při zápisu násobí rozteč hodnotou PatternScale, ale při čtení zpátky nedělí — s nenulovým měřítkem by se vzor s každým kolečkem desetkrát roztáhl (změřeno). Po opravě je rozteč v exportu shodná se zdrojem 1:1 a při zmenšení loga 0,1× klesne desetkrát, takže počet čar přes logo zůstává stejný. Náhledy všech 9 log jsou pixel po pixelu beze změny. 119 testů zelených. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,6 @@ Needituj produkční data: `Rozpisky/Podklady/Rozpiska.dxf`, `vzorky/schema.dxf`
|
|||||||
3. Ověř:
|
3. Ověř:
|
||||||
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
||||||
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q`
|
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q`
|
||||||
(baseline je 114 zelených testů — nesmí ubýt).
|
(baseline je 119 zelených testů — nesmí ubýt).
|
||||||
4. V odpovědi vrať: co jsi změnil (soubor:řádek), proč, a výsledek buildu a testů. Když něco
|
4. V odpovědi vrať: co jsi změnil (soubor:řádek), proč, a výsledek buildu a testů. Když něco
|
||||||
neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to.
|
neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ dotnet test "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.Tests\Rozpis
|
|||||||
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
|
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
|
||||||
a rovnou hlas chyby buildu.
|
a rovnou hlas chyby buildu.
|
||||||
|
|
||||||
Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 114 testů zelených, 0 přeskočených.**
|
Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 119 testů zelených, 0 přeskočených.**
|
||||||
|
|
||||||
Volitelně (jen když o to volající požádá) ověř, že aplikace naběhne:
|
Volitelně (jen když o to volající požádá) ověř, že aplikace naběhne:
|
||||||
spusť `Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces
|
spusť `Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ nebo `Rozpisky/Xlsx/` jen popiš v odpovědi a nech je na volajícím.
|
|||||||
začneš měnit UI.
|
začneš měnit UI.
|
||||||
2. `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně.
|
2. `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně.
|
||||||
3. Ověř: `dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
3. Ověř: `dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
|
||||||
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 114 zelených).
|
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 119 zelených).
|
||||||
Build musí být **bez varování** — chyby v XAML se často projeví jen jako varování.
|
Build musí být **bez varování** — chyby v XAML se často projeví jen jako varování.
|
||||||
4. WPF chyby v bindingu build nezachytí. Pokud změna může binding rozbít, řekni to explicitně
|
4. WPF chyby v bindingu build nezachytí. Pokud změna může binding rozbít, řekni to explicitně
|
||||||
a doporuč ruční spuštění aplikace.
|
a doporuč ruční spuštění aplikace.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q
|
|||||||
dotnet run --project "Rozpisky\Rozpisky.csproj"
|
dotnet run --project "Rozpisky\Rozpisky.csproj"
|
||||||
```
|
```
|
||||||
|
|
||||||
Baseline: build bez chyb a varování, **114 testů zelených**. Před hlášením hotovo obojí spustit
|
Baseline: build bez chyb a varování, **119 testů zelených**. Před hlášením hotovo obojí spustit
|
||||||
(nebo použít `/overit`).
|
(nebo použít `/overit`).
|
||||||
|
|
||||||
## Architektura — jediné pravidlo, které se nesmí porušit
|
## Architektura — jediné pravidlo, které se nesmí porušit
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
using System.IO;
|
||||||
|
using ACadSharp.Entities;
|
||||||
|
using ACadSharp.IO;
|
||||||
|
using Rozpisky.Dxf;
|
||||||
|
using Rozpisky.Rendering;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Rozpisky.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Šrafa v exportovaném DXF. Vzor musí jít ven i s <b>definičními čarami</b> – DXF je nese uvnitř
|
||||||
|
/// entity <c>HATCH</c>, a bez nich si ho cílový CAD dosadí ze svých <c>.pat</c>, ve svých
|
||||||
|
/// jednotkách a měřítku. Pak vypadá v každém výkresu jinak a většinou tak hustě, že to ani
|
||||||
|
/// nevypadá jako šrafa. A protože se logo do rámečku zmenšuje, musí se s ním zmenšit i vzor.
|
||||||
|
/// </summary>
|
||||||
|
public class SrafaExportTesty : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _soubor = Path.Combine(Path.GetTempPath(), "rozpisky-srafa-" + Guid.NewGuid().ToString("N") + ".dxf");
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
try { File.Delete(_soubor); } catch (IOException) { }
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Přehraje logo do DXF (volitelně přes zmenšení do rámečku) a načte výsledek zpět.</summary>
|
||||||
|
private List<Hatch> Kolecko(double meritko)
|
||||||
|
{
|
||||||
|
var dxf = new DxfProfileRenderer();
|
||||||
|
IProfileRenderer cil = meritko == 1 ? dxf : new TransformRenderer(dxf, meritko, 0, 0);
|
||||||
|
new DxfTemplate(TestData.Logo("md_sfdi.dxf")).Replay(cil);
|
||||||
|
DxfWriter.Write(_soubor, dxf.Document, binary: true);
|
||||||
|
return DxfReader.Read(_soubor).Entities.OfType<Hatch>().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rozteč rovnoběžek vzoru = kolmá složka posunu mezi sousedními čarami.</summary>
|
||||||
|
private static double Roztec(HatchPattern.Line cara)
|
||||||
|
{
|
||||||
|
double nx = -Math.Sin(cara.Angle), ny = Math.Cos(cara.Angle);
|
||||||
|
return Math.Abs(cara.Offset.X * nx + cara.Offset.Y * ny);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double RoztecZdroje()
|
||||||
|
{
|
||||||
|
var zdroj = DxfReader.Read(TestData.Logo("md_sfdi.dxf"))
|
||||||
|
.Entities.OfType<Hatch>().First(h => !h.IsSolid);
|
||||||
|
return Roztec(zdroj.Pattern!.Lines[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExportovanaSrafaNeseDefiniciVzoru()
|
||||||
|
{
|
||||||
|
var srafy = Kolecko(meritko: 1).Where(h => !h.IsSolid).ToList();
|
||||||
|
|
||||||
|
Assert.NotEmpty(srafy);
|
||||||
|
foreach (var h in srafy)
|
||||||
|
{
|
||||||
|
Assert.Equal("ANSI31", h.Pattern?.Name);
|
||||||
|
Assert.NotNull(h.Pattern);
|
||||||
|
Assert.NotEmpty(h.Pattern!.Lines); // tohle chybělo: vzor šel ven jen jménem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RoztecVzoruOdpovidaZdroji()
|
||||||
|
{
|
||||||
|
var srafa = Kolecko(meritko: 1).First(h => !h.IsSolid);
|
||||||
|
Assert.Equal(RoztecZdroje(), Roztec(srafa.Pattern!.Lines[0]), 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ZmensenimLogaSeZmensiIVzor()
|
||||||
|
{
|
||||||
|
// Logo se do rámečku rozpisky vejde jen zmenšené. Kdyby si vzor nechal původní rozteč,
|
||||||
|
// vyšla by v CADu buď plná plocha, nebo pár čar přes celé logo.
|
||||||
|
const double meritko = 0.1;
|
||||||
|
var srafa = Kolecko(meritko).First(h => !h.IsSolid);
|
||||||
|
|
||||||
|
Assert.Equal(RoztecZdroje() * meritko, Roztec(srafa.Pattern!.Lines[0]), 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MeritkoAUhelZustavajiNeutralni()
|
||||||
|
{
|
||||||
|
// ACadSharp při zápisu násobí rozteč vzoru hodnotou PatternScale, ale při čtení zpátky
|
||||||
|
// nedělí – s nenulovým měřítkem by se vzor s každým kolečkem roztáhl. Efektivní hodnoty
|
||||||
|
// proto nese samotná definice a měřítko s úhlem zůstávají neutrální.
|
||||||
|
var srafa = Kolecko(meritko: 1).First(h => !h.IsSolid);
|
||||||
|
|
||||||
|
Assert.Equal(1, srafa.PatternScale, 9);
|
||||||
|
Assert.Equal(0, srafa.PatternAngle, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PlnaVyplnZustavaSolidBezVzoru()
|
||||||
|
{
|
||||||
|
// md_sfdi má všechny šrafy vzorované, plné výplně jsou v ostatních logách.
|
||||||
|
var dxf = new DxfProfileRenderer();
|
||||||
|
new DxfTemplate(TestData.Logo("exprojekt.dxf")).Replay(dxf);
|
||||||
|
DxfWriter.Write(_soubor, dxf.Document, binary: true);
|
||||||
|
var solidni = DxfReader.Read(_soubor).Entities.OfType<Hatch>().ToList();
|
||||||
|
|
||||||
|
Assert.NotEmpty(solidni);
|
||||||
|
foreach (var h in solidni)
|
||||||
|
{
|
||||||
|
Assert.True(h.IsSolid);
|
||||||
|
Assert.Equal("SOLID", h.Pattern?.Name);
|
||||||
|
Assert.Empty(h.Pattern!.Lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -384,8 +384,23 @@ public sealed partial class DxfTemplate
|
|||||||
|| vzor.Equals("SOLID", StringComparison.OrdinalIgnoreCase))
|
|| vzor.Equals("SOLID", StringComparison.OrdinalIgnoreCase))
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
|
var definice = DefiniceVzoru(hatch.Pattern);
|
||||||
return Vypln.Vzorovana(vzor, hatch.PatternScale, hatch.PatternAngle * RadToDeg,
|
return Vypln.Vzorovana(vzor, hatch.PatternScale, hatch.PatternAngle * RadToDeg,
|
||||||
SrafaVzor.Cary(hatch.Pattern, smycky));
|
definice, SrafaVzor.Cary(definice, smycky));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Definiční čáry vzoru šrafy z entity. DXF je nese uvnitř <c>HATCH</c> (group 78 a dál),
|
||||||
|
/// tedy už v efektivních hodnotách – měřítko a úhel z vlastností šrafy jsou v nich započítané.</summary>
|
||||||
|
private static List<VzorCara> DefiniceVzoru(HatchPattern? vzor)
|
||||||
|
{
|
||||||
|
var vysledek = new List<VzorCara>();
|
||||||
|
foreach (var cara in vzor?.Lines ?? new List<HatchPattern.Line>())
|
||||||
|
vysledek.Add(new VzorCara(
|
||||||
|
cara.Angle * RadToDeg,
|
||||||
|
(cara.BasePoint.X, cara.BasePoint.Y),
|
||||||
|
(cara.Offset.X, cara.Offset.Y),
|
||||||
|
cara.DashLengths?.ToList() ?? new List<double>()));
|
||||||
|
return vysledek;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Popis hladiny pro kreslítko – celý, ať se dá ve výstupu postavit znovu
|
/// <summary>Popis hladiny pro kreslítko – celý, ať se dá ve výstupu postavit znovu
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using ACadSharp.Entities;
|
|
||||||
using Rozpisky.Rendering;
|
using Rozpisky.Rendering;
|
||||||
|
|
||||||
namespace Rozpisky.Dxf;
|
namespace Rozpisky.Dxf;
|
||||||
@@ -19,10 +18,10 @@ internal static class SrafaVzor
|
|||||||
|
|
||||||
/// <summary>Čáry vzoru uvnitř zadaných smyček. Prázdné, když vzor není použitelný –
|
/// <summary>Čáry vzoru uvnitř zadaných smyček. Prázdné, když vzor není použitelný –
|
||||||
/// volající pak plochu nechá bez výplně (lepší než ji zaplácnout načerno).</summary>
|
/// volající pak plochu nechá bez výplně (lepší než ji zaplácnout načerno).</summary>
|
||||||
public static IReadOnlyList<Usecka> Cary(HatchPattern? vzor, IReadOnlyList<Contour> smycky)
|
public static IReadOnlyList<Usecka> Cary(IReadOnlyList<VzorCara>? definice, IReadOnlyList<Contour> smycky)
|
||||||
{
|
{
|
||||||
var vysledek = new List<Usecka>();
|
var vysledek = new List<Usecka>();
|
||||||
if (vzor?.Lines is not { Count: > 0 }) return vysledek;
|
if (definice is not { Count: > 0 }) return vysledek;
|
||||||
|
|
||||||
var polygony = Zplosti(smycky);
|
var polygony = Zplosti(smycky);
|
||||||
if (polygony.Count == 0) return vysledek;
|
if (polygony.Count == 0) return vysledek;
|
||||||
@@ -30,7 +29,7 @@ internal static class SrafaVzor
|
|||||||
var (minX, minY, maxX, maxY) = Rozsah(polygony);
|
var (minX, minY, maxX, maxY) = Rozsah(polygony);
|
||||||
if (maxX - minX <= 0 || maxY - minY <= 0) return vysledek;
|
if (maxX - minX <= 0 || maxY - minY <= 0) return vysledek;
|
||||||
|
|
||||||
foreach (var cara in vzor.Lines)
|
foreach (var cara in definice)
|
||||||
{
|
{
|
||||||
PridejCaryVzoru(cara, polygony, minX, minY, maxX, maxY, vysledek);
|
PridejCaryVzoru(cara, polygony, minX, minY, maxX, maxY, vysledek);
|
||||||
if (vysledek.Count >= MaxCar) break;
|
if (vysledek.Count >= MaxCar) break;
|
||||||
@@ -42,10 +41,11 @@ internal static class SrafaVzor
|
|||||||
/// rovnoběžkami je vektor <c>Offset</c> ve světových souřadnicích: jeho kolmá složka určuje
|
/// rovnoběžkami je vektor <c>Offset</c> ve světových souřadnicích: jeho kolmá složka určuje
|
||||||
/// rozteč, podélná posouvá čárkování. U ANSI31 vychází přesně kolmý na směr 45°.</summary>
|
/// rozteč, podélná posouvá čárkování. U ANSI31 vychází přesně kolmý na směr 45°.</summary>
|
||||||
private static void PridejCaryVzoru(
|
private static void PridejCaryVzoru(
|
||||||
HatchPattern.Line cara, List<List<(double X, double Y)>> polygony,
|
VzorCara cara, List<List<(double X, double Y)>> polygony,
|
||||||
double minX, double minY, double maxX, double maxY, List<Usecka> vysledek)
|
double minX, double minY, double maxX, double maxY, List<Usecka> vysledek)
|
||||||
{
|
{
|
||||||
double dx = Math.Cos(cara.Angle), dy = Math.Sin(cara.Angle); // směr čáry
|
double uhel = cara.UhelDeg * Math.PI / 180;
|
||||||
|
double dx = Math.Cos(uhel), dy = Math.Sin(uhel); // směr čáry
|
||||||
double nx = -dy, ny = dx; // kolmice
|
double nx = -dy, ny = dx; // kolmice
|
||||||
|
|
||||||
double ox = cara.Offset.X, oy = cara.Offset.Y;
|
double ox = cara.Offset.X, oy = cara.Offset.Y;
|
||||||
@@ -53,7 +53,7 @@ internal static class SrafaVzor
|
|||||||
if (Math.Abs(kolma) < 1e-9) return; // rovnoběžky by splynuly
|
if (Math.Abs(kolma) < 1e-9) return; // rovnoběžky by splynuly
|
||||||
|
|
||||||
// Kolik kroků pokryje bbox: promítni jeho rohy na kolmici.
|
// Kolik kroků pokryje bbox: promítni jeho rohy na kolmici.
|
||||||
double zaklad = cara.BasePoint.X * nx + cara.BasePoint.Y * ny;
|
double zaklad = cara.Zaklad.X * nx + cara.Zaklad.Y * ny;
|
||||||
var projekce = new[]
|
var projekce = new[]
|
||||||
{
|
{
|
||||||
minX * nx + minY * ny, maxX * nx + minY * ny,
|
minX * nx + minY * ny, maxX * nx + minY * ny,
|
||||||
@@ -66,11 +66,11 @@ internal static class SrafaVzor
|
|||||||
int od = (int)Math.Floor(kOd) - 1, doK = (int)Math.Ceiling(kDo) + 1;
|
int od = (int)Math.Floor(kOd) - 1, doK = (int)Math.Ceiling(kDo) + 1;
|
||||||
if ((long)doK - od > MaxCar) return; // nesmyslně husté měřítko
|
if ((long)doK - od > MaxCar) return; // nesmyslně husté měřítko
|
||||||
|
|
||||||
var carky = Carky(cara.DashLengths);
|
var carky = Carky(cara.Carky);
|
||||||
|
|
||||||
for (int k = od; k <= doK; k++)
|
for (int k = od; k <= doK; k++)
|
||||||
{
|
{
|
||||||
double px = cara.BasePoint.X + k * ox, py = cara.BasePoint.Y + k * oy;
|
double px = cara.Zaklad.X + k * ox, py = cara.Zaklad.Y + k * oy;
|
||||||
foreach (var (t1, t2) in Uvnitr(polygony, px, py, dx, dy))
|
foreach (var (t1, t2) in Uvnitr(polygony, px, py, dx, dy))
|
||||||
foreach (var (u1, u2) in Orez(carky, t1, t2))
|
foreach (var (u1, u2) in Orez(carky, t1, t2))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -165,14 +165,20 @@ public sealed class DxfProfileRenderer : IProfileRenderer
|
|||||||
if (loops.Count == 0) return;
|
if (loops.Count == 0) return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Vzor šrafy se zapisuje zpátky jménem, měřítkem a úhlem – v CADu tak zůstane
|
// Vzor šrafy se zapisuje zpátky i s definičními čarami – DXF je nese uvnitř entity
|
||||||
// editovatelný. Rozvinuté čáry (Vypln.Cary) jsou jen pro rastrové renderery.
|
// HATCH, a bez nich si cílový CAD vzor dosadí ze svých .pat, ve svých jednotkách
|
||||||
|
// a měřítku. Rozvinuté čáry (Vypln.Cary) jsou naopak jen pro rastrové renderery.
|
||||||
|
//
|
||||||
|
// Měřítko a úhel zůstávají neutrální (1 a 0), protože definiční čáry jsou už
|
||||||
|
// v efektivních hodnotách – měřítko i natočení jsou v nich započítané. ACadSharp
|
||||||
|
// při zápisu násobí rozteč vzoru hodnotou PatternScale, ale při čtení zpátky nedělí;
|
||||||
|
// s nenulovým měřítkem by se tak vzor s každým kolečkem roztáhl.
|
||||||
var hatch = new Hatch
|
var hatch = new Hatch
|
||||||
{
|
{
|
||||||
IsSolid = vypln.JeSolid,
|
IsSolid = vypln.JeSolid,
|
||||||
Pattern = new HatchPattern(vypln.JeSolid ? "SOLID" : vypln.Vzor!),
|
Pattern = VzorProHatch(vypln),
|
||||||
PatternScale = vypln.JeSolid || vypln.Meritko <= 0 ? 1 : vypln.Meritko,
|
PatternScale = 1,
|
||||||
PatternAngle = vypln.JeSolid ? 0 : vypln.UhelDeg * Math.PI / 180.0,
|
PatternAngle = 0,
|
||||||
};
|
};
|
||||||
foreach (var loop in loops)
|
foreach (var loop in loops)
|
||||||
{
|
{
|
||||||
@@ -201,6 +207,28 @@ public sealed class DxfProfileRenderer : IProfileRenderer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Vzor pro entitu HATCH: jméno a definiční čáry. Bez definičních čar má výstup jen
|
||||||
|
/// jméno vzoru a cílový CAD si podle něj dosadí vlastní definici – v jiných jednotkách
|
||||||
|
/// a jiném měřítku, takže šrafa vypadá v každém výkresu jinak.</summary>
|
||||||
|
private static HatchPattern VzorProHatch(Vypln vypln)
|
||||||
|
{
|
||||||
|
var vzor = new HatchPattern(vypln.JeSolid ? "SOLID" : vypln.Vzor!);
|
||||||
|
if (vypln.JeSolid) return vzor;
|
||||||
|
|
||||||
|
foreach (var cara in vypln.Definice ?? Array.Empty<VzorCara>())
|
||||||
|
{
|
||||||
|
var radek = new HatchPattern.Line
|
||||||
|
{
|
||||||
|
Angle = cara.UhelDeg * Math.PI / 180.0,
|
||||||
|
BasePoint = new XY(cara.Zaklad.X, cara.Zaklad.Y),
|
||||||
|
Offset = new XY(cara.Offset.X, cara.Offset.Y),
|
||||||
|
};
|
||||||
|
foreach (var d in cara.Carky) radek.DashLengths.Add(d);
|
||||||
|
vzor.Lines.Add(radek);
|
||||||
|
}
|
||||||
|
return vzor;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Směrový vektor osy X textu pro dané natočení – tím se natáčí MTEXT.</summary>
|
/// <summary>Směrový vektor osy X textu pro dané natočení – tím se natáčí MTEXT.</summary>
|
||||||
private static XYZ SmerOsyX(double rotationDeg)
|
private static XYZ SmerOsyX(double rotationDeg)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,23 +56,37 @@ public readonly record struct Edge(
|
|||||||
/// <summary>Úsečka ve výkresových mm – čára vzoru šrafy.</summary>
|
/// <summary>Úsečka ve výkresových mm – čára vzoru šrafy.</summary>
|
||||||
public readonly record struct Usecka(double X1, double Y1, double X2, double Y2);
|
public readonly record struct Usecka(double X1, double Y1, double X2, double Y2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jedna definiční čára vzoru šrafy = celá rodina rovnoběžek: směr <paramref name="UhelDeg"/>,
|
||||||
|
/// jedna čára prochází <paramref name="Zaklad"/>, sousední jsou posunuté o vektor
|
||||||
|
/// <paramref name="Offset"/> (kolmá složka = rozteč, podélná posouvá čárkování).
|
||||||
|
/// <paramref name="Carky"/> = délky čárek a mezer (záporná = mezera); prázdné = plná čára.
|
||||||
|
/// <para>Tohle je <b>skutečná definice vzoru</b>, ne jen jeho jméno. DXF ji nese uvnitř entity
|
||||||
|
/// <c>HATCH</c>, takže je nutné ji zapsat i do výstupu – jinak si ji cílový CAD dosadí ze svých
|
||||||
|
/// <c>.pat</c> souborů, ve svých jednotkách a měřítku, a šrafa vyjde pokaždé jinak.</para>
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct VzorCara(
|
||||||
|
double UhelDeg, (double X, double Y) Zaklad, (double X, double Y) Offset, IReadOnlyList<double> Carky);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Druh výplně plochy. <c>default</c> = plná výplň (SOLID).
|
/// Druh výplně plochy. <c>default</c> = plná výplň (SOLID).
|
||||||
/// <para>Nesolidní šrafa (ANSI31 apod.) se rastrovým rendererům předává už rozvinutá na
|
/// <para>Nesolidní šrafa (ANSI31 apod.) se rastrovým rendererům předává už rozvinutá na
|
||||||
/// <paramref name="Cary"/>, protože Skia vzory šraf neumí; DXF export naopak <paramref name="Cary"/>
|
/// <paramref name="Cary"/>, protože Skia vzory šraf neumí; DXF export naopak <paramref name="Cary"/>
|
||||||
/// ignoruje a zapíše zpátky <c>HATCH</c> s původním jménem vzoru, měřítkem a úhlem, aby zůstal
|
/// ignoruje a zapíše zpátky <c>HATCH</c> se jménem vzoru, měřítkem, úhlem a <b>definičními čarami</b>
|
||||||
/// v CADu editovatelný. Dřív se každá šrafa kreslila jako plná výplň – z logotypu
|
/// (<paramref name="Definice"/>), aby zůstal editovatelný a vypadal stejně jako ve zdroji. Dřív se
|
||||||
/// <c>md_sfdi.dxf</c> byl černý blok.</para>
|
/// každá šrafa kreslila jako plná výplň – z logotypu <c>md_sfdi.dxf</c> byl černý blok.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly record struct Vypln(
|
public readonly record struct Vypln(
|
||||||
string? Vzor = null, double Meritko = 1, double UhelDeg = 0, IReadOnlyList<Usecka>? Cary = null)
|
string? Vzor = null, double Meritko = 1, double UhelDeg = 0,
|
||||||
|
IReadOnlyList<Usecka>? Cary = null, IReadOnlyList<VzorCara>? Definice = null)
|
||||||
{
|
{
|
||||||
/// <summary>Plná výplň – buď se vzor neuvádí (<c>default</c>), nebo je to SOLID.</summary>
|
/// <summary>Plná výplň – buď se vzor neuvádí (<c>default</c>), nebo je to SOLID.</summary>
|
||||||
public bool JeSolid => string.IsNullOrEmpty(Vzor) || Vzor.Equals("SOLID", StringComparison.OrdinalIgnoreCase);
|
public bool JeSolid => string.IsNullOrEmpty(Vzor) || Vzor.Equals("SOLID", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
/// <summary>Šrafa se vzorem, už rozvinutá na čáry pro rastrové renderery.</summary>
|
/// <summary>Šrafa se vzorem: definice pro DXF a z ní rozvinuté čáry pro rastr.</summary>
|
||||||
public static Vypln Vzorovana(string vzor, double meritko, double uhelDeg, IReadOnlyList<Usecka> cary)
|
public static Vypln Vzorovana(string vzor, double meritko, double uhelDeg,
|
||||||
=> new(vzor, meritko, uhelDeg, cary);
|
IReadOnlyList<VzorCara> definice, IReadOnlyList<Usecka> cary)
|
||||||
|
=> new(vzor, meritko, uhelDeg, cary, definice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Obrys = zřetězené hrany (konec jedné = počátek další). <paramref name="Closed"/> = true →
|
/// <summary>Obrys = zřetězené hrany (konec jedné = počátek další). <paramref name="Closed"/> = true →
|
||||||
|
|||||||
@@ -132,14 +132,32 @@ public sealed class TransformRenderer : IProfileRenderer
|
|||||||
=> _inner.Fill(
|
=> _inner.Fill(
|
||||||
loops.Select(c => new Contour(
|
loops.Select(c => new Contour(
|
||||||
_zachovavaOblouky ? c.Edges.Select(T).ToList() : Usecky(c), c.Closed)).ToList(),
|
_zachovavaOblouky ? c.Edges.Select(T).ToList() : Usecky(c), c.Closed)).ToList(),
|
||||||
vypln.Cary is null
|
Vypln(vypln));
|
||||||
? vypln
|
|
||||||
: vypln with
|
/// <summary>Přemapuje výplň spolu s geometrií. Šrafa se musí zmenšit se svou plochou –
|
||||||
|
/// jinak logo zmenšené do rámečku dostane vzor v původní rozteči a v CADu z něj vyjde
|
||||||
|
/// buď plná plocha, nebo pár čar přes celé logo.</summary>
|
||||||
|
private Vypln Vypln(Vypln v)
|
||||||
{
|
{
|
||||||
Cary = vypln.Cary
|
if (v.JeSolid) return v;
|
||||||
.Select(u => new Usecka(X(u.X1, u.Y1), Y(u.X1, u.Y1), X(u.X2, u.Y2), Y(u.X2, u.Y2)))
|
return v with
|
||||||
.ToList(),
|
{
|
||||||
});
|
Meritko = v.Meritko * _meritko,
|
||||||
|
UhelDeg = v.UhelDeg + _uhelDeg,
|
||||||
|
Cary = v.Cary?.Select(u => new Usecka(X(u.X1, u.Y1), Y(u.X1, u.Y1), X(u.X2, u.Y2), Y(u.X2, u.Y2))).ToList(),
|
||||||
|
Definice = v.Definice?.Select(Vzor).ToList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Definiční čára vzoru: vztažný bod je bod (celá transformace), rozteč je vektor
|
||||||
|
/// (jen lineární část, bez posunu), délky čárek se škálují, směr se otočí.</summary>
|
||||||
|
private VzorCara Vzor(VzorCara c) => c with
|
||||||
|
{
|
||||||
|
UhelDeg = c.UhelDeg + _uhelDeg,
|
||||||
|
Zaklad = (X(c.Zaklad.X, c.Zaklad.Y), Y(c.Zaklad.X, c.Zaklad.Y)),
|
||||||
|
Offset = (_a * c.Offset.X + _c * c.Offset.Y, _b * c.Offset.X + _d * c.Offset.Y),
|
||||||
|
Carky = c.Carky.Select(d => d * _meritko).ToList(),
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>Obrys rozložený na úsečky (pro transformace, které oblouk nezachovají).</summary>
|
/// <summary>Obrys rozložený na úsečky (pro transformace, které oblouk nezachovají).</summary>
|
||||||
private List<Edge> Usecky(Contour c)
|
private List<Edge> Usecky(Contour c)
|
||||||
|
|||||||
+5
-2
@@ -189,8 +189,11 @@ Implementace:
|
|||||||
- **Zarovnání textu `Aligned` / `Fit`** se aproximuje na `Left` – text se neroztáhne na cílovou délku.
|
- **Zarovnání textu `Aligned` / `Fit`** se aproximuje na `Left` – text se neroztáhne na cílovou délku.
|
||||||
- **Typ čáry** se přenáší jako název, ale rastrové renderery kreslí vždy plnou čáru; čárkované styly
|
- **Typ čáry** se přenáší jako název, ale rastrové renderery kreslí vždy plnou čáru; čárkované styly
|
||||||
se projeví až v DXF (v dodávaných souborech je stejně všude `Continuous`).
|
se projeví až v DXF (v dodávaných souborech je stejně všude `Continuous`).
|
||||||
- **Šrafy** se do DXF zapisují se jménem vzoru, měřítkem a úhlem, ale bez definice čar vzoru – CAD
|
- **Šrafy** se do DXF zapisují i s definičními čarami vzoru (DXF je nese uvnitř entity `HATCH`),
|
||||||
si ji dohledá ve svých `.pat`. Vzor, který cílový CAD nezná, se v něm nevykreslí.
|
takže vypadají stejně jako ve zdroji nezávisle na tom, jaké `.pat` a jednotky má cílový CAD.
|
||||||
|
Měřítko a úhel šrafy zůstávají neutrální (1 a 0) – efektivní hodnoty nese samotná definice.
|
||||||
|
Důvod: ACadSharp při zápisu násobí rozteč vzoru hodnotou `PatternScale`, ale při čtení zpátky
|
||||||
|
nedělí, takže s nenulovým měřítkem by se vzor s každým kolečkem roztáhl.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user