# plant2dxf - Dokumentation ## ⚠️ Wichtiger Hinweis **Zukünftige Anpassungen erforderlich:** Aufgrund möglicher Änderungen im CSV-Dateiformat von RuleDesigner müssen in Zukunft möglicherweise folgende Berechnungen und Logiken angepasst werden: ### AS/ES-Element-Berechnungen Die Logik zur Bestimmung und Platzierung von AS- und ES-Elementen bei Gefällestrecken und Vario-Förderern basiert auf dem aktuellen CSV-Format. Bei Änderungen der Datenstruktur oder der verfügbaren Informationen müssen folgende Methoden in `as_es_methoden.py` überprüft und angepasst werden: - `erstellung_gefaelle_block_verbunden_an_einen()`: Erstellt AS/ES-Elemente für Gefällestrecken, die mit einem Kreisel verbunden sind - `gefaellegerade_erstellung()`: Erstellt AS/ES-Elemente für Gefällestrecken, die mit zwei Kreiseln verbunden sind - `am_kreisel_direct_verbunden()`: Prüft, ob eine Gefällestrecke/Vario-Förderer direkt mit einem Kreisel verbunden ist (ohne Stahlband) ### Höhenvergleiche für Kreisel-Logik Die Höhenvergleiche zur Bestimmung der Verbindungsart zwischen Gefällestrecken/Vario-Förderern und Kreiseln (z.B. ob die Gefällestrecke mit dem höheren oder niedrigeren Teil des Kreisels verbunden ist) basieren auf den aktuellen CSV-Datenfeldern. Änderungen in der Struktur der Höheninformationen erfordern Anpassungen in: **Handler-Funktionen (`lib/plant2dxf.py`):** - `handle_ils_2_0_gefaellestrecke()`: Haupt-Handler für Gefällestrecken - `handle_ils_2_0_variofoerderer()`: Haupt-Handler für Vario-Förderer **Gefällestrecke-Methoden (`lib/Elemente/Gefaellestrecke.py`):** - `rotation_mit_zwei_verbunden()`: Berechnet Rotation bei Verbindung mit zwei Kreiseln (verwendet Höhenvergleiche) **Vario-Förderer-Methoden (`lib/Elemente/VarioFoerderer.py`):** - `vario_verbuden_am_kreisel()`: Erstellt Gefällestrecken-Komponenten bei direkter Kreisel-Verbindung (verwendet Höhenvergleiche mit Kreiseln) **Betroffene Dateien:** - `lib/as_es_methoden.py`: AS/ES-Element-Berechnungen - `lib/plant2dxf.py`: Handler-Funktionen für Gefällestrecken und Vario-Förderer - `lib/Elemente/Gefaellestrecke.py`: Höhenvergleiche und Verbindungslogik - `lib/Elemente/VarioFoerderer.py`: Höhenvergleiche und Verbindungslogik ## Übersicht `plant2dxf` ist ein Python-Skript, das aus einer RuleDesigner-CSV-Datei DXF-Elemente erzeugt. Das Skript liest eine CSV-Datei mit Angaben über alle in einer Anlage enthaltenen Elemente und generiert daraus eine DXF-Datei der kompletten Anlage. ## Hauptfunktionalität Das Skript verarbeitet CSV-Dateien mit folgenden Informationen: - **TeileArt**: Typ der Komponente (z.B. "ILS 2.0 Kreisel", "ILS 2.0 Gefällestrecke") - **TeileId**: Eindeutige Identifikation des Elements - **Planquadrat**: Koordinaten im Format `X: Y:` - **Merkmale**: JSON-String mit spezifischen Eigenschaften des Elements - **NachbarIds**: Kommagetrennte Liste von IDs benachbarter Elemente ## Benötigte Dateien und Verzeichnisse ### Umgebungsvariablen Das Skript benötigt folgende Umgebungsvariablen (werden durch `bin/setenv.bat` gesetzt): - **PROJECT_DATA**: Verzeichnis für Bibliotheksdateien (DXF-Blockbibliotheken) - Standard: `/data` - Enthält Unterverzeichnis `block_libraries/` mit DXF-Bibliotheken - **PROJECT_WORK**: Arbeitsverzeichnis für CSV-Eingabedateien und DXF-Ausgabedateien - Standard: `/work` - **PROJECT_CFG**: Verzeichnis für Konfigurationsdateien - Standard: `/cfg` - Enthält: `allgemein.cfg`, `shapes.cfg` - **PROJECT_LOG**: Verzeichnis für Log-Dateien - Standard: `/log` ### Konfigurationsdateien #### 1. `cfg/allgemein.cfg` Allgemeine Konfigurationsdatei, die die Zuweisung von Block-Bibliotheken zu bestimmten Bauteil-Familien steuert. **Struktur:** ```ini [LOG] log_level = INFO log_format = %%(asctime)s - %%(levelname)s - %%(message)s screen_format = %%(message)s [ILS] libfile = ils_lib.dxf [Omniflo] libfile = omniflo_lib.dxf [BT] libfile = ils_lib.dxf [TEF] libfile = omniflo_lib.dxf ``` **Zweck:** - Definiert, welche DXF-Bibliotheksdatei für welche TeileArt verwendet wird - Bibliotheksdateien werden aus `PROJECT_DATA/block_libraries/` geladen - Falls keine Zuordnung gefunden wird, wird die Standard-Bibliothek (`blocks.dxf`) verwendet #### 2. `cfg/shapes.cfg` Definiert für jede TeileArt die zu verwendenden Blöcke aus der Bibliotheks-DXF und deren Eigenschaften. **Struktur:** ```ini [ILS 2.0 Kreisel] items = SP8, AN8 offset_symb1 = 0,0 offset_symb2 = 0,0 rot_symb1 = 0.0 rot_symb2 = 0.0 [ILS 2.0 Gefällestrecke] items = EE DS, AE DS offset_symb1 = 0,-330 offset_symb2 = 0,1000 rot_symb1 = 0.0 rot_symb2 = 0.0 asoffset = 537.90 esoffset = 479.95 [ILS 2.0 Variofoerderer] Umlenkstation = 500.0, 0.0, 0.0 Motorstation = 500.0, 0.0, 0.0 vario_abstand = 66.5 [Ils 2.0 core winkel] winkel_boegen = 3 winkel_motor = 3.0 winkel_umlenk = 3.0 winkel_as = 3.0 winkel_es = 3.0 [Omniflo] OFgeradesivas = 821106002 Tefgeradesivas = 0_B10030 OFfoerderer = 2 ``` **Parameter:** - **[TeileArt]**: Abschnittsname muss exakt dem Wert aus der CSV-Spalte `TeileArt` entsprechen - **items**: Kommagetrennte Liste der Blocknamen aus der Bibliothek - **offset_symbX**: Offset (x,y) für den X-ten Block relativ zur berechneten Position - **rot_symbX**: Rotation (in Grad) für den X-ten Block - **asoffset/esoffset**: Offsets für AS/ES-Elemente bei Gefällestrecken - **winkel_***: Winkel für verschiedene Komponenten (Bögen, Motor, Umlenkstation, AS/ES) ### DXF-Bibliotheken DXF-Bibliotheken enthalten die Blockdefinitionen für die verschiedenen Komponenten. Sie werden aus `PROJECT_DATA/block_libraries/` geladen. **Bibliotheksdateien:** - `ils_lib.dxf`: Blöcke für ILS 2.0 Komponenten - `omniflo_lib.dxf`: Blöcke für Omniflo-Komponenten - `blocks.dxf`: Standard-Bibliothek (Fallback) **Wichtige Blöcke:** - `SCAN`: Scanner-Block - `S-LP`: Separator-Block - `Pinbereich`: Pinbereich-Block für Kreisel - `AN8`, `SP8`: Standard-Blöcke für Kreisel - `200000241_AS-Element_90_rechts`, `200000217_AS-Element_90_links`: AS-Elemente - `200000146_ES-Element_90_rechts`, `400102632_ES-Element_90_links`: ES-Elemente - Verschiedene Omniflo-Blöcke (identifiziert über SivasNummer) ## Unterstützte Elementtypen ### 1. ILS 2.0 Kreisel **Handler:** `handle_ils_2_0_kreisel()` **Funktionalität:** - Erstellt einen Kreisel mit Scanner und Separatoren - Positioniert Blöcke basierend auf Anzahl der Scanner/Separatoren - Zeichnet Kreisel-Linien und Drehrichtung-Markierung - Verwendet Blöcke aus `shapes.cfg` (z.B. SP8, AN8) **Merkmale:** - `Abstand (Kreiselachse A - Kreiselachse) in Meter`: Abstand zwischen den beiden Kreiselachsen - `Anzahl der Separatoren`: Anzahl der Separator-Blöcke - `Anzahl der Scanner`: Anzahl der Scanner-Blöcke - `Anzahl der Rampen`: Anzahl der Rampen - `Höhe in m`: Höhe des Kreisels - `Drehrichtung`: UZS (Uhrzeigersinn) oder GUZS (Gegen-Uhrzeigersinn) ### 2. ILS 2.0 Gefällestrecke **Handler:** `handle_ils_2_0_gefaellestrecke()` **Funktionalität:** - Erstellt eine Gefällestrecke zwischen verschiedenen Höhen - Berücksichtigt Verbindungen zu Kreiseln, Kurven und anderen Elementen - Fügt AS/ES-Elemente ein, wenn nötig - Erstellt Blöcke dynamisch basierend auf Verbindungen **Merkmale:** - `Länge in Meter`: Länge der Gefällestrecke - `Höhe Anfang`: Start-Höhe in Metern - `Höhe Ende`: End-Höhe in Metern - `Drehung`: Rotation der Strecke - `Anzahl der Zusatzseparatoren`: Zusätzliche Separatoren **Besonderheiten:** - Unterscheidet zwischen Verbindung mit einem oder zwei Kreiseln - Prüft, ob direkte Verbindung zum Kreisel (ohne Stahlband) - Fügt Motor-/Umlenkstationen ein, wenn mit angetriebenen Kurven verbunden - Erstellt AS/ES-Elemente basierend auf Drehrichtung der benachbarten Kreisel ### 3. ILS 2.0 VarioFoerderer **Handler:** `handle_ils_2_0_variofoerderer()` **Funktionalität:** - Erstellt einen Vario-Förderer mit verschiedenen Konfigurationen - Unterstützt Verbindungen zu Kreiseln, Eckrädern und anderen Vario-Förderern - Berücksichtigt Motor- und Umlenkstationen - Erstellt Gefälle-Komponenten innerhalb des Förderers **Merkmale:** - `Winkel`: Winkel des Förderers - `Förderrichtung`: Auf, Ab oder Horizontal - `Motorseite`: links oder rechts - `Höhe Anfang`, `Höhe Ende`: Höhen der Endpunkte - `Gefälle`: Gefälle-Länge und -Winkel - `Motor vorhanden`, `Umlenk vorhanden`: Boolean-Werte **Besonderheiten:** - Unterscheidet zwischen Verbindung mit einem oder zwei Kreiseln/Eckrädern - Erstellt spezielle Blöcke für direkte Kreisel-Verbindungen - Berücksichtigt horizontale Ausrichtung bei Eckrad-Verbindungen - Fügt Gefällestrecken ein, wenn mit Kreiseln verbunden ### 4. ILS 2.0 Kurve angetrieben **Handler:** `handle_ils_2_0_kurve_angetrieben()` **Funktionalität:** - Erstellt eine angetriebene Kurve (Förderer-Kurve) - Verwendet Blocknamen basierend auf Kurvenrichtung, Winkel und Antrieb **Merkmale:** - `Kurvenrichtung`: Links oder Rechts - `Kurvenwinkel`: Winkel der Kurve in Grad - `Antrieb`: TEF-Kurve oder ähnlich - `Höhe Anfang`, `Höhe Ende`: Höhen der Endpunkte ### 5. ILS 2.0 Kurve (Gefälle-Kurve) **Handler:** `handle_ils_2_0_kurve()` **Funktionalität:** - Erstellt eine Gefälle-Kurve - Verwendet Blocknamen basierend auf Kurvenrichtung und Winkel **Merkmale:** - `Kurvenrichtung`: Links oder Rechts - `Kurvenwinkel`: Winkel der Kurve in Grad - `Höhe Anfang`, `Höhe Ende`: Höhen der Endpunkte ### 6. ILS 2.0 Eckrad **Handler:** `handle_ils_2_0_eckrad()` **Funktionalität:** - Erstellt ein Eckrad basierend auf Drehrichtung - Verwendet unterschiedliche Blöcke für UZS/GUZS **Merkmale:** - `Drehrichtung`: UZS oder GUZS - `Höhe`: Höhe des Eckrads ### 7. BT Elemente (Beladung/Entladung) **Handler:** `handle_bt___beladung()`, `handle_bt___entladung()` **Funktionalität:** - Erstellt BT-Elemente für Beladung oder Entladung - Verwendet Standard-Block "AN8" **Merkmale:** - `Höhe`: Höhe des Elements - `Drehung`: Rotation ### 8. Omniflo **Handler:** `handle_omniflo()` **Funktionalität:** - Erstellt Omniflo-Komponenten basierend auf SivasNummer - Unterstützt Geraden, Förderer und spezielle Blöcke - Verwendet Blocknamen direkt aus der SivasNummer **Merkmale:** - `SivasNummer`: Identifikation des Omniflo-Blocks - `Höhe`: Höhe des Elements - `Drehung`: Rotation - `Länge`: Länge (für Geraden) **Besonderheiten:** - Spezielle Behandlung für Geraden (SivasNummer aus Config) - Erstellt Linien für Geraden statt Blöcke - Unterstützt Förderer-Komponenten ## CSV-Dateiformat Die CSV-Datei muss folgende Spalten enthalten (Semikolon-getrennt): ```csv Elementnummer;TeileArt;TeileId;NachbarIds;Bezeichnung;Planquadrat;Merkmale ``` **Spalten:** - **Elementnummer**: Fortlaufende Nummer - **TeileArt**: Typ der Komponente (muss exakt mit Config übereinstimmen) - **TeileId**: Eindeutige ID (z.B. "shape_...") - **NachbarIds**: Kommagetrennte Liste von IDs benachbarter Elemente - **Bezeichnung**: Beschreibung des Elements - **Planquadrat**: Koordinaten im Format `X: Y:` (in mm) - **Merkmale**: JSON-String mit spezifischen Eigenschaften **Beispiel:** ```csv 1;"ILS 2.0 Kreisel";"shape_f81e5c4b-a976-a3c0-3304-d2b30da1ab29";"shape_d76e250a-0a46-f6d0-df52-943ab572cc63";"Kreisel:1";"X:9174.15 Y:12039.11";{"Abstand (Kreiselachse A - Kreiselachse) in Meter":"20.265","Anzahl der Separatoren":"2","Anzahl der Scanner":"0","Höhe in m":"2"} ``` ## Verwendung ### Kommandozeilen-Syntax ```bash python plant2dxf.py -f [Optionen] ``` **Optionen:** - `-f, --file`: CSV-Datei (Pfad oder Dateiname im WORK-Verzeichnis) **[erforderlich]** - `-c, --config`: Pfad zur shapes.cfg (Standard: `PROJECT_CFG/shapes.cfg`) - `-l, --lib`: Pfad zur DXF-Bibliothek (Standard: `PROJECT_DATA/blocks.dxf`) - `-o, --output`: Ausgabe-DXF-Datei (Standard: `.dxf` im WORK-Verzeichnis) - `-v, --verbose`: Ausführliche Ausgaben **Beispiele:** ```bash # Einfache Verwendung python plant2dxf.py -f anlage.csv # Mit expliziten Pfaden python plant2dxf.py -f anlage.csv -c cfg/shapes.cfg -l data/blocks.dxf -o output.dxf # Mit verbose-Ausgabe python plant2dxf.py -f anlage.csv -v ``` ### Batch-Datei (Windows) Verwende `bin/plant2dxf.bat` für einfache Ausführung: ```batch plant2dxf.bat ``` ## Verarbeitungslogik ### 1. Initialisierung 1. Laden der Konfigurationsdateien (`shapes.cfg`, `allgemein.cfg`) 2. Erstellen einer neuen DXF-Datei (DXF R2018, Einheit: Millimeter) 3. Laden der Block-Bibliotheken (mit Caching) ### 2. CSV-Verarbeitung Für jede Zeile in der CSV-Datei: 1. **Parsen der CSV-Zeile:** - Extraktion von TeileArt, TeileId, Koordinaten, Merkmalen - Parsen des Planquadrats zu X/Y-Koordinaten - Parsen der Merkmale (JSON) 2. **Nachbar-Informationen:** - Analyse der NachbarIds - Bestimmung der Verbindungen zu Kreiseln, Kurven, Eckrädern - Berechnung von Drehrichtungen, Höhen, Abständen 3. **Bibliothekszuordnung:** - Bestimmung der Bibliotheksdatei aus `allgemein.cfg` - Laden der Bibliothek (mit Cache) 4. **Handler-Aufruf:** - Normalisierung des TeileArt-Namens zu Funktionsname - Aufruf der entsprechenden `handle_*`-Funktion - Erstellung der DXF-Elemente ### 3. Element-Erstellung Jeder Handler: - Erstellt Objekte aus den Element-Klassen (`Kreisel`, `Gefaellestrecke`, etc.) - Analysiert Verbindungen zu Nachbarn - Erstellt dynamische Blöcke basierend auf Konfiguration - Fügt Blöcke, Linien und andere DXF-Entitäten ein - Setzt Attribute (z.B. TeileId) ### 4. Speicherung - Speicherung der DXF-Datei im angegebenen Ausgabepfad - Logging der Verarbeitung ## Wichtige Module und Klassen ### Element-Klassen (`lib/Elemente/`) - **Kreisel**: Repräsentiert einen Kreisel mit allen Eigenschaften - **Gefaellestrecke**: Repräsentiert eine Gefällestrecke - **VarioFoerderer**: Repräsentiert einen Vario-Förderer - **Angetriebene_Kurve**: Repräsentiert eine angetriebene Kurve - **Eckrad**: Repräsentiert ein Eckrad - **Bt_element**: Repräsentiert BT-Elemente - **Omniflo**: Repräsentiert Omniflo-Komponenten ### Hilfsmodule - **arbeiten_mit_csv.py**: CSV-Parsing, Koordinaten-Extraktion, Nachbar-Analyse - **block_methoden.py**: Block-Import, Rotation, Layer-Verwaltung - **as_es_methoden.py**: AS/ES-Element-Erstellung, Höhen-Vertauschung - **utils.py**: Umgebungsvariablen, Logger-Setup ## Wichtige ezdxf-Methoden Das Skript verwendet verschiedene Methoden der `ezdxf`-Bibliothek zur Erstellung und Manipulation von DXF-Elementen. Die wichtigsten Methoden sind: ### 1. Block-Referenz in Block einfügen ```python block.add_blockref(blockname, (x, y, z), dxfattribs={"rotation": rotation, "layer": layer, "color": color}) ``` **Beschreibung:** Fügt eine Block-Referenz (INSERT) in einen bestehenden Block ein. **Parameter:** - `blockname` (str): Name des Blocks, der eingefügt werden soll - `(x, y, z)` (tuple): Koordinaten der Einfügeposition (3D-Tupel) - `dxfattribs` (dict): Dictionary mit DXF-Attributen: - `rotation` (float): Rotationswinkel in Grad - `layer` (str): Layer-Name - `color` (int): Farbe (optional) **Verwendung:** Wird verwendet, um Blöcke innerhalb von anderen Blöcken zu platzieren (z.B. Motorstationen in Gefällestrecken-Blöcken). **Beispiel:** ```python block.add_blockref("Vario_Motorstation_500mm", (0, 250, 0), dxfattribs={"rotation": 270, "layer": "VARIO"}) ``` ### 2. Block-Referenz ins Modelspace einfügen ```python msp.add_blockref(blockname, (x, y, z), dxfattribs={"rotation": rotation, "layer": layer, "color": color}) ``` **Beschreibung:** Fügt eine Block-Referenz direkt in den Modelspace (die Hauptzeichnung) ein. **Parameter:** - `blockname` (str): Name des Blocks, der eingefügt werden soll - `(x, y, z)` (tuple): Koordinaten der Einfügeposition (3D-Tupel) - `dxfattribs` (dict): Dictionary mit DXF-Attributen (siehe oben) **Verwendung:** Wird verwendet, um fertige Blöcke in die Zeichnung zu platzieren. Dies ist der häufigste Weg, um Elemente in die DXF-Datei einzufügen. **Beispiel:** ```python msp.add_blockref("ILS_2.0_Gefaellestrecke_8200_2000_UZS_higher", (x, y, hoehe_gefaelle), dxfattribs={"rotation": rotation, "layer": "6-SP"}) ``` ### 3. Neuen Block erstellen ```python block = doc.blocks.new(name=blockname, base_point=(0, 0, 0)) ``` **Beschreibung:** Erstellt einen neuen Block im DXF-Dokument. **Parameter:** - `name` (str): Name des neuen Blocks (muss eindeutig sein) - `base_point` (tuple): Basis-Punkt des Blocks (meist (0, 0, 0)) **Rückgabewert:** Block-Objekt, das verwendet werden kann, um Entitäten hinzuzufügen **Wichtiger Hinweis:** Ein Block mit demselben Namen kann nur einmal erstellt werden. Vor der Erstellung sollte geprüft werden: ```python if blockname not in doc.blocks: block = doc.blocks.new(name=blockname, base_point=(0, 0, 0)) ``` **Verwendung:** Wird verwendet, um dynamische Blöcke zu erstellen, die aus mehreren Komponenten bestehen (z.B. Gefällestrecken mit Motorstationen, Vario-Förderer mit verschiedenen Konfigurationen). **Beispiel:** ```python blockname = f"Ils_2.0_Gefaellestrecke_{laenge}_{hoehe_gefaelle}_{hat_motor_0}_{hat_umlenk_0}" if blockname not in doc.blocks: block = doc.blocks.new(name=blockname, base_point=(0, 0, 0)) # Füge Komponenten zum Block hinzu block.add_blockref("Vario_Motorstation_500mm", (0, 0, 0)) line = Line.new(dxfattribs={"start": start, "end": ende}) line.translate(-x, -y, -hoehe_gefaelle) block.add_entity(line) ``` ### 4. DXF-Objekte aus Modelspace abfragen ```python entities = msp.query("INSERT") ``` **Beschreibung:** Holt alle spezifischen DXF-Objekte vom Modelspace. **Parameter:** - `"INSERT"` (str): DXF-Typ des Objekts (z.B. "INSERT" für Block-Referenzen, "LINE" für Linien, "CIRCLE" für Kreise) **Rückgabewert:** Liste von DXF-Entitäten des angegebenen Typs **Verwendung:** Wird verwendet, um bestimmte Objekte aus der Zeichnung zu finden oder zu analysieren. Meistens wird dies für INSERT-Objekte (Block-Referenzen) verwendet. **Beispiel:** ```python # Alle Block-Referenzen im Modelspace finden inserts = msp.query("INSERT") for insert in inserts: print(f"Block: {insert.dxf.name} an Position {insert.dxf.insert}") ``` **Weitere DXF-Typen:** - `"LINE"`: Linien - `"CIRCLE"`: Kreise - `"ARC"`: Bögen - `"TEXT"`: Text - `"ATTDEF"`: Attribut-Definitionen - `"INSERT"`: Block-Referenzen ### 5. Weitere wichtige ezdxf-Methoden #### Linie erstellen und hinzufügen ```python # Linie direkt im Modelspace line = msp.add_line(start=(x1, y1, z1), end=(x2, y2, z2)) line.dxf.layer = "6-SP" # Linie in Block erstellen line = Line.new(dxfattribs={"start": (x1, y1, z1), "end": (x2, y2, z2)}) line.translate(-x, -y, -z) # Relativ zum Block-Ursprung verschieben block.add_entity(line) ``` #### Layer erstellen ```python if "LAYER_NAME" not in doc.layers: doc.layers.add(name="LAYER_NAME", color=7) ``` #### Block-Attribute hinzufügen ```python bref = msp.add_blockref(blockname, (x, y, z)) bref.add_attrib(tag="NAME", text="Bezeichnung", insert=(x, y)) bref.add_auto_attribs({ATTR_TAG: teileid}) # Automatische Attribute aus Block-Definition ``` #### Entität transformieren ```python entity.translate(dx, dy, dz) # Verschieben entity.rotate_z(angle) # Rotation um Z-Achse entity.scale(factor) # Skalierung ``` ## Konstante Parameter - **ATTR_TAG**: `"TeileId"` - Attributtag im Block - **RADIUS**: `400` - Radius der Kreiselkreise (in mm) ## Layer-Verwaltung Das Skript erstellt automatisch folgende Layer: - **VARIO**: Für Vario-Förderer (Farbe: 3) - **6-SP**: Für Gefällestrecken (Farbe: 7) - Weitere Layer werden aus den Bibliotheks-Blöcken übernommen ## Fehlerbehandlung - Fehlende Umgebungsvariablen führen zum Programmabbruch - Fehlende Bibliotheksdateien werden geloggt, Verarbeitung wird fortgesetzt - Fehlerhafte CSV-Zeilen werden übersprungen (mit Warnung) - Fehlende Handler für TeileArt werden geloggt, Element wird übersprungen ## Logging - Log-Dateien werden in `PROJECT_LOG/` gespeichert - Format: `plant2dxf_YYYYMMDD_HHMMSS.log` - Logging erfolgt sowohl in Datei als auch auf Konsole - Format: `%(asctime)s - %(levelname)s - %(message)s` ## Detaillierte Funktionsbeschreibungen ### NachbarId-Entnahme und -Analyse Die Funktion `get_nachbar_information()` in `arbeiten_mit_csv.py` analysiert die CSV-Datei und erstellt ein Dictionary mit allen Nachbar-Informationen für Gefällestrecken und Vario-Förderer. #### Ablauf der Nachbar-Analyse 1. **Erste Durchlauf - Sammeln aller Elemente:** - **Gefällestrecken**: Speichert ID und NachbarIds - **Kreisel**: Erstellt Kreisel-Objekte und speichert: - `drehung`: Drehrichtung (UZS/GUZS) - `höhe`: Höhe des Kreisels - `x`, `y`: Koordinaten - `rotation`: Rotationswinkel - `abstand`: Abstand zwischen Kreiselachsen (in Metern) - **Vario-Förderer**: Speichert ID, NachbarIds, Winkel, Höhen, Förderrichtung, Koordinaten - **Angetriebene Kurven**: Speichert ID, Höhen, Kurvenrichtung, TEF-Kurve-Status, Winkel, Koordinaten - **Eckräder**: Speichert ID, Höhe, Koordinaten 2. **Zweiter Durchlauf - Zuordnung der Nachbarn:** Für jede Gefällestrecke oder Vario-Förderer: **a) Angetriebene Kurven (TEF-Kurven):** - Prüft, ob Kurven-IDs in den NachbarIds enthalten sind - Speichert für erste Kurve (`voerder_anweisung == 0`): - `X_angetrieben`, `Y_angetrieben`: Koordinaten - `vario_hoehe_0`, `vario_hoehe_1`: Höhen der Kurve - `Kurvenrichtung`: Links oder Rechts - `Tefkurve`: Antriebsart (außen/innen) - `Kurvenwinkel`: Winkel der Kurve - Speichert für zweite Kurve (`voerder_anweisung == 1`): - Gleiche Felder mit Suffix `_1` **b) Eckräder:** - Prüft, ob Eckrad-IDs in den NachbarIds enthalten sind - Speichert für erstes Eckrad (`eckrad_anweisung == 0`): - `Eckrad_x`, `Eckrad_y`: Koordinaten - `Eckrad_höhe`: Höhe des Eckrads - Speichert für zweites Eckrad (`eckrad_anweisung == 1`): - Gleiche Felder mit Suffix `_1` **c) Kreisel:** - Prüft, ob Kreisel-IDs in den NachbarIds enthalten sind - Speichert für ersten Kreisel (`anweisungen == 0`): - `Drehung0`: Drehrichtung (UZS/GUZS) - `Hoehe0`: Höhe des Kreisels - `x0`, `y0`: Koordinaten - `rotation0`: Rotationswinkel - `abstand0`: Abstand zwischen Kreiselachsen (in Metern) - Speichert für zweiten Kreisel (`anweisungen == 1`): - Gleiche Felder mit Suffix `1` statt `0` **d) Vario-Förderer (nur für Vario-Förderer):** - Prüft, ob andere Vario-Förderer in den NachbarIds enthalten sind - Speichert für ersten Vario-Förderer (`geraden_anweisung == 0`): - `X_foerderer`, `Y_foerderer`: Koordinaten - `Winkel`: Winkel des Förderers - `h0`, `h1`: Höhen - `Foerderrichtung`: Auf/Ab/Horizontal - Speichert für zweiten Vario-Förderer (`geraden_anweisung == 1`): - Gleiche Felder mit Suffix `_2` #### Verwendung der Nachbar-Informationen Die Nachbar-Informationen werden verwendet für: - **Gefällestrecken**: Bestimmung der AS/ES-Elemente, Motor/Umlenkstationen, direkte Kreisel-Verbindungen - **Vario-Förderer**: Bestimmung der Verbindungsart, Gefälle-Komponenten, Offset-Berechnungen ### Gefällestrecke mit Motor/Umlenkstation - Detaillierte Erstellung Die Erstellung einer Gefällestrecke mit Motor- oder Umlenkstation erfolgt in mehreren Schritten: #### 1. Prüfung auf Motor/Umlenkstation-Bedarf Die Methode `hat_motor_umlenk_station()` prüft, ob eine Gefällestrecke eine Motor- oder Umlenkstation benötigt: **Logik:** - Prüft, ob `Kurvenrichtung` in den Nachbar-Informationen vorhanden ist (d.h. verbunden mit angetriebener Kurve) - Bestimmt `tefkurve_0` basierend auf Kurvenrichtung und Antriebsart: - `kurvenrichtung == "links"` und `Tefkurve == "außen"` → `tefkurve_0 = "rechts"` - `kurvenrichtung == "rechts"` und `Tefkurve == "innen"` → `tefkurve_0 = "rechts"` - Sonst → `tefkurve_0 = "links"` **Höhen-basierte Bestimmung:** - Wenn `upper_hoehe_gefaelle > lower_hoehe_gefaelle`: - Wenn `vario_hoehe_0` oder `vario_hoehe_1 == upper_hoehe_gefaelle` → `hat_motor_0 = True` - Sonst → `hat_umlenk_0 = True` - Wenn `upper_hoehe_gefaelle < lower_hoehe_gefaelle`: - Wenn `vario_hoehe_0` oder `vario_hoehe_1 == lower_hoehe_gefaelle` → `hat_motor_0 = True` - Sonst → `hat_umlenk_0 = True` **Rotations-basierte Bestimmung (bei gleicher Höhe):** - Wenn beide Höhen gleich sind, wird die Position relativ zur Kurve geprüft - Basierend auf Rotation und Koordinaten wird entschieden: - `hat_umlenk_0 = True` + `umlenk_gerade = True` (wenn bestimmte Bedingungen erfüllt) - Oder `hat_motor_0 = True` + `motor_gerade = True` #### 2. Erstellung des Blocks mit Motor/Umlenkstation Die Methode `ein_motor_oder_eine_umlenk()` erstellt die Motor- oder Umlenkstation im Block: **Schritte:** 1. **Import der Vario-Bögen:** - `Vario_Bogen_auf_3°`: Bogen für Aufwärts-Bewegung - `Vario_Bogen_ab_3°`: Bogen für Abwärts-Bewegung - Erstellung der Links-Versionen durch `turn_two_blocks_left()` 2. **Auslesen der Delta-Werte:** - `DELTA_SP_0`, `DELTA_SP_1`: Delta-Werte für Startpunkt - `DELTA_VP_0`, `DELTA_VP_1`: Delta-Werte für Verbindungspunkt - Negative Werte werden in positive umgewandelt 3. **Motor-Station einfügen (`hat_motor_0 == True`):** - **Wenn `tefkurve_0 == "links"`:** - Wenn `motor_gerade == False`: - Fügt `Vario_Bogen_ab_links` ein (Rotation 270°) - Berechnet neuen Startpunkt basierend auf Delta-Werten - Fügt `blockname_motor_links` ein (250mm Offset) - Verschiebt Startpunkt um 500mm * cos(3°) und sin(3°) - Wenn `motor_gerade == True`: - Fügt `Vario_Motorstation_500mm_links` direkt ein - Verschiebt Startpunkt um 500mm - **Wenn `tefkurve_0 == "rechts"`:** - Gleiche Logik mit rechts-Versionen der Blöcke 4. **Umlenk-Station einfügen (`hat_umlenk_0 == True`):** - **Wenn `tefkurve_0 == "links"`:** - Wenn `umlenk_gerade == False`: - Fügt `Vario_Bogen_auf` ein (Rotation 90°) - Berechnet neuen Endpunkt basierend auf Delta-Werten - Fügt `blockname_umlenk_links` ein (250mm Offset) - Verschiebt Endpunkt um 500mm * cos(3°) und sin(3°) - Wenn `umlenk_gerade == True`: - Fügt `Vario_Umlenkstation_500mm_links` direkt ein - Verschiebt Endpunkt um 500mm - **Wenn `tefkurve_0 == "rechts"`:** - Gleiche Logik mit rechts-Versionen der Blöcke 5. **Rückgabe:** - Gibt modifizierte `start` und `ende` Koordinaten zurück #### 3. Blockname-Generierung Der Blockname wird dynamisch generiert: ```python Ils_2.0_Gefaellestrecke_{laenge}_{hoehe_gefaelle}_{hat_umlenk_0}_{hat_motor_0}_{tefkurve_0}_{umlenk_gerade}_{motor_gerade} ``` #### 4. Block-Erstellung 1. Prüft, ob Block bereits existiert (Caching) 2. Erstellt neuen Block mit Basis-Punkt (0,0,0) 3. Ruft `ein_motor_oder_eine_umlenk()` auf, um Motor/Umlenkstation einzufügen 4. Erstellt Linie zwischen modifiziertem Start- und Endpunkt 5. Verschiebt Linie relativ zum Block-Ursprung 6. Fügt Block-Referenz in Modelspace ein ### Vario-Förderer ohne Verbindung - Detaillierte Erstellung Wenn ein Vario-Förderer **nicht** mit einem Kreisel oder Eckrad verbunden ist, wird der einfachste Fall behandelt: #### 1. Vorbereitung ```python halbe_laenge = laenge / 2 dy = halbe_laenge * math.cos(0) # dy = halbe_laenge (da cos(0) = 1) ``` #### 2. Blockname-Generierung **Rechts-Version:** ```python Vario_Foerderer_{winkel}_{foerder_richtung}_{laenge}_{hoehe_vario}_rechts_{motor_vorhanden}_{umlenk_vorhanden}_{gefaelle}_{gefahellewinkel} ``` **Links-Version:** ```python Vario_Foerderer_{winkel}_{foerder_richtung}_{laenge}_{hoehe_vario}_links_{motor_vorhanden}_{umlenk_vorhanden}_{gefaelle}_{gefahellewinkel} ``` **Parameter:** - `winkel`: Winkel des Förderers (z.B. 3, 6, 9) - `foerder_richtung`: Auf, Ab oder Horizontal - `laenge`: Länge in mm - `hoehe_vario`: Mittlere Höhe ((h0 + h1) / 2) - `motor_vorhanden`: True/False - `umlenk_vorhanden`: True/False - `gefaelle`: Länge der Gefällestrecke (falls vorhanden) - `gefahellewinkel`: Winkel der Gefällestrecke (falls vorhanden) #### 3. Block-Erstellung 1. **Prüfung auf existierenden Block:** - Wenn Block bereits existiert, wird nur Block-Referenz eingefügt - Auswahl zwischen Links- und Rechts-Version basierend auf `Motorseite` 2. **Neuer Block wird erstellt:** ```python block = doc.blocks.new(blockname, base_point=(0,0,0)) ``` 3. **Start- und Endpunkt berechnen:** ```python start = (x, y + dy, upper_hoehe_vario) ende = (x, y - dy, lower_hoehe_vario) ``` - Startpunkt: Mitte + halbe Länge in Y-Richtung, obere Höhe - Endpunkt: Mitte - halbe Länge in Y-Richtung, untere Höhe 4. **Vario-Erstellung aufrufen:** ```python VarioFoerderer.VarioFoerderer.vario_erstellung( foerderer, doc, lib_doc, config, block, block_name_links, start, ende, foerder_richtung, winkel_VP_offset_vorne, # None, da keine Verbindung winkel_VP_offset_hinten # None, da keine Verbindung ) ``` #### 4. Vario-Erstellung (`vario_erstellung()`) **Schritte:** 1. **Konfiguration auslesen:** - `winkel_motor`: Winkel für Motorstation (aus Config, z.B. 3.0°) - `winkel_umlenk`: Winkel für Umlenkstation (aus Config, z.B. 3.0°) - `umlenk_laenge`: Länge der Umlenkstation (aus Config, z.B. 500.0mm) - `motor_laenge`: Länge der Motorstation (aus Config, z.B. 500.0mm) - `vario_abstand`: Abstand zwischen Vario-Komponenten (aus Config, z.B. 66.5mm) 2. **Offset-Berechnung:** ```python motor_offset_x = umlenk_laenge[0] * math.cos(math.radians(winkel_motor)) motor_offset_z = umlenk_laenge[0] * math.sin(math.radians(winkel_motor)) umlenk_offset_x = motor_laenge[0] * math.cos(math.radians(winkel_umlenk)) umlenk_offset_z = motor_laenge[0] * math.sin(math.radians(winkel_umlenk)) ``` 3. **Gefälle-Länge anpassen:** - Wenn Motor vorhanden: `gefaelle = gefaelle - motor_offset_x` - Wenn Umlenk vorhanden: `gefaelle = gefaelle - umlenk_offset_x` 4. **Förderrichtung "Auf" oder "Horizontal":** - Erstellt Vario-Komponenten von Start zu Ende - Fügt Motorstation am Start ein (falls vorhanden) - Fügt Gefälle-Komponenten ein (falls vorhanden) - Fügt Umlenkstation am Ende ein (falls vorhanden) 5. **Förderrichtung "Ab":** - Erstellt Vario-Komponenten von Ende zu Start - Fügt Umlenkstation am Ende ein (falls vorhanden) - Fügt Gefälle-Komponenten ein (falls vorhanden) - Fügt Motorstation am Start ein (falls vorhanden) 6. **Links-Version erstellen:** - Erstellt gespiegelte Version des Blocks für Links-Motorseite - Verwendet `turn_two_blocks_left()` für Spiegelung #### 5. Block-Referenz einfügen Basierend auf `Motorseite` aus Merkmalen: - `Motorseite == "links"` → Fügt `block_name_links` ein - `Motorseite == "rechts"` → Fügt `blockname` ein **Position:** `(x, y, hoehe_vario)` **Rotation:** `rotation` aus Merkmalen ## Erweiterbarkeit ### Neue TeileArt hinzufügen 1. **Config hinzufügen** (`shapes.cfg`): ```ini [Neue TeileArt] items = Block1, Block2 offset_symb1 = 0,0 rot_symb1 = 0.0 ``` 2. **Handler-Funktion erstellen** (`plant2dxf.py`): ```python def handle_neue_teileart(msp, teileid, merkmale, x, y, doc, lib_doc, verbose, symbols, strecken_nachbarn, config, config_allgemein): """Erstellt eine neue TeileArt in der neuen Dxf""" # Implementierung ``` 3. **Bibliothekszuordnung** (`allgemein.cfg`): ```ini [Neue TeileArt] libfile = bibliothek.dxf ``` 4. **Element-Klasse** (optional, `lib/Elemente/`): - Falls komplexe Logik benötigt wird, eigene Klasse erstellen ## Abhängigkeiten Siehe `lib/requirements.txt`: - `ezdxf==1.4.1`: DXF-Bibliothek - `svg.path==7.0`: SVG-Pfad-Verarbeitung - `pydantic>=2.0.0`: Datenvalidierung ## Bekannte Einschränkungen - CSV muss UTF-8 kodiert sein - Koordinaten müssen im Format `X: Y:` vorliegen - Bibliotheksdateien müssen im DXF-Format vorliegen - Blocknamen müssen exakt mit Bibliothek übereinstimmen