Files
Rozpisky/docs/ROZPISKY.md
T
marekandClaude Opus 5 10548b5698 Doku: kreslicí rozhraní po změnách, tabulka entit a známá omezení
ROZPISKY.md §3 popisuje nové rozhraní (KresliciStav, Vypln, rozšířený TextStyle)
a hlavně *proč* barva nese původ, ne jen RGB. Přibyla tabulka DXF entit, které
se čtou, a seznam známých omezení — inline kódy MTEXTu, zarovnání Aligned/Fit,
typy čar, dohledávání vzoru šrafy v CADu. Opraveno tvrzení, že tloušťka čáry je
jen Layer.LineWeight; zapisuje se i na entitu.

CLAUDE.md doplněno druhé pravidlo architektury: co má přežít cestu DXF → model
→ DXF, musí protéct rozhraním — obcházet ho zpátky na CadDocument šablony je
porušení toho prvního. Baseline testů 57 → 114 (i v subagentech).

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

306 lines
19 KiB
Markdown
Raw 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.
# Rozpisky generátor výkresových rohových razítek (C# / .NET)
Standalone WPF aplikace (náhrada původního excelového nástroje „Rozpisky 1.0.8"). Spravuje projekt,
seznam příloh, revize a číselníky, a z **DXF šablony rozpisky** generuje **náhled**, **PDF na A4**,
**hromadný DXF** (pole jako `TEXT`/`MTEXT` entity), **seznam příloh do XLSX** a přes připojený CAD
i **tisk rozvržení**.
Celé kreslení stojí na jedné myšlence: existuje jedno **kreslicí rozhraní**
([`IProfileRenderer`](../Rozpisky/Rendering/IProfileRenderer.cs) „kam se kreslí") a několik jeho
implementací. Tatáž kresba teče na obrazovku (náhled), do PNG, do PDF i do DXF. Čtečka šablony
([`DxfTemplate`](../Rozpisky/Dxf/DxfTemplate.cs)) jen čte DXF a „přehrává" ho do tohoto rozhraní.
---
## 1. NuGet balíčky
Cílový framework: **`net10.0-windows`**, `UseWPF=true`, platforma **AnyCPU**. Připojení na běžící CAD
a MS Excel jde přes COM (pozdní vazba přes `dynamic`) bitness běhového procesu se řídí hostitelem,
zvláštní `PlatformTarget` se nenastavuje.
| Balíček | Verze | K čemu | Klíčové API |
|---|---|---|---|
| **SkiaSharp** | `2.88.8` | 2D rasterizace + **PDF**. Kreslení geometrie/textu/obrázků; export PNG i PDF. | `SKCanvas`, `SKPaint`, `SKPath`, `SKBitmap`, `SKImage`, `SKTypeface`, `SKDocument.CreatePdf`, `SKFileWStream` |
| **SkiaSharp.Views.WPF** | `2.88.8` | WPF plátno pro živý náhled (zoom/posun). **Jen pro GUI** headless export ho nepotřebuje. | `SKElement`, `OnPaintSurface(SKPaintSurfaceEventArgs)` |
| **ACadSharp** | `3.6.29` | Čtení i **zápis** DXF (novější verze, výplně HATCH, XRecord metadata atributů). Nahradilo dřívější netDxf. | `DxfReader.Read`, `DxfWriter.Write`, `CadDocument`, `ACadSharp.Entities.*`, `ACadSharp.Tables.TextStyle` |
| **ClosedXML** | `0.105.0` | Vyplnění mustru „Seznam příloh" (`SEZNAM.xlsx`) při exportu seznamu. | `XLWorkbook`, `IXLWorksheet`, `Cell(...).Value`, `SaveAs` |
> **Pozn. k ACadSharp:** na rozdíl od netDxf vystaví font ze stylu i box-šířku víceřádkových atributů
> (XRecord, group 41), takže jde dlouhý text v poli správně zalomit. DXF se zapisuje **binárně**
> (`DxfWriter.Write(..., binary: true)`) výrazně menší soubor. Převod XLSX → PDF (tisk seznamu) jde
> přes **nainstalovaný MS Excel** (COM), ne přes ACadSharp/ClosedXML.
### Zápis DXF co je potřeba vědět
- **Tloušťka čáry** je enum v 1/100 mm (standardní hodnoty
`0,5,9,13,15,18,20,25,30,35,40,50,53,60,70,80,90,100,106,120,140,158,200,211`); zapisuje se
na hladinu (`Layer.LineWeight`) i na entitu, která ji přebíjí. Milimetry z kreslicího rozhraní
se přichytí k nejbližší standardní hodnotě.
- **Barva** se zapisuje ve stejném druhu, v jakém přišla ze zdroje: indexovaná číslem (group 62),
přímá jako RGB (group 420), `ByLayer`/`ByBlock` odkazem. Zplošťování na RGB by výkres v CADu
odřízlo od tabulky barev.
- **Diakritika** se do DXF ukládá jako Unicode escape `\U+XXXX` (např. `š``\U+0161`) AutoCAD
i ostatní čtečky to čtou správně.
- Pole, která byla v šabloně `ATTDEF`, se při vyplnění zapisují jako obyčejné `TEXT`/`MTEXT` entity
(žádné atributy bloku).
- Objekty v netisknutelných hladinách (`PlotFlag=0`) se **nevykreslují**, ale ví se o nich kvůli
místům pro loga (viz [`DxfTemplate.PlaceholderHladiny`](../Rozpisky/Dxf/DxfTemplate.cs)).
---
## 2. Struktura kódu
Aplikace je členěná dle vrstev (MVVM + služby):
```
Rozpisky/
├─ Rendering/ ← kreslicí vrstva (nezávislá na cíli)
│ ├─ IProfileRenderer.cs ← kreslicí rozhraní + HAlign/VAlign + TextStyle + Vypln
│ ├─ SkiaProfileRenderer.cs ← výstup přes SkiaSharp (obrazovka / PNG / PDF)
│ ├─ DxfProfileRenderer.cs ← výstup do DXF (ACadSharp)
│ ├─ BoundsRenderer.cs ← spočítá bbox kresby (fit / umístění na A4)
│ ├─ TransformRenderer.cs ← dekorátor: afinní transformace (fit loga, rozvinutí bloku)
│ ├─ KresliciStav.cs ← barva s původem (ACI/RGB/ByLayer), hladina, typ čáry, tloušťka
│ └─ AciPaleta.cs ← indexované barvy AutoCADu (ACI 0255) → RGB
├─ Dxf/ ← čtení šablony, mapování hodnot, kódy
│ ├─ DxfTemplate.cs ← načte DXF šablonu a přehraje ji do IProfileRenderer
│ ├─ SrafaVzor.cs ← rozvine vzor šrafy (ANSI31 apod.) na čáry
│ ├─ RozpiskaRenderer.cs ← spojuje šablonu + hodnoty + exportéry (fasáda pro UI)
│ ├─ RozpiskaValues.cs ← „tag DXF → hodnota" z projektu/přílohy/číselníků (reflexí dle mapovani.json)
│ ├─ MapovaniStore.cs ← načtení mapování z mapovani.json (config vedle .exe)
│ ├─ KodPrilohy.cs ← 47-poziční strojový kód přílohy (manuál SŽ kap. 3)
│ ├─ Exporters.cs ← PngA4 / PdfA4 / RenderView (kreslí libovolný Action<IProfileRenderer>)
│ └─ LogaResolver.cs ← dohledání DXF log/schémat k příloze
├─ Cad/ ← připojení na běžící CAD (COM) + aktualizace/tisk rozvržení
├─ Xlsx/ ← export seznamu příloh (ClosedXML) + XLSX→PDF (Excel COM)
├─ Data/ ← persistence: .rzp projekt, číselníky, atomický zápis configů
├─ Models/ ← datový model (Projekt, Priloha, Revize, Ciselniky, Nastaveni…)
├─ ViewModels/ ← MainViewModel (stav, akce, dirty-flag, export)
├─ Views/ + Themes/ + Theming/ + Behaviors/ ← WPF UI (dark theme, grid chování)
├─ App.xaml(.cs) ← vstupní bod + globální záchytná síť výjimek (error.log)
└─ MainWindow.xaml(.cs) ← hlavní okno
```
Datové soubory jsou rozdělené podle toho, kdo je vlastní:
| Kde | Co | Kdo zapisuje |
|---|---|---|
| vedle `.exe` (zdroj `Rozpisky/Podklady/`) | `Rozpiska.dxf`, `SEZNAM.xlsx`, `ciselniky.vychozi.json`, `mapovani.json`, `loga/`, manuál SŽ (PDF) | nikdo dodává se s programem, každá instalace přepíše |
| `%APPDATA%\Rozpisky` ([`UzivatelskaData`](../Rozpisky/Data/UzivatelskaData.cs)) | `ciselniky.user.json`, `cad.json`, `layout.json`, `error.log`, `tisk_diag.log`, `loga/` (vlastní loga uživatele) | aplikace a uživatel |
| kdekoli u uživatele | projekty `.rzp` | aplikace na vyžádání |
Rozdělení existuje proto, aby přeinstalace novou verzí (nebo `.exe` v `Program Files`, kam se nesmí
psát) nesmazala nic, co si uživatel nastřádal. Portable režim se zapíná souborem `portable.txt`
vedle `.exe` pak se všechno vrátí do složky aplikace.
---
## 3. Kreslicí rozhraní
Vše se kreslí ve **výkresových milimetrech**, osa **Y nahoru** (jako v CAD/DXF).
```csharp
public enum HAlign { Left, Center, Right }
public enum VAlign { Baseline, Bottom, Middle, Top }
public enum BarvaDruh { ByLayer, ByBlock, Index, True }
public readonly record struct Barva(BarvaDruh Druh, byte Index, int Rgb);
public readonly record struct HladinaStav(string Nazev, Barva Barva, string TypCary = "Continuous",
double TloustkaMm = 0, bool Tisknout = true, bool Zapnuta = true);
public readonly record struct KresliciStav(HladinaStav Hladina, Barva Barva,
string? TypCary = null, double? TloustkaMm = null);
public readonly record struct TextStyle(string? FontFamily = null, bool Bold = false, bool Italic = false,
string? FontFile = null, double ObliqueDeg = 0,
string? Nazev = null, bool JeShx = false);
public readonly record struct Usecka(double X1, double Y1, double X2, double Y2);
public readonly record struct Vypln(string? Vzor = null, double Meritko = 1, double UhelDeg = 0,
IReadOnlyList<Usecka>? Cary = null);
public interface IProfileRenderer
{
void Stav(in KresliciStav s);
void Line(double x1, double y1, double x2, double y2);
void Polyline(IReadOnlyList<(double X, double Y)> pts, bool closed = false);
void Circle(double cx, double cy, double r);
void Stroke(Contour c); // obrys se zachovanými oblouky
void Text(double x, double y, string text, double heightMm,
HAlign h = HAlign.Left, VAlign v = VAlign.Baseline, double rotationDeg = 0,
TextStyle style = default, double widthFactor = 1, double maxWidthMm = 0);
void Fill(IReadOnlyList<Contour> loops, Vypln vypln = default); // HATCH výplně (even-odd)
}
```
**Proč `KresliciStav` a ne `int rgb`.** Šablona i všech 9 log jsou zapsané výhradně
**indexovanými barvami AutoCADu** (group 62). Kdyby rozhraní neslo jen výsledné RGB, export by
z nich udělal natvrdo true color (group 420), výkres by v CADu přestal reagovat na tabulku barev
a `ByLayer` by se rozpadlo na kopie barvy hladiny. Barva proto nese **původ**, ne jen hodnotu:
rastrové renderery si vyžádají hotové RGB přes `RgbProKresbu()`, DXF export zapíše původní druh.
Ze stejného důvodu se přenáší celý popis hladiny ve výstupu se staví z tabulky hladin, ne
z první entity, která na ní přistála.
- **Paleta ACI** ([`AciPaleta`](../Rozpisky/Rendering/AciPaleta.cs)) je vlastní, ne z ACadSharp:
ta se od AutoCADu liší u 33 indexů, mimo jiné u 8 (v šabloně použitý) a u posledního indexu
většiny desítek odstínů, kde vrací barvu ze sousední skupiny (29 a 39 mají dokonce tutéž).
Rozsah 10249 se dopočítává, ne vypisuje: 24 odstínů × 5 jasů × 2 sytosti.
- `TextStyle.Nazev` = jméno stylu ve zdroji („Popis základní"). Nese se dál, aby ho export zapsal
beze změny jeden zdrojový styl = jeden styl ve výstupu.
- `widthFactor` = horizontální měřítko písma (DXF width factor). `maxWidthMm` = šířka pro zalomení
víceřádkového textu (0 = jen explicitní zlomy).
- **Bílá se kreslí černě** (bílá na bílém papíře by zmizela) jen v rastrových rendererech;
do DXF jde barva beze změny.
Implementace:
- **`SkiaProfileRenderer`** kreslí na `SKCanvas`. Dostane funkci `Map(mmX, mmY) → bod plátna`
a `pxPerMm`. Text vzpřímeně (rotace opačně, plátno má Y dolů), fonty ze stylu/souboru s fallbackem.
Je `IDisposable` (uvolní typefaces) používat přes `using`.
- **`DxfProfileRenderer`** staví ACadSharp entity do `Document`; hladiny, textové styly i barvy
podle zdroje. Verze výstupu je zafixovaná (`VerzeVystupu`), ať ji neurčí default balíčku.
Výsledek zapíše `DxfWriter.Write(path, renderer.Document, binary: true)`.
- **`BoundsRenderer`** nic nekreslí, jen sčítá min/max souřadnic (fit / vystředění na A4).
- **`TransformRenderer`** dekorátor s afinní transformací: měřítko + posun (umístění loga do
rámečku), nebo měřítko os + natočení + posun (rozvinutí bloku, `ProBlok`). Oblouk zůstane
obloukem jen u podobnosti bez zrcadlení jinak by z kružnice byla elipsa, kterou rozhraní nezná,
a tvar se rozloží na lomenou čáru.
### Které DXF entity se čtou
| Entita | Jak se kreslí |
|---|---|
| `LINE`, `CIRCLE`, `ARC` | přímo; oblouk analyticky, hladký v každém přiblížení |
| `LWPOLYLINE`, `POLYLINE` (2D) | lomená čára, s `bulge` jako obrys s kruhovými oblouky |
| `POLYLINE` (3D) | promítnuto do roviny XY |
| `SPLINE` | lomená čára, dílek ≈ 0,3 mm podle délky řídicího polygonu |
| `ELLIPSE` | lomená čára (rozhraní zná jen kruhové oblouky) |
| `TEXT`, `MTEXT`, `ATTDEF` | text se stylem, zarovnáním, sklonem a šířkovým faktorem |
| `HATCH` | plná výplň, nebo vzor rozvinutý na čáry ([`SrafaVzor`](../Rozpisky/Dxf/SrafaVzor.cs)) |
| `SOLID`, `3DFACE` | vyplněný čtyřúhelník (v DXF jsou 3. a 4. roh prohozené) |
| `POINT` | tečka |
| `INSERT` | rekurzivně rozvinutý blok: vztažný bod, měřítko os, natočení, `ByBlock` barvy |
| ostatní | nekreslí se, ale **spočítá se** do `DxfTemplate.NezpracovaneEntity`; souhrn exportu to vypíše |
### Známá omezení
- **Vnitřní formátovací kódy MTEXTu** (`\H` výška, `\C` barva, `\f` font, `\S` zlomek) se ztrácejí
čte se `MText.PlainText`, který je odstraňuje. Zvlášť se čte jen `\W` (šířkový faktor), protože
ho ACadSharp v `PlainText` nechává a jinak by se vykreslil jako text.
- **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
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
si ji dohledá ve svých `.pat`. Vzor, který cílový CAD nezná, se v něm nevykreslí.
---
## 4. Čtečka šablony a fasáda pro UI
Nízkoúrovňová čtečka [`DxfTemplate`](../Rozpisky/Dxf/DxfTemplate.cs):
```csharp
var tpl = new DxfTemplate("Rozpiska.dxf");
tpl.Replay(g); // NÁHLED: pole se vykreslí jako jejich názvy (tagy)
tpl.Replay(g, values); // VYPLNĚNÍ: pole = hodnota; nevyplněné = prázdné
tpl.Replay(g, values, includeNoPrint); // + volitelně A4 rámeček !!NOPRINT (pro DXF export/tisk)
```
`Replay` přehraje statickou geometrii (`Line`/`Polyline`), popisky (`Text`/`MText`) a pole
(`ATTDEF` z model space) do rozhraní. `SheetFrame` vrací A4 rámeček šablony (referenční obdélník pro
vystředění), `Placeholders` vrací rámečky pro loga/schéma podle vyhrazených hladin, `Bounds` bbox
kresby (pro fit vkládaného fragmentu).
Fasáda pro UI [`RozpiskaRenderer`](../Rozpisky/Dxf/RozpiskaRenderer.cs) spojuje šablonu, mapování hodnot
a exportéry na jedno místo (šablona se cachuje):
```csharp
var r = new RozpiskaRenderer(Nastaveni.SablonaDxf);
Action<IProfileRenderer> draw = r.BuildDraw(projekt, priloha, revize, ciselniky,
vyplnene: true, loga, ramecekA4: false);
r.ExportPdf(projekt, priloha, revize, ciselniky, "rozpiska.pdf", loga); // A4 PDF vč. log
r.ExportPng(projekt, priloha, revize, ciselniky, "nahled.png", loga); // A4 PNG
```
Hodnoty polí sestavuje [`RozpiskaValues`](../Rozpisky/Dxf/RozpiskaValues.cs): mapování „pole programu →
DXF atribut" **není v kódu**, čte se z `mapovani.json` a hodnoty se dotahují reflexí. K tomu se
algoritmicky doplní 47-poziční strojový kód (`K1``K47`, viz
[`KodPrilohy`](../Rozpisky/Dxf/KodPrilohy.cs)).
---
## 5. Export na A4 (`Exporters`)
[`Exporters`](../Rozpisky/Dxf/Exporters.cs) vykreslí **libovolný** `Action<IProfileRenderer>`:
```csharp
Exporters.PngA4(draw, "rozpiska.png", dpi: 200, frame: sheetFrame); // A4 raster
Exporters.PdfA4(draw, "rozpiska.pdf", frame: sheetFrame); // A4 vektor (PDF, 1:1)
Exporters.RenderView(canvas, wpx, hpx, draw, frame, zoom, panX, panY); // živý náhled na SKElement
```
Princip A4: referenční obdélník (`frame` = A4 rámeček šablony, jinak `BoundsRenderer`) se mapuje
**1:1** a vystředí na papír; zmenší se jen tehdy, když se do tisknutelné plochy nevejde. PDF se kreslí
přes `SKDocument.CreatePdf` (1 mm = 72/25,4 bodu; text vektorový, tloušťky v reálných mm).
---
## 6. Demo data (`DemoData`)
[`DemoData`](../Rozpisky/Data/DemoData.cs) naplní úvodní stav při startu:
`SeedProjekt(vm)` (ukázkový projekt + přílohy), `SeedCiselniky(c)` a `SeedKraje(c)` (výchozí
číselníky, pokud `ciselniky.json` chybí).
---
## 7. Toky exportu (přehled)
| Akce | Vstup | Výstup | Přes |
|---|---|---|---|
| Náhled rozpisky | příloha | okno se SKElement | `RozpiskaRenderer.BuildDraw``Exporters.RenderView` |
| Export do PDF (výběr/vše) | přílohy | `Rozpisky\*.pdf` | `RozpiskaRenderer.ExportPdf` (per-item, chyby se sbírají) |
| Export do DXF (hromadně) | přílohy | `__ Otevřená\_rozpisky.dxf` | dlaždicová mřížka do `DxfProfileRenderer``DxfWriter.Write` |
| Export seznamu | objekt | `SEZNAM_<objekt>.xlsx` vedle .rzp | `SeznamExporter` (ClosedXML mustr) |
| Tisk seznamu | dřív exportovaný XLSX | `Přílohy\*.pdf` | `ExcelToPdf` (MS Excel COM) |
| Aktualizace/tisk rozvržení | přílohy | přejmenování/tisk v CADu | `Cad/CadLayoutService`, `Cad/CadTiskService` (COM) |
Aplikace **nemá CLI režim** vše jede z GUI. Neočekávané výjimky zachytává globální síť v
[`App.xaml.cs`](../Rozpisky/App.xaml.cs) a zapisuje je do `error.log` vedle .exe.
---
## 8. Persistence a robustnost
- **Projekt `.rzp`** ([`ProjektStore`](../Rozpisky/Data/ProjektStore.cs)) JSON s exkluzivním zámkem
drženým po dobu otevření (ostatní procesy smí jen číst). Zápis serializuje **před** useknutím
souboru, aby pád nezanechal prázdný soubor.
- **JSON configy** (`ciselniky.user.json`, `cad.json`, `layout.json` v uživatelské složce,
`mapovani.json` vedle .exe) jdou přes [`JsonConfigFile`](../Rozpisky/Data/JsonConfigFile.cs):
**atomický zápis** (přes `.tmp`) a při poškozeném souboru **záloha do `.bak` + upozornění**
(místo tichého přepsání výchozími daty).
- **Číselníky ve dvou vrstvách** ([`CiselnikyStore`](../Rozpisky/Data/CiselnikyStore.cs)) dodávaná
`ciselniky.vychozi.json` (jen ke čtení) a uživatelská `ciselniky.user.json`, kde je uložený jen
**rozdíl** proti ní (přidané/upravené položky + klíče skrytých). Nová verze programu tak smí
dodávaný číselník měnit, aniž by uživatel přišel o svoje záznamy.
- **Migrace starých poloh** ([`Migrace`](../Rozpisky/Data/Migrace.cs)) data zapisovaná dřív vedle
.exe se při startu přenesou do uživatelské složky; originál se jen přejmenuje na `.migrovano`.
- **Loga ve dvou složkách** ([`LogaResolver`](../Rozpisky/Dxf/LogaResolver.cs)) hledá se nejdřív
v uživatelské `loga\`, pak v dodávané; nabídka v UI je sjednocením obou.
- **Zamčený cíl exportu** před zápisem PDF/XLSX/DXF se ověří, zda soubor nedrží jiný program
(Excel, prohlížeč PDF), a nabídne Pokračovat/Přerušit.
---
## 9. Závislost na šabloně
Šablona je libovolný DXF, kde:
- rám a mřížku tvoří `LINE` / `LWPOLYLINE`,
- pevné popisky jsou `TEXT` / `MTEXT`,
- **vyplňovaná pole jsou `ATTDEF`** v model space (jejich `Tag` = klíč do hodnot),
- placeholdery pro loga jsou uzavřené polylinie na vyhrazených netisknutelných hladinách
(`Rozpiska_logo1`…, `Rozpiska_Orientační schéma`) logo/schéma se fitne do jejich ohraničení,
samotný placeholder se nekreslí (`PlotFlag=0`),
- A4 hranici papíru vymezuje rámeček na hladině `!!NOPRINT`.
Tím je celý vzhled rozpisky daný **datovým souborem a mapováním** (`Rozpiska.dxf` + `mapovani.json`),
ne kódem pro jinou rozpisku stačí jiný DXF a mapování.