Ausserhalb der Bereiche liegende Symbole werden im dxf von einem Kreis umgeben, um neben der Consoleausgabe auch ein graphisches Feedback zu bekommen. Angabe des Einfügepunktes am Symbol in der Config möglich. Der MA Block kann im Zentrum, an der linken oberen Ecke etc. positioniert werden. Nummerierung2 wurde vergrössert, damit die Symbole sich nicht überschneiden.

This commit is contained in:
2026-01-26 19:09:02 +01:00
parent b82c751ba9
commit e214e9d21e
3 changed files with 365 additions and 77 deletions
+28 -26
View File
@@ -71,70 +71,70 @@
}, },
"Nummerierung2": { "Nummerierung2": {
"description": "Test-Szene mit komplexem Layout - 8 MA-1@@ Symbole in verschiedenen Anordnungen", "description": "Test-Szene mit zirkulärer Nummerierung des Layouts - 8 MA-1@@ Symbole entlang einer Polyline nummerieren",
"ma_groups": [ "ma_groups": [
{ {
"name": "MA-1@@_top_left", "name": "MA-1@@_top_left",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 200, "base_x": 400,
"base_y": 1400, "base_y": 2800,
"spacing": 0 "spacing": 0
}, },
{ {
"name": "MA-1@@_top_right", "name": "MA-1@@_top_right",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 240000, "base_x": 4800,
"base_y": 1400, "base_y": 2800,
"spacing": 0 "spacing": 0
}, },
{ {
"name": "MA-1@@_middle_left", "name": "MA-1@@_middle_left",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 600, "base_x": 1200,
"base_y": 900, "base_y": 1800,
"spacing": 0 "spacing": 0
}, },
{ {
"name": "MA-1@@_middle_right", "name": "MA-1@@_middle_right",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 1700, "base_x": 3400,
"base_y": 900, "base_y": 1800,
"spacing": 0 "spacing": 0
}, },
{ {
"name": "MA-1@@_bottom_center", "name": "MA-1@@_bottom_center",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 1240, "base_x": 2480,
"base_y": 400, "base_y": 800,
"spacing": 0 "spacing": 0
}, },
{ {
"name": "MA-1@@_right_top", "name": "MA-1@@_right_top",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 3200, "base_x": 6400,
"base_y": 1200, "base_y": 2400,
"spacing": 0 "spacing": 0
}, },
{ {
"name": "MA-1@@_right_middle", "name": "MA-1@@_right_middle",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 3200, "base_x": 6400,
"base_y": 700, "base_y": 1400,
"spacing": 0 "spacing": 0
}, },
{ {
"name": "MA-1@@_right_bottom", "name": "MA-1@@_right_bottom",
"count": 1, "count": 1,
"layout_type": "single", "layout_type": "single",
"base_x": 3040, "base_x": 6080,
"base_y": 400, "base_y": 800,
"spacing": 0 "spacing": 0
} }
], ],
@@ -147,15 +147,15 @@
"layer_name": "ILS_MOTOR", "layer_name": "ILS_MOTOR",
"direction": "POLYLINE_PATH", "direction": "POLYLINE_PATH",
"path_points": [ "path_points": [
{"x": 200, "y": 1400}, {"x": 400, "y": 2800},
{"x": 2400, "y": 1400}, {"x": 4800, "y": 2800},
{"x": 3200, "y": 1200}, {"x": 6400, "y": 2400},
{"x": 3200, "y": 700}, {"x": 6400, "y": 1400},
{"x": 3040, "y": 400}, {"x": 6080, "y": 800},
{"x": 1240, "y": 400}, {"x": 2480, "y": 800},
{"x": 600, "y": 900}, {"x": 3400, "y": 1800},
{"x": 1700, "y": 900}, {"x": 1200, "y": 1800},
{"x": 200, "y": 1400} {"x": 400, "y": 2800}
] ]
} }
] ]
@@ -194,6 +194,8 @@
"_description": "MA Frame-Typ Attribute (einfache Frames für MA, MG, etc.)", "_description": "MA Frame-Typ Attribute (einfache Frames für MA, MG, etc.)",
"layer": "ILS_RENAMER", "layer": "ILS_RENAMER",
"color": 1, "color": 1,
"insert_point": "center",
"_insert_point_description": "Einfügepunkt für MA-Blöcke. Mögliche Werte: left_top_corner, left_bottom_corner, right_top_corner, right_bottom_corner, center, top_center, bottom_center",
"attdef_positions": { "attdef_positions": {
"_description": "ATTDEF Positionen im inneren Block RENAMER_ATTRIB_MA", "_description": "ATTDEF Positionen im inneren Block RENAMER_ATTRIB_MA",
"NAME": { "NAME": {
+307 -44
View File
@@ -88,6 +88,7 @@ class TestDataGenerator:
{'name': '0', 'color': 7}, # Standard-Layer {'name': '0', 'color': 7}, # Standard-Layer
{'name': 'ILS_MOTOR', 'color': 7}, {'name': 'ILS_MOTOR', 'color': 7},
{'name': 'ILS_RENAMER', 'color': 1}, # Rot {'name': 'ILS_RENAMER', 'color': 1}, # Rot
{'name': 'VALIDATION_ERROR', 'color': 1}, # Rot für Fehlerkreise
{'name': 'TEXT-D', 'color': -7}, # Ausgeschaltet (negativ) {'name': 'TEXT-D', 'color': -7}, # Ausgeschaltet (negativ)
{'name': 'TEXT-E', 'color': 7}, {'name': 'TEXT-E', 'color': 7},
{'name': 'TEXT-ES', 'color': 7}, {'name': 'TEXT-ES', 'color': 7},
@@ -573,7 +574,31 @@ class TestDataGenerator:
else: else:
print("Fehler in Config ... terminating") print("Fehler in Config ... terminating")
def _generate_ma_group(self, group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list): def _calculate_insert_offset(self, insert_point, symbol_width, symbol_height):
"""
Berechnet den Offset vom Einfügepunkt zur linken oberen Ecke
Args:
insert_point: String mit dem Einfügepunkt (z.B. "left_top_corner", "center")
symbol_width: Breite des Symbols
symbol_height: Höhe des Symbols
Returns:
(offset_x, offset_y) - Offset vom Einfügepunkt zur linken oberen Ecke
"""
offset_map = {
'left_top_corner': (0, 0),
'left_bottom_corner': (0, symbol_height),
'right_top_corner': (-symbol_width, 0),
'right_bottom_corner': (-symbol_width, symbol_height),
'center': (-symbol_width / 2, symbol_height / 2),
'top_center': (-symbol_width / 2, 0),
'bottom_center': (-symbol_width / 2, symbol_height)
}
return offset_map.get(insert_point, (0, 0)) # Default: left_top_corner
def _generate_ma_group(self, group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list, insert_point='left_top_corner'):
""" """
Generiert eine Gruppe von MA-Symbolen Generiert eine Gruppe von MA-Symbolen
@@ -584,6 +609,7 @@ class TestDataGenerator:
fixed_height: Fixe Symbol-Höhe fixed_height: Fixe Symbol-Höhe
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")
""" """
name = group['name'] name = group['name']
count = group['count'] count = group['count']
@@ -607,6 +633,9 @@ class TestDataGenerator:
attributes = ma_defaults.get('attributes', {}) attributes = ma_defaults.get('attributes', {})
layer = ma_defaults.get('layer', 'ILS_MOTOR') layer = ma_defaults.get('layer', 'ILS_MOTOR')
# Berechne Offset basierend auf Einfügepunkt
offset_x, offset_y = self._calculate_insert_offset(insert_point, symbol_width, fixed_height)
# 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':
@@ -622,12 +651,15 @@ class TestDataGenerator:
print(f"Unbekannter Layout-Typ: {layout_type}") print(f"Unbekannter Layout-Typ: {layout_type}")
continue continue
# Berechne die linke obere Ecke basierend auf dem Einfügepunkt
left_top_x = x + offset_x
left_top_y = y + offset_y
# 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( blockref = self.msp.add_blockref(
'io', 'io',
#insert=(block_insert_x, block_insert_y), insert=(left_top_x, left_top_y - fixed_height),
insert=(x, y),
dxfattribs={'layer': layer} dxfattribs={'layer': layer}
) )
@@ -637,11 +669,12 @@ class TestDataGenerator:
attrib_values['BEZEICHNUNG'] = f"Motor {io_pattern}" attrib_values['BEZEICHNUNG'] = f"Motor {io_pattern}"
blockref.add_auto_attribs(attrib_values) blockref.add_auto_attribs(attrib_values)
# Speichere Symbol-Position für spätere Validierung # Speichere Symbol-Position für spätere Validierung
# Speichere immer die linke obere Ecke
self.generated_symbols.append({ self.generated_symbols.append({
'x': x, 'x': left_top_x,
'y': y, 'y': left_top_y,
'io': io_pattern, 'io': io_pattern,
'group_name': name, 'group_name': name,
'layer': layer 'layer': layer
@@ -668,9 +701,13 @@ class TestDataGenerator:
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)
ma_frame = general.get('ma_frame', {})
insert_point = ma_frame.get('insert_point', 'left_top_corner')
# Generiere MA-Gruppen aus Config # Generiere MA-Gruppen aus Config
for group in scene.get('ma_groups', []): for group in scene.get('ma_groups', []):
self._generate_ma_group(group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list) self._generate_ma_group(group, ma_defaults, width_per_char, fixed_height, spacing_factor, y_offsets_list, 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', []):
@@ -704,16 +741,46 @@ class TestDataGenerator:
def _point_in_rectangle(self, point, rect_x, rect_y, rect_width, rect_height): def _point_in_rectangle(self, point, rect_x, rect_y, rect_width, rect_height):
""" """
Prüft ob ein Punkt innerhalb eines Rechtecks liegt. Prüft ob ein Punkt innerhalb eines Rechtecks liegt.
Args: Args:
point: (x, y) Tupel point: (x, y) Tupel
rect_x, rect_y: Linke obere Ecke des Rechtecks rect_x, rect_y: Linke obere Ecke des Rechtecks
rect_width, rect_height: Breite und Höhe des Rechtecks rect_width, rect_height: Breite und Höhe des Rechtecks
""" """
x, y = point x, y = point
return (rect_x <= x <= rect_x + rect_width and return (rect_x <= x <= rect_x + rect_width and
rect_y <= y <= rect_y + rect_height) rect_y <= y <= rect_y + rect_height)
def _calculate_shift_to_rectangle(self, point, rect_x, rect_y, rect_width, rect_height):
"""
Berechnet den minimalen Verschiebungsvektor, um einen Punkt in ein Rechteck zu verschieben.
Args:
point: (x, y) Tupel
rect_x, rect_y: Linke untere Ecke des Rechtecks
rect_width, rect_height: Breite und Höhe des Rechtecks
Returns:
(delta_x, delta_y) - Verschiebungsvektor
"""
x, y = point
delta_x = 0
delta_y = 0
# Berechne Verschiebung in x-Richtung
if x < rect_x:
delta_x = rect_x - x
elif x > rect_x + rect_width:
delta_x = (rect_x + rect_width) - x
# Berechne Verschiebung in y-Richtung
if y < rect_y:
delta_y = rect_y - y
elif y > rect_y + rect_height:
delta_y = (rect_y + rect_height) - y
return (delta_x, delta_y)
def _point_in_polygon(self, point, polygon): def _point_in_polygon(self, point, polygon):
""" """
Prüft, ob ein Punkt innerhalb eines Polygons liegt (Ray-casting Algorithmus). Prüft, ob ein Punkt innerhalb eines Polygons liegt (Ray-casting Algorithmus).
@@ -736,34 +803,109 @@ class TestDataGenerator:
return inside return inside
def _circle_intersects_polyline(self, center, radius, polyline):
"""
Prüft ob ein Kreis mit Mittelpunkt 'center' und Radius 'radius'
irgendein Segment der Polylinie schneidet.
Args:
center: (x, y) Mittelpunkt des Kreises
radius: Radius des Kreises
polyline: Liste von (x, y) Punkten der Polylinie
Returns:
True wenn der Kreis die Polylinie schneidet
"""
cx, cy = center
# Prüfe jedes Segment der Polylinie
for i in range(len(polyline) - 1):
p1 = polyline[i]
p2 = polyline[i + 1]
# Berechne den kürzesten Abstand vom Kreis-Mittelpunkt zum Linien-Segment
dist = self._distance_point_to_segment(center, p1, p2)
# Wenn Abstand <= Radius, dann gibt es eine Schnittmenge
if dist <= radius:
return True
return False
def _distance_point_to_segment(self, point, seg_start, seg_end):
"""
Berechnet den kürzesten Abstand von einem Punkt zu einem Linien-Segment.
Args:
point: (x, y) Punkt
seg_start: (x, y) Start des Segments
seg_end: (x, y) Ende des Segments
Returns:
Kürzester Abstand
"""
px, py = point
x1, y1 = seg_start
x2, y2 = seg_end
# Vektor von seg_start zu seg_end
dx = x2 - x1
dy = y2 - y1
# Spezialfall: Segment ist ein Punkt
if dx == 0 and dy == 0:
return ((px - x1)**2 + (py - y1)**2)**0.5
# Parameter t für die Projektion des Punktes auf die Linie
# t = 0 bedeutet seg_start, t = 1 bedeutet seg_end
t = max(0, min(1, ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy)))
# Nächster Punkt auf dem Segment
closest_x = x1 + t * dx
closest_y = y1 + t * dy
# Abstand vom Punkt zum nächsten Punkt auf dem Segment
return ((px - closest_x)**2 + (py - closest_y)**2)**0.5
def _validate_symbols_in_frames(self, scene): def _validate_symbols_in_frames(self, scene):
""" """
Prüft ob alle generierten Symbole aus ma_groups in mindestens einem Renamer-Frame liegen. Prüft ob alle generierten Symbole aus ma_groups in mindestens einem Renamer-Frame liegen.
Gibt Warnungen aus für Symbole, die außerhalb liegen.
Für POLYLINE_PATH Frames: Prüft ob die Polylinie einen Kreis um das Symbol schneidet
Für andere Frames: Prüft ob das Symbol innerhalb des Frames liegt
Gibt Warnungen aus für Symbole, die nicht die Kriterien erfüllen.
""" """
if not self.generated_symbols: if not self.generated_symbols:
return return
print("\n" + "="*60) print("\n" + "="*60)
print("Prüfe ob alle generierten Symbole in Renamer-Frames liegen") print("Prüfe ob alle generierten Symbole in Renamer-Frames liegen")
print("="*60) print("="*60)
# Hole Symbol-Dimensionen aus Config
dimensions = self.config.get('ma_defaults', {}).get('dimensions', {})
width_per_char = dimensions.get('width_per_char', 201.49)
fixed_height = dimensions.get('fixed_height', 380.94)
# Sammle alle Frame-Boundaries # Sammle alle Frame-Boundaries
frame_boundaries = [] frame_boundaries = []
for frame_config in scene.get('renaming_frames', []): for frame_config in scene.get('renaming_frames', []):
frame_type = frame_config.get('type', 'rectangle') frame_type = frame_config.get('type', 'rectangle')
direction = frame_config.get('direction', '')
layer_name = frame_config.get('layer_name', '') layer_name = frame_config.get('layer_name', '')
if frame_type == 'polyline_path': if frame_type == 'polyline_path' or direction == 'POLYLINE_PATH':
# Polyline-Pfad: verwende path_points direkt # Polyline-Pfad: verwende path_points direkt
path_points = frame_config.get('path_points', []) path_points = frame_config.get('path_points', [])
if path_points: if path_points:
polygon = [(p['x'], p['y']) for p in path_points] polyline = [(p['x'], p['y']) for p in path_points]
frame_boundaries.append({ frame_boundaries.append({
'type': 'polygon', 'validation_type': 'polyline_path',
'boundary': polygon, 'polyline': polyline,
'name': frame_config.get('name', 'UNKNOWN'), 'name': frame_config.get('name', 'UNKNOWN'),
'layer_name': layer_name 'layer_name': layer_name,
'direction': direction
}) })
else: else:
# Rechteck: berechne Eckpunkte # Rechteck: berechne Eckpunkte
@@ -771,7 +913,7 @@ class TestDataGenerator:
rect_y = frame_config.get('y', 0) rect_y = frame_config.get('y', 0)
rect_width = frame_config.get('width', 0) rect_width = frame_config.get('width', 0)
rect_height = frame_config.get('height', 0) rect_height = frame_config.get('height', 0)
# Erstelle Rechteck als Polygon (4 Eckpunkte) # Erstelle Rechteck als Polygon (4 Eckpunkte)
polygon = [ polygon = [
(rect_x, rect_y), (rect_x, rect_y),
@@ -780,29 +922,60 @@ class TestDataGenerator:
(rect_x, rect_y + rect_height) (rect_x, rect_y + rect_height)
] ]
frame_boundaries.append({ frame_boundaries.append({
'type': 'rectangle', 'validation_type': 'containment',
'boundary': polygon, 'boundary': polygon,
'rect_x': rect_x, 'rect_x': rect_x,
'rect_y': rect_y, 'rect_y': rect_y,
'rect_width': rect_width, 'rect_width': rect_width,
'rect_height': rect_height, 'rect_height': rect_height,
'name': frame_config.get('name', 'UNKNOWN'), 'name': frame_config.get('name', 'UNKNOWN'),
'layer_name': layer_name 'layer_name': layer_name,
'direction': direction
}) })
# Prüfe jedes generierte Symbol # Prüfe jedes generierte Symbol
symbols_outside = [] symbols_outside = []
for symbol in self.generated_symbols: for symbol in self.generated_symbols:
symbol_point = (symbol['x'], symbol['y']) # Berechne Symbol-Dimensionen
io_text = symbol['io']
symbol_width = len(io_text) * width_per_char
symbol_height = fixed_height
# Berechne Mittelpunkt des Symbols
# symbol['x'], symbol['y'] ist die linke obere Ecke
symbol_center_x = symbol['x'] + symbol_width / 2
symbol_center_y = symbol['y'] - symbol_height / 2
symbol_point = (symbol_center_x, symbol_center_y)
# Radius für Kreis-Test
symbol_radius = max(symbol_width, symbol_height) / 2
in_any_frame = False in_any_frame = False
validation_type_used = None
closest_frame = None
min_shift_distance = float('inf')
best_shift = (0, 0)
for frame in frame_boundaries: for frame in frame_boundaries:
# Prüfe ob Symbol auf dem richtigen Layer ist # Prüfe ob Symbol auf dem richtigen Layer ist
if frame['layer_name'] and symbol['layer'] != frame['layer_name']: if frame['layer_name'] and symbol['layer'] != frame['layer_name']:
continue continue
# Prüfe ob Symbol innerhalb des Frames liegt # Speichere den Validierungstyp für Fehlermeldung
if frame['type'] == 'rectangle': if validation_type_used is None:
validation_type_used = frame['validation_type']
# Wähle Validierungsmethode basierend auf Frame-Typ
if frame['validation_type'] == 'polyline_path':
# POLYLINE_PATH: Prüfe ob Polylinie den Kreis um das Symbol schneidet
if self._circle_intersects_polyline(symbol_point, symbol_radius, frame['polyline']):
in_any_frame = True
break
# Für Polyline-Pfade: Berechne minimalen Abstand zur Polylinie
# (vereinfacht: nicht implementiert, da komplex)
else:
# Containment: Prüfe ob Symbol innerhalb des Frames liegt
if self._point_in_rectangle( if self._point_in_rectangle(
symbol_point, symbol_point,
frame['rect_x'], frame['rect_x'],
@@ -812,23 +985,79 @@ class TestDataGenerator:
): ):
in_any_frame = True in_any_frame = True
break break
else: # polygon
if self._point_in_polygon(symbol_point, frame['boundary']): # Berechne Verschiebung für diesen Frame
in_any_frame = True shift_x, shift_y = self._calculate_shift_to_rectangle(
break symbol_point,
frame['rect_x'],
frame['rect_y'],
frame['rect_width'],
frame['rect_height']
)
shift_distance = (shift_x**2 + shift_y**2)**0.5
# Speichere den nächsten Frame
if shift_distance < min_shift_distance:
min_shift_distance = shift_distance
best_shift = (shift_x, shift_y)
closest_frame = frame
if not in_any_frame: if not in_any_frame:
symbols_outside.append(symbol) symbol_with_type = symbol.copy()
symbol_with_type['validation_type'] = validation_type_used
# Gib Warnungen aus symbol_with_type['symbol_radius'] = symbol_radius
symbol_with_type['shift_x'] = best_shift[0]
symbol_with_type['shift_y'] = best_shift[1]
symbol_with_type['closest_frame'] = closest_frame.get('name', 'UNKNOWN') if closest_frame else 'UNKNOWN'
symbols_outside.append(symbol_with_type)
# Gib Warnungen aus und zeichne Fehlerkreise
if symbols_outside: if symbols_outside:
print(f"\nWARNUNG: {len(symbols_outside)} Symbol(e) liegen außerhalb aller Renamer-Frames:") print(f"\nWARNUNG: {len(symbols_outside)} Symbol(e) erfüllen nicht die Frame-Kriterien:")
for symbol in symbols_outside: for symbol in symbols_outside:
print(f" Symbol IO='{symbol['io']}' aus Gruppe '{symbol['group_name']}' " base_msg = (f" Symbol IO='{symbol['io']}' aus Gruppe '{symbol['group_name']}' "
f"an Position ({symbol['x']:.1f}, {symbol['y']:.1f}) " f"an Position ({symbol['x']:.1f}, {symbol['y']:.1f}) "
f"auf Layer '{symbol['layer']}' liegt in keinem Renamer-Frame") f"auf Layer '{symbol['layer']}'")
if symbol.get('validation_type') == 'polyline_path':
print(f"{base_msg}: "
f"Die Polylinie schneidet nicht den Umkreis des Symbols "
f"(Radius: {symbol['symbol_radius']:.1f})")
else:
shift_x = symbol.get('shift_x', 0)
shift_y = symbol.get('shift_y', 0)
closest_frame = symbol.get('closest_frame', 'UNKNOWN')
if shift_x != 0 or shift_y != 0:
print(f"{base_msg}: "
f"Symbol liegt nicht innerhalb des Renamer-Rahmens '{closest_frame}'. "
f"Verschieben Sie um dx={shift_x:.1f}, dy={shift_y:.1f}")
else:
print(f"{base_msg}: "
f"Symbol liegt nicht innerhalb des Renamer-Rahmens")
# Zeichne Fehlerkreis um das Symbol
self._draw_error_circle(symbol['x'], symbol['y'], symbol['symbol_radius'])
else: else:
print(f"\n Alle {len(self.generated_symbols)} generierten Symbole liegen in Renamer-Frames") print(f"\nOK: Alle {len(self.generated_symbols)} generierten Symbole erfüllen die Frame-Kriterien")
def _draw_error_circle(self, center_x, center_y, radius):
"""
Zeichnet einen roten Kreis um ein fehlerhaftes Symbol in die DXF-Datei.
Args:
center_x: X-Koordinate des Kreismittelpunkts
center_y: Y-Koordinate des Kreismittelpunkts
radius: Radius des Kreises
"""
self.msp.add_circle(
center=(center_x, center_y),
radius=radius,
dxfattribs={
'layer': 'VALIDATION_ERROR',
'color': 1 # Rot
}
)
@@ -858,8 +1087,42 @@ class TestDataGenerator:
def main(): def main():
"""Hauptfunktion mit Argparse""" """Hauptfunktion mit Argparse"""
# Versuche Config zu lesen für --help Ausgabe
default_config = 'create_tests.json'
config_path = Path(PROJECT_CFG) / default_config
available_scenes = []
scene_descriptions = {}
if config_path.exists():
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
if 'test_scenes' in config:
for scene_name, scene_data in config['test_scenes'].items():
available_scenes.append(scene_name)
if 'description' in scene_data:
scene_descriptions[scene_name] = scene_data['description']
except Exception:
pass # Ignoriere Fehler beim Lesen für --help
# Erstelle Epilog mit verfügbaren Szenen
epilog_text = "\nVerfügbare Test-Szenen:\n"
if available_scenes:
for scene in sorted(available_scenes):
desc = scene_descriptions.get(scene, '')
if desc:
epilog_text += f" {scene}: {desc}\n"
else:
epilog_text += f" {scene}\n"
epilog_text += f"\n(aus Konfigurationsdatei: {config_path})"
else:
epilog_text += " (keine Szenen konfiguriert oder Config-Datei nicht gefunden)\n"
epilog_text += f" Standard-Pfad: {config_path}"
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Erzeugt Test-DXF-Dateien für verschiedene Testszenarien' description='Erzeugt Test-DXF-Dateien für verschiedene Testszenarien',
epilog=epilog_text,
formatter_class=argparse.RawDescriptionHelpFormatter
) )
parser.add_argument( parser.add_argument(
@@ -886,8 +1149,8 @@ def main():
parser.add_argument( parser.add_argument(
'--config', '--config',
type=str, type=str,
default='create_tests.json', default=default_config,
help='JSON-Konfigurationsdatei (Standard: create_tests.json)' help=f'JSON-Konfigurationsdatei (Standard: {default_config})'
) )
args = parser.parse_args() args = parser.parse_args()
+30 -7
View File
@@ -693,7 +693,35 @@ def process_renamer_blocks(doc, msp, renamer_layers, error_collector):
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Nummeriert Symbole in DXF-Dateien basierend auf Renamer-Blöcken', prog='create_numbers') # Lese Umgebungsvariablen vor argparse, um Config-Info in Hilfe anzuzeigen
out_dir = check_environment_var('PROJECT_DATA')
work_dir = check_environment_var('PROJECT_WORK')
config_dir = check_environment_var("PROJECT_CFG")
# Versuche Config zu lesen für --help Ausgabe
config_path = Path(config_dir) / "enumerate.cfg"
available_layers = []
if config_path.exists():
try:
available_layers = read_config_layers(config_path)
except Exception:
pass # Ignoriere Fehler beim Lesen für --help
# Erstelle Epilog mit verfügbaren Layern
epilog_text = "\nKonfigurierte Renamer-Layer:\n"
if available_layers:
epilog_text += " " + ", ".join(available_layers) + "\n"
epilog_text += f"\n(aus Konfigurationsdatei: {config_path})"
else:
epilog_text += " (keine Layer konfiguriert oder Config-Datei nicht gefunden)\n"
epilog_text += f" Standard-Pfad: {config_path}"
parser = argparse.ArgumentParser(
description='Nummeriert Symbole in DXF-Dateien basierend auf Renamer-Blöcken',
prog='create_numbers',
epilog=epilog_text,
formatter_class=argparse.RawDescriptionHelpFormatter
)
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('-w', '--write', action='store', help='Schreibe Ergebnisse der Nummerierung in eine JSON-Datei')
@@ -701,10 +729,6 @@ if __name__ == '__main__':
args = parser.parse_args() args = parser.parse_args()
out_dir = check_environment_var('PROJECT_DATA')
work_dir = check_environment_var('PROJECT_WORK')
config_dir = check_environment_var("PROJECT_CFG")
# Initialisiere ErrorCollector # Initialisiere ErrorCollector
error_collector = ErrorCollector() error_collector = ErrorCollector()
@@ -719,8 +743,7 @@ if __name__ == '__main__':
parser.print_help() parser.print_help()
exit(1) exit(1)
# Lese Konfiguration # Config-Datei existiert bereits (wurde für --help geprüft)
config_path = Path(config_dir) / "enumerate.cfg"
if not config_path.exists(): if not config_path.exists():
print(f"Konfigurationsdatei nicht gefunden: {config_path}") print(f"Konfigurationsdatei nicht gefunden: {config_path}")
exit(1) exit(1)