Files
Rozpisky/docs/PODKLAD_CAD_Layout_Tisk.md
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

225 lines
17 KiB
Markdown
Raw Permalink 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.
# Podklad: Práce s CADem, rozvržení a tisk
> Výcuc funkcionality (bez kódu) pro převod VBA verze 1.0.8 do C#.
> Zaměřeno **čistě na práci s CADem, rozvržení (layouty) a tisk** bez rozpisek.
> Popisuje funkcionalitu + použité ActiveX/COM API, které se bude převádět.
---
## 1. Připojení na CAD (`clsCadLink`)
Centrální objekt pro veškerou komunikaci s CADem. Vše ostatní jde přes něj (`cad.Doc.*`).
**Funkcionalita:**
- **Late-binding** přes `GetObject(, ProgID)` připojuje se POUZE na **již běžící** CAD, nic nespouští.
- Zkouší v pořadí: `AutoCAD.Application``ZWCAD.Application``BricscadApp.AcadApplication`. První, který běží, vyhraje.
- Po napojení na aplikaci si vezme `mApp.ActiveDocument`. Když CAD běží, ale není otevřený výkres → chyba.
- Drží: `App` (aplikace), `Doc` (aktivní dokument), `Engine` (který ProgID se chytil), `LastError`, `IsConnected`.
**Pomocné metody nad dokumentem (volitelné, používá je generování):**
- `CheckLayer(name)` → existuje hladina? (`Doc.Layers(name)`)
- `AddLayer(name, color, lineWeight)``Doc.Layers.Add`
- `CheckBlock(name)` → existuje definice bloku? (`Doc.Blocks(name)`)
- `AddBlock(path)` → vloží blok ze souboru do ModelSpace (`InsertBlock`) jen kvůli načtení definice a hned referenci smaže.
**Pro C#:** V .NET přes COM Interop (`Marshal.GetActiveObject("AutoCAD.Application")`) nebo silně typované `Autodesk.AutoCAD.Interop` (`AcadApplication`, `AcadDocument`). Pozor `GetActiveObject` je v moderním .NET potřeba volat ručně přes `Marshal`. Pořadí ProgID zachovej jako fallback řetězec.
---
## 2. Datové nosiče (čtení dat z Excelu, bez CADu)
### `Layout` (data jednoho rozvržení)
Načítá řádek z listu **SEZNAM** + pojmenovanou buňku `stavba`. Drží metadata jednoho výkresu:
- `JmenoZakazka` (z named range `stavba`), `JmenoObjekt`, `JmenoRozvrzeni` (= název layoutu, sl. 28), `NazevPrilohy`, `MeritkoPrilohy` (sl. 23), `PocetPapiru`, `Ctb` (název CTB stylu, sl. 30).
- `InicializaceDat(radek)` načte tyto hodnoty z konkrétního řádku.
- Spojování hodnot oddělovačem (název + dílčí část) přes pomocnou funkci.
### `NastaveniTiskarny` (držák tiskových hodnot)
Defaultní hodnoty tisku převedené z ActiveX konstant na **číselné literály** (kvůli late-bindingu):
- `Tiskarna` = `"DWG to PDF.pc5"`, `StyleSheet` = `"zwcad.ctb"`
- `PaperUnits = 1` (mm), `PlotRotation = 0`, `PlotType = 5` (Layout), `StandardScale = 16` (ac1_1, tj. 1:1 pozor, AcPlotScale je enum, hodnota 1 by byla ac1_128in_1ft = 1:1536), `PlotViewportFirst = True`, `PlotOrigin = (0,0)`.
- `NastavRozmerPapiru(sirka, vyska)` → sestaví `CanonicalMediaName` jako `"420x297"` (z reálných rozměrů rámečku).
**Pro C#:** Tohle jsou jednoduché DTO/POCO třídy. V C# můžeš vrátit silně typované enumy (`AcPlotType.acLayout` atd.) místo magických čísel, ale zachovej mapování:
| Konstanta | Hodnota | Význam |
|---|---|---|
| PaperUnits mm | 1 | acMillimeters |
| Rotation 0° | 0 | ac0degrees |
| PlotType Layout | 5 | acLayout |
| PlotType Window | 4 | acWindow |
| Scale 1:1 | 1 | ac1_1 |
| Scale ToFit | 0 | acScaleToFit |
---
## 3. Konfigurace tiskárny a papíru (`modPlotConfig`)
**Funkcionalita:**
- **Cache do skrytého listu `_Plot`** (xlSheetVeryHidden): ukládá poslední zvolenou tiskárnu (`ConfigName`, B1) a papír (`MediaName`, B2). Při portu nahradíš nějakým config souborem / settings.
- **`TiskarnyZCad(cad)`** → seznam dostupných tiskáren: `cad.Doc.ActiveLayout.GetPlotDeviceNames`.
- **`PapiryZCad(cad, device)`** → seznam papírů pro danou tiskárnu. Trik: vytvoří dočasnou plot konfiguraci `Doc.PlotConfigurations.Add("__tmpQ", True)`, nastaví `configName = device`, zavolá `RefreshPlotDeviceInfo`, načte `GetCanonicalMediaNames` a dočasnou konfiguraci zase smaže.
- **`OveritKonfiguraci(...)`** → před tiskem ověří, že uložená tiskárna i papír reálně existují v aktuálním CADu (stejnou technikou). Vrací popis chyby.
- **`ZobrazPlotConfig`** → modální formulář pro výběr (UI nahradíš WinForms/WPF dialogem).
**Klíčové API:** `Layout.GetPlotDeviceNames`, `PlotConfigurations.Add/Item/Delete`, `PlotConfiguration.configName`, `.RefreshPlotDeviceInfo`, `.GetCanonicalMediaNames`.
---
## 4. Aktualizace / úprava rozvržení (`modAktualizace`) — JÁDRO
Hlavní procedura `TestAktualizace`. Podle vybraného rozsahu řádků na listu SEZNAM upraví odpovídající layouty ve výkresu.
**Tok funkcionality:**
1. **Ochranné podmínky:** musí běžet na listu SEZNAM, výběr musí začínat řádkem ≥ 2.
2. Načte pole `Layout` objektů z vybraných řádků.
3. Připojí se na CAD (`clsCadLink`).
4. **Sestaví seznam všech layoutů** ve výkresu (`Doc.Layouts`), pro každý `Name` a `TabOrder`, a **seřadí podle TabOrder** (bubble sort). Pozn.: `Layouts.Count - 1` = počet rozvržení bez modelového prostoru.
5. **Kontroly počtu:** rozsah nesmí být větší než počet layoutů ani než počet zbývajících layoutů od aktivního.
6. **Aktivní layout nesmí být "Model"** jinak vyzve uživatele aby aktivoval rozvržení. Začíná se od `ActiveLayout.TabOrder`.
7. **Dvoufázové přejmenování (kvůli konfliktům jmen):** nejdřív přejmenuje cílové layouty na **dočasné náhodné názvy** (`GenerateSecureAlphaNumeric(15) & i`), aby se předešlo kolizi při finálním přejmenování.
8. **Hlavní smyčka** přes layouty podle TabOrder (s progress formulářem + možnost zrušit):
- Přejmenuje layout na cílový `JmenoRozvrzeni` (chráněno error handlerem).
- **Automatické vyhodnocení měřítka** (pokud zapnuto named range `MERITKOAUTOMATICKY`) viz níže.
- **Projde všechny entity v `Layout.Block`** a hledá blokové reference (`blk.ObjectName = "AcDbBlockReference"`):
- **Blok `vykres_hlavicka`** → pokud `HasAttributes`, projde `GetAttributes` a podle `TagString` zapíše hodnoty: `STAVBA`, `OBJEKT`, `PRILOHA`, `MERITKO` (formátuje jako `"M 1:500"` jen když obsahuje `"1:"`). Po změně `TextString` zavolá `AttList(k).Update`.
- **Blok `ramecek`** → pokud je dynamický (`IsDynamicBlock`), `GetBoundingBox Poc, Kon` → z rozměrů spočítá **počet papírů**: `delka = RoundUp(Kon(0)/210)`, `vyska = RoundUp(Kon(1)/297)`, `PocetPapiru = delka*vyska`, a nastaví formát papíru (`NastavRozmerPapiru`).
- **Nastavení tiskárny na layout** (pokud zapnuto named range `NASTAVENITISKARNY = 1`): zapíše do layoutu `ConfigName, CanonicalMediaName, PaperUnits, PlotOrigin, PlotRotation, PlotType, StandardScale, PlotViewportsFirst, StyleSheet`. Pokud je vyplněn vlastní CTB, použije `Ctb & ".ctb"`.
9. **Zápis zpět do Excelu:** počet papírů (sl. 24) a měřítka (sl. 23) zpět do listu SEZNAM.
### Vyhodnocení měřítka z výřezů (`VyhodnotMeritka`)
- Projde entity layoutu a hledá **viewporty** (`ObjectName = "AcDbViewport"`) v hladině **`!!NOPRINT`**.
- Z `viewport.CustomScale` spočítá jmenovatel měřítka: `jmenovatel = Round((1/CustomScale) * jednotkyFaktor)`.
- `jednotkyFaktor` podle systémové proměnné `INSUNITS` (`Doc.GetVariable("INSUNITS")`): 4 = mm (1), 6 = m (1000).
- Sesbírá unikátní měřítka, seřadí vzestupně, vrátí např. `"1:500, 1:1000"`.
- **Pozor (zjištěno při portu):** blok layoutu obsahuje kromě nakreslených výřezů i neviditelný
pseudoviewport „pohledu na papír“ (DXF VIEWPORT id 1; vzniká při aktivaci layoutu a kopíruje se
s layoutem, může ležet i v hladině `!!NOPRINT` a kdekoliv v pořadí bloku). Jeho `CustomScale`
je aktuální zoom → falešná měřítka (u výkresů v metrech typicky 1:1000). Jednoznačná identifikace:
aktivovat layout, vypnout `MSpace` a přečíst `Doc.ActivePViewport.Handle` to je on (ověřeno na
AutoCADu). Nouzová heuristika bez aktivace: pseudoviewport má `StandardScale = 2` (`acVp1_1`)
trvale zamrzlé; skutečné výřezy s měřítkem ze seznamu mají `1` (`acVpCustomScale`) heuristika
by ale zahodila i skutečný výřez nastavený přes enum na 1:1.
**Pro C# nejdůležitější API této části:**
- `Doc.Layouts` (kolekce), `Layout.Name`, `.TabOrder`, `.Block` (iterace entit).
- Entita: `.ObjectName` (`"AcDbBlockReference"`, `"AcDbViewport"`), `.Name`, `.IsDynamicBlock`, `.HasAttributes`, `.GetAttributes()`, `.GetBoundingBox(out min, out max)`, `.Layer`, `.CustomScale`.
- Atribut: `.TagString`, `.TextString`, `.Update()`.
- Layout plot props: `.ConfigName, .CanonicalMediaName, .PaperUnits, .PlotOrigin, .PlotRotation, .PlotType, .StandardScale, .PlotViewportsFirst, .StyleSheet`.
- `Doc.GetVariable("INSUNITS")`.
- **Pozor na late-bound rozdíl:** ve verzi 1.0.7 (ZWCAD) se typ poznával přes `TypeName(x)="IZcadBlockReference"`. Tady se to dělá robustněji přes `x.ObjectName`. V C# se silně typovaným interopem můžeš použít `is AcadBlockReference` / `is AcadPViewport`, ale `ObjectName` je univerzálnější napříč enginy.
---
## 5. Tisk rozvržení do PDF (`modTiskPriloh`)
`TiskPrilohyDoPdf` hromadný tisk vybraných layoutů do PDF souborů.
**Tok:**
1. Ochranné podmínky (list SEZNAM, řádek ≥ 2), připojení CAD.
2. **Připraví cílovou složku** exportu.
3. Sestaví seznam názvů layoutů k tisku (sl. 28), prázdné přeskočí.
4. **Zazálohuje a přenastaví systémové proměnné** výkresu:
- `BACKGROUNDPLOT` → 0 (tisk na popředí = čeká se na dokončení důležité pro spolehlivost!).
- `PLOTTRANSPARENCYOVERRIDE` → 2 (ignorovat průhlednost).
- `LTSCALE` ukládá originál, volitelně přenastaví dle named range `LTSCALE` a hodnoty z buňky (sl. 29) per řádek (s validací: prázdné/nečíselné/≤0 → 1).
5. **Smyčka tisku** (progress + cancel):
- Cesta PDF = `složka & nazev & ".pdf"`.
- **Kontrola, zda PDF není otevřené** (`IsFileOpen`) → dialog Abort/Retry/Ignore.
- Najde layout `Doc.Layouts(nazev)`, neexistuje → přeskočí.
- Nastaví LTSCALE.
- **Aktivuje layout** (`Doc.ActiveLayout = Layout`) a **vytiskne** (`Doc.Plot.PlotToFile cesta`).
- Error handling per soubor, počítá vytisknuto/přeskočeno.
6. **Obnoví** původní systémové proměnné.
**Klíčové API:** `Doc.GetVariable/SetVariable`, `Doc.Layouts(name)`, `Doc.ActiveLayout = layout`, `Doc.Plot.PlotToFile(path)`.
---
## 6. Tisk bloku oknem (`modPrint`)
Dvě varianty tisku jednotlivého bloku podle jeho bounding boxu (window plot):
- **`VytiskniRozpisku`** → `Doc.Plot.PlotToDevice` (na zařízení).
- **`VytiskniRozpiskuDoSouboru`** → `Doc.Plot.PlotToFile(cesta)` (do souboru, nastaví `BACKGROUNDPLOT=0`).
**Postup nastavení layoutu pro window-plot:**
1. `blk.GetBoundingBox(min, max)` → rohy okna.
2. Na `ActiveLayout`: `ConfigName`, `RefreshPlotDeviceInfo`, `CanonicalMediaName`, `PlotRotation = 0`, `PlotType = 4` (acWindow), `SetWindowToPlot(ll, ur)`, `CenterPlot = True`, `UseStandardScale = True`, `StandardScale = 0` (acScaleToFit), `PlotWithPlotStyles = True`, `StyleSheet = ""`, `PlotViewportBorders = False`, `PlotHidden = False`.
3. Tisk.
**Pro C#:** `SetWindowToPlot` bere dvě 2-prvková `double[]` pole (lower-left, upper-right). Pozor na předávání polí přes COM (varianty).
---
## 7. Čtení dat / atributů z CADu
### Vyhledání bloků v ModelSpace (`modRozpisky.NajdiRozpisky`)
Obecný vzor procházení výkresu (použitelný i mimo rozpisky):
- `cad.Doc.ModelSpace` → iterace `For Each ent In ms`.
- Filtr: `ent.ObjectName = "AcDbBlockReference"` a porovnání názvu.
- **Název bloku včetně dynamických:** zkusí `EffectiveName`, fallback `Name` (`NazevBloku`).
- Čte `insertionPoint` (pole `[x,y,z]`), filtruje X > 0, sbírá do kolekce.
- **Seřazení podle X** (index sort) vzestupně.
- Progress každých 50 entit + možnost zrušit (vyhozením vlastní chyby `vbObjectError + 100`).
### Výpis atributů (`modVypisAtributu`)
Diagnostika pro daný blok:
- `blk.insertionPoint`.
- `blk.HasAttributes``blk.GetAttributes` (proměnné/vyplnitelné) → `TagString` + `TextString`.
- `blk.GetConstantAttributes` (konstantní, jen pro info).
**Klíčové API:** `Doc.ModelSpace` (iterovatelná kolekce), `entity.ObjectName`, `.EffectiveName/.Name`, `.InsertionPoint`, `.HasAttributes`, `.GetAttributes()`, `.GetConstantAttributes()`.
---
## 8. Pomocné funkce (`Funkce`) — bez CADu
- `TurnOffStuff` / `TurnOnStuff` vypnutí/zapnutí přepočtu, událostí, překreslování Excelu (zrychlení). V C# přes Excel interop nebo to vynecháš.
- `VratZnakNeboX` ořez/doplnění textu na pevnou délku.
- `GetValueFromTable` vyhledávání v Excel ListObject tabulce dle klíče (jako VLOOKUP).
- **`IsFileOpen`** zjistí, zda je soubor zamčený jiným procesem (otevře `Binary Access Read Lock Read`, chyba 70 = otevřeno). V C# → `try` na `FileStream` s `FileShare.None`.
- **`VybratCestuKeSlozce`** dialog výběru složky. V C# → `FolderBrowserDialog`/`OpenFolderDialog`.
---
## 9. Poznámky pro převod do C#
1. **Late-binding → typed interop:** VBA tu používá `As Object` + late-binding kvůli podpoře více CADů (AutoCAD/ZWCAD/BricsCAD). V C# zvážit: buď `dynamic` (zachová multi-engine flexibilitu), nebo `Autodesk.AutoCAD.Interop` (typová bezpečnost, ale váže tě na AutoCAD). Detekce typu entit přes `.ObjectName` (string) funguje univerzálně napříč enginy doporučuji ponechat.
2. **Magická čísla** plot konstant (viz tabulka v sekci 2) v C# nahraď enumy z interopu, ale ověř hodnoty proti cílovému CADu (komentář v `NastaveniTiskarny` na to upozorňuje).
3. **Pole přes COM:** `GetBoundingBox`, `GetAttributes`, `InsertionPoint`, `PlotOrigin`, `SetWindowToPlot` pracují s `object`/`Variant` poli `double[]`. V C# je třeba je správně boxovat/castovat (`(double[])`, `out object`).
4. **Systémové proměnné** se čtou/zapisují přes `Doc.GetVariable/SetVariable` stejné názvy (`BACKGROUNDPLOT`, `PLOTTRANSPARENCYOVERRIDE`, `LTSCALE`, `INSUNITS`). Vždy zazálohovat a obnovit.
5. **`BACKGROUNDPLOT = 0`** je nutný pro synchronní tisk (jinak `PlotToFile` nečeká na dokončení a soubory mohou být nedokončené).
6. **Plot konfigurace** se vytváří/maže jako dočasné objekty (`__tmpQ`) v C# obal do `try/finally`, ať se vždy uklidí.
7. **Iterace `Layout.Block`** vrací modelspace/paperspace entity daného layoutu v typed interopu `AcadLayout.Block` je `AcadBlock`.
8. Zdroj dat (list SEZNAM, named ranges `stavba`, `MERITKOAUTOMATICKY`, `NASTAVENITISKARNY`, `LTSCALE`) je dnes Excel při portu rozhodni, odkud je vezme nová appka (DB/JSON/UI).
9. **Celočíselné systémové proměnné přes `SetVariable` posílat jako `short` (VT_I2):** VBA `Integer`
je 16bitový, .NET `int` se přes COM marshaluje jako VT_I4 a AutoCAD na
`SetVariable("PLOTTRANSPARENCYOVERRIDE", 2)` s VT_I4 **spadl nativně** (fatal error bez výjimky,
bez error.log; lokalizováno breadcrumb logem `tisk_diag.log`, 2026-07-05). V C# proto `(short)0`,
`(short)2` apod.; `LTSCALE` je RTREAL → `double` je správně. Navíc: zápis PLOTTRANSPARENCYOVERRIDE
se přeskočí, když už cílovou hodnotu má, selhání téhle kosmetické proměnné tisk nezastaví (vlastní
try/catch) a při obnově se vrací jen proměnné, které se skutečně změnily
(`CadTiskService.VytiskniRozvrzeni`). **Stejná třída problému: `Doc.Layouts.Item(neexistující
jméno)`** místo chytatelné výjimky nativní pád aplikace (2026-07-05). Layout se proto hledá
výčtem kolekce `Layouts` a porovnáním `Name` (`CadTiskService.NajdiLayout`); obecně na COM
rozhraní CADu **nestavět control flow na vyhazujících voláních** tam, kde jde výsledek zjistit
výčtem/porovnáním, udělat to tak.
10. **Odchylka od VBA rozměr rámečku:** originál (§4, bod `ramecek`) čte rozměr přes `GetBoundingBox`. C# port to **záměrně nedělá** bounding box je nespolehlivý (závisí na tom, co je v bloku zrovna vykreslené/viditelné). Blok `ramecek` je dynamický se dvěma viditelnostmi (`Benefit`/`Libovolný`); port čte rozměr přímo z dynamických parametrů `GetDynamicBlockProperties()` dvojice `VzdálenostBx`/`VzdálenostBy` pro viditelnost `Benefit`, `VzdálenostLx`/`VzdálenostLy` pro `Libovolný`. Aktivní viditelnost se pozná podle toho, která vlastnost má aktuální hodnotu rovnou `"Benefit"`/`"Libovolný"` (viz `CadLayoutService.NajdiViditelnost`), ne podle názvu vlastnosti viditelnosti samotné (ten nemusí být `Visibility1`).
---
## 10. Mapa souborů (kde co hledat)
| Soubor | Odpovídá za |
|---|---|
| `clsCadLink.cls` | Připojení k CADu, helpery pro hladiny a bloky |
| `Layout.cls` | Data jednoho rozvržení (čtení ze SEZNAM) |
| `NastaveniTiskarny.cls` | Default hodnoty tisku (DTO) |
| `modPlotConfig.bas` | Seznam tiskáren/papírů, ověření, cache `_Plot` |
| `modAktualizace.bas` | **Jádro** úprava layoutů, atributů, měřítka, počtu papírů |
| `modTiskPriloh.bas` | Hromadný tisk layoutů do PDF |
| `modPrint.bas` | Window-plot jednoho bloku (na device / do souboru) |
| `modRozpisky.bas` | Vyhledávání bloků v ModelSpace |
| `modVypisAtributu.bas` | Diagnostický výpis atributů bloku |
| `Funkce.bas` | Sdílené pomocné funkce (Excel/IO, bez CADu) |