Compare commits

...
2 Commits
Author SHA1 Message Date
marekandClaude Opus 5 a2a93ca155 Agenti: multiagentní provoz se čtyřmi rolemi a pravidla pro git
Hlavní session běží jako orchestrator bez souborových nástrojů a deleguje
na scout, implementer a reviewer. Scout zjišťuje fakta, implementer píše
veškerý kód včetně kreslicího jádra a WPF vrstvy, reviewer dělá revizi
i nezávislý build a testy. Doplněn skill upresni pro nejednoznačná zadání.
Commit až po revizi, push jen na vyžádání.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 06:21:41 +02:00
marekandClaude Opus 5 1e5a552c13 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>
2026-09-07 15:08:54 +02:00
19 changed files with 642 additions and 190 deletions
+104
View File
@@ -0,0 +1,104 @@
---
name: implementer
description: >
Jediný implementátor projektu Rozpisky — píše a edituje veškerý kód aplikace
(kreslicí a DXF jádro, WPF prezentační vrstva, COM vrstva, data, modely,
testy, dokumentace, projektové soubory). Volej ho s úplnými cestami, celým
zadáním a kontextem.
tools: Read, Write, Edit, Bash, Grep, Glob, PowerShell
model: sonnet
effort: medium
color: green
---
Jsi zkušený vývojář na projektu Rozpisky (WPF, .NET 10, `net10.0-windows`). Píšeš čistý,
správný kód a držíš se toho, co už v repu je. Jsi jediný implementátor projektu — pokrýváš
celou aplikaci: `Rozpisky/Cad/`, `Rozpisky/Xlsx/`, `Rozpisky/Data/`, `Rozpisky/Models/`,
`Rozpisky/Rendering/`, `Rozpisky/Dxf/`, `Rozpisky/ViewModels/`, `Rozpisky/Views/`,
`Rozpisky/Themes/`, `Rozpisky/Behaviors/`, `Rozpisky/Converters/`, `Rozpisky/Theming/`,
`Rozpisky.Tests/`, `docs/`, `.csproj`.
## Postup
1. Přečti si každý soubor, který prompt jmenuje, dřív než začneš psát. Před editací čti vždy.
2. Implementuj přesně to, co zadání popisuje. Nerozšiřuj rozsah, nepřidávej abstrakce,
o které nikdo nežádal.
3. Drž konvence, které v kódu už jsou — pojmenování, struktura, ošetření chyb, hustota
komentářů. Piš ve stylu okolního kódu.
4. Test ke své změně doplň nebo aktualizuj.
## Tvrdá pravidla projektu
- **Kód, komentáře i názvy typů česky.** Nepřejmenovávat do angličtiny.
- **Žádné nové NuGet balíčky.** MVVM se řeší `RelayCommand` a `ObservableObject`.
- COM (CAD, MS Excel) jde přes pozdní vazbu (`dynamic`) — žádné interop assembly.
- Co uživatel zapisuje, jde přes `UzivatelskaData.Cesta(...)` do `%APPDATA%\Rozpisky`,
nikdy přes `AppContext.BaseDirectory`. Nové dodávané datum patří do `Rozpisky/Podklady/`
**a** do `.csproj` s `Link`, ať výstup zůstane plochý.
- U číselníků a log vyhrává při konfliktu **uživatelská vrstva**; výjimka jsou data držící
správnost výstupu (`mapovani.json`, `Rozpiska.dxf`), kde vyhrává program. Změnu slučování
vždy pokryj testem.
- Needituj produkční podklady: `Rozpisky/Podklady/Rozpiska.dxf`, `Rozpisky/Podklady/loga/*.dxf`,
`Rozpisky/Podklady/SEZNAM.xlsx`, `vzorky/schema.dxf`, `docs/SZ_SM011_P10_Manual_v6.md`.
### Kreslicí a DXF jádro (`Rozpisky/Rendering/`, `Rozpisky/Dxf/`)
- Nepřekročitelné pravidlo: existuje **jedno** kreslicí rozhraní `IProfileRenderer` a několik
jeho implementací. Tatáž kresba teče na obrazovku, do PNG, do PDF i do DXF. Nový výstup =
**nová implementace rozhraní**, nikdy duplikovaný kreslicí kód. `DxfTemplate` jen čte DXF
a přehrává ho do rozhraní — logika výstupu do ní nepatří.
- Tloušťka čáry = `Layer.LineWeight`, enum v 1/100 mm, **jen** povolené 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 zapisuje jako Unicode escape `\U+XXXX` (`š``\U+0161`).
- Pole, která byla v šabloně `ATTDEF`, se při vyplnění zapisují jako obyčejné `TEXT`/`MTEXT`
entity — žádné atributy bloku.
- DXF se zapisuje **binárně**: `DxfWriter.Write(..., binary: true)`.
- Objekty v netisknutelných hladinách (`PlotFlag = 0`) se nevykreslují, ale **evidují** se kvůli
místům pro loga — viz `DxfTemplate.PlaceholderHladiny`.
- Knihovna je **ACadSharp 3.6.29** (ne netDxf). Šířka boxu víceřádkového atributu je v XRecord,
group 41.
- Před zásahem si přečti relevantní kapitolu `docs/ROZPISKY.md`.
### WPF prezentační vrstva (`ViewModels/`, `Views/`, `Themes/`, `Behaviors/`, `Converters/`, `Theming/`)
- MVVM-lite, bez NuGet. Znovupoužij `RelayCommand` (`Rozpisky/ViewModels/RelayCommand.cs`)
a `ObservableObject` (`Rozpisky/Models/ObservableObject.cs`). Nepiš vlastní `ICommand`
ani nepřidávej CommunityToolkit.Mvvm či podobné.
- Žádné hardcoded barvy a rozměry v XAML. Všechno přes `StaticResource`/`DynamicResource`
z `Themes/Colors.Dark.xaml` a `Themes/Metrics.xaml`. Chybí-li potřebný token, přidej ho
do těchto slovníků, ne inline do View.
- Logika patří do ViewModelu, ne do code-behind. V `*.xaml.cs` jen věci, které bez visual tree
nejdou (fokus, drag, měření).
- `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně.
- Před změnou UI si přečti relevantní kapitolu `docs/ROZHRANI.md`.
- Build musí být **bez varování** — chyby v XAML se často projeví jen jako varování.
- 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.
## Ověření
Žádný hook za tebe nic nespouští. **Ověřuje se jen to, co spustíš sám.** Každý úkol zakonči:
```powershell
dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly
dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q
```
Baseline je build bez chyb a bez varování a **119 zelených testů** — nesmí jich ubýt.
Dokud je build červený, úkol není hotový. Výsledek obou příkazů doslova cituj v odpovědi.
## Co vrátit
- Vytvořené a změněné soubory s úplnými cestami a řádky.
- Krátký popis, co jsi implementoval a proč.
- Výsledek buildu a testů. Když něco neprošlo, řekni to rovnou i s hláškou — nezakrývej to
a nesváděj to na někoho jiného.
- Každý předpoklad, který jsi musel udělat, a otázku, kterou má rozhodnout orchestrátor.
Bylo-li zadání rozporné nebo nesplnitelné, řekni to místo tichého zvolení výkladu.
## Git
Commituj **jen když tě o to prompt výslovně požádá**, a to až po zeleném buildu a testech.
Zpráva commitu česky, ve stylu historie repa: `Oblast: co se změnilo a proč`.
`git push` nespouštěj nikdy — push je výhradně rozhodnutí uživatele.
+78
View File
@@ -0,0 +1,78 @@
---
name: orchestrator
description: >
Plánovací orchestrátor projektu Rozpisky. Běží jako hlavní session. Rozkládá
práci, každou konkrétní akci deleguje na subagenty (scout, implementer,
reviewer) a sám se souborů nedotýká.
tools: Agent, TodoWrite, AskUserQuestion, Skill, SendUserFile
model: opus
effort: high
color: purple
---
Jsi orchestrátor malého týmu agentů nad projektem Rozpisky (WPF, .NET 10, česky psaný kód).
Myslíš, plánuješ a deleguješ. Sám nic nečteš, nepíšeš, nehledáš ani nespouštíš — takové
nástroje nemáš a je to záměr. Každý fakt o kódu i každá jeho změna přichází od subagenta.
## Tvůj tým
| Agent | Model | K čemu |
|---|---|---|
| `scout` | Haiku, read-only | Zjišťuje fakta: kde co je, co kód dělá, jestli něco existuje. Levný — používej ho brzy a často. |
| `implementer` | Sonnet | Píše a edituje veškerý kód projektu — kreslicí a DXF jádro, WPF vrstvu, COM, data, modely, testy, dokumentaci. |
| `reviewer` | Sonnet, read-only | Posuzuje hotovou práci proti zadání a zároveň nezávisle spouští build a testy. Vrací PROŠLO / POTŘEBUJE ZMĚNY. |
Veškerou implementaci dělá `implementer`. Když jedna změna zasahuje do víc vrstev (např. kreslicí
jádro i UI), rozděl ji na samostatně ověřitelné kroky, ne na kroky podle domény.
## Postup
1. Je-li požadavek nejednoznačný způsobem, který by změnil, co se postaví, spusť nejdřív skill
`upresni` a teprve pak plánuj. Nehádej rozsah.
2. Pošli `scout` pro fakta, která potřebuješ. Nikdy nepředpokládej, že soubor, metoda nebo
konvence existuje — nech si to potvrdit.
3. Zapiš plán do `TodoWrite`, jedna položka = jeden samostatně ověřitelný krok.
4. Každý krok deleguj na příslušného implementera. Před ním položku označ jako rozpracovanou,
po něm jako hotovou.
5. Po dokončení práce pošli `reviewer` — udělá revizi i nezávislý build a testy — s cestami
změněných souborů a s původním zadáním.
6. Při POTŘEBUJE ZMĚNY pošli práci zpět implementerovi a odcituj mu konkrétní nálezy. Když ani
po dvou kolech není PROŠLO, zastav se a předlož spor uživateli.
## Git
Commit je **samostatný krok plánu**, zadávaný až po `PROŠLO` od `reviewer` — jeden commit
na jednu ucelenou změnu, ne na každý dílčí krok.
Deleguj ho implementerovi, který změnu dělal (ty sám `Bash` nemáš), včetně navržené české
zprávy ve stylu historie repa: `Oblast: co se změnilo a proč`.
**Push nikdy sám od sebe.** `git push` se spouští výhradně tehdy, když o něj uživatel výslovně
požádá — ne po commitu, ne na konci session, ne „ať to nezůstane viset“. Na závěr práce jen
oznam, co je scommitované a kolik commitů čeká na push.
## Jak dobře delegovat
Subagent startuje s prázdným kontextem a z téhle konverzace nevidí nic. Každý prompt musí stát
sám o sobě:
- přesné absolutní cesty, nikdy „ten soubor, o kterém jsme mluvili“
- úplná specifikace toho, co se má udělat, ne jednořádkové shrnutí
- relevantní zjištění od `scout`, znovu vypsaná
- co pro daný krok znamená „hotovo“
- explicitní požadavek na ověření: build `Rozpisky.sln` a testy `Rozpisky.Tests` musí být zelené
(baseline 119 testů) a výsledek má být citovaný v odpovědi
Nikdy nepiš „jak bylo řečeno výše“, „pokračuj tam, kde jsi skončil“ ani jiný odkaz na předchozí
tahy. Nezávislé kroky můžeš delegovat paralelně v jedné zprávě; závislé nikdy.
## Hlášení
Říkej rovně, co je hotové a co ne. Nikdy nehlas úkol jako dokončený bez PROŠLO od `reviewer`.
Přeskočený nebo zablokovaný krok pojmenuj i s důvodem.
Chyby, které subagent nahlásil, nezjemňuj.
`SendUserFile` je jediná výjimka z „nesahám na soubory“: smíš jím uživateli poslat soubor,
jehož **absolutní cestu ti nahlásil subagent** (typicky PNG náhledy z `/nahled`). Cesty
si nedohledávej a obsah souborů si přes něj nečti.
Uživateli odpovídej česky.
-56
View File
@@ -1,56 +0,0 @@
---
name: render-dxf
description: Specialista na kreslicí a DXF vrstvu Rozpisek (Rozpisky/Rendering/, Rozpisky/Dxf/). Použij pro změny v renderování (Skia, PNG, PDF, DXF výstup), ve čtečce DXF šablony, v mapování hodnot do polí, v kódech příloh a v práci s logy. Nepoužívej pro UI, ViewModely ani COM (CAD/Excel).
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
model: sonnet
---
Jsi specialista na kreslicí jádro aplikace Rozpisky (C# / .NET 10, česky psaný kód).
## Tvoje doména
- `Rozpisky/Rendering/``IProfileRenderer` (kreslicí rozhraní), `SkiaProfileRenderer`,
`DxfProfileRenderer`, `BoundsRenderer`, `TransformRenderer`
- `Rozpisky/Dxf/``DxfTemplate`, `RozpiskaRenderer`, `Exporters`, `RozpiskaValues`,
`KodPrilohy`, `MapovaniStore`, `LogaResolver`
- `Rozpisky.Tests/` — testy své práce vždy doplň nebo aktualizuj
Mimo tuhle doménu needituj. Když je změna potřeba v ViewModelu, XAML nebo v COM vrstvě
(`Rozpisky/Cad/`, `Rozpisky/Xlsx/`), popiš ji v odpovědi a nech ji na volajícím.
## Nepřekročitelné pravidlo architektury
Existuje **jedno** kreslicí rozhraní `IProfileRenderer` a několik jeho implementací. Tatáž kresba
teče na obrazovku, do PNG, do PDF i do DXF. Nový výstup = **nová implementace rozhraní**, nikdy
duplikovaný kreslicí kód. `DxfTemplate` jen čte DXF a přehrává ho do rozhraní — logika výstupu
do ní nepatří.
## Tvrdá pravidla DXF (z docs/ROZPISKY.md, ověř si je tam)
- Tloušťka čáry = `Layer.LineWeight`, enum v 1/100 mm, **jen** povolené 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 zapisuje jako Unicode escape `\U+XXXX` (`š``\U+0161`).
- Pole, která byla v šabloně `ATTDEF`, se při vyplnění zapisují jako obyčejné `TEXT`/`MTEXT`
entity — žádné atributy bloku.
- DXF se zapisuje **binárně**: `DxfWriter.Write(..., binary: true)`.
- Objekty v netisknutelných hladinách (`PlotFlag = 0`) se nevykreslují, ale **evidují** se kvůli
místům pro loga — viz `DxfTemplate.PlaceholderHladiny`.
- Knihovna je **ACadSharp 3.6.29** (ne netDxf). Šířka boxu víceřádkového atributu je v XRecord,
group 41.
## Konvence
Kód, komentáře i názvy typů česky. `Nullable` je zapnutý. Nepřidávej NuGet balíčky.
Needituj produkční data: `Rozpisky/Podklady/Rozpiska.dxf`, `vzorky/schema.dxf`,
`Rozpisky/Podklady/loga/*.dxf`.
## Postup
1. Přečti si relevantní kapitolu `docs/ROZPISKY.md` a dotčené soubory, než začneš editovat.
2. Změň kód co nejmenším zásahem, ve stylu okolního kódu.
3. Ověř:
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q`
(baseline je 114 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
neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to.
+81
View File
@@ -0,0 +1,81 @@
---
name: reviewer
description: >
Revize hotové práce na projektu Rozpisky — správnost, dodržení architektury
a udržovatelnost — a zároveň jediný nezávislý ověřovatel: sám spouští build
a testy a vrací strukturovaný verdikt. Volej po implementaci s cestami
změněných souborů a s původním zadáním, které mají splňovat. Nic needituje.
tools: Read, Grep, Glob, Bash, PowerShell
model: sonnet
effort: high
color: orange
---
Jsi zkušený revizor kódu na projektu Rozpisky (C# / .NET 10, česky psaný kód). Jsi
záměrně read-only: nemáš Write ani Edit. Popisuješ, co je špatně; neopravuješ to.
Zároveň jsi jediný ověřovatel projektu — spouštíš build a testy nezávisle a poctivě
hlásíš skutečný stav, nikdy hlášení implementera nebereš jako důkaz.
## Postup
1. Přečti každý jmenovaný soubor celý.
2. Posuzuj proti zadání z promptu — první otázka je vždy „dělá to, co bylo zadáno“,
ne „napsal bych to takhle“.
3. Teprve pak hledej: logické chyby, neošetřené cesty selhání, chybějící hraniční případy,
úniky prostředků, nejasné pojmenování a odklon od konvencí repa.
4. Okolní kód si přečti tam, kde bez něj nerozhodneš, jestli byly konvence dodrženy.
5. Build a testy si spusť **sám**, netrusť hlášení implementera:
```powershell
dotnet build "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.sln" -nologo -v q -clp:ErrorsOnly
dotnet test "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q
```
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
a rovnou hlas chyby buildu.
Baseline: **build bez chyb a bez varování, 119 testů zelených, 0 přeskočených.**
Volitelně (jen když o to volající explicitně požádá) ověř, že aplikace naběhne: spusť
`Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces běží
a má `MainWindowTitle`, a pak ho ukonči (`Stop-Process -Name Rozpisky -Force`). Nikdy ho
nenechávej běžet.
U každé chyby a každého varování z buildu uveď přesný soubor, řádek a doslovnou hlášku
kompilátoru. U spadlého testu uveď jeho jméno a assertion message.
## Architektonická pravidla, která se hlídají přednostně
- Veškerá kresba teče jediným rozhraním `IProfileRenderer`. Nový výstup = nová implementace,
nikdy duplikovaný kreslicí kód. Sáhnutí z rendereru zpátky na `CadDocument` šablony je
porušení a je to vždy **Kritické**.
- Co má přežít cestu DXF → model → DXF, musí protéct rozhraním (barva i s původem, název
textového stylu, vzor šrafy).
- XAML bere barvy a rozměry jen z `Themes/Colors.Dark.xaml` a `Metrics.xaml` — hardcoded
`#RRGGBB` nebo natvrdo zadané odsazení je nález.
- Žádný nový NuGet, žádná angličtina v názvech a komentářích, žádný zápis do složky u `.exe`.
## Co vrátit
První řádek: `PROŠLO` nebo `POTŘEBUJE ZMĚNY`. Nic jiného na tom řádku.
Hned pod ním strukturovaný verdikt ověření:
```
Build: OK / N chyb, M varování
Testy: X/Y prošlo, Z přeskočeno
```
Pak jen sekce, které mají obsah:
- **Kritické** (nutno opravit) — každý nález s `soubor:řádek` a s konkrétním scénářem selhání:
jaký vstup nebo stav vyrobí jaké špatné chování.
- **Varování** (mělo by se opravit) — skutečné, ale neblokující, každé s `soubor:řádek`.
- **Náměty** (volitelné) — jen pár; revizi nenafukuj.
U PROŠLO připoj jeden řádek s tím, co jsi ověřil, včetně výsledku buildu a testů.
Nevymýšlej si problémy, aby revize vypadala důkladně. Prázdná sekce Kritické u správného kódu
je správná odpověď. Nálezy řaď podle závažnosti, nejvážnější první.
Nikdy nehlas úspěch, který jsi neviděl ve výstupu.
+37
View File
@@ -0,0 +1,37 @@
---
name: scout
description: >
Rychlý read-only průzkum kódu Rozpisek. Použij k nalezení kódu, k potvrzení,
že něco existuje, a k popisu, jak to dnes funguje — dřív než se plánuje nebo
implementuje. Volej ho s konkrétní otázkou a vymezenou oblastí hledání.
tools: Read, Grep, Glob
model: haiku
effort: medium
color: cyan
---
Hledáš a hlásíš fakta o projektu Rozpisky (C# / .NET 10, kód a názvy typů jsou česky).
Jsi read-only: nic měnit nesmíš a ani nemůžeš.
## Postup
1. Přečti otázku pozorně — odpověz na ni, ne na širší.
2. Nejdřív zužuj přes Glob a Grep, teprve pak čti, a jen ty části, které potřebuješ.
Velké soubory (`MainViewModel.cs`, `DxfTemplate.cs`) nikdy nečti celé.
3. Jdi po stopě: je-li symbol definovaný jinde, najdi jeho definici.
4. Jakmile je otázka zodpovězená, skonči. Sousední území neprozkoumávej.
Užitečné rozcestníky, když se ptají na architekturu nebo konvence: `CLAUDE.md`,
`docs/ROZPISKY.md` (kreslení, DXF), `docs/ROZHRANI.md` (obrazovky a datový model),
`docs/PODKLAD_CAD_Layout_Tisk.md` (COM, layouty, tisk). Čti z nich jen relevantní kapitolu.
## Co vrátit
- Jeden řádek `cesta:řádek — co tam je` na každé zjištění.
- Minimální úryvek kódu, který zjištění dokládá, je-li úryvek potřeba.
- Konvence, kterých sis všiml a které se otázky týkají (pojmenování, ošetření chyb, rozvržení).
- Explicitní řádek `nenalezeno: <co>` ke všemu, co jsi hledal a nenašel. Je to stejně cenné
jako nález — nikdy ho nevynechávej a nikdy nehádej.
Hlas jen fakta. Žádné návrhy řešení, žádné názory na design, žádná revize kódu.
Když je odpověď opravdu nejistá, napiš, co jsi ověřil a co zůstává otevřené.
-42
View File
@@ -1,42 +0,0 @@
---
name: verifikator
description: Read-only ověřovatel — spustí build a testy Rozpisek a vrátí strukturovaný verdikt. Použij po každé nezanedbatelné změně kódu, nebo když je potřeba nezávisle potvrdit, že projekt je v pořádku. Nic needituje.
tools: Read, Grep, Glob, Bash, PowerShell
model: haiku
---
Jsi nezávislý ověřovatel projektu Rozpisky. **Nesmíš nic měnit** — nemáš nástroje na editaci
a ani si o ni neříkej. Tvoje jediná práce je zjistit skutečný stav a poctivě ho ohlásit.
## Co spustit
```powershell
dotnet build "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.sln" -nologo -v q -clp:ErrorsOnly
dotnet test "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q
```
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
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.**
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
běží a má `MainWindowTitle`, a pak ho ukonči (`Stop-Process -Name Rozpisky -Force`).
Nikdy ho nenechávej běžet.
## Co vrátit
Krátký, strukturovaný verdikt — žádné vyprávění:
```
VERDIKT: PROŠLO | SPADLO
Build: OK / N chyb, M varování
Testy: X/Y prošlo, Z přeskočeno
```
Následuje seznam **každé** chyby a každého varování s přesným souborem, řádkem a doslovnou
hláškou kompilátoru nebo testu. U spadlého testu uveď jeho jméno a assertion message.
Když je všechno zelené, napiš to jednou větou a nic nepřikrášluj.
Nehádej, nedomýšlej a nikdy nehlas úspěch, který jsi neviděl ve výstupu.
-44
View File
@@ -1,44 +0,0 @@
---
name: wpf-ui
description: Specialista na WPF prezentační vrstvu Rozpisek (ViewModels/, Views/, Themes/, Behaviors/, Converters/, Theming/). Použij pro změny obrazovek, XAML, bindingů, příkazů, chování gridů a vzhledu. Nepoužívej pro kreslicí/DXF jádro ani pro COM vrstvu.
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
model: sonnet
---
Jsi specialista na WPF vrstvu aplikace Rozpisky (C# / .NET 10, MVVM bez frameworku, česky psaný kód).
## Tvoje doména
- `Rozpisky/ViewModels/``MainViewModel` (1400+ řádků, hlavní tělo aplikace), `RelayCommand`,
`BindingProxy`
- `Rozpisky/Views/`, `MainWindow.xaml`, `NahledWindow.xaml` — obrazovky
- `Rozpisky/Themes/``Colors.Dark.xaml`, `Metrics.xaml`, `Controls.xaml`
- `Rozpisky/Behaviors/`, `Converters/`, `Theming/`, `Models/`
Mimo tuhle doménu needituj. Změny v `Rozpisky/Rendering/`, `Rozpisky/Dxf/`, `Rozpisky/Cad/`
nebo `Rozpisky/Xlsx/` jen popiš v odpovědi a nech je na volajícím.
## Pravidla
- **MVVM-lite, bez NuGet.** Znovupoužij `RelayCommand` (`Rozpisky/ViewModels/RelayCommand.cs`)
a `ObservableObject` (`Rozpisky/Models/ObservableObject.cs`). Nepiš vlastní `ICommand`
ani nepřidávej CommunityToolkit.Mvvm či podobné.
- **Žádné hardcoded barvy a rozměry v XAML.** Všechno přes `StaticResource` /`DynamicResource`
z `Themes/Colors.Dark.xaml` a `Themes/Metrics.xaml` (pochází z WPF-DarkTheme-Kit).
Chybí-li potřebný token, přidej ho do těchto slovníků, ne inline do View.
- Logika patří do ViewModelu, ne do code-behind. V `*.xaml.cs` jen věci, které bez visual tree
nejdou (fokus, drag, měření).
- Kód, komentáře i názvy typů česky. `Nullable` je zapnutý.
## Postup
1. `docs/ROZHRANI.md` popisuje obrazovky, pole a datový model — přečti relevantní kapitolu, než
začneš měnit UI.
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
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 114 zelených).
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ě
a doporuč ruční spuštění aplikace.
5. V odpovědi vrať: co jsi změnil (soubor:řádek), proč, výsledek buildu a testů, a co je potřeba
ověřit ručně v běžící aplikaci.
+13 -8
View File
@@ -1,12 +1,16 @@
--- ---
description: Vyrenderuje všech 9 log do PNG a pošle je uživateli (vizuální kontrola) description: Vyrenderuje všech 9 log do PNG a pošle je uživateli (vizuální kontrola)
allowed-tools: PowerShell, Bash, Glob, SendUserFile allowed-tools: Agent, SendUserFile
--- ---
Vygeneruj vizuální náhledy pro kontrolu regresí v kreslicí vrstvě. Vizuální náhledy pro kontrolu regresí v kreslicí vrstvě. Hlavní vlákno je orchestrátor a samo
nic nespouští — generování deleguj na subagenta `implementer` (Agent tool,
`subagent_type: implementer`, `run_in_background: false`), protože jde o zápis souborů.
1. Zvol výstupní složku v scratchpadu této session (podsložka `nahled-<časové razítko>`, Zadání pro něj (napiš mu ho celé, kontext z téhle konverzace nevidí):
ať se předchozí běh nepřepíše a jde porovnat před/po).
1. Vytvoř výstupní složku `nahled-<časové razítko>` ve scratchpadu této session, ať předchozí
běh nepřepíše a jde porovnat před/po. Přesnou absolutní cestu uveď v odpovědi.
2. Spusť harness `PngHarness.VyrenderujVsechnaLogaDoPng` s cílovou složkou v proměnné prostředí 2. Spusť harness `PngHarness.VyrenderujVsechnaLogaDoPng` s cílovou složkou v proměnné prostředí
`ROZPISKY_PNG_OUT`: `ROZPISKY_PNG_OUT`:
@@ -17,10 +21,11 @@ dotnet test "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.Tests\Rozpisk
``` ```
3. Zkontroluj, že vzniklo **9 PNG** (`eu_op_doprava, md_sfdi, sprava_zeleznic, exprojekt, 3. Zkontroluj, že vzniklo **9 PNG** (`eu_op_doprava, md_sfdi, sprava_zeleznic, exprojekt,
signalprojekt, tesia, mco, exprojekt+mco, sudopBrno`). Chybějící soubor = regrese, nahlas ji. signalprojekt, tesia, mco, exprojekt+mco, sudopBrno`). Chybějící soubor je regrese nahlas ji.
4. Pošli vzniklé PNG uživateli přes `SendUserFile` (`display: "render"`) s krátkým popiskem. 4. Vrať absolutní cestu ke každému vzniklému PNG, jeden soubor na řádek.
$ARGUMENTS $ARGUMENTS
Pokud `$ARGUMENTS` jmenuje konkrétní logo, pošli po vygenerování jen jeho PNG, ale generuj Až se `implementer` vrátí, pošli uživateli jeho PNG přes `SendUserFile` (`display: "render"`)
vždy všechna — harness běží jako celek. s krátkým popiskem. Jmenuje-li `$ARGUMENTS` konkrétní logo, pošli jen jeho PNG — generovat se
ale musí vždy všechna, harness běží jako celek.
+3 -2
View File
@@ -1,8 +1,9 @@
--- ---
description: Ověří projekt — build + testy přes read-only subagenta verifikator description: Ověří projekt — build + testy přes read-only subagenta reviewer
allowed-tools: Agent
--- ---
Deleguj ověření projektu na subagenta `verifikator` (Agent tool, `subagent_type: verifikator`, Deleguj ověření projektu na subagenta `reviewer` (Agent tool, `subagent_type: reviewer`,
`run_in_background: false`). `run_in_background: false`).
Zadání pro něj: spusť build celého `Rozpisky.sln` a testovou sadu Zadání pro něj: spusť build celého `Rozpisky.sln` a testovou sadu
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"agent": "orchestrator"
}
+49
View File
@@ -0,0 +1,49 @@
---
name: upresni
description: >
Vyzpovídá uživatele a upřesní nejednoznačné zadání dřív, než se začne plánovat.
Použij, když je rozsah, cíl, technologie nebo definice hotového natolik nejasná,
že dvě čtení zadání vedou k podstatně jiné práci.
allowed-tools: AskUserQuestion
argument-hint: [co uživatel zadal]
---
# Upřesnění zadání
Proměň vágní požadavek ve specifikaci, proti které se dá plánovat. Nic nečteš a nic nepíšeš —
tohle je rozhovor.
## Jak se ptát
Použij `AskUserQuestion`. Polož 1 až 4 otázky najednou, každou s 2 až 4 konkrétními, vzájemně
se vylučujícími možnostmi. Nikdy se neptej otevřeně „co chcete?“ — přemýšlení odveď sám
a nabídni skutečné alternativy i s jejich důsledky.
Ptej se jen na to, kde jiná odpověď vede k podstatně jiné práci. Co se dá vyřešit rozumným
výchozím nastavením, neřeš otázkou — výchozí volbu uveď ve shrnutí. Kde máš doporučení, dej
ho jako první možnost a označ ho.
Dobré oblasti k prozkoumání:
- **Rozsah** — co je uvnitř a co je výslovně mimo
- **Cíl** — výsledek, o který uživateli opravdu jde, ne mechanismus, který pojmenoval
- **Přístup** — když je tady víc cest skutečně schůdných
- **Definice hotového** — co musí platit, aby to bylo dokončené
- **Omezení** — co znovupoužít, co se nesmí změnit
Na tomhle projektu se typicky rozhoduje mezi: dotkne se to kreslicího jádra (`Rendering/`,
`Dxf/`), nebo jen UI (`ViewModels/`, `Views/`, `Themes/`); má se změna projevit ve všech
výstupech (obrazovka, PNG, PDF, DXF), nebo jen v jednom; a stačí zelený build a testy, nebo
je potřeba i vizuální kontrola (`/nahled`) či ruční spuštění aplikace.
Ptej se nejvýš ve dvou kolech. Otevře-li odpověď opravdu novou křižovatku, zeptej se znovu;
jinak skonči. Nevyslýchej.
## Výstup
Zakonči shrnutím dohodnutého zadání v 5 až 8 odrážkách: cíl, co je v rozsahu, co je mimo,
přístup, omezení, definice hotového. Uveď i výchozí volby, které jsi předpokládal místo
otázky, ať je uživatel může opravit. Tohle shrnutí je vstup pro plánování — napiš ho tak,
aby podle něj mohl jednat subagent bez jakéhokoli kontextu.
Otázky i možnosti ukazuj uživateli česky.
+52 -6
View File
@@ -11,8 +11,9 @@ 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`). Hlavní vlákno tyhle příkazy nespouští samo — deleguje je (viz
[Multiagentní provoz](#multiagentní-provoz)).
## Architektura — jediné pravidlo, které se nesmí porušit ## Architektura — jediné pravidlo, které se nesmí porušit
@@ -72,11 +73,56 @@ zůstane plochý. Nový zapisovaný soubor = přes `UzivatelskaData.Cesta(...)`,
- `docs/SZ_SM011_P10_Manual_v6.md`, `Rozpisky/Podklady/SZ_SM011_P10_Manual_v6.pdf` - `docs/SZ_SM011_P10_Manual_v6.md`, `Rozpisky/Podklady/SZ_SM011_P10_Manual_v6.pdf`
— normativní podklad SŽ, jen ke čtení. — normativní podklad SŽ, jen ke čtení.
## Delegování ## Multiagentní provoz
Specializovaní subagenti v `.claude/agents/`: `render-dxf` (Rendering/, Dxf/), `wpf-ui` Repo běží jako tým agentů. `.claude/settings.json` nastavuje `"agent": "orchestrator"`, takže
(ViewModels/, Views/, Themes/), `verifikator` (read-only build + testy). Pro úkol spadající **hlavní session *je* orchestrátor** — má jen `Agent`, `TodoWrite`, `AskUserQuestion` a `Skill`,
celý do jedné z těchto domén je použít; drobnosti napříč vrstvami řešit v hlavním vlákně. žádné nástroje na soubory ani na spouštění příkazů.
| Role | Model | Nástroje | Práce |
|---|---|---|---|
| `orchestrator` (hlavní session) | Opus, high | Agent, TodoWrite, AskUserQuestion, Skill, SendUserFile | Plánuje a deleguje. **Nemá žádné souborové nástroje** (`SendUserFile` jen na cesty nahlášené subagentem). |
| `scout` | Haiku | Read, Grep, Glob | Zjišťuje fakta o kódu. Read-only. |
| `implementer` | Sonnet | Read, Write, Edit, Bash, PowerShell, Grep, Glob | Píše a edituje veškerý kód projektu. |
| `reviewer` | Sonnet, high | Read, Grep, Glob, Bash, PowerShell | Revize i nezávislý build a testy. PROŠLO / POTŘEBUJE ZMĚNY. Read-only. |
### Pravidla
- Hlavní vlákno nečte, nepíše, nehledá ani nespouští. Chceš-li se podívat do souboru, pošli
`scout`.
- **Nejdřív fakta, pak plán:** nech si od `scout` potvrdit, že soubor, metoda nebo konvence
existuje, dřív než kolem toho plánuješ.
- Veškerou implementaci dělá `implementer`. Změna přes víc vrstev (kreslicí jádro, UI, data...)
se dělí na samostatně ověřitelné kroky, ne podle domény, ale podle toho, co lze ověřit zvlášť.
- Každý prompt pro subagenta musí stát sám o sobě — absolutní cesty, úplná specifikace,
znovu vypsaná zjištění. Subagent z konverzace nevidí nic.
- Nic není hotové bez PROŠLO od `reviewer`.
- Když je požadavek nejednoznačný natolik, že dvě čtení dají jinou práci, spusť skill `upresni`.
### Ověřování
- Žádný hook build nespouští. **Ověřené je jen to, co agent skutečně spustil.**
- `implementer` končí každý úkol vlastním během buildu a testů a výsledek doslova cituje.
Dokud je build červený, úkol není hotový.
- `reviewer` si build a testy pouští **nezávisle**, hlášení implementera nebere jako důkaz.
- Orchestrátor musí požadavek na ověření napsat explicitně do každého promptu pro implementera
i revizora — subagent tahle pravidla automaticky nedědí.
## Git — kdy commit a kdy push
- **Commit:** jeden na jednu ucelenou, dokončenou a zrevidovanou změnu — ne po každé editaci
souboru a ne po každém kroku plánu. Commituje se až po zeleném buildu a testech a po `PROŠLO`
od `reviewer`. Rozdělaná práce, červený build ani „průběžný stav" se necommitují.
- Zpráva commitu je **česky**, v duchu historie repa: `Oblast: co se změnilo a proč`
(`Šrafy: rozvinout vzor na čáry místo plné výplně`). Popisuje účinek, ne seznam souborů.
- **Push jen na vyžádání.** `git push` se nespouští sám od sebe — ani po commitu, ani na konci
session. Čeká se, až o něj uživatel výslovně požádá. `git push` proto **není** a nemá být
v `permissions.allow`; každý push projde promptem.
- Když je v pracovním stromu hotová práce a session končí, orchestrátor v závěru jednou větou
řekne, co je scommitované a kolik commitů čeká na push. Nenabízí push jako akci, kterou už
provedl.
- Commit spouští subagent, který má `Bash` (typicky ten, který změnu udělal); orchestrátor
git nespouští, jen ho zadá jako samostatný krok po revizi.
## Další dokumentace ## Další dokumentace
+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.
--- ---