# utils.py API Documentation Comprehensive documentation for the `utils.py` module containing helper functions for file operations, DXF manipulation, and data processing. ## Table of Contents 1. [File Operations](#file-operations) 2. [Environment Variables](#environment-variables) 3. [JSON Operations](#json-operations) 4. [DXF File Operations](#dxf-file-operations) 5. [ezdxf Operations (High-Level DXF Helpers)](#ezdxf-operations) 6. [Dictionary Operations](#dictionary-operations) --- ## File Operations ### `check_file_in_work(work_dir, filename) → Tuple[Path, bool]` Check if a file exists, either at the given path or in the work directory. **Args:** - `work_dir` (str | Path): Working directory to check if file not found at filename path - `filename` (str | Path): Path to the file to check **Returns:** - `Tuple[Path, bool]`: (resolved_path, exists_flag) - `resolved_path`: Path where file was found (or would be) - `exists_flag`: True if file exists, False otherwise **Example:** ```python from utils import check_file_in_work work_dir = Path("c:/work") filepath, exists = check_file_in_work(work_dir, "test.dxf") if exists: print(f"File found at: {filepath}") ``` --- ## Environment Variables ### `check_environment_var(env_str: str) → Path` Get and validate an environment variable as a Path. **Args:** - `env_str` (str): Name of the environment variable **Returns:** - `Path`: Path object from the environment variable **Exits:** - Exits the program if the environment variable is not set or empty **Example:** ```python from utils import check_environment_var work_dir = check_environment_var('PROJECT_WORK') config_dir = check_environment_var('PROJECT_CFG') ``` --- ## JSON Operations ### `to_json(d: Any, pretty: bool = True) → str` Convert a Python object to a JSON string. **Args:** - `d` (Any): Object to convert to JSON - `pretty` (bool): If True, format with indentation for readability (default: True) **Returns:** - `str`: JSON string representation of the object **Note:** Uses `ensure_ascii=False` to properly display German umlauts (ä, ö, ü) **Example:** ```python from utils import to_json data = {"name": "Test", "wert": 42} json_str = to_json(data) print(json_str) ``` ### `load_json(jsonfilename: str) → dict` Load JSON data from a file. **Args:** - `jsonfilename` (str): Path to the JSON file **Returns:** - `dict`: Parsed JSON data as a dictionary **Example:** ```python from utils import load_json config = load_json("config.json") ``` ### `write_results(jsn_results: str, out_dir: Path, filename: str) → None` Write JSON results to a file. **Args:** - `jsn_results` (str): JSON string to write - `out_dir` (Path): Output directory path - `filename` (str): Name of the output file **Example:** ```python from utils import write_results, to_json from pathlib import Path data = {"results": [1, 2, 3]} write_results(to_json(data), Path("output"), "results.json") ``` --- ## DXF File Operations ### `dxf_is_binary(dxf_path: Path) → bool` Check if a DXF file is in binary format. **Args:** - `dxf_path` (Path): Path to the DXF file **Returns:** - `bool`: True if the file is a binary DXF, False otherwise **Example:** ```python from utils import dxf_is_binary from pathlib import Path if dxf_is_binary(Path("drawing.dxf")): print("Binary DXF file detected") else: print("ASCII DXF file") ``` ### `get_dxf_file(filepath: Path) → ezdxf.Drawing` Load a DXF file using ezdxf. **Args:** - `filepath` (Path): Path to the DXF file **Returns:** - `ezdxf.Drawing`: ezdxf Drawing object **Exits:** - Exit code 1: I/O errors - Exit code 2: Invalid/corrupted DXF files **Example:** ```python from utils import get_dxf_file from pathlib import Path doc = get_dxf_file(Path("drawing.dxf")) msp = doc.modelspace() ``` --- ## ezdxf Operations High-level helper functions for working with ezdxf objects. ### Attribute Extraction #### `extract_insert_attributes(insert, error_collector=None) → dict` Extrahiert alle Attribute aus einem INSERT-Block. Unterstützt zweistufige Blockstruktur. **Args:** - `insert`: INSERT-Entity des Blocks - `error_collector` (optional): ErrorCollector für Warnungen **Returns:** - `dict`: Dictionary mit allen gefundenen Attributen `{tag: text}` **Note:** - Funktioniert ohne explizites doc-Objekt wenn `insert.doc` verfügbar - Für neue Code: Verwenden Sie `extract_insert_attributes_with_doc()` **Example:** ```python from utils import extract_insert_attributes for entity in msp.query('INSERT'): attributes = extract_insert_attributes(entity) if 'IO' in attributes: print(f"IO: {attributes['IO']}") ``` #### `extract_insert_attributes_with_doc(doc, insert, error_collector=None) → dict` Extrahiert alle Attribute aus einem INSERT-Block mit explizitem doc-Parameter. **Empfohlen für neuen Code** - bessere Unterstützung für zweistufige Blockstrukturen. **Args:** - `doc`: DXF-Dokument - `insert`: INSERT-Entity des Blocks - `error_collector` (optional): ErrorCollector für Warnungen **Returns:** - `dict`: Dictionary mit allen gefundenen Attributen `{tag: text}` **Example:** ```python from utils import extract_insert_attributes_with_doc doc = get_dxf_file(Path("drawing.dxf")) msp = doc.modelspace() for entity in msp.query('INSERT'): attributes = extract_insert_attributes_with_doc(doc, entity) print(f"Block: {entity.dxf.name}, Attributes: {attributes}") ``` #### `extract_attributes_with_positions(insert_iterable, round_decimals=1) → Tuple[list, list]` Wandelt eine Iterable von INSERT-Objekten in zwei Listen um. **Kompatibilität** mit `getpositions.py attribs_to_dicts()`. **Args:** - `insert_iterable`: Iterable von INSERT-Entities (z.B. msp oder iterdxf.modelspace()) - `round_decimals` (int): Anzahl Nachkommastellen für Positions-Rundung (default: 1) **Returns:** - `Tuple[list, list]`: (all_inserts, all_positions) - `all_inserts`: Liste von Dicts mit Attribut-Tags und deren Textwerten - `all_positions`: Liste von Dicts mit Attribut-Tags und deren (x, y, z)-Positionen **Note:** - Blöcke ohne Attribute werden übersprungen - Ersetzt die alte `attribs_to_dicts()` aus getpositions.py **Example:** ```python from utils import extract_attributes_with_positions all_inserts, all_positions = extract_attributes_with_positions(msp) for insert_data, positions in zip(all_inserts, all_positions): print(f"Attributes: {insert_data}") print(f"Positions: {positions}") ``` ### Layer Operations #### `ensure_layer_exists(doc, layer_name, color=None, linetype=None, lineweight=None, description=None) → Layer` Stellt sicher, dass ein Layer existiert und erstellt ihn falls nötig. **Args:** - `doc`: DXF-Dokument - `layer_name` (str): Name des Layers - `color` (int, optional): ACI Color Index (1-255) - `linetype` (str, optional): Name des Linientyps (z.B. 'CONTINUOUS', 'DASHED') - `lineweight` (int, optional): Linienbreite in mm*100 (z.B. 25 für 0.25mm) - `description` (str, optional): Layer-Beschreibung **Returns:** - `Layer`: Layer-Objekt (existierend oder neu erstellt) **Example:** ```python from utils import ensure_layer_exists # Einfacher Layer ensure_layer_exists(doc, 'ILS_MOTOR') # Layer mit Farbe und Beschreibung ensure_layer_exists(doc, 'ILS_CABLE', color=5, description='Cable routing layer') # Layer mit allen Optionen ensure_layer_exists(doc, 'ILS_SPECIAL', color=3, linetype='DASHED', lineweight=25, description='Special equipment') ``` #### `create_layers_from_config(doc, layer_definitions) → None` Erstellt mehrere Layer aus einer Konfiguration. **Args:** - `doc`: DXF-Dokument - `layer_definitions` (dict): Dictionary mit Layer-Definitionen **Format:** ```python { 'layer_name': { 'color': int (optional), 'linetype': str (optional), 'lineweight': int (optional), 'description': str (optional) }, ... } ``` **Example:** ```python from utils import create_layers_from_config layer_config = { 'ILS_MOTOR': {'color': 1, 'description': 'Motor layer'}, 'ILS_SENSOR': {'color': 3, 'lineweight': 25}, 'ILS_CABLE': {'color': 5, 'linetype': 'DASHED'} } create_layers_from_config(doc, layer_config) ``` ### Geometry Creation #### `add_rectangle_lwpolyline(msp, x, y, width, height, layer='0', color=None, rgb=None, closed=True) → LWPolyline` Fügt eine rechteckige LWPOLYLINE hinzu. **Args:** - `msp`: Modelspace des DXF-Dokuments - `x` (float): X-Koordinate der unteren linken Ecke - `y` (float): Y-Koordinate der unteren linken Ecke - `width` (float): Breite des Rechtecks - `height` (float): Höhe des Rechtecks - `layer` (str): Layer-Name (default: '0') - `color` (int, optional): ACI Color Index (1-255) - `rgb` (tuple, optional): RGB-Tuple (r, g, b) mit Werten 0-255 - `closed` (bool): Ob die Polylinie geschlossen sein soll (default: True) **Returns:** - `LWPolyline`: LWPOLYLINE-Entity **Example:** ```python from utils import add_rectangle_lwpolyline # Einfaches Rechteck add_rectangle_lwpolyline(msp, 0, 0, 100, 50, layer='ILS_FRAME') # Rechteck mit Farbe add_rectangle_lwpolyline(msp, 10, 10, 200, 100, color=5) # Rechteck mit RGB-Farbe add_rectangle_lwpolyline(msp, 20, 20, 150, 75, rgb=(255, 0, 0)) ``` #### `add_text_with_alignment(msp, text, x, y, height=50, layer='0', color=None, halign=0, valign=0, rotation=0.0, style='Standard') → Text` Fügt einen Text mit Ausrichtung hinzu. **Args:** - `msp`: Modelspace des DXF-Dokuments - `text` (str): Textinhalt - `x` (float): X-Koordinate - `y` (float): Y-Koordinate - `height` (float): Texthöhe (default: 50) - `layer` (str): Layer-Name (default: '0') - `color` (int, optional): ACI Color Index (1-255) - `halign` (int): Horizontale Ausrichtung (0=links, 1=mitte, 2=rechts) - `valign` (int): Vertikale Ausrichtung (0=basis, 1=unten, 2=mitte, 3=oben) - `rotation` (float): Rotation in Grad (default: 0.0) - `style` (str): Textstil-Name (default: 'Standard') **Returns:** - `Text`: TEXT-Entity **Example:** ```python from utils import add_text_with_alignment # Einfacher Text links unten add_text_with_alignment(msp, "Test", 100, 100, layer='ILS_TEXT') # Zentrierter Text add_text_with_alignment(msp, "Centered", 200, 200, halign=1, valign=2) # Rotierter Text mit Farbe add_text_with_alignment(msp, "Rotated", 300, 300, color=3, rotation=45.0, height=75) ``` ### Block Operations #### `add_blockref_with_attributes(msp, block_name, insert_point, attributes=None, layer='0', scale=1.0, rotation=0.0) → Insert` Fügt eine Block-Referenz (INSERT) mit Attributen hinzu. **Args:** - `msp`: Modelspace des DXF-Dokuments - `block_name` (str): Name des Blocks - `insert_point` (tuple): Einfügepunkt als (x, y) oder (x, y, z) Tuple - `attributes` (dict, optional): Dictionary mit Attribut-Namen und -Werten - `layer` (str): Layer-Name (default: '0') - `scale` (float): Skalierungsfaktor (default: 1.0) - `rotation` (float): Rotation in Grad (default: 0.0) **Returns:** - `Insert`: INSERT-Entity mit gesetzten Attributen **Example:** ```python from utils import add_blockref_with_attributes # Block ohne Attribute add_blockref_with_attributes(msp, "MOTOR_SYMBOL", (100, 100)) # Block mit Attributen attrs = {"IO": "MA0001", "KENNZEICHNUNG": "=A01+UH00"} add_blockref_with_attributes(msp, "io", (200, 200), attributes=attrs) # Block mit Skalierung und Rotation add_blockref_with_attributes(msp, "SENSOR", (300, 300), scale=0.5, rotation=90.0, layer='ILS_SENSOR') ``` ### Entity Queries #### `query_entities_by_layer(msp, entity_type=None, layers=None, exclude_layers=None) → list` Filtert Entities nach Typ und Layer. **Args:** - `msp`: Modelspace des DXF-Dokuments - `entity_type` (str, optional): Entity-Typ (z.B. 'INSERT', 'LINE', 'TEXT'), None für alle - `layers` (list, optional): Liste der zu inkludierenden Layer (None = alle außer exclude_layers) - `exclude_layers` (list, optional): Liste der zu exkludierenden Layer (None = keine Exklusion) **Returns:** - `list`: Liste der gefilterten Entities **Examples:** ```python from utils import query_entities_by_layer # Alle INSERTs auf Layer 'ILS_MOTOR' motors = query_entities_by_layer(msp, 'INSERT', layers=['ILS_MOTOR']) # Alle Entities außer auf Layer '0' entities = query_entities_by_layer(msp, exclude_layers=['0']) # Alle LINEs auf ILS_* Layern (mit zusätzlichem Filtern) all_lines = query_entities_by_layer(msp, 'LINE') ils_lines = [e for e in all_lines if e.dxf.layer.startswith('ILS_')] # Alle INSERTs auf mehreren Layern inserts = query_entities_by_layer(msp, 'INSERT', layers=['ILS_MOTOR', 'ILS_SENSOR']) ``` --- ## Dictionary Operations ### `merge_two_dicts(x, y) → dict` Merge two dictionaries, with values from y overwriting those in x. **Args:** - `x` (dict): First dictionary (base) - `y` (dict): Second dictionary (override values) **Returns:** - `dict`: New dictionary with merged values **Example:** ```python from utils import merge_two_dicts defaults = {"color": 1, "layer": "0", "scale": 1.0} overrides = {"color": 5, "scale": 2.0} result = merge_two_dicts(defaults, overrides) # Result: {"color": 5, "layer": "0", "scale": 2.0} ``` --- ## Migration Guide ### From old `attribs_to_dicts()` to new API **Old code (getpositions.py):** ```python from getpositions import attribs_to_dicts all_inserts, all_positions = attribs_to_dicts(msp) ``` **New code:** ```python from utils import extract_attributes_with_positions all_inserts, all_positions = extract_attributes_with_positions(msp) ``` ### From old `extract_block_attributes()` to new API **Old code (create_numbers.py):** ```python def extract_block_attributes(doc, insert, error_collector=None): # 48 lines of code... return attributes ``` **New code:** ```python from utils import extract_insert_attributes_with_doc as extract_block_attributes # Direct usage - no local function needed ``` --- ## Performance Notes 1. **`extract_attributes_with_positions`**: O(n) where n = number of INSERT entities 2. **`query_entities_by_layer`**: O(n) where n = total entities in msp 3. **`ensure_layer_exists`**: O(1) - uses dict lookup 4. **`create_layers_from_config`**: O(m) where m = number of layers to create --- ## Error Handling Most functions follow these error handling patterns: 1. **Silent fallback**: Functions like `extract_insert_attributes()` return empty dict on error 2. **ErrorCollector integration**: Functions accepting `error_collector` parameter log warnings 3. **System exit**: `check_environment_var()` and `get_dxf_file()` exit on critical errors 4. **Exception propagation**: JSON and file operations raise native Python exceptions --- ## Best Practices 1. **Always use `extract_insert_attributes_with_doc()`** for new code (better error handling) 2. **Use `ensure_layer_exists()`** before adding entities to avoid missing layer errors 3. **Prefer `query_entities_by_layer()`** over manual filtering for better readability 4. **Use `create_layers_from_config()`** when setting up multiple layers from configuration files 5. **Pass `error_collector`** when available for comprehensive error tracking --- ## Version History - **v1.2.0** (2026-01-30): Added 8 new ezdxf helper functions - `extract_insert_attributes_with_doc()` - `extract_attributes_with_positions()` - `ensure_layer_exists()` - `add_rectangle_lwpolyline()` - `add_text_with_alignment()` - `add_blockref_with_attributes()` - `query_entities_by_layer()` - `create_layers_from_config()` - **v1.1.0**: Refactored `extract_insert_attributes()` to support two-level blocks - **v1.0.0**: Initial release with file, JSON, and DXF operations