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

17 KiB
Raw Permalink Blame History

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.


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.ApplicationZWCAD.ApplicationBricscadApp.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):

  • VytiskniRozpiskuDoc.Plot.PlotToDevice (na zařízení).
  • VytiskniRozpiskuDoSouboruDoc.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.HasAttributesblk.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)