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>
17 KiB
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.AddCheckBlock(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 rangestavba),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íCanonicalMediaNamejako"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 konfiguraciDoc.PlotConfigurations.Add("__tmpQ", True), nastavíconfigName = device, zavoláRefreshPlotDeviceInfo, načteGetCanonicalMediaNamesa 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:
- Ochranné podmínky: musí běžet na listu SEZNAM, výběr musí začínat řádkem ≥ 2.
- Načte pole
Layoutobjektů z vybraných řádků. - Připojí se na CAD (
clsCadLink). - Sestaví seznam všech layoutů ve výkresu (
Doc.Layouts), pro každýNameaTabOrder, a seřadí podle TabOrder (bubble sort). Pozn.:Layouts.Count - 1= počet rozvržení bez modelového prostoru. - 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.
- Aktivní layout nesmí být "Model" – jinak vyzve uživatele aby aktivoval rozvržení. Začíná se od
ActiveLayout.TabOrder. - 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í. - 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.Blocka hledá blokové reference (blk.ObjectName = "AcDbBlockReference"):- Blok
vykres_hlavicka→ pokudHasAttributes, projdeGetAttributesa podleTagStringzapíše hodnoty:STAVBA,OBJEKT,PRILOHA,MERITKO(formátuje jako"M 1:500"jen když obsahuje"1:"). Po změněTextStringzavolá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).
- Blok
- Nastavení tiskárny na layout (pokud zapnuto named range
NASTAVENITISKARNY = 1): zapíše do layoutuConfigName, CanonicalMediaName, PaperUnits, PlotOrigin, PlotRotation, PlotType, StandardScale, PlotViewportsFirst, StyleSheet. Pokud je vyplněn vlastní CTB, použijeCtb & ".ctb".
- Přejmenuje layout na cílový
- 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.CustomScalespočítá jmenovatel měřítka:jmenovatel = Round((1/CustomScale) * jednotkyFaktor). jednotkyFaktorpodle 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ě
!!NOPRINTa kdekoliv v pořadí bloku). JehoCustomScaleje aktuální zoom → falešná měřítka (u výkresů v metrech typicky 1:1000). Jednoznačná identifikace: aktivovat layout, vypnoutMSpacea přečístDoc.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řesx.ObjectName. V C# se silně typovaným interopem můžeš použítis AcadBlockReference/is AcadPViewport, aleObjectNameje univerzálnější napříč enginy.
5. Tisk rozvržení do PDF (modTiskPriloh)
TiskPrilohyDoPdf – hromadný tisk vybraných layoutů do PDF souborů.
Tok:
- Ochranné podmínky (list SEZNAM, řádek ≥ 2), připojení CAD.
- Připraví cílovou složku exportu.
- Sestaví seznam názvů layoutů k tisku (sl. 28), prázdné přeskočí.
- 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 rangeLTSCALEa hodnoty z buňky (sl. 29) per řádek (s validací: prázdné/nečíselné/≤0 → 1).
- 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.
- Cesta PDF =
- 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:
blk.GetBoundingBox(min, max)→ rohy okna.- 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. - 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→ iteraceFor Each ent In ms.- Filtr:
ent.ObjectName = "AcDbBlockReference"a porovnání názvu. - Název bloku včetně dynamických: zkusí
EffectiveName, fallbackName(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řeBinary Access Read Lock Read, chyba 70 = otevřeno). V C# →trynaFileStreamsFileShare.None.VybratCestuKeSlozce– dialog výběru složky. V C# →FolderBrowserDialog/OpenFolderDialog.
9. Poznámky pro převod do C#
- 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), neboAutodesk.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. - 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
NastaveniTiskarnyna to upozorňuje). - Pole přes COM:
GetBoundingBox,GetAttributes,InsertionPoint,PlotOrigin,SetWindowToPlotpracují sobject/Variantpolidouble[]. V C# je třeba je správně boxovat/castovat ((double[]),out object). - 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. BACKGROUNDPLOT = 0je nutný pro synchronní tisk (jinakPlotToFilenečeká na dokončení a soubory mohou být nedokončené).- Plot konfigurace se vytváří/maže jako dočasné objekty (
__tmpQ) – v C# obal dotry/finally, ať se vždy uklidí. - Iterace
Layout.Blockvrací modelspace/paperspace entity daného layoutu – v typed interopuAcadLayout.BlockjeAcadBlock. - 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). - Celočíselné systémové proměnné přes
SetVariableposílat jakoshort(VT_I2): VBAIntegerje 16bitový, .NETintse přes COM marshaluje jako VT_I4 – a AutoCAD naSetVariable("PLOTTRANSPARENCYOVERRIDE", 2)s VT_I4 spadl nativně (fatal error bez výjimky, bez error.log; lokalizováno breadcrumb logemtisk_diag.log, 2026-07-05). V C# proto(short)0,(short)2apod.;LTSCALEje RTREAL →doubleje 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 kolekceLayoutsa porovnánímName(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. - Odchylka od VBA – rozměr rámečku: originál (§4, bod
ramecek) čte rozměr přesGetBoundingBox. C# port to záměrně nedělá – bounding box je nespolehlivý (závisí na tom, co je v bloku zrovna vykreslené/viditelné). Blokramecekje dynamický se dvěma viditelnostmi (Benefit/Libovolný); port čte rozměr přímo z dynamických parametrůGetDynamicBlockProperties()– dvojiceVzdálenostBx/VzdálenostBypro viditelnostBenefit,VzdálenostLx/VzdálenostLyproLibovolný. Aktivní viditelnost se pozná podle toho, která vlastnost má aktuální hodnotu rovnou"Benefit"/"Libovolný"(vizCadLayoutService.NajdiViditelnost), ne podle názvu vlastnosti viditelnosti samotné (ten nemusí býtVisibility1).
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) |