První commit — WPF aplikace pro generování výkresových rohových razítek (.NET 10, SkiaSharp, ACadSharp, ClosedXML) včetně xUnit testů, DXF šablony, log a dokumentace. Build výstupy (bin/, obj/, .vs/) jsou ignorovány. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
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 – „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) jen čte DXF a „přehrává" ho do tohoto rozhraní.
1. NuGet balíčky
Cílový framework: net9.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/MTEXTentity (žá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 (vizDxfTemplate.PlaceholderHladiny).
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).
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)
}
rgbje barva0xRRGGBBrozbalená 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í naSKCanvas. Dostane funkciMap(mmX, mmY) → bod plátnaapxPerMm. Text vzpřímeně (rotace opačně, plátno má Y dolů), fonty ze stylu/souboru s fallbackem. JeIDisposable(uvolní typefaces) – používat přesusing.DxfProfileRenderer– pro každou hladinu založíLayer(barva + tloušťka), entity přidává doDocument. Výsledek zapíšeDxfWriter.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:
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 spojuje šablonu, mapování hodnot
a exportéry na jedno místo (šablona se cachuje):
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: 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).
5. Export na A4 (Exporters)
Exporters vykreslí libovolný Action<IProfileRenderer>:
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 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 a zapisuje je do error.log vedle .exe.
8. Persistence a robustnost
- Projekt
.rzp(ProjektStore) – 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řesJsonConfigFile: 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
ATTDEFv model space (jejichTag= 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í.