utils enthält jetzt generelle Routinen der verschiedenen Programme. create_example refactored. utils wird jetzt entsprechend überall importiert
This commit is contained in:
Vendored
+12
@@ -370,5 +370,17 @@
|
|||||||
"--write", "BGMG_symbols.json"
|
"--write", "BGMG_symbols.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "create_numbers.py with BGMG-UndefSymbols.dxf",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${file}",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"args": [
|
||||||
|
"--filename", "BGMG-UndefSymbols.dxf",
|
||||||
|
"--errorfile", "BGMG-UndefSymbols_errors.json",
|
||||||
|
"--write", "BGMG-UndefSymbols_symbols.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-19
@@ -163,7 +163,7 @@
|
|||||||
|
|
||||||
"BGMG": {
|
"BGMG": {
|
||||||
"description": "Test-Szene mit drei Rechtecken für BG-1@@@ und BG-2@@@ Symbole mit mehreren Layern",
|
"description": "Test-Szene mit drei Rechtecken für BG-1@@@ und BG-2@@@ Symbole mit mehreren Layern",
|
||||||
"ma_groups": [
|
"bg_groups": [
|
||||||
{
|
{
|
||||||
"name": "BG-1@@_frame1",
|
"name": "BG-1@@_frame1",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
@@ -248,8 +248,8 @@
|
|||||||
"renaming_frames": [
|
"renaming_frames": [
|
||||||
{
|
{
|
||||||
"comment": "Rahmen 1: Links unten - 1x BG-1@@@ (Polylinie) - Angepasst für left_bottom_corner",
|
"comment": "Rahmen 1: Links unten - 1x BG-1@@@ (Polylinie) - Angepasst für left_bottom_corner",
|
||||||
"name": "BG-1@@@",
|
"name": "BG-1@@",
|
||||||
"name2": "MB-1@@@",
|
"name2": "MB-1@@",
|
||||||
"kennzeichnung": "",
|
"kennzeichnung": "",
|
||||||
"layer_name1": "ILS_Eingang",
|
"layer_name1": "ILS_Eingang",
|
||||||
"layer_name2": "ILS_Ausgang",
|
"layer_name2": "ILS_Ausgang",
|
||||||
@@ -293,9 +293,9 @@
|
|||||||
|
|
||||||
"BGMG-UndefSymbols": {
|
"BGMG-UndefSymbols": {
|
||||||
"description": "Test-Szene wie BGMB, aber mit nur 2x BG-1@@ Symbolen, um Undefiniert-Symbole zu testen",
|
"description": "Test-Szene wie BGMB, aber mit nur 2x BG-1@@ Symbolen, um Undefiniert-Symbole zu testen",
|
||||||
"ma_groups": [
|
"bg_groups": [
|
||||||
{
|
{
|
||||||
"name": "BG-1@@_frame1",
|
"name": "BG-1@@@_frame1",
|
||||||
"count": 5,
|
"count": 5,
|
||||||
"layout_type": "horizontal",
|
"layout_type": "horizontal",
|
||||||
"base_x": 1000,
|
"base_x": 1000,
|
||||||
@@ -305,10 +305,10 @@
|
|||||||
],
|
],
|
||||||
"renaming_frames": [
|
"renaming_frames": [
|
||||||
{
|
{
|
||||||
"comment": "Rahmen: 2x BG-1@@@ (Polylinie) - Angepasst für linke Seite - Akzeptiert alle Layer",
|
"comment": "Rahmen: 2x BG-1@@@ (Polylinie) - Angepasst für linke Seite",
|
||||||
"name": "BG-1@@@",
|
"name": "BG-1@@@",
|
||||||
"kennzeichnung": "",
|
"kennzeichnung": "",
|
||||||
"layer_name": "",
|
"layer_name1": "ILS_Eingang",
|
||||||
"direction": "LEFT_RIGHT",
|
"direction": "LEFT_RIGHT",
|
||||||
"x": 800,
|
"x": 800,
|
||||||
"y": 800,
|
"y": 800,
|
||||||
@@ -317,12 +317,12 @@
|
|||||||
"use_polyline": true
|
"use_polyline": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"comment": "Rahmen: 2x BG-1@@@ (Polylinie) - Angepasst für rechte Seite - Akzeptiert alle Layer",
|
"comment": "Rahmen: 2x BG-1@@@ (Polylinie) - Angepasst für rechte Seite",
|
||||||
"name": "BG-1@@@",
|
"name": "BG-1@@@",
|
||||||
"kennzeichnung": "",
|
"kennzeichnung": "",
|
||||||
"layer_name": "",
|
"layer_name1": "ILS_Eingang",
|
||||||
"direction": "LEFT_RIGHT",
|
"direction": "LEFT_RIGHT",
|
||||||
"x": 5000,
|
"x": 6000,
|
||||||
"y": 800,
|
"y": 800,
|
||||||
"width": 3210,
|
"width": 3210,
|
||||||
"height": 600,
|
"height": 600,
|
||||||
@@ -338,7 +338,9 @@
|
|||||||
"layer": "ILS_MOTOR",
|
"layer": "ILS_MOTOR",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"SPS": "1",
|
"SPS": "1",
|
||||||
"VERW": "Motor",
|
"IO": "MA0000",
|
||||||
|
"VERW": "ILSMotor",
|
||||||
|
"BEZEICHNUNG": "Motor MA0000",
|
||||||
"ARTIKELNR": "790902001",
|
"ARTIKELNR": "790902001",
|
||||||
"ARTIKELBEZEICHN": "E-Teile für SEW Motor ASE1-HAN10ES-BG",
|
"ARTIKELBEZEICHN": "E-Teile für SEW Motor ASE1-HAN10ES-BG",
|
||||||
"KENNZEICHNUNG": "=A01+UH00",
|
"KENNZEICHNUNG": "=A01+UH00",
|
||||||
@@ -348,7 +350,8 @@
|
|||||||
"TEXT-ES": "",
|
"TEXT-ES": "",
|
||||||
"TEXT-F": "",
|
"TEXT-F": "",
|
||||||
"TEXT-I": "",
|
"TEXT-I": "",
|
||||||
"ID": ""
|
"ID": "",
|
||||||
|
"REALE_POSITION": "x"
|
||||||
},
|
},
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
"_comment": "Symbol-Dimensionen für Layout-Berechnung. Feste Werte basierend auf IO-Feld Analyse (6 Zeichen MA-1@@: 1210 × 381)",
|
"_comment": "Symbol-Dimensionen für Layout-Berechnung. Feste Werte basierend auf IO-Feld Analyse (6 Zeichen MA-1@@: 1210 × 381)",
|
||||||
@@ -357,18 +360,70 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pot_defaults": {
|
"pot_defaults": {
|
||||||
"_description": "Standard-Werte für POT-Symbole",
|
"_description": "Standard-Werte für POT-Symbole. Basiert auf Analyse von ILS_POT-Erdung aus Nummerierung_IO.dxf",
|
||||||
"block_name": "Erdungspunkt",
|
"block_name": "ILS_POT-Erdung",
|
||||||
"layer": "POT-Erdung",
|
"layer": "ILS_POT-Erdung",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"REALE_POSITION": "x",
|
"REALE_POSITION": "x",
|
||||||
"KENNZEICHNUNG": "=A01+UH00-X01",
|
"KENNZEICHNUNG": "=A01+UH00-X01",
|
||||||
"NAME": "POT-FS-01-02"
|
"NAME": "POT-1@@@"
|
||||||
},
|
},
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
"_comment": "Symbol-Dimensionen für Layout-Berechnung.",
|
"_comment": "Symbol-Dimensionen für POT-Erdung. Basiert auf Block-Analyse: 816 × 618",
|
||||||
"width": 1210,
|
"width": 820,
|
||||||
"height": 300
|
"height": 620
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bg_defaults": {
|
||||||
|
"_description": "Standard-Werte für BG/MB-Symbole (Eingänge/Ausgänge). Basiert auf Analyse von BG3240 aus easy.dxf",
|
||||||
|
"block_name": "io",
|
||||||
|
"layer": "ILS_Eingang",
|
||||||
|
"attributes": {
|
||||||
|
"SPS": "1",
|
||||||
|
"IO": "BG0000",
|
||||||
|
"VERW": "Sensor",
|
||||||
|
"BEZEICHNUNG": "",
|
||||||
|
"ARTIKELNR": "",
|
||||||
|
"ARTIKELBEZEICHN": "",
|
||||||
|
"KENNZEICHNUNG": "=A01+UC0101",
|
||||||
|
"TEXT-D": "",
|
||||||
|
"TEXT-E": "",
|
||||||
|
"TEXT-ES": "",
|
||||||
|
"TEXT-F": "",
|
||||||
|
"TEXT-I": "",
|
||||||
|
"ID": "",
|
||||||
|
"REALE_POSITION": "x"
|
||||||
|
},
|
||||||
|
"dimensions": {
|
||||||
|
"_comment": "Symbol-Dimensionen für BG/MB-Symbole. 7 Zeichen (BG-1@@@): 1410 × 381",
|
||||||
|
"width": 1410,
|
||||||
|
"height": 381
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mb_defaults": {
|
||||||
|
"_description": "Standard-Werte für MB-Symbole (Ausgänge). Verwendet dieselbe Struktur wie BG, nur anderer Layer",
|
||||||
|
"block_name": "io",
|
||||||
|
"layer": "ILS_Ausgang",
|
||||||
|
"attributes": {
|
||||||
|
"SPS": "1",
|
||||||
|
"IO": "MB0000",
|
||||||
|
"VERW": "Aktor",
|
||||||
|
"BEZEICHNUNG": "",
|
||||||
|
"ARTIKELNR": "",
|
||||||
|
"ARTIKELBEZEICHN": "",
|
||||||
|
"KENNZEICHNUNG": "=A01+UC0101",
|
||||||
|
"TEXT-D": "",
|
||||||
|
"TEXT-E": "",
|
||||||
|
"TEXT-ES": "",
|
||||||
|
"TEXT-F": "",
|
||||||
|
"TEXT-I": "",
|
||||||
|
"ID": "",
|
||||||
|
"REALE_POSITION": "x"
|
||||||
|
},
|
||||||
|
"dimensions": {
|
||||||
|
"_comment": "Symbol-Dimensionen für MB-Symbole. 7 Zeichen (MB-1@@@): 1410 × 381",
|
||||||
|
"width": 1410,
|
||||||
|
"height": 381
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
|||||||
+58
-20
@@ -635,18 +635,17 @@ class TestDataGenerator:
|
|||||||
|
|
||||||
return offset_map.get(insert_point, (0, 0)) # Default: left_top_corner
|
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:
|
Args:
|
||||||
group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing)
|
group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing)
|
||||||
ma_defaults: Dict mit MA-Default-Attributen
|
symbol_defaults: Dict mit Symbol-Default-Attributen (ma_defaults, bg_defaults, etc.)
|
||||||
symbol_width: Feste Breite des Symbols
|
symbol_type: Typ des Symbols ('MA', 'BG', 'MB', 'POT') für BEZEICHNUNG
|
||||||
symbol_height: Feste Höhe des Symbols
|
|
||||||
spacing_factor: Spacing-Faktor zwischen Symbolen
|
spacing_factor: Spacing-Faktor zwischen Symbolen
|
||||||
y_offsets_list: Liste von Y-Offsets für horizontal_offset Layout
|
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']
|
name = group['name']
|
||||||
count = group['count']
|
count = group['count']
|
||||||
@@ -657,19 +656,33 @@ class TestDataGenerator:
|
|||||||
# Extrahiere IO-Pattern aus Name (z.B. "MA-1@@_top" -> "MA-1@@")
|
# Extrahiere IO-Pattern aus Name (z.B. "MA-1@@_top" -> "MA-1@@")
|
||||||
io_pattern = name.split('_')[0]
|
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
|
# Berechne Spacing
|
||||||
if layout_type in ['horizontal', 'horizontal_offset']:
|
if layout_type in ['horizontal', 'horizontal_offset']:
|
||||||
actual_spacing = symbol_width * spacing_factor
|
actual_spacing = symbol_width * spacing_factor
|
||||||
else:
|
else:
|
||||||
actual_spacing = 0
|
actual_spacing = 0
|
||||||
|
|
||||||
# Hole Attribute aus ma_defaults
|
# Hole Attribute und Block-Info aus defaults
|
||||||
attributes = ma_defaults.get('attributes', {})
|
attributes = symbol_defaults.get('attributes', {})
|
||||||
layer = ma_defaults.get('layer', 'ILS_MOTOR')
|
layer = symbol_defaults.get('layer', 'ILS_MOTOR')
|
||||||
|
block_name = symbol_defaults.get('block_name', 'io')
|
||||||
|
|
||||||
# Berechne Offset basierend auf Einfügepunkt
|
# Berechne Offset basierend auf Einfügepunkt
|
||||||
offset_x, offset_y = self._calculate_insert_offset(insert_point, symbol_width, symbol_height)
|
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
|
# Generiere Symbole basierend auf Layout-Typ
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
if layout_type == 'single':
|
if layout_type == 'single':
|
||||||
@@ -692,7 +705,7 @@ class TestDataGenerator:
|
|||||||
# DXF-Block wird an der linken unteren Ecke eingefügt
|
# DXF-Block wird an der linken unteren Ecke eingefügt
|
||||||
# (da der io-Block bei (0,0) startet und nach oben geht)
|
# (da der io-Block bei (0,0) startet und nach oben geht)
|
||||||
blockref = self.msp.add_blockref(
|
blockref = self.msp.add_blockref(
|
||||||
'io',
|
block_name,
|
||||||
insert=(left_top_x, left_top_y - symbol_height),
|
insert=(left_top_x, left_top_y - symbol_height),
|
||||||
dxfattribs={'layer': layer}
|
dxfattribs={'layer': layer}
|
||||||
)
|
)
|
||||||
@@ -700,7 +713,10 @@ class TestDataGenerator:
|
|||||||
# Setze Attribute (dynamisch IO und BEZEICHNUNG)
|
# Setze Attribute (dynamisch IO und BEZEICHNUNG)
|
||||||
attrib_values = attributes.copy()
|
attrib_values = attributes.copy()
|
||||||
attrib_values['IO'] = io_pattern
|
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)
|
blockref.add_auto_attribs(attrib_values)
|
||||||
|
|
||||||
@@ -724,26 +740,48 @@ class TestDataGenerator:
|
|||||||
scene_name: Name der Testszene in der Config
|
scene_name: Name der Testszene in der Config
|
||||||
"""
|
"""
|
||||||
scene = self.config['test_scenes'][scene_name]
|
scene = self.config['test_scenes'][scene_name]
|
||||||
ma_defaults = self.config.get('ma_defaults', {})
|
|
||||||
general = self.config.get('general', {})
|
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
|
# Hole Layout-Einstellungen
|
||||||
layout = general.get('layout', {})
|
layout = general.get('layout', {})
|
||||||
spacing_factor = layout.get('symbol_spacing_factor', 1.2)
|
spacing_factor = layout.get('symbol_spacing_factor', 1.2)
|
||||||
y_offsets_list = layout.get('horizontal_offset_y_offsets', [0, -50, 50])
|
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', {})
|
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
|
# Generiere MA-Gruppen aus Config
|
||||||
|
ma_defaults = self.config.get('ma_defaults', {})
|
||||||
for group in scene.get('ma_groups', []):
|
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
|
# Generiere Renamer-Rahmen aus Config
|
||||||
for frame_config in scene.get('renaming_frames', []):
|
for frame_config in scene.get('renaming_frames', []):
|
||||||
|
|||||||
+299
-128
@@ -3,7 +3,10 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
from error_collector import ErrorCollector, write_json_file
|
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
|
from symbol_frames import draw_symbol_frames
|
||||||
|
|
||||||
|
|
||||||
@@ -80,56 +83,6 @@ def read_config_layers(config_path: Path) -> list:
|
|||||||
return layers
|
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):
|
def is_rectangle_polyline(points):
|
||||||
"""
|
"""
|
||||||
Prüft, ob eine Liste von Punkten ein Rechteck darstellt.
|
Prüft, ob eine Liste von Punkten ein Rechteck darstellt.
|
||||||
@@ -374,6 +327,26 @@ def enumerate_symbols(symbols, attributes):
|
|||||||
return renamed
|
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):
|
def get_renamer_config_key(attributes):
|
||||||
"""
|
"""
|
||||||
Erstellt einen eindeutigen Schlüssel für die Renamer-Block-Konfiguration.
|
Erstellt einen eindeutigen Schlüssel für die Renamer-Block-Konfiguration.
|
||||||
@@ -516,6 +489,148 @@ def collect_and_group_renamer_blocks(doc, msp, renamer_layers, error_collector):
|
|||||||
|
|
||||||
return renamer_groups
|
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):
|
def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_collector):
|
||||||
"""
|
"""
|
||||||
Zweiter Pass: Verarbeitet jede Renamer-Block-Gruppe.
|
Zweiter Pass: Verarbeitet jede Renamer-Block-Gruppe.
|
||||||
@@ -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)")
|
print(f"\n Verarbeite Gruppe: {block_name} ({len(group_blocks)} Renamer-Blöcke)")
|
||||||
|
|
||||||
# Sammle Symbole aus ALLEN Boundaries dieser Gruppe
|
# Sammle Symbole aus ALLEN Boundaries dieser Gruppe
|
||||||
all_symbols = []
|
all_symbols = collect_symbols_from_group(doc, msp, renamer_layers, group_blocks, block_name, error_collector)
|
||||||
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})
|
|
||||||
|
|
||||||
print(f" Gesamt {len(all_symbols)} eindeutige Template-Symbole aus {len(group_blocks)} Blöcken")
|
print(f" Gesamt {len(all_symbols)} eindeutige Template-Symbole aus {len(group_blocks)} Blöcken")
|
||||||
|
|
||||||
if not all_symbols:
|
if not all_symbols:
|
||||||
# Fehlermeldung: Keine Symbole gefunden
|
# Fehlermeldung: Keine Symbole gefunden
|
||||||
layer_names = []
|
layer_names = get_layer_names_from_attributes(reference_attributes)
|
||||||
for i in range(1, 4):
|
error_msg = create_no_symbols_found_error_message(
|
||||||
layer_key = f"LAYER_NAME{i}"
|
block_name, direction, layer_names, group_blocks, doc, msp, error_collector
|
||||||
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)}"
|
|
||||||
)
|
)
|
||||||
print(f" FEHLER: {error_msg}")
|
print(f" FEHLER: {error_msg}")
|
||||||
error_collector.add_errors({"no_symbols_found": error_msg})
|
error_collector.add_errors({"no_symbols_found": error_msg})
|
||||||
@@ -603,21 +678,19 @@ def process_renamer_groups(doc, msp, renamer_layers, renamer_groups, error_colle
|
|||||||
|
|
||||||
return all_renamed
|
return all_renamed
|
||||||
|
|
||||||
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
|
def find_symbols_with_at_in_io(doc, msp, error_collector):
|
||||||
all_boundaries = []
|
"""
|
||||||
for group_blocks in renamer_groups.values():
|
Findet alle Symbole mit "@" im IO-Attribut.
|
||||||
for insert, boundary, attributes, is_rectangle in group_blocks:
|
|
||||||
all_boundaries.append((boundary, attributes))
|
|
||||||
|
|
||||||
# Finde 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
|
||||||
|
"""
|
||||||
symbols_with_at = []
|
symbols_with_at = []
|
||||||
for entity in msp.query('INSERT'):
|
for entity in msp.query('INSERT'):
|
||||||
symbol_attribs = extract_block_attributes(doc, entity, error_collector)
|
symbol_attribs = extract_block_attributes(doc, entity, error_collector)
|
||||||
@@ -636,31 +709,119 @@ def check_symbols_in_boundaries(doc, msp, renamer_groups, error_collector):
|
|||||||
'layer': entity.dxf.layer,
|
'layer': entity.dxf.layer,
|
||||||
'io': io_value
|
'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
|
# Prüfe für jedes Symbol, ob es in mindestens einem Renamer-Bereich liegt
|
||||||
symbols_not_in_any_boundary = []
|
symbols_not_in_any_boundary = []
|
||||||
for symbol in symbols_with_at:
|
for symbol in symbols_with_at:
|
||||||
in_any_boundary = False
|
in_any_boundary = False
|
||||||
for boundary, attributes in all_boundaries:
|
wrong_layer_info = None
|
||||||
# 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:
|
for boundary, attributes, renamer_pos in all_boundaries:
|
||||||
continue
|
is_in_boundary, layer_info = check_symbol_in_boundary(symbol, boundary, attributes, renamer_pos)
|
||||||
|
|
||||||
# Prüfe ob Symbol innerhalb des Bereichs liegt
|
if is_in_boundary:
|
||||||
if point_in_polygon(symbol['position'], boundary):
|
# Layer passt auch - alles OK
|
||||||
in_any_boundary = True
|
in_any_boundary = True
|
||||||
break
|
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:
|
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
|
# Melde Fehler für Symbole, die in keinem Bereich liegen
|
||||||
if symbols_not_in_any_boundary:
|
if 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('-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('-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('-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')
|
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")
|
print("\nDry-run Modus: DXF-Datei wurde nicht überschrieben")
|
||||||
|
|
||||||
# Schreibe JSON-Ausgabe wenn gewünscht
|
# Schreibe JSON-Ausgabe wenn gewünscht
|
||||||
if args.write:
|
if args.info:
|
||||||
output_data = {
|
output_data = {
|
||||||
'renamed_symbols': renamed_symbols,
|
'renamed_symbols': renamed_symbols,
|
||||||
'total_count': len(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)
|
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
|
# Schreibe Fehler-Datei nur wenn Fehler oder Warnungen vorhanden sind
|
||||||
if error_collector.has_errors_or_warnings():
|
if error_collector.has_errors_or_warnings():
|
||||||
if args.errorfile:
|
if args.errorfile:
|
||||||
error_data = error_collector.get_all_issues()
|
error_data = error_collector.get_all_issues()
|
||||||
error_path = Path(args.errorfile)
|
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:
|
else:
|
||||||
# Keine explizite Fehlerdatei angegeben, generiere eine basierend auf Input-Datei
|
# Keine explizite Fehlerdatei angegeben, generiere eine basierend auf Input-Datei
|
||||||
error_filename = f"{dxf_path.stem}_errors.json"
|
error_filename = f"{dxf_path.stem}_errors.json"
|
||||||
|
|||||||
+1
-30
@@ -20,6 +20,7 @@ from utils import (
|
|||||||
merge_two_dicts,
|
merge_two_dicts,
|
||||||
to_json,
|
to_json,
|
||||||
write_results,
|
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_}@UNKNOWN" # Fallback für fehlenden SPS-Präfix
|
||||||
return f"{id_}@{sps_praefix}"
|
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]:
|
def create_mappings(positions: dict) -> tuple[dict, dict]:
|
||||||
unterverteiler_pfad = ""
|
unterverteiler_pfad = ""
|
||||||
dnamen = dict()
|
dnamen = dict()
|
||||||
|
|||||||
+381
@@ -171,6 +171,387 @@ def get_dxf_file(filepath: Path):
|
|||||||
return doc
|
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
|
# Dictionary Operations
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user