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:
2026-09-07 15:08:54 +02:00
co-authored by Claude Opus 5
parent 10548b5698
commit 1e5a552c13
11 changed files with 225 additions and 36 deletions
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+111
View File
@@ -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);
}
}
}
+16 -1
View File
@@ -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
+9 -9
View File
@@ -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))
{ {
+33 -5
View File
@@ -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)
{ {
+21 -7
View File
@@ -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 →
+26 -8
View File
@@ -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
Cary = vypln.Cary /// buď plná plocha, nebo pár čar přes celé logo.</summary>
.Select(u => new Usecka(X(u.X1, u.Y1), Y(u.X1, u.Y1), X(u.X2, u.Y2), Y(u.X2, u.Y2))) private Vypln Vypln(Vypln v)
.ToList(), {
}); if (v.JeSolid) return v;
return v with
{
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
View File
@@ -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.
--- ---