utils enthält jetzt generelle Routinen der verschiedenen Programme. create_example refactored. utils wird jetzt entsprechend überall importiert
This commit is contained in:
+58
-20
@@ -635,18 +635,17 @@ class TestDataGenerator:
|
||||
|
||||
return offset_map.get(insert_point, (0, 0)) # Default: left_top_corner
|
||||
|
||||
def _generate_ma_group(self, group, ma_defaults, symbol_width, symbol_height, spacing_factor, y_offsets_list, insert_point='left_top_corner'):
|
||||
def _generate_symbol_group(self, group, symbol_defaults, symbol_type='MA', spacing_factor=1.2, y_offsets_list=[0, -50, 50], insert_point='left_bottom_corner'):
|
||||
"""
|
||||
Generiert eine Gruppe von MA-Symbolen
|
||||
Generiert eine Gruppe von Symbolen (MA, BG, MB, POT, etc.)
|
||||
|
||||
Args:
|
||||
group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing)
|
||||
ma_defaults: Dict mit MA-Default-Attributen
|
||||
symbol_width: Feste Breite des Symbols
|
||||
symbol_height: Feste Höhe des Symbols
|
||||
symbol_defaults: Dict mit Symbol-Default-Attributen (ma_defaults, bg_defaults, etc.)
|
||||
symbol_type: Typ des Symbols ('MA', 'BG', 'MB', 'POT') für BEZEICHNUNG
|
||||
spacing_factor: Spacing-Faktor zwischen Symbolen
|
||||
y_offsets_list: Liste von Y-Offsets für horizontal_offset Layout
|
||||
insert_point: Einfügepunkt des Blocks (z.B. "left_top_corner", "center")
|
||||
insert_point: Einfügepunkt des Blocks (z.B. "left_bottom_corner", "center")
|
||||
"""
|
||||
name = group['name']
|
||||
count = group['count']
|
||||
@@ -657,19 +656,33 @@ class TestDataGenerator:
|
||||
# Extrahiere IO-Pattern aus Name (z.B. "MA-1@@_top" -> "MA-1@@")
|
||||
io_pattern = name.split('_')[0]
|
||||
|
||||
# Hole Dimensionen aus defaults
|
||||
dimensions = symbol_defaults.get('dimensions', {})
|
||||
symbol_width = dimensions.get('width', 1210)
|
||||
symbol_height = dimensions.get('height', 381)
|
||||
|
||||
# Berechne Spacing
|
||||
if layout_type in ['horizontal', 'horizontal_offset']:
|
||||
actual_spacing = symbol_width * spacing_factor
|
||||
else:
|
||||
actual_spacing = 0
|
||||
|
||||
# Hole Attribute aus ma_defaults
|
||||
attributes = ma_defaults.get('attributes', {})
|
||||
layer = ma_defaults.get('layer', 'ILS_MOTOR')
|
||||
# Hole Attribute und Block-Info aus defaults
|
||||
attributes = symbol_defaults.get('attributes', {})
|
||||
layer = symbol_defaults.get('layer', 'ILS_MOTOR')
|
||||
block_name = symbol_defaults.get('block_name', 'io')
|
||||
|
||||
# Berechne Offset basierend auf Einfügepunkt
|
||||
offset_x, offset_y = self._calculate_insert_offset(insert_point, symbol_width, symbol_height)
|
||||
|
||||
# Bestimme BEZEICHNUNG basierend auf Symbol-Typ
|
||||
bezeichnung_prefix = {
|
||||
'MA': 'Motor',
|
||||
'BG': 'Eingang',
|
||||
'MB': 'Ausgang',
|
||||
'POT': 'Erdung'
|
||||
}.get(symbol_type, 'Symbol')
|
||||
|
||||
# Generiere Symbole basierend auf Layout-Typ
|
||||
for i in range(count):
|
||||
if layout_type == 'single':
|
||||
@@ -692,7 +705,7 @@ class TestDataGenerator:
|
||||
# DXF-Block wird an der linken unteren Ecke eingefügt
|
||||
# (da der io-Block bei (0,0) startet und nach oben geht)
|
||||
blockref = self.msp.add_blockref(
|
||||
'io',
|
||||
block_name,
|
||||
insert=(left_top_x, left_top_y - symbol_height),
|
||||
dxfattribs={'layer': layer}
|
||||
)
|
||||
@@ -700,7 +713,10 @@ class TestDataGenerator:
|
||||
# Setze Attribute (dynamisch IO und BEZEICHNUNG)
|
||||
attrib_values = attributes.copy()
|
||||
attrib_values['IO'] = io_pattern
|
||||
attrib_values['BEZEICHNUNG'] = f"Motor {io_pattern}"
|
||||
|
||||
# Setze BEZEICHNUNG nur wenn im Template vorhanden
|
||||
if 'BEZEICHNUNG' in attrib_values:
|
||||
attrib_values['BEZEICHNUNG'] = f"{bezeichnung_prefix} {io_pattern}"
|
||||
|
||||
blockref.add_auto_attribs(attrib_values)
|
||||
|
||||
@@ -724,26 +740,48 @@ class TestDataGenerator:
|
||||
scene_name: Name der Testszene in der Config
|
||||
"""
|
||||
scene = self.config['test_scenes'][scene_name]
|
||||
ma_defaults = self.config.get('ma_defaults', {})
|
||||
general = self.config.get('general', {})
|
||||
|
||||
# Hole Dimensions aus Config (feste Werte)
|
||||
dimensions = ma_defaults.get('dimensions', {})
|
||||
symbol_width = dimensions.get('width', 1210)
|
||||
symbol_height = dimensions.get('height', 381)
|
||||
|
||||
# Hole Layout-Einstellungen
|
||||
layout = general.get('layout', {})
|
||||
spacing_factor = layout.get('symbol_spacing_factor', 1.2)
|
||||
y_offsets_list = layout.get('horizontal_offset_y_offsets', [0, -50, 50])
|
||||
|
||||
# Hole Einfügepunkt aus Config (default: left_top_corner)
|
||||
# Hole Einfügepunkt aus Config (default: left_bottom_corner)
|
||||
ma_frame = general.get('ma_frame', {})
|
||||
insert_point = ma_frame.get('insert_point', 'left_top_corner')
|
||||
insert_point = ma_frame.get('insert_point', 'left_bottom_corner')
|
||||
|
||||
# Generiere MA-Gruppen aus Config
|
||||
ma_defaults = self.config.get('ma_defaults', {})
|
||||
for group in scene.get('ma_groups', []):
|
||||
self._generate_ma_group(group, ma_defaults, symbol_width, symbol_height, spacing_factor, y_offsets_list, insert_point)
|
||||
self._generate_symbol_group(group, ma_defaults, symbol_type='MA',
|
||||
spacing_factor=spacing_factor,
|
||||
y_offsets_list=y_offsets_list,
|
||||
insert_point=insert_point)
|
||||
|
||||
# Generiere BG-Gruppen aus Config (Eingänge)
|
||||
bg_defaults = self.config.get('bg_defaults', {})
|
||||
for group in scene.get('bg_groups', []):
|
||||
self._generate_symbol_group(group, bg_defaults, symbol_type='BG',
|
||||
spacing_factor=spacing_factor,
|
||||
y_offsets_list=y_offsets_list,
|
||||
insert_point=insert_point)
|
||||
|
||||
# Generiere MB-Gruppen aus Config (Ausgänge)
|
||||
mb_defaults = self.config.get('mb_defaults', {})
|
||||
for group in scene.get('mb_groups', []):
|
||||
self._generate_symbol_group(group, mb_defaults, symbol_type='MB',
|
||||
spacing_factor=spacing_factor,
|
||||
y_offsets_list=y_offsets_list,
|
||||
insert_point=insert_point)
|
||||
|
||||
# Generiere POT-Gruppen aus Config (Erdungspunkte)
|
||||
pot_defaults = self.config.get('pot_defaults', {})
|
||||
for group in scene.get('pot_groups', []):
|
||||
self._generate_symbol_group(group, pot_defaults, symbol_type='POT',
|
||||
spacing_factor=spacing_factor,
|
||||
y_offsets_list=y_offsets_list,
|
||||
insert_point=insert_point)
|
||||
|
||||
# Generiere Renamer-Rahmen aus Config
|
||||
for frame_config in scene.get('renaming_frames', []):
|
||||
|
||||
+306
-135
@@ -3,7 +3,10 @@ from pathlib import Path
|
||||
|
||||
|
||||
from error_collector import ErrorCollector, write_json_file
|
||||
from utils import check_environment_var, check_file_in_work, dxf_is_binary, get_dxf_file
|
||||
from utils import (
|
||||
check_environment_var, check_file_in_work, dxf_is_binary, get_dxf_file,
|
||||
extract_insert_attributes_with_doc as extract_block_attributes
|
||||
)
|
||||
from symbol_frames import draw_symbol_frames
|
||||
|
||||
|
||||
@@ -80,56 +83,6 @@ def read_config_layers(config_path: Path) -> list:
|
||||
return layers
|
||||
|
||||
|
||||
def extract_block_attributes(doc, insert, error_collector=None) -> dict:
|
||||
"""
|
||||
Extrahiert alle Attribute aus einem INSERT-Block.
|
||||
Unterstützt zweistufige Blockstruktur:
|
||||
- Äußerer Block enthält LWPOLYLINE und INSERT zu Attribut-Block
|
||||
- Innerer Attribut-Block enthält die eigentlichen Attribute
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
insert: INSERT-Entity des äußeren Blocks
|
||||
error_collector: Optional ErrorCollector für Warnungen
|
||||
|
||||
Returns:
|
||||
Dictionary mit allen gefundenen Attributen
|
||||
"""
|
||||
attributes = {}
|
||||
if insert.dxftype() != 'INSERT':
|
||||
return attributes
|
||||
|
||||
# Prüfe zuerst ob der Block direkt Attribute hat (alte Struktur)
|
||||
# Sammle alle direkten Attribute
|
||||
direct_attribs = list(insert.attribs)
|
||||
if direct_attribs:
|
||||
for attrib in direct_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
return attributes
|
||||
|
||||
# Neue zweistufige Struktur: Suche im Block nach einem INSERT mit Attributen
|
||||
try:
|
||||
block_layout = doc.blocks.get(insert.dxf.name)
|
||||
for entity in block_layout:
|
||||
if entity.dxftype() == 'INSERT':
|
||||
inner_attribs = list(entity.attribs)
|
||||
if inner_attribs:
|
||||
# Gefunden: innerer Block mit Attributen
|
||||
for attrib in inner_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
break # Nur das erste INSERT mit Attributen verwenden
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Extrahieren der Attribute aus Block '{insert.dxf.name}': {e}"
|
||||
if error_collector:
|
||||
error_collector.add_warnings({"attribute_extraction_error": error_msg})
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def is_rectangle_polyline(points):
|
||||
"""
|
||||
Prüft, ob eine Liste von Punkten ein Rechteck darstellt.
|
||||
@@ -374,6 +327,26 @@ def enumerate_symbols(symbols, attributes):
|
||||
return renamed
|
||||
|
||||
|
||||
def get_layer_names_from_attributes(attributes):
|
||||
"""
|
||||
Extrahiert alle LAYER_NAME Attribute aus einem Renamer-Block.
|
||||
|
||||
Args:
|
||||
attributes: Dictionary mit Block-Attributen
|
||||
|
||||
Returns:
|
||||
Liste der Layer-Namen (z.B. ['ILS_Eingang', 'ILS_Ausgang'])
|
||||
"""
|
||||
layer_names = []
|
||||
if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]:
|
||||
layer_names.append(attributes["LAYER_NAME"])
|
||||
for i in range(1, 4):
|
||||
layer_key = f"LAYER_NAME{i}"
|
||||
if layer_key in attributes and attributes[layer_key]:
|
||||
layer_names.append(attributes[layer_key])
|
||||
return layer_names
|
||||
|
||||
|
||||
def get_renamer_config_key(attributes):
|
||||
"""
|
||||
Erstellt einen eindeutigen Schlüssel für die Renamer-Block-Konfiguration.
|
||||
@@ -516,11 +489,153 @@ def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
|
||||
|
||||
return renamer_groups
|
||||
|
||||
|
||||
def find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector):
|
||||
"""
|
||||
Findet alle Symbole mit '@' die geometrisch in den Renamer-Bereichen liegen,
|
||||
unabhängig vom Layer.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
msp: Modelspace
|
||||
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
|
||||
error_collector: ErrorCollector für Fehlerbehandlung
|
||||
|
||||
Returns:
|
||||
Liste von Dictionaries mit 'layer', 'position', 'io' für jedes gefundene Symbol
|
||||
"""
|
||||
symbols_in_geometry = []
|
||||
for insert, boundary, attributes, is_rectangle in group_blocks:
|
||||
for entity in msp.query('INSERT'):
|
||||
symbol_attribs = extract_block_attributes(doc, entity, error_collector)
|
||||
if not symbol_attribs:
|
||||
continue
|
||||
|
||||
# Prüfe ob irgendein Attribut "@" enthält
|
||||
has_template = any('@' in str(value) for value in symbol_attribs.values())
|
||||
|
||||
if has_template:
|
||||
pos = entity.dxf.insert
|
||||
point = (pos[0], pos[1])
|
||||
if point_in_polygon(point, boundary):
|
||||
symbols_in_geometry.append({
|
||||
'layer': entity.dxf.layer,
|
||||
'position': point,
|
||||
'io': symbol_attribs.get('IO', 'N/A')
|
||||
})
|
||||
return symbols_in_geometry
|
||||
|
||||
|
||||
def create_no_symbols_found_error_message(block_name, direction, layer_names, group_blocks,
|
||||
doc, msp, error_collector):
|
||||
"""
|
||||
Erstellt eine detaillierte Fehlermeldung wenn keine Symbole in einem Renamer-Bereich gefunden wurden.
|
||||
|
||||
Args:
|
||||
block_name: Name des Renamer-Blocks
|
||||
direction: DIRECTION-Attribut
|
||||
layer_names: Liste der erwarteten Layer-Namen
|
||||
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
|
||||
doc: DXF-Dokument
|
||||
msp: Modelspace
|
||||
error_collector: ErrorCollector für Fehlerbehandlung
|
||||
|
||||
Returns:
|
||||
Fehlermeldungs-String
|
||||
"""
|
||||
renamer_positions = [f"({insert.dxf.insert[0]:.1f}, {insert.dxf.insert[1]:.1f})"
|
||||
for insert, _, _, _ in group_blocks]
|
||||
|
||||
# Prüfe ob es Symbole mit @ gibt, die geometrisch im Bereich liegen (egal auf welchem Layer)
|
||||
symbols_in_geometry = find_template_symbols_in_geometry(doc, msp, group_blocks, error_collector)
|
||||
|
||||
# Erstelle Fehlermeldung basierend auf gefundenen Symbolen
|
||||
if symbols_in_geometry and not layer_names:
|
||||
# Es gibt Symbole im Bereich, aber keine LAYER_NAME Attribute definiert
|
||||
return (
|
||||
f"Renamer-Block-Gruppe '{block_name}' (DIRECTION: {direction}) "
|
||||
f"hat keine passenden Symbole gefunden. "
|
||||
f"Es wurden {len(symbols_in_geometry)} Symbol(e) mit '@' geometrisch im Bereich gefunden, "
|
||||
f"aber der Renamer-Block hat KEINE LAYER_NAME Attribute (LAYER_NAME1, LAYER_NAME2, LAYER_NAME3) definiert. "
|
||||
f"Ohne diese Attribute können Symbole nicht zugeordnet werden. "
|
||||
f"Renamer-Blöcke an Positionen: {', '.join(renamer_positions)}"
|
||||
)
|
||||
elif symbols_in_geometry and layer_names:
|
||||
# Es gibt Symbole im Bereich, aber sie sind auf falschen Layern
|
||||
found_layers = set(s['layer'] for s in symbols_in_geometry)
|
||||
return (
|
||||
f"Renamer-Block-Gruppe '{block_name}' (DIRECTION: {direction}) "
|
||||
f"hat keine passenden Symbole gefunden. "
|
||||
f"Es wurden {len(symbols_in_geometry)} Symbol(e) mit '@' geometrisch im Bereich gefunden, "
|
||||
f"aber diese sind auf Layer: {', '.join(found_layers)}. "
|
||||
f"Erwartet werden Layer: {', '.join(layer_names)}. "
|
||||
f"Prüfen Sie die LAYER_NAME Attribute im Renamer-Block. "
|
||||
f"Renamer-Blöcke an Positionen: {', '.join(renamer_positions)}"
|
||||
)
|
||||
else:
|
||||
# Keine Symbole im Bereich gefunden (weder geometrisch noch auf richtigen Layern)
|
||||
return (
|
||||
f"Renamer-Block-Gruppe '{block_name}' (DIRECTION: {direction}) "
|
||||
f"hat keine passenden Symbole gefunden. "
|
||||
f"Gesucht auf Layer: {', '.join(layer_names) if layer_names else 'N/A'}. "
|
||||
f"Renamer-Blöcke an Positionen: {', '.join(renamer_positions)}"
|
||||
)
|
||||
|
||||
|
||||
def collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, error_collector):
|
||||
"""
|
||||
Sammelt alle Symbole aus allen Boundaries einer Renamer-Gruppe.
|
||||
Prüft auf Duplikate (Symbole in mehreren Bereichen).
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
msp: Modelspace
|
||||
renamer_layers: Layer auf denen nach Renamer-Blöcken gesucht wird
|
||||
group_blocks: Liste von (insert, boundary, attributes, is_rectangle) Tupeln
|
||||
block_name: Name des Renamer-Blocks (für Fehlermeldungen)
|
||||
error_collector: ErrorCollector für Fehlerbehandlung
|
||||
|
||||
Returns:
|
||||
Liste aller eindeutigen Symbole
|
||||
"""
|
||||
all_symbols = []
|
||||
seen_entities = {} # {entity_id: (symbol, [list of renamer block positions])}
|
||||
|
||||
for insert, boundary, attributes, is_rectangle in group_blocks:
|
||||
# Finde Symbole innerhalb dieses Bereichs
|
||||
symbols = find_symbols_in_boundary(doc, msp, boundary, renamer_layers, attributes, error_collector)
|
||||
|
||||
# Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
|
||||
for symbol in symbols:
|
||||
entity_id = id(symbol['entity']) # Eindeutige ID des Entity-Objekts
|
||||
renamer_pos = insert.dxf.insert
|
||||
|
||||
if entity_id not in seen_entities:
|
||||
seen_entities[entity_id] = (symbol, [renamer_pos])
|
||||
all_symbols.append(symbol)
|
||||
else:
|
||||
# Symbol wurde bereits in einem anderen Bereich gefunden - Fehler
|
||||
existing_symbol, existing_positions = seen_entities[entity_id]
|
||||
existing_positions.append(renamer_pos)
|
||||
symbol_pos = symbol['position']
|
||||
io_value = symbol['attributes'].get('IO', 'N/A')
|
||||
error_msg = (
|
||||
f"Symbol an Position ({symbol_pos[0]:.1f}, {symbol_pos[1]:.1f}) "
|
||||
f"mit IO='{io_value}' liegt in mehreren Renamer-Bereichen der Gruppe '{block_name}'. "
|
||||
f"Gefunden in Renamer-Blöcken an Positionen: "
|
||||
f"{', '.join([f'({p[0]:.1f}, {p[1]:.1f})' for p in existing_positions])}"
|
||||
)
|
||||
print(f" FEHLER: {error_msg}")
|
||||
error_collector.add_errors({"symbol_in_multiple_boundaries": error_msg})
|
||||
|
||||
return all_symbols
|
||||
|
||||
|
||||
def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector):
|
||||
"""
|
||||
Zweiter Pass: Verarbeitet jede Renamer-Block-Gruppe.
|
||||
Sammelt Symbole, sortiert sie und nummeriert sie.
|
||||
|
||||
|
||||
Returns:
|
||||
Liste aller umbenannten Symbole
|
||||
"""
|
||||
@@ -538,55 +653,15 @@ def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_colle
|
||||
print(f"\n Verarbeite Gruppe: {block_name} ({len(group_blocks)} Renamer-Blöcke)")
|
||||
|
||||
# Sammle Symbole aus ALLEN Boundaries dieser Gruppe
|
||||
all_symbols = []
|
||||
seen_entities = {} # {entity_id: (symbol, [list of renamer block positions])}
|
||||
|
||||
for insert, boundary, attributes, is_rectangle in group_blocks:
|
||||
# Finde Symbole innerhalb dieses Bereichs
|
||||
symbols = find_symbols_in_boundary(doc, msp, boundary, renamer_layers, attributes, error_collector)
|
||||
|
||||
# Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
|
||||
for symbol in symbols:
|
||||
entity_id = id(symbol['entity']) # Eindeutige ID des Entity-Objekts
|
||||
renamer_pos = insert.dxf.insert
|
||||
|
||||
if entity_id not in seen_entities:
|
||||
seen_entities[entity_id] = (symbol, [renamer_pos])
|
||||
all_symbols.append(symbol)
|
||||
else:
|
||||
# Symbol wurde bereits in einem anderen Bereich gefunden - Fehler
|
||||
existing_symbol, existing_positions = seen_entities[entity_id]
|
||||
existing_positions.append(renamer_pos)
|
||||
symbol_pos = symbol['position']
|
||||
io_value = symbol['attributes'].get('IO', 'N/A')
|
||||
error_msg = (
|
||||
f"Symbol an Position ({symbol_pos[0]:.1f}, {symbol_pos[1]:.1f}) "
|
||||
f"mit IO='{io_value}' liegt in mehreren Renamer-Bereichen der Gruppe '{block_name}'. "
|
||||
f"Gefunden in Renamer-Blöcken an Positionen: "
|
||||
f"{', '.join([f'({p[0]:.1f}, {p[1]:.1f})' for p in existing_positions])}"
|
||||
)
|
||||
print(f" FEHLER: {error_msg}")
|
||||
error_collector.add_errors({"symbol_in_multiple_boundaries": error_msg})
|
||||
all_symbols = collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, error_collector)
|
||||
|
||||
print(f" Gesamt {len(all_symbols)} eindeutige Template-Symbole aus {len(group_blocks)} Blöcken")
|
||||
|
||||
if not all_symbols:
|
||||
# Fehlermeldung: Keine Symbole gefunden
|
||||
layer_names = []
|
||||
for i in range(1, 4):
|
||||
layer_key = f"LAYER_NAME{i}"
|
||||
if layer_key in reference_attributes and reference_attributes[layer_key]:
|
||||
layer_names.append(reference_attributes[layer_key])
|
||||
if "LAYER_NAME" in reference_attributes and reference_attributes["LAYER_NAME"]:
|
||||
layer_names.append(reference_attributes["LAYER_NAME"])
|
||||
|
||||
renamer_positions = [f"({insert.dxf.insert[0]:.1f}, {insert.dxf.insert[1]:.1f})"
|
||||
for insert, _, _, _ in group_blocks]
|
||||
error_msg = (
|
||||
f"Renamer-Block-Gruppe '{block_name}' (DIRECTION: {direction}) "
|
||||
f"hat keine passenden Symbole gefunden. "
|
||||
f"Gesucht auf Layer: {', '.join(layer_names) if layer_names else 'N/A'}. "
|
||||
f"Renamer-Blöcke an Positionen: {', '.join(renamer_positions)}"
|
||||
layer_names = get_layer_names_from_attributes(reference_attributes)
|
||||
error_msg = create_no_symbols_found_error_message(
|
||||
block_name, direction, layer_names, group_blocks, doc, msp, error_collector
|
||||
)
|
||||
print(f" FEHLER: {error_msg}")
|
||||
error_collector.add_errors({"no_symbols_found": error_msg})
|
||||
@@ -603,27 +678,25 @@ def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_colle
|
||||
|
||||
return all_renamed
|
||||
|
||||
def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector):
|
||||
|
||||
def find_symbols_with_at_in_io(doc, msp, error_collector):
|
||||
"""
|
||||
Dritter Pass: Prüft ob alle Symbole mit "@" im IO in einem Renamer-Bereich liegen.
|
||||
Findet alle Symbole mit "@" im IO-Attribut.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
msp: Modelspace
|
||||
error_collector: ErrorCollector für Fehlerbehandlung
|
||||
|
||||
Returns:
|
||||
Liste von Dictionaries mit Symbol-Informationen
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Prüfe ob alle Symbole mit '@' im IO in einem Renamer-Bereich liegen")
|
||||
print("="*60)
|
||||
|
||||
# Sammle alle Renamer-Boundaries
|
||||
all_boundaries = []
|
||||
for group_blocks in renamer_groups.values():
|
||||
for insert, boundary, attributes, is_rectangle in group_blocks:
|
||||
all_boundaries.append((boundary, attributes))
|
||||
|
||||
# Finde alle Symbole mit "@" im IO-Attribut
|
||||
symbols_with_at = []
|
||||
for entity in msp.query('INSERT'):
|
||||
symbol_attribs = extract_block_attributes(doc, entity, error_collector)
|
||||
if not symbol_attribs:
|
||||
continue
|
||||
|
||||
|
||||
# Prüfe ob IO-Attribut "@" enthält
|
||||
io_value = symbol_attribs.get('IO', '')
|
||||
if '@' in str(io_value):
|
||||
@@ -636,32 +709,120 @@ def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector):
|
||||
'layer': entity.dxf.layer,
|
||||
'io': io_value
|
||||
})
|
||||
|
||||
return symbols_with_at
|
||||
|
||||
|
||||
def check_symbol_in_boundary(symbol, boundary, attributes, renamer_pos):
|
||||
"""
|
||||
Prüft ob ein Symbol geometrisch und vom Layer her in einem Renamer-Bereich liegt.
|
||||
|
||||
Args:
|
||||
symbol: Dictionary mit Symbol-Informationen
|
||||
boundary: Polygon-Punkte des Renamer-Bereichs
|
||||
attributes: Attribute des Renamer-Blocks
|
||||
renamer_pos: Position des Renamer-Blocks
|
||||
|
||||
Returns:
|
||||
Tuple (in_boundary, wrong_layer_info):
|
||||
- in_boundary: True wenn Symbol geometrisch und vom Layer her im Bereich liegt
|
||||
- wrong_layer_info: Dictionary mit Fehlerinformationen wenn Layer falsch ist, sonst None
|
||||
"""
|
||||
# Prüfe ob Symbol geometrisch innerhalb des Bereichs liegt
|
||||
if not point_in_polygon(symbol['position'], boundary):
|
||||
return False, None
|
||||
|
||||
# Symbol liegt geometrisch im Bereich - prüfe Layer
|
||||
search_layers = get_layer_names_from_attributes(attributes)
|
||||
|
||||
if symbol['layer'] in search_layers:
|
||||
# Layer passt auch - alles OK
|
||||
return True, None
|
||||
else:
|
||||
# Symbol liegt geometrisch im Bereich, aber Layer passt nicht
|
||||
block_name = attributes.get('NAME', attributes.get('NAME1', 'UNKNOWN'))
|
||||
wrong_layer_info = {
|
||||
'block_name': block_name,
|
||||
'renamer_pos': renamer_pos,
|
||||
'expected_layers': search_layers,
|
||||
'attributes': attributes
|
||||
}
|
||||
return False, wrong_layer_info
|
||||
|
||||
|
||||
def create_wrong_layer_warning(symbol, wrong_layer_info):
|
||||
"""
|
||||
Erstellt eine Warnung wenn ein Symbol im Bereich liegt, aber der Layer nicht passt.
|
||||
|
||||
Args:
|
||||
symbol: Dictionary mit Symbol-Informationen
|
||||
wrong_layer_info: Dictionary mit Fehlerinformationen
|
||||
|
||||
Returns:
|
||||
Warnmeldungs-String
|
||||
"""
|
||||
if wrong_layer_info['expected_layers']:
|
||||
return (
|
||||
f"Symbol an Position ({symbol['position'][0]:.1f}, {symbol['position'][1]:.1f}) "
|
||||
f"mit IO='{symbol['io']}' liegt geometrisch im Renamer-Bereich '{wrong_layer_info['block_name']}' "
|
||||
f"(Renamer-Position: ({wrong_layer_info['renamer_pos'][0]:.1f}, {wrong_layer_info['renamer_pos'][1]:.1f})), "
|
||||
f"aber das Symbol ist auf Layer '{symbol['layer']}', erwartet wird: {', '.join(wrong_layer_info['expected_layers'])}. "
|
||||
f"Prüfen Sie die LAYER_NAME Attribute im Renamer-Block."
|
||||
)
|
||||
else:
|
||||
return (
|
||||
f"Symbol an Position ({symbol['position'][0]:.1f}, {symbol['position'][1]:.1f}) "
|
||||
f"mit IO='{symbol['io']}' liegt geometrisch im Renamer-Bereich '{wrong_layer_info['block_name']}' "
|
||||
f"(Renamer-Position: ({wrong_layer_info['renamer_pos'][0]:.1f}, {wrong_layer_info['renamer_pos'][1]:.1f})), "
|
||||
f"aber der Renamer-Block hat KEINE LAYER_NAME Attribute definiert. "
|
||||
f"Symbole können nur gefunden werden, wenn LAYER_NAME1/LAYER_NAME2/LAYER_NAME3 Attribute gesetzt sind."
|
||||
)
|
||||
|
||||
|
||||
def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector):
|
||||
"""
|
||||
Dritter Pass: Prüft ob alle Symbole mit "@" im IO in einem Renamer-Bereich liegen.
|
||||
"""
|
||||
print("\n" + "="*60)
|
||||
print("Prüfe ob alle Symbole mit '@' im IO in einem Renamer-Bereich liegen")
|
||||
print("="*60)
|
||||
|
||||
# Sammle alle Renamer-Boundaries
|
||||
all_boundaries = []
|
||||
for group_blocks in renamer_groups.values():
|
||||
for insert, boundary, attributes, is_rectangle in group_blocks:
|
||||
all_boundaries.append((boundary, attributes, insert.dxf.insert))
|
||||
|
||||
# Finde alle Symbole mit "@" im IO-Attribut
|
||||
symbols_with_at = find_symbols_with_at_in_io(doc, msp, error_collector)
|
||||
|
||||
# Prüfe für jedes Symbol, ob es in mindestens einem Renamer-Bereich liegt
|
||||
symbols_not_in_any_boundary = []
|
||||
for symbol in symbols_with_at:
|
||||
in_any_boundary = False
|
||||
for boundary, attributes in all_boundaries:
|
||||
# Prüfe ob Symbol auf dem richtigen Layer ist
|
||||
search_layers = []
|
||||
for i in range(1, 4):
|
||||
layer_key = f"LAYER_NAME{i}"
|
||||
if layer_key in attributes and attributes[layer_key]:
|
||||
search_layers.append(attributes[layer_key])
|
||||
if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]:
|
||||
search_layers.append(attributes["LAYER_NAME"])
|
||||
|
||||
if symbol['layer'] not in search_layers:
|
||||
continue
|
||||
|
||||
# Prüfe ob Symbol innerhalb des Bereichs liegt
|
||||
if point_in_polygon(symbol['position'], boundary):
|
||||
wrong_layer_info = None
|
||||
|
||||
for boundary, attributes, renamer_pos in all_boundaries:
|
||||
is_in_boundary, layer_info = check_symbol_in_boundary(symbol, boundary, attributes, renamer_pos)
|
||||
|
||||
if is_in_boundary:
|
||||
# Layer passt auch - alles OK
|
||||
in_any_boundary = True
|
||||
break
|
||||
|
||||
elif layer_info is not None:
|
||||
# Symbol liegt geometrisch im Bereich, aber Layer passt nicht
|
||||
wrong_layer_info = layer_info
|
||||
|
||||
if not in_any_boundary:
|
||||
symbols_not_in_any_boundary.append(symbol)
|
||||
|
||||
if wrong_layer_info:
|
||||
# Warnung: Symbol liegt geometrisch im Bereich, aber Layer stimmt nicht
|
||||
warning_msg = create_wrong_layer_warning(symbol, wrong_layer_info)
|
||||
print(f" WARNUNG: {warning_msg}")
|
||||
error_collector.add_warnings({"symbol_wrong_layer": warning_msg})
|
||||
# NICHT als Fehler hinzufügen - Warnung reicht aus
|
||||
else:
|
||||
# Fehler: Symbol liegt weder geometrisch noch vom Layer her in einem Renamer-Bereich
|
||||
symbols_not_in_any_boundary.append(symbol)
|
||||
|
||||
# Melde Fehler für Symbole, die in keinem Bereich liegen
|
||||
if symbols_not_in_any_boundary:
|
||||
for symbol in symbols_not_in_any_boundary:
|
||||
@@ -768,7 +929,7 @@ if __name__ == '__main__':
|
||||
)
|
||||
parser.add_argument('-f', '--filename', action='store', required=True, help='DXF-Datei die verarbeitet werden soll', metavar='myfile.dxf')
|
||||
parser.add_argument('-e', '--errorfile', action='store', required=False, help='JSON-Datei für Fehler und Warnungen', metavar='errors.json')
|
||||
parser.add_argument('-w', '--write', action='store', help='Schreibe Ergebnisse der Nummerierung in eine JSON-Datei')
|
||||
parser.add_argument('-i', '--info', action='store', help='Schreibe Ergebnisse der Nummerierung in eine JSON-Datei')
|
||||
parser.add_argument('-d', '--dryrun', action='store_true', help='Symbole nicht in der DXF-Datei überschreiben, nur Ausgabe auf Konsole')
|
||||
parser.add_argument('--show_symbol_frames', action='store_true', help='Zeichne Rahmen um jedes Symbol, um die Grenze des Symbols zu visualisieren')
|
||||
|
||||
@@ -844,22 +1005,32 @@ if __name__ == '__main__':
|
||||
print("\nDry-run Modus: DXF-Datei wurde nicht überschrieben")
|
||||
|
||||
# Schreibe JSON-Ausgabe wenn gewünscht
|
||||
if args.write:
|
||||
if args.info:
|
||||
output_data = {
|
||||
'renamed_symbols': renamed_symbols,
|
||||
'total_count': len(renamed_symbols)
|
||||
}
|
||||
output_path = Path(args.write)
|
||||
output_path = Path(args.info)
|
||||
write_json_file(output_data, output_path.parent, output_path.name)
|
||||
print(f"Ergebnisse in JSON gespeichert: {args.write}")
|
||||
print(f"Ergebnisse in JSON gespeichert: {args.info}")
|
||||
|
||||
# Schreibe Fehler-Datei nur wenn Fehler oder Warnungen vorhanden sind
|
||||
if error_collector.has_errors_or_warnings():
|
||||
if args.errorfile:
|
||||
error_data = error_collector.get_all_issues()
|
||||
error_path = Path(args.errorfile)
|
||||
write_json_file(error_data, error_path.parent, error_path.name)
|
||||
print(f"Fehler/Warnungen gespeichert: {args.errorfile}")
|
||||
|
||||
# Wenn nur ein Dateiname ohne Pfad angegeben wurde, speichere im work-Ordner
|
||||
if error_path.parent == Path('.'):
|
||||
output_dir = dxf_path.parent
|
||||
output_filename = error_path.name
|
||||
else:
|
||||
# Absoluter oder relativer Pfad mit Verzeichnis angegeben
|
||||
output_dir = error_path.parent
|
||||
output_filename = error_path.name
|
||||
|
||||
write_json_file(error_data, output_dir, output_filename)
|
||||
print(f"Fehler/Warnungen gespeichert: {output_dir / output_filename}")
|
||||
else:
|
||||
# Keine explizite Fehlerdatei angegeben, generiere eine basierend auf Input-Datei
|
||||
error_filename = f"{dxf_path.stem}_errors.json"
|
||||
|
||||
+1
-30
@@ -20,6 +20,7 @@ from utils import (
|
||||
merge_two_dicts,
|
||||
to_json,
|
||||
write_results,
|
||||
extract_attributes_with_positions as attribs_to_dicts,
|
||||
)
|
||||
|
||||
|
||||
@@ -371,36 +372,6 @@ def create_new_id(id_: str, dict1: dict, dict2: dict, error_collector: ErrorColl
|
||||
return f"{id_}@UNKNOWN" # Fallback für fehlenden SPS-Präfix
|
||||
return f"{id_}@{sps_praefix}"
|
||||
|
||||
def attribs_to_dicts(insert_iterable) -> tuple[list, list]:
|
||||
"""
|
||||
Wandelt eine Iterable von INSERT-Objekten in zwei Listen um:
|
||||
- all_inserts: Liste von Dictionaries mit Attribut-Tags und deren Textwerten
|
||||
- all_positions: Liste von Dictionaries mit Attribut-Tags und deren (x, y, z)-Positionen (jeweils gerundet auf eine Nachkommastelle)
|
||||
|
||||
Jeder Eintrag in den Listen entspricht einem INSERT-Block mit Attributen.
|
||||
Blöcke ohne Attribute werden übersprungen.
|
||||
"""
|
||||
all_inserts = list()
|
||||
all_positions = list()
|
||||
for insert in insert_iterable:
|
||||
if insert.dxftype() != 'INSERT':
|
||||
continue
|
||||
itemdata = dict()
|
||||
positions = dict()
|
||||
typ = 'unknown'
|
||||
for attrib in insert.attribs:
|
||||
if len(insert.attribs) == 0:
|
||||
continue # Überspringe Blöcke ohne Attribute
|
||||
attr_tag = attrib.dxf.tag
|
||||
attr_text = attrib.dxf.text
|
||||
pos = attrib.dxf.insert
|
||||
itemdata[attr_tag] = attr_text
|
||||
positions[attr_tag] = (round(pos.x, 1), round(pos.y, 1), round(pos.z, 1))
|
||||
if len(itemdata) > 0:
|
||||
all_inserts.append(itemdata)
|
||||
all_positions.append(positions)
|
||||
return all_inserts, all_positions
|
||||
|
||||
def create_mappings(positions: dict) -> tuple[dict, dict]:
|
||||
unterverteiler_pfad = ""
|
||||
dnamen = dict()
|
||||
|
||||
+381
@@ -171,6 +171,387 @@ def get_dxf_file(filepath: Path):
|
||||
return doc
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ezdxf Operations (High-Level DXF Helpers)
|
||||
# ============================================================================
|
||||
|
||||
def extract_insert_attributes(insert, error_collector=None) -> dict:
|
||||
"""
|
||||
Extrahiert alle Attribute aus einem INSERT-Block.
|
||||
Unterstützt zweistufige Blockstruktur:
|
||||
- Äußerer Block enthält LWPOLYLINE und INSERT zu Attribut-Block
|
||||
- Innerer Attribut-Block enthält die eigentlichen Attribute
|
||||
|
||||
Args:
|
||||
insert: INSERT-Entity des Blocks
|
||||
error_collector: Optional ErrorCollector für Warnungen
|
||||
|
||||
Returns:
|
||||
Dictionary mit allen gefundenen Attributen {tag: text}
|
||||
"""
|
||||
attributes = {}
|
||||
if insert.dxftype() != 'INSERT':
|
||||
return attributes
|
||||
|
||||
# Prüfe zuerst ob der Block direkt Attribute hat (alte Struktur)
|
||||
direct_attribs = list(insert.attribs)
|
||||
if direct_attribs:
|
||||
for attrib in direct_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
return attributes
|
||||
|
||||
# Neue zweistufige Struktur: Suche im Block nach einem INSERT mit Attributen
|
||||
# Dies erfordert das doc-Objekt, daher nur wenn verfügbar
|
||||
if hasattr(insert, 'doc') and insert.doc:
|
||||
try:
|
||||
block_layout = insert.doc.blocks.get(insert.dxf.name)
|
||||
for entity in block_layout:
|
||||
if entity.dxftype() == 'INSERT':
|
||||
inner_attribs = list(entity.attribs)
|
||||
if inner_attribs:
|
||||
# Gefunden: innerer Block mit Attributen
|
||||
for attrib in inner_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
break # Nur das erste INSERT mit Attributen verwenden
|
||||
except Exception as e:
|
||||
if error_collector:
|
||||
error_msg = f"Fehler beim Extrahieren der Attribute aus Block '{insert.dxf.name}': {e}"
|
||||
if hasattr(error_collector, 'add_warnings'):
|
||||
error_collector.add_warnings({"attribute_extraction_error": error_msg})
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def 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 neue Code, da es zweistufige Blockstrukturen besser unterstützt.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
insert: INSERT-Entity des Blocks
|
||||
error_collector: Optional ErrorCollector für Warnungen
|
||||
|
||||
Returns:
|
||||
Dictionary mit allen gefundenen Attributen {tag: text}
|
||||
"""
|
||||
attributes = {}
|
||||
if insert.dxftype() != 'INSERT':
|
||||
return attributes
|
||||
|
||||
# Prüfe zuerst ob der Block direkt Attribute hat (alte Struktur)
|
||||
direct_attribs = list(insert.attribs)
|
||||
if direct_attribs:
|
||||
for attrib in direct_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
return attributes
|
||||
|
||||
# Neue zweistufige Struktur: Suche im Block nach einem INSERT mit Attributen
|
||||
try:
|
||||
block_layout = doc.blocks.get(insert.dxf.name)
|
||||
for entity in block_layout:
|
||||
if entity.dxftype() == 'INSERT':
|
||||
inner_attribs = list(entity.attribs)
|
||||
if inner_attribs:
|
||||
# Gefunden: innerer Block mit Attributen
|
||||
for attrib in inner_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
break # Nur das erste INSERT mit Attributen verwenden
|
||||
except Exception as e:
|
||||
if error_collector:
|
||||
error_msg = f"Fehler beim Extrahieren der Attribute aus Block '{insert.dxf.name}': {e}"
|
||||
if hasattr(error_collector, 'add_warnings'):
|
||||
error_collector.add_warnings({"attribute_extraction_error": error_msg})
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def ensure_layer_exists(doc, layer_name: str, color: int = None, linetype: str = None,
|
||||
lineweight: int = None, description: str = None):
|
||||
"""
|
||||
Stellt sicher, dass ein Layer existiert und erstellt ihn falls nötig.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
layer_name: Name des Layers
|
||||
color: ACI Color Index (1-255), optional
|
||||
linetype: Name des Linientyps (z.B. 'CONTINUOUS', 'DASHED'), optional
|
||||
lineweight: Linienbreite in mm*100 (z.B. 25 für 0.25mm), optional
|
||||
description: Layer-Beschreibung, optional
|
||||
|
||||
Returns:
|
||||
Layer-Objekt (existierend oder neu erstellt)
|
||||
"""
|
||||
if layer_name in doc.layers:
|
||||
layer = doc.layers.get(layer_name)
|
||||
else:
|
||||
layer = doc.layers.add(layer_name)
|
||||
|
||||
# Setze optionale Attribute
|
||||
if color is not None:
|
||||
layer.color = color
|
||||
if linetype is not None:
|
||||
layer.dxf.linetype = linetype
|
||||
if lineweight is not None:
|
||||
layer.dxf.lineweight = lineweight
|
||||
if description is not None:
|
||||
layer.dxf.description = description
|
||||
|
||||
return layer
|
||||
|
||||
|
||||
def add_rectangle_lwpolyline(msp, x: float, y: float, width: float, height: float,
|
||||
layer: str = '0', color: int = None, rgb: tuple = None,
|
||||
closed: bool = True):
|
||||
"""
|
||||
Fügt eine rechteckige LWPOLYLINE hinzu.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
x: X-Koordinate der unteren linken Ecke
|
||||
y: Y-Koordinate der unteren linken Ecke
|
||||
width: Breite des Rechtecks
|
||||
height: Höhe des Rechtecks
|
||||
layer: Layer-Name (default: '0')
|
||||
color: ACI Color Index (1-255), optional
|
||||
rgb: RGB-Tuple (r, g, b) mit Werten 0-255, optional
|
||||
closed: Ob die Polylinie geschlossen sein soll (default: True)
|
||||
|
||||
Returns:
|
||||
LWPOLYLINE-Entity
|
||||
"""
|
||||
points = [
|
||||
(x, y),
|
||||
(x + width, y),
|
||||
(x + width, y + height),
|
||||
(x, y + height)
|
||||
]
|
||||
|
||||
polyline = msp.add_lwpolyline(points, close=closed)
|
||||
polyline.dxf.layer = layer
|
||||
|
||||
if color is not None:
|
||||
polyline.dxf.color = color
|
||||
if rgb is not None:
|
||||
polyline.rgb = rgb
|
||||
|
||||
return polyline
|
||||
|
||||
|
||||
def add_text_with_alignment(msp, text: str, x: float, y: float,
|
||||
height: float = 50, layer: str = '0',
|
||||
color: int = None, halign: int = 0, valign: int = 0,
|
||||
rotation: float = 0.0, style: str = 'Standard'):
|
||||
"""
|
||||
Fügt einen Text mit Ausrichtung hinzu.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
text: Textinhalt
|
||||
x: X-Koordinate
|
||||
y: Y-Koordinate
|
||||
height: Texthöhe (default: 50)
|
||||
layer: Layer-Name (default: '0')
|
||||
color: ACI Color Index (1-255), optional
|
||||
halign: Horizontale Ausrichtung (0=links, 1=mitte, 2=rechts)
|
||||
valign: Vertikale Ausrichtung (0=basis, 1=unten, 2=mitte, 3=oben)
|
||||
rotation: Rotation in Grad (default: 0.0)
|
||||
style: Textstil-Name (default: 'Standard')
|
||||
|
||||
Returns:
|
||||
TEXT-Entity
|
||||
"""
|
||||
text_entity = msp.add_text(text, height=height)
|
||||
text_entity.dxf.layer = layer
|
||||
text_entity.dxf.style = style
|
||||
text_entity.dxf.rotation = rotation
|
||||
|
||||
if color is not None:
|
||||
text_entity.dxf.color = color
|
||||
|
||||
# Setze Ausrichtung
|
||||
text_entity.set_placement((x, y), align=f"{'LEFT' if halign == 0 else 'CENTER' if halign == 1 else 'RIGHT'}_{'BASELINE' if valign == 0 else 'BOTTOM' if valign == 1 else 'MIDDLE' if valign == 2 else 'TOP'}")
|
||||
|
||||
return text_entity
|
||||
|
||||
|
||||
def extract_attributes_with_positions(insert_iterable, round_decimals: int = 1):
|
||||
"""
|
||||
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: Anzahl Nachkommastellen für Positions-Rundung (default: 1)
|
||||
|
||||
Returns:
|
||||
Tuple von (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.
|
||||
Diese Funktion ist kompatibel mit der alten attribs_to_dicts() aus getpositions.py
|
||||
"""
|
||||
all_inserts = []
|
||||
all_positions = []
|
||||
|
||||
for insert in insert_iterable:
|
||||
if insert.dxftype() != 'INSERT':
|
||||
continue
|
||||
|
||||
itemdata = {}
|
||||
positions = {}
|
||||
|
||||
# Direkte Attribute (alte Struktur)
|
||||
for attrib in insert.attribs:
|
||||
if len(insert.attribs) == 0:
|
||||
continue # Überspringe Blöcke ohne Attribute
|
||||
attr_tag = attrib.dxf.tag
|
||||
attr_text = attrib.dxf.text
|
||||
pos = attrib.dxf.insert
|
||||
itemdata[attr_tag] = attr_text
|
||||
positions[attr_tag] = (
|
||||
round(pos.x, round_decimals),
|
||||
round(pos.y, round_decimals),
|
||||
round(pos.z, round_decimals)
|
||||
)
|
||||
|
||||
if len(itemdata) > 0:
|
||||
all_inserts.append(itemdata)
|
||||
all_positions.append(positions)
|
||||
|
||||
return all_inserts, all_positions
|
||||
|
||||
|
||||
def add_blockref_with_attributes(msp, block_name: str, insert_point: tuple,
|
||||
attributes: dict = None, layer: str = '0',
|
||||
scale: float = 1.0, rotation: float = 0.0):
|
||||
"""
|
||||
Fügt eine Block-Referenz (INSERT) mit Attributen hinzu.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
block_name: Name des Blocks
|
||||
insert_point: Einfügepunkt als (x, y) oder (x, y, z) Tuple
|
||||
attributes: Dictionary mit Attribut-Namen und -Werten (optional)
|
||||
layer: Layer-Name (default: '0')
|
||||
scale: Skalierungsfaktor (default: 1.0)
|
||||
rotation: Rotation in Grad (default: 0.0)
|
||||
|
||||
Returns:
|
||||
INSERT-Entity mit gesetzten Attributen
|
||||
"""
|
||||
# Füge Block-Referenz hinzu
|
||||
blockref = msp.add_blockref(block_name, insert_point)
|
||||
blockref.dxf.layer = layer
|
||||
|
||||
if scale != 1.0:
|
||||
blockref.dxf.xscale = scale
|
||||
blockref.dxf.yscale = scale
|
||||
blockref.dxf.zscale = scale
|
||||
|
||||
if rotation != 0.0:
|
||||
blockref.dxf.rotation = rotation
|
||||
|
||||
# Füge Attribute hinzu falls vorhanden
|
||||
if attributes:
|
||||
blockref.add_auto_attribs(attributes)
|
||||
|
||||
return blockref
|
||||
|
||||
|
||||
def query_entities_by_layer(msp, entity_type: str = None, layers: list = None,
|
||||
exclude_layers: list = None):
|
||||
"""
|
||||
Filtert Entities nach Typ und Layer.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
entity_type: Entity-Typ (z.B. 'INSERT', 'LINE', 'TEXT'), None für alle
|
||||
layers: Liste der zu inkludierenden Layer (None = alle außer exclude_layers)
|
||||
exclude_layers: Liste der zu exkludierenden Layer (None = keine Exklusion)
|
||||
|
||||
Returns:
|
||||
Liste der gefilterten Entities
|
||||
|
||||
Examples:
|
||||
# Alle INSERTs auf Layer 'ILS_MOTOR'
|
||||
query_entities_by_layer(msp, 'INSERT', layers=['ILS_MOTOR'])
|
||||
|
||||
# Alle Entities außer auf Layer '0'
|
||||
query_entities_by_layer(msp, exclude_layers=['0'])
|
||||
|
||||
# Alle LINEs auf ILS_* Layern (manuelles Filtern nötig)
|
||||
all_lines = query_entities_by_layer(msp, 'LINE')
|
||||
ils_lines = [e for e in all_lines if e.dxf.layer.startswith('ILS_')]
|
||||
"""
|
||||
# Baue Query-String
|
||||
if entity_type:
|
||||
entities = msp.query(entity_type)
|
||||
else:
|
||||
entities = msp
|
||||
|
||||
# Filtere nach Layern
|
||||
result = []
|
||||
for entity in entities:
|
||||
layer = entity.dxf.layer
|
||||
|
||||
# Prüfe Exklusion
|
||||
if exclude_layers and layer in exclude_layers:
|
||||
continue
|
||||
|
||||
# Prüfe Inklusion
|
||||
if layers is None or layer in layers:
|
||||
result.append(entity)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def create_layers_from_config(doc, layer_definitions: dict):
|
||||
"""
|
||||
Erstellt mehrere Layer aus einer Konfiguration.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
layer_definitions: Dictionary mit Layer-Definitionen
|
||||
Format: {
|
||||
'layer_name': {
|
||||
'color': int (optional),
|
||||
'linetype': str (optional),
|
||||
'lineweight': int (optional),
|
||||
'description': str (optional)
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
Example:
|
||||
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)
|
||||
"""
|
||||
for layer_name, props in layer_definitions.items():
|
||||
ensure_layer_exists(
|
||||
doc,
|
||||
layer_name,
|
||||
color=props.get('color'),
|
||||
linetype=props.get('linetype'),
|
||||
lineweight=props.get('lineweight'),
|
||||
description=props.get('description')
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Dictionary Operations
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user