Baseline: současný stav projektu Rozpisky
První commit — WPF aplikace pro generování výkresových rohových razítek (.NET 10, SkiaSharp, ACadSharp, ClosedXML) včetně xUnit testů, DXF šablony, log a dokumentace. Build výstupy (bin/, obj/, .vs/) jsou ignorovány. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
# 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) |
|
||||
Reference in New Issue
Block a user