# 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) |