Files
Rozpisky/docs/ROZPISKY.md
T
marekandClaude Opus 5 d4eb3f2102 Restrukturalizace složky podle účelu podkladů
Kořen mísil tři nesouvisející věci. Nově:
- docs/            – podklady jen pro tvorbu programu (ROZPISKY, ROZHRANI,
                     PODKLAD_CAD, manuál SŽ v .md)
- Rozpisky/Podklady/ – podklady nutné k běhu, dodávané vedle .exe
                     (Rozpiska.dxf, SEZNAM.xlsx, manuál .pdf, ciselniky.json,
                     mapovani.json, loga/)
- vzorky/          – cizí vstupy pro ruční zkoušení (schema.dxf)

Mění se jen zdrojový strom. Rozvržení vedle .exe zůstává ploché (+ loga\)
přes metadata Link v .csproj, takže všechny resolvery nad AppContext.BaseDirectory
fungují beze změny. Konec odkazů přes ..\ ven z projektu.

Doprovodně: cesty v TestData.Logo, odkazy v dokumentaci, CLAUDE.md a v promptech
subagentů.

Build 0 chyb / 0 varování, 36 testů zelených.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:35:50 +02:00

223 lines
13 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** = `Layer.LineWeight` (enum, hodnota 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`).
- **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
│ ├─ 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: měřítko + posun (fit fragmentů/loga)
├─ Dxf/ ← čtení šablony, mapování hodnot, kódy
│ ├─ DxfTemplate.cs ← načte DXF šablonu a přehraje ji do IProfileRenderer
│ ├─ 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
```
Vedle .exe se dodávají editovatelné soubory: `Rozpiska.dxf` (šablona), `SEZNAM.xlsx` (mustr seznamu),
`ciselniky.json`, `mapovani.json`, `cad.json`, `layout.json`, `loga/` a manuál SŽ (PDF).
---
## 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 readonly record struct TextStyle(string? FontFamily = null, bool Bold = false,
bool Italic = false, string? FontFile = null);
public interface IProfileRenderer
{
void Layer(string name, int rgb, double lineWeightMm = 0.25); // barva 0xRRGGBB, tloušťka ByLayer
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 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<IReadOnlyList<(double X, double Y)>> loops); // HATCH výplně (even-odd)
}
```
- `rgb` je barva `0xRRGGBB` rozbalená z DXF (ByLayer i index/true-color). Bílá se kreslí černě
(bílá na bílém papíře by zmizela).
- `widthFactor` = horizontální měřítko písma (DXF width factor). `maxWidthMm` = šířka pro zalomení
víceřádkového textu (0 = jen explicitní zlomy).
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`** pro každou hladinu založí `Layer` (barva + tloušťka), entity přidává do
`Document`. 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: na souřadnice aplikuje uniformní měřítko + posun. Slouží
k umístění vkládaného DXF fragmentu (logo / schéma) do vymezeného rámečku.
---
## 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.
- **Configy vedle .exe** (`ciselniky.json`, `cad.json`, `layout.json`, `mapovani.json`) 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).
- **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í.