Test Routinen als Unittest integriert
This commit is contained in:
@@ -0,0 +1,191 @@
|
|||||||
|
"""Vollständige Tests für ErrorCollector - Alle Datentypen"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
|
||||||
|
from error_collector import ErrorCollector
|
||||||
|
|
||||||
|
print("="*70)
|
||||||
|
print("Test Suite: ErrorCollector - Alle Datentypen und Kombinationen")
|
||||||
|
print("="*70)
|
||||||
|
|
||||||
|
# Test 1: Mehrere einzelne String-Fehler (create_numbers.py Szenario)
|
||||||
|
print("\nTest 1: Mehrere einzelne String-Fehler")
|
||||||
|
ec1 = ErrorCollector()
|
||||||
|
ec1.add_errors({"symbol_error": "Fehler 1"})
|
||||||
|
ec1.add_errors({"symbol_error": "Fehler 2"})
|
||||||
|
ec1.add_errors({"symbol_error": "Fehler 3"})
|
||||||
|
assert ec1.errors["symbol_error"] == ["Fehler 1", "Fehler 2", "Fehler 3"]
|
||||||
|
assert isinstance(ec1.errors["symbol_error"], list)
|
||||||
|
print("✓ Strings werden zu flacher Liste zusammengefasst")
|
||||||
|
|
||||||
|
# Test 2: Dictionary bleibt Dictionary (getpositions.py Szenario)
|
||||||
|
print("\nTest 2: Dictionary bleibt Dictionary (double_ids)")
|
||||||
|
ec2 = ErrorCollector()
|
||||||
|
double_ids = {"ID1": [[100, 200], [300, 400]], "ID2": [[500, 600]]}
|
||||||
|
ec2.add_errors({"double_ids": double_ids})
|
||||||
|
assert isinstance(ec2.errors["double_ids"], dict)
|
||||||
|
assert ec2.errors["double_ids"]["ID1"] == [[100, 200], [300, 400]]
|
||||||
|
assert ec2.errors["double_ids"]["ID2"] == [[500, 600]]
|
||||||
|
print("✓ Dictionary bleibt Dictionary (nicht zu Liste verpackt)")
|
||||||
|
|
||||||
|
# Test 3: Dictionary Update/Merge bei mehrmaligem Hinzufügen
|
||||||
|
print("\nTest 3: Dictionary Merge bei zweimaligem Hinzufügen")
|
||||||
|
ec3 = ErrorCollector()
|
||||||
|
ec3.add_errors({"duplicate_ids": {"ID1": [[10, 20]], "ID2": [[30, 40]]}})
|
||||||
|
ec3.add_errors({"duplicate_ids": {"ID3": [[50, 60]]}})
|
||||||
|
assert isinstance(ec3.errors["duplicate_ids"], dict)
|
||||||
|
assert ec3.errors["duplicate_ids"]["ID1"] == [[10, 20]]
|
||||||
|
assert ec3.errors["duplicate_ids"]["ID2"] == [[30, 40]]
|
||||||
|
assert ec3.errors["duplicate_ids"]["ID3"] == [[50, 60]]
|
||||||
|
print("✓ Dictionaries werden gemerged (nicht verschachtelt)")
|
||||||
|
|
||||||
|
# Test 4: Liste bleibt Liste
|
||||||
|
print("\nTest 4: Liste bleibt Liste (overdefined_tunnel)")
|
||||||
|
ec4 = ErrorCollector()
|
||||||
|
ec4.add_errors({"overdefined_tunnel": ["TUNNEL1", "TUNNEL2"]})
|
||||||
|
assert isinstance(ec4.errors["overdefined_tunnel"], list)
|
||||||
|
assert ec4.errors["overdefined_tunnel"] == ["TUNNEL1", "TUNNEL2"]
|
||||||
|
print("✓ Liste bleibt Liste")
|
||||||
|
|
||||||
|
# Test 5: Liste Extend bei mehrmaligem Hinzufügen
|
||||||
|
print("\nTest 5: Liste Extend bei zweimaligem Hinzufügen")
|
||||||
|
ec5 = ErrorCollector()
|
||||||
|
ec5.add_errors({"tunnel_errors": ["T1", "T2"]})
|
||||||
|
ec5.add_errors({"tunnel_errors": ["T3", "T4"]})
|
||||||
|
assert isinstance(ec5.errors["tunnel_errors"], list)
|
||||||
|
assert ec5.errors["tunnel_errors"] == ["T1", "T2", "T3", "T4"]
|
||||||
|
print("✓ Listen werden extended (nicht verschachtelt)")
|
||||||
|
|
||||||
|
# Test 6: Warnungen mit Dictionary (missing_attributes)
|
||||||
|
print("\nTest 6: Warnungen mit Dictionary (missing_attributes)")
|
||||||
|
ec6 = ErrorCollector()
|
||||||
|
missing_attrs = {"BX0101": "Fehlendes Attribut A", "BX0102": "Fehlendes Attribut B"}
|
||||||
|
ec6.add_warnings({"missing_attributes": missing_attrs})
|
||||||
|
assert isinstance(ec6.warnings["missing_attributes"], dict)
|
||||||
|
assert ec6.warnings["missing_attributes"]["BX0101"] == "Fehlendes Attribut A"
|
||||||
|
print("✓ Warnung Dictionary bleibt Dictionary")
|
||||||
|
|
||||||
|
# Test 7: Warnungen Dictionary Merge
|
||||||
|
print("\nTest 7: Warnungen Dictionary Merge")
|
||||||
|
ec7 = ErrorCollector()
|
||||||
|
ec7.add_warnings({"missing_attributes": {"ID1": "Error 1"}})
|
||||||
|
ec7.add_warnings({"missing_attributes": {"ID2": "Error 2"}})
|
||||||
|
assert isinstance(ec7.warnings["missing_attributes"], dict)
|
||||||
|
assert ec7.warnings["missing_attributes"]["ID1"] == "Error 1"
|
||||||
|
assert ec7.warnings["missing_attributes"]["ID2"] == "Error 2"
|
||||||
|
print("✓ Warnungs-Dictionaries werden gemerged")
|
||||||
|
|
||||||
|
# Test 8: Mixed - mehrere Keys mit verschiedenen Typen
|
||||||
|
print("\nTest 8: Mixed - verschiedene Typen gleichzeitig")
|
||||||
|
ec8 = ErrorCollector()
|
||||||
|
ec8.add_errors({
|
||||||
|
"string_errors": "Error 1",
|
||||||
|
"dict_errors": {"ID1": [[1, 2]]},
|
||||||
|
"list_errors": ["Item1", "Item2"]
|
||||||
|
})
|
||||||
|
ec8.add_errors({
|
||||||
|
"string_errors": "Error 2",
|
||||||
|
"dict_errors": {"ID2": [[3, 4]]},
|
||||||
|
"list_errors": ["Item3"]
|
||||||
|
})
|
||||||
|
assert ec8.errors["string_errors"] == ["Error 1", "Error 2"]
|
||||||
|
assert ec8.errors["dict_errors"] == {"ID1": [[1, 2]], "ID2": [[3, 4]]}
|
||||||
|
assert ec8.errors["list_errors"] == ["Item1", "Item2", "Item3"]
|
||||||
|
print("✓ Verschiedene Typen werden korrekt behandelt")
|
||||||
|
|
||||||
|
# Test 9: Reales getpositions.py Szenario
|
||||||
|
print("\nTest 9: Reales getpositions.py Szenario")
|
||||||
|
ec9 = ErrorCollector()
|
||||||
|
# Wie in getpositions.py
|
||||||
|
double_ids = {}
|
||||||
|
double_ids["MA-1001"] = [[100, 200], [300, 400]]
|
||||||
|
double_ids["MA-1002"] = [[500, 600]]
|
||||||
|
ec9.add_errors({"double_ids": double_ids})
|
||||||
|
|
||||||
|
missing_attribs = {}
|
||||||
|
missing_attribs["BX0101"] = "Nur ein Block und/oder fehlende Attribute: dict_keys(['A', 'B'])"
|
||||||
|
ec9.add_warnings({"missing_attributes": missing_attribs})
|
||||||
|
|
||||||
|
assert isinstance(ec9.errors["double_ids"], dict)
|
||||||
|
assert isinstance(ec9.warnings["missing_attributes"], dict)
|
||||||
|
# Verifiziere dass es für .items() funktioniert
|
||||||
|
for id_name, positions in ec9.errors["double_ids"].items():
|
||||||
|
assert isinstance(id_name, str)
|
||||||
|
assert isinstance(positions, list)
|
||||||
|
print("✓ getpositions.py Format funktioniert für ioconverter.py")
|
||||||
|
|
||||||
|
# Test 10: Reales create_numbers.py Szenario
|
||||||
|
print("\nTest 10: Reales create_numbers.py Szenario")
|
||||||
|
ec10 = ErrorCollector()
|
||||||
|
# Mehrmals einzelne Strings mit gleichem Key
|
||||||
|
ec10.add_errors({"symbol_with_at_not_in_boundary": "Symbol at (10, 20) not in boundary"})
|
||||||
|
ec10.add_errors({"symbol_with_at_not_in_boundary": "Symbol at (30, 40) not in boundary"})
|
||||||
|
ec10.add_errors({"symbol_with_at_not_in_boundary": "Symbol at (50, 60) not in boundary"})
|
||||||
|
|
||||||
|
# Liste mit mehreren Elementen
|
||||||
|
ec10.add_errors({"no_symbols_found": [
|
||||||
|
"Renamer 'BG-1@@@' hat keine Symbole gefunden",
|
||||||
|
"Renamer 'BG-2@@@' hat keine Symbole gefunden"
|
||||||
|
]})
|
||||||
|
|
||||||
|
assert isinstance(ec10.errors["symbol_with_at_not_in_boundary"], list)
|
||||||
|
assert len(ec10.errors["symbol_with_at_not_in_boundary"]) == 3
|
||||||
|
assert isinstance(ec10.errors["no_symbols_found"], list)
|
||||||
|
assert len(ec10.errors["no_symbols_found"]) == 2
|
||||||
|
|
||||||
|
# Prüfe auf Verschachtelung
|
||||||
|
for item in ec10.errors["symbol_with_at_not_in_boundary"]:
|
||||||
|
assert isinstance(item, str), f"Verschachtelung gefunden: {type(item)}"
|
||||||
|
print("✓ create_numbers.py Format - keine Verschachtelung")
|
||||||
|
|
||||||
|
# Test 11: Realistisches Szenario mit vielen Fehleraufrufen (wie test_real_scenario.py)
|
||||||
|
print("\nTest 11: Realistisches Szenario mit vielen einzelnen Fehleraufrufen")
|
||||||
|
ec11 = ErrorCollector()
|
||||||
|
|
||||||
|
# Simuliere "no_symbols_found" Fehler als Liste
|
||||||
|
ec11.add_errors({
|
||||||
|
"no_symbols_found": [
|
||||||
|
"Renamer-Block-Gruppe 'BG-1@@@' (DIRECTION: LEFT_RIGHT) hat keine passenden Symbole gefunden. Gesucht auf Layer: ILS_Eingang, ILS_Ausgang. Renamer-Blöcke an Positionen: (300.0, 500.0), (3000.0, 2000.0)",
|
||||||
|
"Renamer-Block-Gruppe 'BG-2@@@' (DIRECTION: LEFT_RIGHT) hat keine passenden Symbole gefunden. Gesucht auf Layer: ILS_Eingang, ILS_Ausgang. Renamer-Blöcke an Positionen: (3200.0, 500.0)"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Simuliere viele einzelne "symbol_with_at_not_in_boundary" Fehler
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (95.5, 509.5) mit IO='BG-1@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (2895.5, 2159.5) mit IO='BG-1@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (4195.5, 2209.5) mit IO='BG-1@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (5495.5, 2109.5) mit IO='BG-1@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (2895.5, 2609.5) mit IO='MB-1@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (4195.5, 2659.5) mit IO='MB-1@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (5495.5, 2559.5) mit IO='MB-1@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (3095.5, 509.5) mit IO='BG-2@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (4395.5, 509.5) mit IO='BG-2@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
ec11.add_errors({"symbol_with_at_not_in_boundary": "Symbol an Position (5695.5, 509.5) mit IO='BG-2@@' auf Layer 'ILS_MOTOR' enthält '@' im IO-Attribut, liegt aber in keinem Renamer-Bereich."})
|
||||||
|
|
||||||
|
# Verifiziere Struktur
|
||||||
|
assert isinstance(ec11.errors["symbol_with_at_not_in_boundary"], list)
|
||||||
|
assert len(ec11.errors["symbol_with_at_not_in_boundary"]) == 10
|
||||||
|
assert isinstance(ec11.errors["no_symbols_found"], list)
|
||||||
|
assert len(ec11.errors["no_symbols_found"]) == 2
|
||||||
|
|
||||||
|
# Überprüfe, dass KEINE Verschachtelung vorliegt
|
||||||
|
symbol_errors = ec11.errors["symbol_with_at_not_in_boundary"]
|
||||||
|
all_strings = all(isinstance(item, str) for item in symbol_errors)
|
||||||
|
no_nested_lists = not any(isinstance(item, list) for item in symbol_errors)
|
||||||
|
assert all_strings, "Nicht alle Einträge sind Strings!"
|
||||||
|
assert no_nested_lists, "Verschachtelte Listen gefunden!"
|
||||||
|
|
||||||
|
print("✓ Realistisches Szenario mit 10 einzelnen Fehleraufrufen - keine Verschachtelung")
|
||||||
|
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("✓✓✓ ALLE TESTS BESTANDEN ✓✓✓")
|
||||||
|
print("="*70)
|
||||||
|
print("\nZusammenfassung:")
|
||||||
|
print(" - Strings → Listen (flach, keine Verschachtelung)")
|
||||||
|
print(" - Dictionaries → Dictionaries (gemerged bei mehrmaligem Hinzufügen)")
|
||||||
|
print(" - Listen → Listen (extended bei mehrmaligem Hinzufügen)")
|
||||||
|
print(" - Mixed Types werden korrekt behandelt")
|
||||||
|
print(" - Kompatibel mit getpositions.py und create_numbers.py")
|
||||||
|
print(" - Kompatibel mit ioconverter.py Erwartungen")
|
||||||
|
print(" - Realistisches Szenario mit vielen einzelnen Aufrufen funktioniert")
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Test-Skript zum Generieren der Flaggen-Icons.
|
||||||
|
Erstellt Icons für verschiedene Sprachen basierend auf dxfCs.png.
|
||||||
|
"""
|
||||||
|
import flags
|
||||||
|
|
||||||
|
# Generiere alle Flaggen-Icons
|
||||||
|
icons = []
|
||||||
|
icons.append(flags.add_flag(flags.flag_en, "dxfen"))
|
||||||
|
icons.append(flags.add_flag(flags.flag_fr, "dxffr"))
|
||||||
|
icons.append(flags.add_flag(flags.flag_it, "dxfit"))
|
||||||
|
icons.append(flags.add_flag(flags.flag_es, "dxfes"))
|
||||||
|
|
||||||
|
print("Generierte Icons:")
|
||||||
|
for icon in icons:
|
||||||
|
print(f" - {icon}")
|
||||||
|
print(f"\nAlle Icons wurden in {flags.ICON_DIR} gespeichert.")
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Test für die check_rack_z_coordinates Funktion
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Füge lib zum Path hinzu
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
|
||||||
|
from getpositions import check_rack_z_coordinates
|
||||||
|
from errorhandler import ErrorCollector
|
||||||
|
|
||||||
|
def test_no_deviation():
|
||||||
|
"""Test mit Racks ohne starke Abweichung (< 2000mm)"""
|
||||||
|
print("Test 1: Keine starke Abweichung (< 2000mm)")
|
||||||
|
|
||||||
|
racks = {
|
||||||
|
'Rack_1': [[100.0, 200.0, 0.0], [150.0, 200.0, 0.0]],
|
||||||
|
'Rack_2': [[200.0, 300.0, 500.0], [250.0, 300.0, 500.0]],
|
||||||
|
'Rack_3': [[300.0, 400.0, 1000.0], [350.0, 400.0, 1000.0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
error_collector = ErrorCollector()
|
||||||
|
check_rack_z_coordinates(racks, error_collector)
|
||||||
|
|
||||||
|
warnings = error_collector.warnings
|
||||||
|
if warnings:
|
||||||
|
print(f" FEHLER: Warnung wurde ausgegeben, obwohl keine erwartet: {warnings}")
|
||||||
|
else:
|
||||||
|
print(" OK: Keine Warnung (erwartet)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def test_with_deviation():
|
||||||
|
"""Test mit Racks mit starker Abweichung (> 2000mm)"""
|
||||||
|
print("Test 2: Starke Abweichung (> 2000mm)")
|
||||||
|
|
||||||
|
racks = {
|
||||||
|
'Rack_1': [[100.0, 200.0, 0.0], [150.0, 200.0, 0.0]],
|
||||||
|
'Rack_2': [[200.0, 300.0, 50000.0], [250.0, 300.0, 50000.0]], # 50m Höhe!
|
||||||
|
'Rack_3': [[300.0, 400.0, 100.0], [350.0, 400.0, 100.0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
error_collector = ErrorCollector()
|
||||||
|
check_rack_z_coordinates(racks, error_collector)
|
||||||
|
|
||||||
|
warnings = error_collector.warnings
|
||||||
|
if warnings:
|
||||||
|
print(f" OK: Warnung wurde ausgegeben (erwartet): {warnings}")
|
||||||
|
else:
|
||||||
|
print(" FEHLER: Keine Warnung, aber eine war erwartet!")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def test_exactly_2000mm():
|
||||||
|
"""Test mit genau 2000mm Abweichung (Grenzfall)"""
|
||||||
|
print("Test 3: Genau 2000mm Abweichung (Grenzfall)")
|
||||||
|
|
||||||
|
racks = {
|
||||||
|
'Rack_1': [[100.0, 200.0, 0.0], [150.0, 200.0, 0.0]],
|
||||||
|
'Rack_2': [[200.0, 300.0, 2000.0], [250.0, 300.0, 2000.0]],
|
||||||
|
}
|
||||||
|
|
||||||
|
error_collector = ErrorCollector()
|
||||||
|
check_rack_z_coordinates(racks, error_collector)
|
||||||
|
|
||||||
|
warnings = error_collector.warnings
|
||||||
|
if warnings:
|
||||||
|
print(f" Warnung wurde ausgegeben: {warnings}")
|
||||||
|
else:
|
||||||
|
print(" Keine Warnung (Grenzfall, hängt von > oder >= ab)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def test_dict_format():
|
||||||
|
"""Test mit Dictionary-Format für Koordinaten"""
|
||||||
|
print("Test 4: Dictionary-Format für Koordinaten")
|
||||||
|
|
||||||
|
racks = {
|
||||||
|
'Rack_1': [{'x': 100.0, 'y': 200.0, 'z': 0.0}, {'x': 150.0, 'y': 200.0, 'z': 0.0}],
|
||||||
|
'Rack_2': [{'x': 200.0, 'y': 300.0, 'z': 50000.0}, {'x': 250.0, 'y': 300.0, 'z': 50000.0}],
|
||||||
|
}
|
||||||
|
|
||||||
|
error_collector = ErrorCollector()
|
||||||
|
check_rack_z_coordinates(racks, error_collector)
|
||||||
|
|
||||||
|
warnings = error_collector.warnings
|
||||||
|
if warnings:
|
||||||
|
print(f" OK: Warnung wurde ausgegeben (erwartet): {warnings}")
|
||||||
|
else:
|
||||||
|
print(" FEHLER: Keine Warnung, aber eine war erwartet!")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("="*80)
|
||||||
|
print("Tests für check_rack_z_coordinates")
|
||||||
|
print("="*80)
|
||||||
|
print()
|
||||||
|
|
||||||
|
test_no_deviation()
|
||||||
|
test_with_deviation()
|
||||||
|
test_exactly_2000mm()
|
||||||
|
test_dict_format()
|
||||||
|
|
||||||
|
print("="*80)
|
||||||
|
print("Tests abgeschlossen")
|
||||||
|
print("="*80)
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for ezdxf helper functions in utils.py
|
||||||
|
|
||||||
|
Tests cover:
|
||||||
|
- Attribute extraction from INSERT blocks
|
||||||
|
- Layer creation and management
|
||||||
|
- Geometry creation (rectangles, text)
|
||||||
|
- Block reference operations
|
||||||
|
- Entity querying
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import ezdxf
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Add lib directory to path for imports
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent / 'lib'))
|
||||||
|
|
||||||
|
from utils import (
|
||||||
|
extract_insert_attributes,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def doc():
|
||||||
|
"""Create a new DXF document for testing"""
|
||||||
|
return ezdxf.new('R2010')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def msp(doc):
|
||||||
|
"""Get modelspace from document"""
|
||||||
|
return doc.modelspace()
|
||||||
|
|
||||||
|
|
||||||
|
class TestAttributeExtraction:
|
||||||
|
"""Tests for attribute extraction functions"""
|
||||||
|
|
||||||
|
def test_extract_insert_attributes_with_doc_simple(self, doc, msp):
|
||||||
|
"""Test extracting attributes from a simple block with direct attributes"""
|
||||||
|
# Create block with attributes
|
||||||
|
block = doc.blocks.new('TEST_BLOCK')
|
||||||
|
block.add_attdef('IO', (0, 0), dxfattribs={'height': 2.5})
|
||||||
|
block.add_attdef('KENNZEICHNUNG', (0, 5), dxfattribs={'height': 2.5})
|
||||||
|
|
||||||
|
# Insert block and set attributes
|
||||||
|
insert = msp.add_blockref('TEST_BLOCK', (10, 10))
|
||||||
|
insert.add_auto_attribs({'IO': 'MA0001', 'KENNZEICHNUNG': '=A01+UH00'})
|
||||||
|
|
||||||
|
# Extract attributes
|
||||||
|
attrs = extract_insert_attributes_with_doc(doc, insert)
|
||||||
|
|
||||||
|
assert attrs['IO'] == 'MA0001'
|
||||||
|
assert attrs['KENNZEICHNUNG'] == '=A01+UH00'
|
||||||
|
|
||||||
|
def test_extract_insert_attributes_empty_block(self, doc, msp):
|
||||||
|
"""Test extracting attributes from block without attributes"""
|
||||||
|
block = doc.blocks.new('EMPTY_BLOCK')
|
||||||
|
insert = msp.add_blockref('EMPTY_BLOCK', (10, 10))
|
||||||
|
|
||||||
|
attrs = extract_insert_attributes_with_doc(doc, insert)
|
||||||
|
|
||||||
|
assert attrs == {}
|
||||||
|
|
||||||
|
def test_extract_insert_attributes_non_insert(self, doc, msp):
|
||||||
|
"""Test that non-INSERT entities return empty dict"""
|
||||||
|
line = msp.add_line((0, 0), (10, 10))
|
||||||
|
|
||||||
|
attrs = extract_insert_attributes_with_doc(doc, line)
|
||||||
|
|
||||||
|
assert attrs == {}
|
||||||
|
|
||||||
|
def test_extract_attributes_with_positions(self, doc, msp):
|
||||||
|
"""Test extracting attributes with position information"""
|
||||||
|
# Create block with attributes
|
||||||
|
block = doc.blocks.new('POS_TEST')
|
||||||
|
block.add_attdef('IO', (0, 0), dxfattribs={'height': 2.5})
|
||||||
|
block.add_attdef('NAME', (0, 5), dxfattribs={'height': 2.5})
|
||||||
|
|
||||||
|
# Insert blocks
|
||||||
|
insert1 = msp.add_blockref('POS_TEST', (100, 200))
|
||||||
|
insert1.add_auto_attribs({'IO': 'MA0001', 'NAME': 'Motor1'})
|
||||||
|
|
||||||
|
insert2 = msp.add_blockref('POS_TEST', (300, 400))
|
||||||
|
insert2.add_auto_attribs({'IO': 'MA0002', 'NAME': 'Motor2'})
|
||||||
|
|
||||||
|
# Extract with positions
|
||||||
|
all_inserts, all_positions = extract_attributes_with_positions(msp)
|
||||||
|
|
||||||
|
assert len(all_inserts) == 2
|
||||||
|
assert len(all_positions) == 2
|
||||||
|
assert all_inserts[0]['IO'] == 'MA0001'
|
||||||
|
assert all_inserts[1]['IO'] == 'MA0002'
|
||||||
|
|
||||||
|
|
||||||
|
class TestLayerOperations:
|
||||||
|
"""Tests for layer creation and management functions"""
|
||||||
|
|
||||||
|
def test_ensure_layer_exists_new_layer(self, doc):
|
||||||
|
"""Test creating a new layer"""
|
||||||
|
layer = ensure_layer_exists(doc, 'TEST_LAYER', color=5)
|
||||||
|
|
||||||
|
assert 'TEST_LAYER' in doc.layers
|
||||||
|
assert doc.layers.get('TEST_LAYER').color == 5
|
||||||
|
|
||||||
|
def test_ensure_layer_exists_existing_layer(self, doc):
|
||||||
|
"""Test that existing layer is returned unchanged"""
|
||||||
|
# Create layer first
|
||||||
|
doc.layers.add('EXISTING', color=1)
|
||||||
|
|
||||||
|
# Ensure it exists (should not error)
|
||||||
|
layer = ensure_layer_exists(doc, 'EXISTING', color=3)
|
||||||
|
|
||||||
|
assert 'EXISTING' in doc.layers
|
||||||
|
# Color should be updated
|
||||||
|
assert doc.layers.get('EXISTING').color == 3
|
||||||
|
|
||||||
|
def test_ensure_layer_with_all_options(self, doc):
|
||||||
|
"""Test creating layer with all options"""
|
||||||
|
layer = ensure_layer_exists(
|
||||||
|
doc, 'FULL_LAYER',
|
||||||
|
color=5,
|
||||||
|
linetype='DASHED',
|
||||||
|
lineweight=25,
|
||||||
|
description='Test layer description'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert 'FULL_LAYER' in doc.layers
|
||||||
|
layer_obj = doc.layers.get('FULL_LAYER')
|
||||||
|
assert layer_obj.color == 5
|
||||||
|
assert layer_obj.dxf.linetype == 'DASHED'
|
||||||
|
assert layer_obj.dxf.lineweight == 25
|
||||||
|
|
||||||
|
def test_create_layers_from_config(self, doc):
|
||||||
|
"""Test creating multiple layers from configuration"""
|
||||||
|
layer_config = {
|
||||||
|
'ILS_MOTOR': {'color': 1, 'description': 'Motor layer'},
|
||||||
|
'ILS_SENSOR': {'color': 3, 'lineweight': 25},
|
||||||
|
'ILS_CABLE': {'color': 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
create_layers_from_config(doc, layer_config)
|
||||||
|
|
||||||
|
assert 'ILS_MOTOR' in doc.layers
|
||||||
|
assert 'ILS_SENSOR' in doc.layers
|
||||||
|
assert 'ILS_CABLE' in doc.layers
|
||||||
|
assert doc.layers.get('ILS_MOTOR').color == 1
|
||||||
|
assert doc.layers.get('ILS_SENSOR').color == 3
|
||||||
|
assert doc.layers.get('ILS_CABLE').color == 5
|
||||||
|
|
||||||
|
|
||||||
|
class TestGeometryCreation:
|
||||||
|
"""Tests for geometry creation functions"""
|
||||||
|
|
||||||
|
def test_add_rectangle_lwpolyline_simple(self, doc, msp):
|
||||||
|
"""Test adding a simple rectangle"""
|
||||||
|
rect = add_rectangle_lwpolyline(msp, 0, 0, 100, 50)
|
||||||
|
|
||||||
|
assert rect.dxftype() == 'LWPOLYLINE'
|
||||||
|
assert rect.closed is True
|
||||||
|
assert len(list(rect.get_points())) == 4
|
||||||
|
|
||||||
|
def test_add_rectangle_with_color(self, doc, msp):
|
||||||
|
"""Test adding rectangle with color"""
|
||||||
|
rect = add_rectangle_lwpolyline(msp, 10, 10, 200, 100, color=5)
|
||||||
|
|
||||||
|
assert rect.dxf.color == 5
|
||||||
|
|
||||||
|
def test_add_rectangle_with_rgb(self, doc, msp):
|
||||||
|
"""Test adding rectangle with RGB color"""
|
||||||
|
rect = add_rectangle_lwpolyline(msp, 20, 20, 150, 75, rgb=(255, 0, 0))
|
||||||
|
|
||||||
|
assert rect.rgb == (255, 0, 0)
|
||||||
|
|
||||||
|
def test_add_rectangle_on_layer(self, doc, msp):
|
||||||
|
"""Test adding rectangle on specific layer"""
|
||||||
|
ensure_layer_exists(doc, 'TEST_LAYER')
|
||||||
|
rect = add_rectangle_lwpolyline(msp, 0, 0, 100, 50, layer='TEST_LAYER')
|
||||||
|
|
||||||
|
assert rect.dxf.layer == 'TEST_LAYER'
|
||||||
|
|
||||||
|
def test_add_text_with_alignment_simple(self, doc, msp):
|
||||||
|
"""Test adding simple text"""
|
||||||
|
text = add_text_with_alignment(msp, "Test", 100, 100)
|
||||||
|
|
||||||
|
assert text.dxftype() == 'TEXT'
|
||||||
|
assert text.dxf.text == "Test"
|
||||||
|
|
||||||
|
def test_add_text_with_rotation(self, doc, msp):
|
||||||
|
"""Test adding text with rotation"""
|
||||||
|
text = add_text_with_alignment(msp, "Rotated", 200, 200, rotation=45.0)
|
||||||
|
|
||||||
|
assert text.dxf.rotation == 45.0
|
||||||
|
|
||||||
|
def test_add_text_with_color_and_height(self, doc, msp):
|
||||||
|
"""Test adding text with color and height"""
|
||||||
|
text = add_text_with_alignment(msp, "Styled", 300, 300, height=75, color=3)
|
||||||
|
|
||||||
|
assert text.dxf.height == 75
|
||||||
|
assert text.dxf.color == 3
|
||||||
|
|
||||||
|
|
||||||
|
class TestBlockOperations:
|
||||||
|
"""Tests for block reference operations"""
|
||||||
|
|
||||||
|
def test_add_blockref_without_attributes(self, doc, msp):
|
||||||
|
"""Test adding block reference without attributes"""
|
||||||
|
# Create a simple block
|
||||||
|
block = doc.blocks.new('SIMPLE_BLOCK')
|
||||||
|
block.add_line((0, 0), (10, 10))
|
||||||
|
|
||||||
|
# Add block reference
|
||||||
|
insert = add_blockref_with_attributes(msp, 'SIMPLE_BLOCK', (100, 100))
|
||||||
|
|
||||||
|
assert insert.dxftype() == 'INSERT'
|
||||||
|
assert insert.dxf.name == 'SIMPLE_BLOCK'
|
||||||
|
|
||||||
|
def test_add_blockref_with_attributes(self, doc, msp):
|
||||||
|
"""Test adding block reference with attributes"""
|
||||||
|
# Create block with attribute definitions
|
||||||
|
block = doc.blocks.new('ATTR_BLOCK')
|
||||||
|
block.add_attdef('IO', (0, 0))
|
||||||
|
block.add_attdef('NAME', (0, 5))
|
||||||
|
|
||||||
|
# Add block reference with attributes
|
||||||
|
attrs = {'IO': 'MA0001', 'NAME': 'Motor'}
|
||||||
|
insert = add_blockref_with_attributes(
|
||||||
|
msp, 'ATTR_BLOCK', (200, 200), attributes=attrs
|
||||||
|
)
|
||||||
|
|
||||||
|
assert insert.dxf.name == 'ATTR_BLOCK'
|
||||||
|
# Check attributes were set
|
||||||
|
attrib_values = {a.dxf.tag: a.dxf.text for a in insert.attribs}
|
||||||
|
assert attrib_values['IO'] == 'MA0001'
|
||||||
|
assert attrib_values['NAME'] == 'Motor'
|
||||||
|
|
||||||
|
def test_add_blockref_with_scale_and_rotation(self, doc, msp):
|
||||||
|
"""Test adding block reference with scale and rotation"""
|
||||||
|
block = doc.blocks.new('TRANSFORM_BLOCK')
|
||||||
|
block.add_line((0, 0), (10, 10))
|
||||||
|
|
||||||
|
insert = add_blockref_with_attributes(
|
||||||
|
msp, 'TRANSFORM_BLOCK', (300, 300),
|
||||||
|
scale=0.5, rotation=90.0
|
||||||
|
)
|
||||||
|
|
||||||
|
assert insert.dxf.xscale == 0.5
|
||||||
|
assert insert.dxf.yscale == 0.5
|
||||||
|
assert insert.dxf.rotation == 90.0
|
||||||
|
|
||||||
|
|
||||||
|
class TestEntityQuerying:
|
||||||
|
"""Tests for entity querying functions"""
|
||||||
|
|
||||||
|
def test_query_entities_by_layer_single_layer(self, doc, msp):
|
||||||
|
"""Test querying entities on a single layer"""
|
||||||
|
ensure_layer_exists(doc, 'MOTOR')
|
||||||
|
ensure_layer_exists(doc, 'SENSOR')
|
||||||
|
|
||||||
|
# Add entities on different layers
|
||||||
|
msp.add_line((0, 0), (10, 10), dxfattribs={'layer': 'MOTOR'})
|
||||||
|
msp.add_line((0, 0), (20, 20), dxfattribs={'layer': 'SENSOR'})
|
||||||
|
msp.add_line((0, 0), (30, 30), dxfattribs={'layer': 'MOTOR'})
|
||||||
|
|
||||||
|
# Query only MOTOR layer
|
||||||
|
motor_entities = query_entities_by_layer(msp, 'LINE', layers=['MOTOR'])
|
||||||
|
|
||||||
|
assert len(motor_entities) == 2
|
||||||
|
assert all(e.dxf.layer == 'MOTOR' for e in motor_entities)
|
||||||
|
|
||||||
|
def test_query_entities_by_type(self, doc, msp):
|
||||||
|
"""Test querying entities by type"""
|
||||||
|
msp.add_line((0, 0), (10, 10))
|
||||||
|
msp.add_circle((0, 0), 5)
|
||||||
|
msp.add_line((0, 0), (20, 20))
|
||||||
|
|
||||||
|
lines = query_entities_by_layer(msp, 'LINE')
|
||||||
|
|
||||||
|
assert len(lines) == 2
|
||||||
|
assert all(e.dxftype() == 'LINE' for e in lines)
|
||||||
|
|
||||||
|
def test_query_entities_exclude_layers(self, doc, msp):
|
||||||
|
"""Test querying entities excluding certain layers"""
|
||||||
|
msp.add_line((0, 0), (10, 10), dxfattribs={'layer': '0'})
|
||||||
|
msp.add_line((0, 0), (20, 20), dxfattribs={'layer': 'ILS'})
|
||||||
|
msp.add_line((0, 0), (30, 30), dxfattribs={'layer': '0'})
|
||||||
|
|
||||||
|
# Exclude layer '0'
|
||||||
|
entities = query_entities_by_layer(msp, 'LINE', exclude_layers=['0'])
|
||||||
|
|
||||||
|
assert len(entities) == 1
|
||||||
|
assert entities[0].dxf.layer == 'ILS'
|
||||||
|
|
||||||
|
def test_query_entities_multiple_layers(self, doc, msp):
|
||||||
|
"""Test querying entities from multiple layers"""
|
||||||
|
msp.add_line((0, 0), (10, 10), dxfattribs={'layer': 'L1'})
|
||||||
|
msp.add_line((0, 0), (20, 20), dxfattribs={'layer': 'L2'})
|
||||||
|
msp.add_line((0, 0), (30, 30), dxfattribs={'layer': 'L3'})
|
||||||
|
|
||||||
|
entities = query_entities_by_layer(msp, 'LINE', layers=['L1', 'L2'])
|
||||||
|
|
||||||
|
assert len(entities) == 2
|
||||||
|
assert set(e.dxf.layer for e in entities) == {'L1', 'L2'}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__, '-v'])
|
||||||
Reference in New Issue
Block a user