This commit is contained in:
2026-04-15 17:44:01 +02:00
4 changed files with 387 additions and 115 deletions
+283
View File
@@ -0,0 +1,283 @@
# Symbole und Benennungen im DXF-Layout
Diese Dokumentation beschreibt, wie Symbole und Texte in der DXF-Zeichnung benannt und strukturiert sein muessen, damit sie vom System erkannt werden.
---
## 1. Sensoren / Aktoren / Motoren (Equipment)
Equipment wird ueber **INSERT-Bloecke mit Attributen** erkannt.
### Block-Attribute
| Attribut-Tag | Bedeutung | Beispiel | Pflicht |
|---|---|---|---|
| `NAME` | Geraete-ID (moderner Block) | `MA0062`, `BG3240` | Ja (oder IO+B) |
| `IO` | Geraete-ID (I/O-Block, Legacy) | `MA0062` | Alternativ zu NAME |
| `B` | Geraete-ID (Technik-Block, Legacy) | `MA0062` | Alternativ zu NAME |
| `ARTINR` | SIVAS-Artikelnummer | `790902001` | Ja |
| `KENNZEICHNUNG` | Routing-Adresse | `=A01+UH01-KF1DQ04` | Ja |
| `SPS` | SPS-Praefix / Anlage | `1` | Ja (fuer Zuordnung) |
| `VERW` | Verwendung/Beschreibung | `CV-M0062_0,75` | Nein |
| `REALE_POSITION` | Marker fuer tatsaechliche Position | `x` | Nein |
### Geraete-Praefix (erste 2 Zeichen der ID)
Welche Geraete geroutet werden, bestimmt die `cfg/BMK.cfg`:
**Routing-Include** (werden geroutet):
| Praefix | Typ |
|---|---|
| `MA` | Motoren |
| `MB` | Ventile |
| `QM` | Ventile/Pumpen |
| `BG` | Sensoren/Naeherungsschalter |
| `BP` | Naeherungsschalter |
| `BX` | Mehrzweckgeraete |
| `PO` | Pneumatik-Elemente |
| `SF` | Sicherheitselemente |
| `PF` | Programmierbare Logik |
**Routing-Ignore** (Schaltschrankelemente, nicht geroutet):
| Praefix | Typ |
|---|---|
| `FC` | Steuerungselemente |
| `UH` | Unterverteiler |
| `UC` | Unterverteiler |
| `UZ` | Spezialverteiler |
| `DI` | Digitale Eingaenge |
| `DQ` | Digitale Ausgaenge |
| `QA` | Ausgabegeraete |
### KENNZEICHNUNG-Format
```
=ANLAGE+VERTEILER-KARTE
```
**Beispiel:** `=A01+UH01-KF1DQ04`
| Teil | Bedeutung | Beispiel |
|---|---|---|
| ANLAGE | Anlagengruppe | `A01` |
| VERTEILER | Unterverteiler-ID | `UH01` |
| KARTE | Karte/Kanal | `KF1DQ04` |
Trennzeichen: `=` (Start), `+` (zwischen Anlage und Verteiler), `-` (zwischen Verteiler und Karte)
### Block-Zusammenfuehrung (Legacy Dual-Block)
Bei der alten Dual-Block-Struktur werden zwei Bloecke mit gleicher ID (z.B. `MA0062`) zusammengefuehrt, wenn:
- Beide innerhalb von **1000mm** Abstand liegen
- Einer das `SPS`-Attribut hat, der andere nicht
- Ergebnis-ID: `{id}@{sps}` (z.B. `MA0062@1`)
---
## 2. Unterverteiler (Distributoren)
Unterverteiler koennen auf **zwei Arten** definiert werden: als Text oder als Symbol.
### Variante A: Text (MTEXT)
Ein MTEXT-Element auf einem erlaubten Layer mit folgendem Muster:
```
-UH01 (Minus vor dem Namen)
+UH01 (Plus vor dem Namen)
```
Der Text muss den Verteilernamen als Teilstring enthalten, mit `-` oder `+` davor.
**Erlaubte Layer** (konfiguriert in `cfg/allgemein.cfg`, Sektion `GetPos-Layer_Distributors`):
- `Busverteiler-Kennzeichnung`
- `0-0_ILS_Busverteiler-Kennzeichnung`
- `0-0_ILS_UNTERVERTEILER`
- `0-0_ILS_Unterverteiler`
- `0-0_Omniflo_UNTERVERTEILER`
- `0-0_Omniflo_Unterverteiler`
- `UNTERVERTEILER`
- `0-0_Omniflo_Busverteiler-Kennzeichnung`
- `Schaltschrank-ILS`
### Variante B: Symbol (INSERT-Block)
Ein INSERT-Block mit dem Attribut `NAME`, dessen Wert einem **Cabinet-Pattern** entspricht.
**Cabinet-Patterns** (Regex, konfiguriert in `cfg/BMK.cfg`, Sektion `Cabinet-Pattern`):
| Pattern | Beispiel-Match | Extrahierte ID |
|---|---|---|
| `A\d\d\+(UH0\d)` | `A01+UH01` | `UH01` |
| `A\d\d\+(UC\d\d\d)` | `A01+UC001` | `UC001` |
| `\+(UC\d\d\d\d)` | `+UC0101` | `UC0101` |
| `\+(UH\d\d)` | `+UH01` | `UH01` |
Die Klammer-Gruppe im Regex bestimmt die extrahierte Verteiler-ID.
**Optionale Attribute:**
- `REALE_POSITION` = `x` : Position wird aus Attribut-Position berechnet (Mittelpunkt des Markers)
- `SPS` : SPS-Zuordnung
---
## 3. Tunnel
Tunnel koennen ebenfalls als **Text** oder **Symbol** definiert werden.
### Variante A: Text (MTEXT)
Ein MTEXT-Element auf einem erlaubten Layer mit folgendem Muster:
```
TUNNEL1-5 (Tunnelname-Laenge)
TUNNEL2-10
```
**Regex:** `(TUNNEL\d+)-(\d+)`
| Teil | Bedeutung | Beispiel |
|---|---|---|
| Gruppe 1 | Tunnelname | `TUNNEL1` |
| Gruppe 2 | Laenge in Metern | `5` |
Pro Tunnel muessen genau **2 MTEXT-Elemente** vorhanden sein (Ein- und Ausgang).
**Erlaubte Layer** (konfiguriert in `cfg/allgemein.cfg`, Sektion `GetPos-Layer_Tunnel`):
- `Busverteiler-Kennzeichnung`
- `0-0_Tunnel`
### Variante B: Symbol (INSERT-Block)
Ein INSERT-Block mit dem Attribut `NAME`, dessen Wert einem **Tunnel-Pattern** entspricht.
**Tunnel-Patterns** (Regex, konfiguriert in `cfg/BMK.cfg`, Sektion `Tunnel-Pattern`):
| Pattern | Beispiel |
|---|---|
| `Tunnel\d+` | `Tunnel1`, `Tunnel2` |
| `Tunnel_\d+` | `Tunnel_1`, `Tunnel_2` |
| `Tunnel-\d+` | `Tunnel-1`, `Tunnel-2` |
| `TUNNEL\d+` | `TUNNEL1`, `TUNNEL2` |
**Attribute:**
| Attribut-Tag | Bedeutung | Beispiel | Pflicht |
|---|---|---|---|
| `NAME` | Tunnelname | `Tunnel1` | Ja |
| `LAENGE` | Tunnellaenge in Metern | `5` | Nein (Default: 5) |
| `REALE_POSITION` | Positionsmarker | `x` | Nein |
Pro Tunnel muessen genau **2 Symbole** platziert werden (Ein- und Ausgang).
**Fehler bei Tunnel-Definition:**
- Weniger als 2 Positionen: Fehler `missing_tunnel`
- Mehr als 2 Positionen: Fehler `overdefined_tunnel`
- Fehlende Laenge: Warnung, Default 5m wird verwendet
---
## 4. Kabelpritschen (Racks)
Kabelpritschen werden als **LWPOLYLINE** oder **POLYLINE** auf bestimmten Layern erkannt.
Es sind keine Attribute oder Texte noetig - nur der **Layer** entscheidet.
**Erlaubte Layer** (konfiguriert in `cfg/allgemein.cfg`, Sektion `GetPos-Layer_Racks`):
| Layer | Beschreibung |
|---|---|
| `PRITSCHE_100-60` | Standard 100x60 |
| `PRITSCHE_100-60-SCHRAFF` | Standard 100x60 schraffiert |
| `PRITSCHE_200-60` | Standard 200x60 |
| `PRITSCHE_200-60_ILS` | ILS 200x60 |
| `PRITSCHE_200-60_OMNIFLO` | Omniflo 200x60 |
| `0-0_ILS_PRITSCHE_200-60_storage_Level1` | ILS Storage Level 1 |
| `0-0_ILS_PRITSCHE_200-60_storage_Level2` | ILS Storage Level 2 |
| `0-0_ILS_PRITSCHE_200-60_Workstation` | ILS Workstation |
| `0-0_ILS_Pritsche_200-60_AMR` | ILS AMR |
| `0-0_ILS_PRITSCHE_200-60_Highway` | ILS Highway |
| `0-0_ILS_Pritsche_200-60_Inbound` | ILS Inbound |
| `0-0_Omniflo_Pritsche_200-60_Workstation-Outbound` | Omniflo Workstation Outbound |
| `0-0_Omniflo_Pritsche_200-60_outbound` | Omniflo Outbound |
| `0-0_Omniflo_Pritsche_200-60_AMR` | Omniflo AMR |
| `0-0_Omniflo_Pritsche_200-60_Highway` | Omniflo Highway |
**Validierung:** Wenn die Z-Koordinaten aller Racks mehr als 2000mm auseinanderliegen, wird eine Warnung ausgegeben (Schwellwert konfigurierbar in `cfg/allgemein.cfg`, Sektion `Racks`, Schluessel `MaximalTotalHeightDifferences`).
---
## 5. Zusammenfassung: Was muss im Layout vorhanden sein?
| Element | Erkennungsart | Mindestangaben |
|---|---|---|
| **Sensor/Motor** | INSERT-Block | `NAME` (oder `IO`+`B`), `ARTINR`, `KENNZEICHNUNG` |
| **Unterverteiler** | MTEXT **oder** INSERT-Block | Text: `-UH01` auf richtigem Layer / Block: `NAME` = `A01+UH01` |
| **Tunnel** | MTEXT **oder** INSERT-Block | Text: `TUNNEL1-5` (2x) / Block: `NAME` = `Tunnel1`, `LAENGE` = `5` (2x) |
| **Kabelpritsche** | LWPOLYLINE/POLYLINE | Auf richtigem Layer gezeichnet |
---
## 6. Funktionsreferenz (getpositions.py)
Uebersicht der zentralen Funktionen, die die Erkennung durchfuehren.
### Sensoren / Equipment
| Funktion | Zeile | Aufgabe |
|---|---|---|
| `get_attributes_of_insert(d_insert, d_pos)` | ~76 | Extrahiert ID, Typ und Position aus einem einzelnen INSERT-Block. Prueft zuerst `IO`, dann `NAME`, dann `B`. Bei `REALE_POSITION='x'` wird der Mittelpunkt aus Marker-Geometrie berechnet (Breite/Hoehe aus Config). Gibt `(dict, id, typ)` zurueck. |
| `extract_input_positions(all_inserts, all_positions, error_collector)` | ~221 | Hauptfunktion: Iteriert ueber alle Bloecke, ruft `get_attributes_of_insert()` auf, trennt Sensoren von Schaltschrankelementen anhand des Praefixes. Fuehrt Block-Zusammenfuehrung via `CompareBuffer` durch. |
| `allocate_blocks_together(...)` | ~329 | Fuehrt die Zusammenfuehrung von Dual-Bloecken durch (Legacy). Nutzt `CompareBuffer.positions_are_close()` mit 1000mm Toleranz. |
### Unterverteiler
| Funktion | Zeile | Aufgabe |
|---|---|---|
| `get_subdistributor_positions_from_entities(entities, dist2sensors)` | ~547 | **Text-Erkennung:** Iteriert ueber MTEXT-Entities auf erlaubten Layern. Sucht nach `-{name}` oder `+{name}` im Text. Gibt `{distname: (x, y)}` zurueck. |
| `get_subdistributor_positions_from_symbols(all_inserts, all_positions, dist2sensors)` | ~472 | **Symbol-Erkennung:** Iteriert ueber alle INSERT-Bloecke mit `NAME`-Attribut. Prueft NAME gegen Cabinet-Patterns aus BMK.cfg. Ruft `get_subdistributor_position_of_symbol()` fuer die Positionsextraktion auf. |
| `get_subdistributor_position_of_symbol(d_insert, d_pos)` | ~413 | Hilfsfunktion: Extrahiert Position aus einem einzelnen Unterverteiler-Block. Beruecksichtigt `REALE_POSITION` fuer Mittelpunktberechnung. Gibt `(dict, id)` zurueck. |
### Tunnel
| Funktion | Zeile | Aufgabe |
|---|---|---|
| `get_tunnel_positions_from_entities(entities)` | ~574 | **Text-Erkennung:** Iteriert ueber MTEXT-Entities auf erlaubten Layern. Regex `(TUNNEL\d+)-(\d+)` extrahiert Name und Laenge. Sammelt alle Positionen pro Tunnelname. |
| `get_tunnel_positions_from_symbols(all_inserts, all_positions, error_collector)` | ~493 | **Symbol-Erkennung:** Iteriert ueber alle INSERT-Bloecke mit `NAME`-Attribut. Prueft NAME gegen Tunnel-Patterns aus BMK.cfg. Liest `LAENGE`-Attribut (Default: 5m). Warnung bei fehlender Laenge. |
| `get_tunnel_position_of_symbol(d_insert, d_pos)` | ~441 | Hilfsfunktion: Extrahiert Position und Laenge aus einem einzelnen Tunnel-Block. Beruecksichtigt `REALE_POSITION`. Gibt `(dict, id)` zurueck. |
### Kabelpritschen
| Funktion | Zeile | Aufgabe |
|---|---|---|
| `get_rack_positions(msp)` | ~614 | Sucht LWPOLYLINE und POLYLINE auf erlaubten Layern. Nummeriert Racks automatisch (`Rack_1`, `Rack_2`, ...). Delegiert an `handle_lwpolyline()` bzw. `handle_polyline()`. |
| `handle_lwpolyline(entity, rack_key, ret)` | ~636 | Verarbeitet 2D-Polylinien. Z-Wert kommt global aus `elevation`. |
| `handle_polyline(entity, rack_key, ret)` | ~644 | Verarbeitet 3D-Polylinien. Jeder Vertex hat individuelle x/y/z-Koordinaten. |
| `check_rack_z_coordinates(res_racks, error_collector, config)` | ~654 | Validierung: Prueft ob Z-Koordinaten-Differenz den Schwellwert uebersteigt. |
### Erkennungsablauf im Hauptprogramm
```
1. DXF laden: get_dxf_file() -> msp
2. Alle Bloecke extrahieren: attribs_to_dicts(msp) -> all_inserts, all_positions
3. Sensoren: extract_input_positions(all_inserts, all_positions)
4. Mappings: create_mappings(res_sens) -> sensor2unterverteiler
5. Unterverteiler (Text): get_subdistributor_positions_from_entities(msp.query('MTEXT'))
6. Unterverteiler (Symbol): get_subdistributor_positions_from_symbols(all_inserts, all_positions)
7. Tunnel (Text): get_tunnel_positions_from_entities(msp.query('MTEXT'))
8. Tunnel (Symbol): get_tunnel_positions_from_symbols(all_inserts, all_positions)
9. Racks: get_rack_positions(msp)
```
---
## 7. Konfigurationsdateien
| Datei | Inhalt |
|---|---|
| `cfg/allgemein.cfg` | Layer-Zuordnungen, Geometrie-Parameter, Toleranzen |
| `cfg/BMK.cfg` | Geraete-Praefixe, Cabinet-Patterns, Tunnel-Patterns, Kabelzuordnungen |
| `cfg/kabel.cfg` | SIVAS-Artikelnummern nach Kabeltyp und Laenge |
| `cfg/bezeichner.cfg` | Artikelnummer-Beschreibungen |
+9 -77
View File
@@ -6,16 +6,12 @@ import re
import sys import sys
from pathlib import Path from pathlib import Path
import ezdxf
from ezdxf.addons import iterdxf
from shapely.geometry import Point from shapely.geometry import Point
from ezdxf.lldxf.const import DXFStructureError
from error_collector import ErrorCollector, write_json_file from error_collector import ErrorCollector
from utils import ( from utils import (
check_environment_var, check_environment_var,
check_file_in_work, check_file_in_work,
dxf_is_binary,
get_dxf_file, get_dxf_file,
merge_two_dicts, merge_two_dicts,
to_json, to_json,
@@ -551,10 +547,10 @@ def get_tunnel_positions_from_symbols(all_inserts: list, all_positions: list, er
def get_subdistributor_positions_from_entities(entities, dist2sensors: dict) -> dict: def get_subdistributor_positions_from_entities(entities, dist2sensors: dict) -> dict:
"""Ermittelt Unterverteiler-Positionen aus einer beliebigen Entity-Iterable. """Ermittelt Unterverteiler-Positionen aus einer beliebigen Entity-Iterable.
Erwartet eine Iterable von DXF-Entities (z. B. aus `msp.query("MTEXT")` oder Erwartet eine Iterable von DXF-Entities (z. B. aus `msp.query("MTEXT")`)
`iterdxf.modelspace(dxf_path)`) und filtert nach den in der Config erlaubten und filtert nach den in der Config erlaubten Layern. Es werden beide bisher
Layern. Es werden beide bisher verwendeten Suchmuster in der MTEXT-Zeile verwendeten Suchmuster in der MTEXT-Zeile unterstützt ("-<distname>" und
unterstützt ("-<distname>" und "+<distname>"). "+<distname>").
""" """
ret = {} ret = {}
all_distributors = dist2sensors.keys() all_distributors = dist2sensors.keys()
@@ -578,10 +574,10 @@ def get_subdistributor_positions_from_entities(entities, dist2sensors: dict) ->
def get_tunnel_positions_from_entities(entities) -> dict: def get_tunnel_positions_from_entities(entities) -> dict:
"""Ermittelt Tunnel-Ein/Ausgangs-Positionen aus einer beliebigen Entity-Iterable. """Ermittelt Tunnel-Ein/Ausgangs-Positionen aus einer beliebigen Entity-Iterable.
Erwartet eine Iterable von DXF-Entities (z. B. aus `msp.query("MTEXT")` oder Erwartet eine Iterable von DXF-Entities (z. B. aus `msp.query("MTEXT")`)
`iterdxf.modelspace(dxf_path)`) und filtert nach den in der Config erlaubten und filtert nach den in der Config erlaubten Layern. Erkennt Tunnel anhand
Layern. Erkennt Tunnel anhand des Musters "TUNNEL<nr>-<laenge>" und sammelt des Musters "TUNNEL<nr>-<laenge>" und sammelt pro Tunnelname die gefundenen
pro Tunnelname die gefundenen Positionen sowie die Länge. Positionen sowie die Länge.
""" """
all_tunnels = dict() all_tunnels = dict()
tunnel_length = dict() tunnel_length = dict()
@@ -614,17 +610,6 @@ def get_tunnel_positions_from_entities(entities) -> dict:
all_tunnels['length'] = tunnel_length all_tunnels['length'] = tunnel_length
return all_tunnels return all_tunnels
# helper function
def print_line(e) -> None:
print(f"LINE on layer: {e.dxf.layer}\n")
print(f"points: {repr(e.dxf)}\n")
def print_polyline(e) -> None:
print(f"POLYLINE on layer: {e.dxf.layer}\n")
for x, y, start_width, end_width, bulge in e.get_points():
print(f" Punkt: ({x}, {y}), Startbreite: ({start_width}, Endbreite: {end_width})")
if e.is_closed:
print("Diese Polyline ist geschlossen.")
def get_rack_positions(msp) -> dict: def get_rack_positions(msp) -> dict:
"""Hole alle Positionen aller Kabelpritschen und nummeriere Racks.""" """Hole alle Positionen aller Kabelpritschen und nummeriere Racks."""
@@ -647,30 +632,6 @@ def get_rack_positions(msp) -> dict:
return ret return ret
def get_rack_positions_iter(dxf_path) -> dict:
"""Hole alle Positionen aller Kabelpritschen (Racks) mithilfe von iterdxf."""
ret = dict()
rack_counter = 1
all_layers = config.items('GetPos-Layer_Racks')
for entity in iterdxf.modelspace(dxf_path):
layer = entity.dxf.layer
if not any(layer == cfg_layer for cfg_layer, _ in all_layers):
continue
rack_key = f"Rack_{rack_counter}"
if entity.dxftype() == "LWPOLYLINE":
handle_lwpolyline(entity, rack_key, ret)
elif entity.dxftype() == "POLYLINE":
handle_polyline(entity, rack_key, ret)
else:
continue
rack_counter += 1
return ret
def handle_lwpolyline(entity, rack_key: str, ret: dict) -> None: def handle_lwpolyline(entity, rack_key: str, ret: dict) -> None:
"""Verarbeitet eine 2D LWPOLYLINE mit globalem Z-Wert (elevation).""" """Verarbeitet eine 2D LWPOLYLINE mit globalem Z-Wert (elevation)."""
@@ -689,15 +650,6 @@ def handle_polyline(entity, rack_key: str, ret: dict) -> None:
z = vertex.dxf.location.z z = vertex.dxf.location.z
ret[rack_key].append([round(x, 1), round(y, 1), round(z, 1)]) ret[rack_key].append([round(x, 1), round(y, 1), round(z, 1)])
def scan(dxf_source) -> dict:
layer_names_inside = list(dxf_source.layers.names())
alle_block_defs = set(dxf_source.blocks.block_names())
used_block_names = set(insert.dxf.name for insert in dxf_source.modelspace().query("INSERT"))
ret = dict()
ret['all_layers'] = layer_names_inside
ret['used_blocks'] = used_block_names
ret['all_blocks'] = alle_block_defs
return ret
def check_rack_z_coordinates(res_racks: dict, error_collector, config) -> None: def check_rack_z_coordinates(res_racks: dict, error_collector, config) -> None:
""" """
@@ -828,17 +780,10 @@ if __name__ == '__main__':
parser.print_help() parser.print_help()
exit() exit()
if dxf_is_binary(dxf_path): # Wenn dxf eine binary ist, dann komplett parsen und modelspace anlegen
print("Given .dxf-file is binary dxf. Proceeding to read file. Watch RAM-usage.")
doc = get_dxf_file(dxf_path) doc = get_dxf_file(dxf_path)
msp = doc.modelspace() msp = doc.modelspace()
use_iter = False
else:
print("Given .dxf-file is ASCII-dxf. Proceeding to use iterative functions. Process may take longer.")
use_iter = True
res_sens = dict() res_sens = dict()
res_cables = dict()
res_dist = dict() res_dist = dict()
res_rac = dict() res_rac = dict()
res_mappings = dict() res_mappings = dict()
@@ -870,15 +815,11 @@ if __name__ == '__main__':
if args.sensors: if args.sensors:
# Sensoren auslesen aus den Symbolen # Sensoren auslesen aus den Symbolen
if use_iter:
all_inserts, all_positions = attribs_to_dicts(iterdxf.modelspace(dxf_path))
else:
all_inserts, all_positions = attribs_to_dicts(msp) all_inserts, all_positions = attribs_to_dicts(msp)
res_sens, res_schaltschrank_elemente = extract_input_positions(all_inserts, all_positions, error_collector) res_sens, res_schaltschrank_elemente = extract_input_positions(all_inserts, all_positions, error_collector)
output_results['sensors'] = res_sens output_results['sensors'] = res_sens
output_results['schaltschrank_elemente'] = res_schaltschrank_elemente output_results['schaltschrank_elemente'] = res_schaltschrank_elemente
#output_results['cables'] = res_cables
if args.console: if args.console:
print(to_json(res_sens)) print(to_json(res_sens))
@@ -893,9 +834,6 @@ if __name__ == '__main__':
print(to_json(res_mappings)) print(to_json(res_mappings))
# Distributoren auslesen (generisch über Entities) # Distributoren auslesen (generisch über Entities)
if use_iter:
entities = iterdxf.modelspace(dxf_path)
else:
entities = msp.query('MTEXT') entities = msp.query('MTEXT')
# die Infos aus den Texten (alter Stil) # die Infos aus den Texten (alter Stil)
res_dist = get_subdistributor_positions_from_entities(entities, res_mappings) res_dist = get_subdistributor_positions_from_entities(entities, res_mappings)
@@ -907,9 +845,6 @@ if __name__ == '__main__':
print(to_json(res_dist)) print(to_json(res_dist))
# Tunnel auslesen (generisch über Entities) # Tunnel auslesen (generisch über Entities)
if use_iter:
t_entities = iterdxf.modelspace(dxf_path)
else:
t_entities = msp.query('MTEXT') t_entities = msp.query('MTEXT')
# die Infos aus den Texten (alter Stil) # die Infos aus den Texten (alter Stil)
res_tunnel = get_tunnel_positions_from_entities(t_entities) res_tunnel = get_tunnel_positions_from_entities(t_entities)
@@ -921,9 +856,6 @@ if __name__ == '__main__':
print(to_json(res_tunnel)) print(to_json(res_tunnel))
if args.rack: if args.rack:
if use_iter:
res_rac = get_rack_positions_iter(dxf_path)
else:
res_rac = get_rack_positions(msp) res_rac = get_rack_positions(msp)
output_results['racks'] = res_rac output_results['racks'] = res_rac
+13 -4
View File
@@ -4,6 +4,7 @@
Test für die check_rack_z_coordinates Funktion Test für die check_rack_z_coordinates Funktion
""" """
import configparser
import sys import sys
from pathlib import Path from pathlib import Path
@@ -13,6 +14,14 @@ sys.path.insert(0, str(Path(__file__).parent))
from getpositions import check_rack_z_coordinates from getpositions import check_rack_z_coordinates
from error_collector import ErrorCollector from error_collector import ErrorCollector
def _make_config(max_height_diff=2000.0):
"""Erzeugt ein minimales Config-Objekt für die Tests."""
config = configparser.ConfigParser()
config.add_section("Racks")
config.set("Racks", "MaximalTotalHeightDifferences", str(max_height_diff))
return config
def test_no_deviation(): def test_no_deviation():
"""Test mit Racks ohne starke Abweichung (< 2000mm)""" """Test mit Racks ohne starke Abweichung (< 2000mm)"""
print("Test 1: Keine starke Abweichung (< 2000mm)") print("Test 1: Keine starke Abweichung (< 2000mm)")
@@ -24,7 +33,7 @@ def test_no_deviation():
} }
error_collector = ErrorCollector() error_collector = ErrorCollector()
check_rack_z_coordinates(racks, error_collector) check_rack_z_coordinates(racks, error_collector, _make_config())
warnings = error_collector.warnings warnings = error_collector.warnings
if warnings: if warnings:
@@ -44,7 +53,7 @@ def test_with_deviation():
} }
error_collector = ErrorCollector() error_collector = ErrorCollector()
check_rack_z_coordinates(racks, error_collector) check_rack_z_coordinates(racks, error_collector, _make_config())
warnings = error_collector.warnings warnings = error_collector.warnings
if warnings: if warnings:
@@ -63,7 +72,7 @@ def test_exactly_2000mm():
} }
error_collector = ErrorCollector() error_collector = ErrorCollector()
check_rack_z_coordinates(racks, error_collector) check_rack_z_coordinates(racks, error_collector, _make_config())
warnings = error_collector.warnings warnings = error_collector.warnings
if warnings: if warnings:
@@ -82,7 +91,7 @@ def test_dict_format():
} }
error_collector = ErrorCollector() error_collector = ErrorCollector()
check_rack_z_coordinates(racks, error_collector) check_rack_z_coordinates(racks, error_collector, _make_config())
warnings = error_collector.warnings warnings = error_collector.warnings
if warnings: if warnings:
+73 -25
View File
@@ -517,10 +517,10 @@ def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]:
return wildcard_patterns, regex_patterns return wildcard_patterns, regex_patterns
def translate_text(text: str, multi: dict[str, str], trigramme: dict[str, str], bigramme: dict[str, str], single: dict[str, str]) -> str: def translate_text(text: str, multi: dict[str, str], trigramme: dict[str, str], bigramme: dict[str, str], single: dict[str, str], transfer_terms: set[str] | None = None) -> str:
""" """
Übersetzt einen Text durch Ersetzen von Multi-Phrasen, Trigrammen, Bigrammen und Einzelwörtern. Übersetzt einen Text durch Ersetzen von Multi-Phrasen, Trigrammen, Bigrammen und Einzelwörtern.
Ersetzungen erfolgen in dieser Reihenfolge: Multi → Trigramme → Bigramme → Einzelwörter. Ersetzungen erfolgen in dieser Reihenfolge: Transfer → Multi → Trigramme → Bigramme → Einzelwörter.
Bei Einzelwörtern werden auch Satzzeichen und eingebettete Wörter berücksichtigt: Bei Einzelwörtern werden auch Satzzeichen und eingebettete Wörter berücksichtigt:
- "Weight:" wird zu "Hmotnost:" (Doppelpunkt bleibt erhalten) - "Weight:" wird zu "Hmotnost:" (Doppelpunkt bleibt erhalten)
@@ -529,16 +529,23 @@ def translate_text(text: str, multi: dict[str, str], trigramme: dict[str, str],
(alle Wörter in einem Token werden durchsucht, Trennzeichen bleiben erhalten) (alle Wörter in einem Token werden durchsucht, Trennzeichen bleiben erhalten)
Bindestriche im Wort (wie "Sub-distributor") bleiben Teil des Kern-Worts. Bindestriche im Wort (wie "Sub-distributor") bleiben Teil des Kern-Worts.
Transfer-Begriffe werden 1:1 übernommen (ohne Übersetzung) wenn der Text exakt übereinstimmt.
Args: Args:
text: Zu übersetzender Text text: Zu übersetzender Text
multi: Dictionary mit Multi-Wort-Phrasen (4+ Wörter) multi: Dictionary mit Multi-Wort-Phrasen (4+ Wörter)
trigramme: Dictionary mit Drei-Wort-Phrasen trigramme: Dictionary mit Drei-Wort-Phrasen
bigramme: Dictionary mit Zwei-Wort-Phrasen bigramme: Dictionary mit Zwei-Wort-Phrasen
single: Dictionary mit Einzelwörtern single: Dictionary mit Einzelwörtern
transfer_terms: Set mit Begriffen die 1:1 übernommen werden (aus [transfer] Sektion)
Returns: Returns:
Übersetzter Text (leer wenn keine Übersetzung gefunden wurde) Übersetzter Text (leer wenn keine Übersetzung gefunden wurde)
""" """
# 0. Prüfe Transfer-Begriffe (exakte Übereinstimmung, 1:1 Übernahme)
if transfer_terms is not None and text in transfer_terms:
return text
translated_text = text translated_text = text
has_translation = False has_translation = False
@@ -1668,7 +1675,8 @@ def _retranslate_language_block(
trigramme: dict[str, str], trigramme: dict[str, str],
bigramme: dict[str, str], bigramme: dict[str, str],
single: dict[str, str], single: dict[str, str],
label: str) -> tuple[int, int, int]: label: str,
transfer_terms: set[str] | None = None) -> tuple[int, int, int, int]:
""" """
Verarbeitet einen einzelnen Sprach-Block einer JSON-Übersetzungsdatei in-place. Verarbeitet einen einzelnen Sprach-Block einer JSON-Übersetzungsdatei in-place.
@@ -1687,6 +1695,8 @@ def _retranslate_language_block(
Einträge mit bereits gesetztem target (manuell befüllt) werden Einträge mit bereits gesetztem target (manuell befüllt) werden
ebenfalls direkt nach translations_list übernommen. ebenfalls direkt nach translations_list übernommen.
Schritt 3 Transfer-Begriffe werden 1:1 übernommen (ohne Übersetzung).
Args: Args:
translations_list: Liste der bereits übersetzten Einträge (in-place verändert) translations_list: Liste der bereits übersetzten Einträge (in-place verändert)
untranslated_list: Liste der noch nicht übersetzten Einträge (in-place verändert) untranslated_list: Liste der noch nicht übersetzten Einträge (in-place verändert)
@@ -1695,13 +1705,15 @@ def _retranslate_language_block(
bigramme: Bigramme-Dict bigramme: Bigramme-Dict
single: Einzelwörter-Dict single: Einzelwörter-Dict
label: Bezeichnung für Warnmeldungen, z.B. "[CS]" label: Bezeichnung für Warnmeldungen, z.B. "[CS]"
transfer_terms: Set mit Begriffen die 1:1 übernommen werden (aus [transfer] Sektion)
Returns: Returns:
Tuple (aktualisiert, unveraendert, neu_aus_untranslated) Tuple (aktualisiert, unveraendert, neu_aus_untranslated, transferred)
""" """
updated = 0 updated = 0
unchanged = 0 unchanged = 0
newly = 0 newly = 0
transferred = 0
# Schritt 0: Deduplizierung erstes Vorkommen je source wird behalten # Schritt 0: Deduplizierung erstes Vorkommen je source wird behalten
deduped_t, seen_t = _dedup_source_list( deduped_t, seen_t = _dedup_source_list(
@@ -1718,7 +1730,7 @@ def _retranslate_language_block(
continue continue
source = entry['source'] source = entry['source']
old_target = entry.get('target', '') old_target = entry.get('target', '')
new_target = translate_text(source, multi, trigramme, bigramme, single) new_target = translate_text(source, multi, trigramme, bigramme, single, transfer_terms)
if new_target: if new_target:
if new_target != old_target: if new_target != old_target:
entry['target'] = new_target entry['target'] = new_target
@@ -1743,18 +1755,23 @@ def _retranslate_language_block(
newly += 1 newly += 1
continue continue
source = entry['source'] source = entry['source']
new_target = translate_text(source, multi, trigramme, bigramme, single) new_target = translate_text(source, multi, trigramme, bigramme, single, transfer_terms)
if new_target: if new_target:
translations_list.append({"source": source, "target": new_target}) translations_list.append({"source": source, "target": new_target})
untranslated_list.remove(entry) untranslated_list.remove(entry)
# Prüfe ob es ein Transfer-Begriff war
if transfer_terms and source in transfer_terms:
transferred += 1
else:
newly += 1 newly += 1
return updated, unchanged, newly return updated, unchanged, newly, transferred
def _retranslate_legacy(data: dict, def _retranslate_legacy(data: dict,
translation_languages: list[str], translation_languages: list[str],
translation_dicts_all: dict[str, tuple]) -> dict: translation_dicts_all: dict[str, tuple],
transfer_terms_all: dict[str, set[str]] | None = None) -> dict:
""" """
Verarbeitet eine Legacy-Flat-JSON-Struktur und konvertiert sie ins Multilang-Format. Verarbeitet eine Legacy-Flat-JSON-Struktur und konvertiert sie ins Multilang-Format.
@@ -1762,6 +1779,7 @@ def _retranslate_legacy(data: dict,
data: Geladene JSON-Daten (flache Struktur mit 'translations'/'untranslated') data: Geladene JSON-Daten (flache Struktur mit 'translations'/'untranslated')
translation_languages: Sprachen in der Reihenfolge der Verarbeitung translation_languages: Sprachen in der Reihenfolge der Verarbeitung
translation_dicts_all: Dict mit {LANG: (multi, trigramme, bigramme, single), ...} translation_dicts_all: Dict mit {LANG: (multi, trigramme, bigramme, single), ...}
transfer_terms_all: Dict mit {LANG: set[transfer_terms], ...} (aus [transfer] Sektion)
Returns: Returns:
Konvertierte Daten im Multilang-Format Konvertierte Daten im Multilang-Format
@@ -1778,11 +1796,13 @@ def _retranslate_legacy(data: dict,
lang = translation_languages[0] lang = translation_languages[0]
multi, trigramme, bigramme, single = translation_dicts_all[lang] multi, trigramme, bigramme, single = translation_dicts_all[lang]
transfer_terms = transfer_terms_all.get(lang, set()) if transfer_terms_all else set()
updated, unchanged, newly = _retranslate_language_block( updated, unchanged, newly, transferred = _retranslate_language_block(
data['translations'], data['untranslated'], data['translations'], data['untranslated'],
multi, trigramme, bigramme, single, multi, trigramme, bigramme, single,
f"[{lang}]" f"[{lang}]",
transfer_terms
) )
print(f"\nRe-Übersetzung abgeschlossen (alte Struktur → wird zu multilang konvertiert, Sprache: {lang}):") print(f"\nRe-Übersetzung abgeschlossen (alte Struktur → wird zu multilang konvertiert, Sprache: {lang}):")
@@ -1790,6 +1810,8 @@ def _retranslate_legacy(data: dict,
print(f" Unverändert: {unchanged}") print(f" Unverändert: {unchanged}")
if newly: if newly:
print(f" Neu aus 'untranslated' übersetzt/verschoben: {newly}") print(f" Neu aus 'untranslated' übersetzt/verschoben: {newly}")
if transferred:
print(f" Transfer-Begriffe (1:1): {transferred}")
# Legacy → Multilang konvertieren: flache Struktur in [LANG]-Block einpacken # Legacy → Multilang konvertieren: flache Struktur in [LANG]-Block einpacken
return { return {
@@ -1804,7 +1826,8 @@ def _retranslate_legacy(data: dict,
def _retranslate_multilang(data: dict, def _retranslate_multilang(data: dict,
existing_languages: list[str], existing_languages: list[str],
translation_languages: list[str], translation_languages: list[str],
translation_dicts_all: dict[str, tuple]) -> None: translation_dicts_all: dict[str, tuple],
transfer_terms_all: dict[str, set[str]] | None = None) -> None:
""" """
Verarbeitet eine Multilang-JSON-Struktur in-place. Verarbeitet eine Multilang-JSON-Struktur in-place.
@@ -1813,12 +1836,14 @@ def _retranslate_multilang(data: dict,
existing_languages: Sprachen die bereits in der Datei vorhanden sind existing_languages: Sprachen die bereits in der Datei vorhanden sind
translation_languages: Sprachen in der Reihenfolge der Verarbeitung translation_languages: Sprachen in der Reihenfolge der Verarbeitung
translation_dicts_all: Dict mit {LANG: (multi, trigramme, bigramme, single), ...} translation_dicts_all: Dict mit {LANG: (multi, trigramme, bigramme, single), ...}
transfer_terms_all: Dict mit {LANG: set[transfer_terms], ...} (aus [transfer] Sektion)
""" """
print(f"Erkannte mehrsprachige Struktur mit Sprachen: {', '.join(existing_languages)}") print(f"Erkannte mehrsprachige Struktur mit Sprachen: {', '.join(existing_languages)}")
total_updated = 0 total_updated = 0
total_unchanged = 0 total_unchanged = 0
total_newly = 0 total_newly = 0
total_transferred = 0
new_block_count = 0 new_block_count = 0
for lang in translation_languages: for lang in translation_languages:
@@ -1844,30 +1869,38 @@ def _retranslate_multilang(data: dict,
continue continue
multi, trigramme, bigramme, single = translation_dicts_all[lang] multi, trigramme, bigramme, single = translation_dicts_all[lang]
updated, unchanged, newly = _retranslate_language_block( transfer_terms = transfer_terms_all.get(lang, set()) if transfer_terms_all else set()
updated, unchanged, newly, transferred = _retranslate_language_block(
lang_data['translations'], lang_data['untranslated'], lang_data['translations'], lang_data['untranslated'],
multi, trigramme, bigramme, single, multi, trigramme, bigramme, single,
f"[{lang}]" f"[{lang}]",
transfer_terms
) )
total_updated += updated + newly total_updated += updated + newly
total_unchanged += unchanged total_unchanged += unchanged
total_newly += newly total_newly += newly
total_transferred += transferred
print(f" [{lang}] Aktualisiert: {updated}, Unverändert: {unchanged}", end="") print(f" [{lang}] Aktualisiert: {updated}, Unverändert: {unchanged}", end="")
if newly: if newly:
print(f", Neu aus 'untranslated': {newly}", end="") print(f", Neu aus 'untranslated': {newly}", end="")
if transferred:
print(f", Transfer-Begriffe (1:1): {transferred}", end="")
print() print()
print(f"\nRe-Übersetzung abgeschlossen (mehrsprachige Struktur):") print(f"\nRe-Übersetzung abgeschlossen (mehrsprachige Struktur):")
print(f" Gesamt aktualisiert: {total_updated}") print(f" Gesamt aktualisiert: {total_updated}")
print(f" Gesamt unverändert: {total_unchanged}") print(f" Gesamt unverändert: {total_unchanged}")
if total_transferred:
print(f" Gesamt Transfer-Begriffe (1:1): {total_transferred}")
if new_block_count: if new_block_count:
print(f" Neue Sprach-Blöcke erstellt: {new_block_count}") print(f" Neue Sprach-Blöcke erstellt: {new_block_count}")
def retranslate_json_file(json_path: Path, def retranslate_json_file(json_path: Path,
translation_dicts_all: dict[str, tuple], translation_dicts_all: dict[str, tuple],
translation_languages: list[str]): translation_languages: list[str],
transfer_terms_all: dict[str, set[str]] | None = None):
""" """
Re-übersetzt eine JSON-Datei mit neuen Übersetzungs-Configs. Re-übersetzt eine JSON-Datei mit neuen Übersetzungs-Configs.
Überschreibt die ursprüngliche JSON-Datei mit den aktualisierten Übersetzungen. Überschreibt die ursprüngliche JSON-Datei mit den aktualisierten Übersetzungen.
@@ -1894,9 +1927,9 @@ def retranslate_json_file(json_path: Path,
structure_type, existing_languages = detect_json_structure(data) structure_type, existing_languages = detect_json_structure(data)
if structure_type == "legacy": if structure_type == "legacy":
data = _retranslate_legacy(data, translation_languages, translation_dicts_all) data = _retranslate_legacy(data, translation_languages, translation_dicts_all, transfer_terms_all)
elif structure_type == "multilang": elif structure_type == "multilang":
_retranslate_multilang(data, existing_languages, translation_languages, translation_dicts_all) _retranslate_multilang(data, existing_languages, translation_languages, translation_dicts_all, transfer_terms_all)
else: else:
print(f"FEHLER: JSON-Datei hat unbekannte Struktur") print(f"FEHLER: JSON-Datei hat unbekannte Struktur")
sys.exit(1) sys.exit(1)
@@ -2004,7 +2037,8 @@ def retranslate_txt_file(txt_path: Path,
translation_dicts_all: dict[str, tuple], translation_dicts_all: dict[str, tuple],
translation_languages: list[str], translation_languages: list[str],
wildcard_patterns: list[str] | None = None, wildcard_patterns: list[str] | None = None,
regex_patterns: list[str] | None = None) -> None: regex_patterns: list[str] | None = None,
transfer_terms_all: dict[str, set[str]] | None = None) -> None:
""" """
Re-übersetzt eine Textdatei im Format von write_texts_to_text. Re-übersetzt eine Textdatei im Format von write_texts_to_text.
Überschreibt die ursprüngliche Datei mit aktualisierten Inhalten. Überschreibt die ursprüngliche Datei mit aktualisierten Inhalten.
@@ -2015,6 +2049,7 @@ def retranslate_txt_file(txt_path: Path,
2. Einträge aus untranslated werden gegen die Ignore-Pattern geprüft: 2. Einträge aus untranslated werden gegen die Ignore-Pattern geprüft:
- Treffer → ignored (neu erkannte Pattern seit dem letzten Extrakt) - Treffer → ignored (neu erkannte Pattern seit dem letzten Extrakt)
- Kein Treffer → auto-übersetzen; Erfolg → translations, sonst bleibt in untranslated. - Kein Treffer → auto-übersetzen; Erfolg → translations, sonst bleibt in untranslated.
3. Transfer-Begriffe werden 1:1 übernommen (ohne Übersetzung).
Arbeitet immer mit der ersten Sprache aus translation_languages. Arbeitet immer mit der ersten Sprache aus translation_languages.
@@ -2024,6 +2059,7 @@ def retranslate_txt_file(txt_path: Path,
translation_languages: Liste der Sprachen (nur die erste wird verwendet) translation_languages: Liste der Sprachen (nur die erste wird verwendet)
wildcard_patterns: Wildcard-Muster zum Filtern (aus translator.cfg) wildcard_patterns: Wildcard-Muster zum Filtern (aus translator.cfg)
regex_patterns: Regex-Muster zum Filtern (aus translator.cfg) regex_patterns: Regex-Muster zum Filtern (aus translator.cfg)
transfer_terms_all: Dict mit {LANG: set[transfer_terms], ...} (aus [transfer] Sektion)
""" """
if not translation_languages or translation_languages[0] not in translation_dicts_all: if not translation_languages or translation_languages[0] not in translation_dicts_all:
print(f"FEHLER: Keine Übersetzungs-Config für Re-Übersetzung verfügbar") print(f"FEHLER: Keine Übersetzungs-Config für Re-Übersetzung verfügbar")
@@ -2034,6 +2070,7 @@ def retranslate_txt_file(txt_path: Path,
lang = translation_languages[0] lang = translation_languages[0]
multi, trigramme, bigramme, single = translation_dicts_all[lang] multi, trigramme, bigramme, single = translation_dicts_all[lang]
transfer_terms = transfer_terms_all.get(lang, set()) if transfer_terms_all else set()
existing_translations, untranslated, ignored = _parse_txt_sections(txt_path) existing_translations, untranslated, ignored = _parse_txt_sections(txt_path)
@@ -2048,7 +2085,7 @@ def retranslate_txt_file(txt_path: Path,
# Schritt 1: Bestehende Übersetzungen neu übersetzen # Schritt 1: Bestehende Übersetzungen neu übersetzen
for source, old_target in existing_translations: for source, old_target in existing_translations:
new_target = translate_text(source, multi, trigramme, bigramme, single) new_target = translate_text(source, multi, trigramme, bigramme, single, transfer_terms)
if new_target: if new_target:
new_translations.append((source, new_target)) new_translations.append((source, new_target))
if new_target != old_target: if new_target != old_target:
@@ -2064,15 +2101,20 @@ def retranslate_txt_file(txt_path: Path,
still_untranslated: list[str] = list(moved_to_untranslated) still_untranslated: list[str] = list(moved_to_untranslated)
newly = 0 newly = 0
newly_ignored = 0 newly_ignored = 0
transferred = 0
for text in untranslated: for text in untranslated:
# Neu hinzugefügte Ignore-Pattern prüfen # Neu hinzugefügte Ignore-Pattern prüfen
if should_ignore_text(text, wildcard_patterns, regex_patterns): if should_ignore_text(text, wildcard_patterns, regex_patterns):
ignored.append(text) ignored.append(text)
newly_ignored += 1 newly_ignored += 1
continue continue
new_target = translate_text(text, multi, trigramme, bigramme, single) new_target = translate_text(text, multi, trigramme, bigramme, single, transfer_terms)
if new_target: if new_target:
new_translations.append((text, new_target)) new_translations.append((text, new_target))
# Prüfe ob es ein Transfer-Begriff war
if transfer_terms and text in transfer_terms:
transferred += 1
else:
newly += 1 newly += 1
else: else:
still_untranslated.append(text) still_untranslated.append(text)
@@ -2098,6 +2140,8 @@ def retranslate_txt_file(txt_path: Path,
print(f" Unverändert: {unchanged}") print(f" Unverändert: {unchanged}")
if newly: if newly:
print(f" Neu übersetzt: {newly}") print(f" Neu übersetzt: {newly}")
if transferred:
print(f" Transfer-Begriffe (1:1): {transferred}")
if newly_ignored: if newly_ignored:
print(f" Neu ignoriert (Pattern): {newly_ignored}") print(f" Neu ignoriert (Pattern): {newly_ignored}")
if moved_to_untranslated: if moved_to_untranslated:
@@ -2112,7 +2156,8 @@ def _run_retranslate_txt(
translation_dicts_all: dict, translation_dicts_all: dict,
translation_languages: list, translation_languages: list,
wildcard_patterns: list, wildcard_patterns: list,
regex_patterns: list) -> None: regex_patterns: list,
transfer_terms_all: dict) -> None:
"""Workflow: Re-Übersetzung einer bestehenden TXT-Datei (--retranslate-txt).""" """Workflow: Re-Übersetzung einer bestehenden TXT-Datei (--retranslate-txt)."""
print("=== Re-Übersetzung TXT-Datei ===\n") print("=== Re-Übersetzung TXT-Datei ===\n")
@@ -2127,7 +2172,7 @@ def _run_retranslate_txt(
print(f"Sprache: {translation_languages[0]}") print(f"Sprache: {translation_languages[0]}")
retranslate_txt_file(txt_file, translation_dicts_all, translation_languages, retranslate_txt_file(txt_file, translation_dicts_all, translation_languages,
wildcard_patterns, regex_patterns) wildcard_patterns, regex_patterns, transfer_terms_all)
print("\nVerarbeitung erfolgreich abgeschlossen.") print("\nVerarbeitung erfolgreich abgeschlossen.")
sys.exit(0) sys.exit(0)
@@ -2227,7 +2272,8 @@ def _run_retranslate(
args, args,
work_dir: Path, work_dir: Path,
translation_dicts_all: dict, translation_dicts_all: dict,
translation_languages: list) -> None: translation_languages: list,
transfer_terms_all: dict) -> None:
"""Workflow: Re-Übersetzung einer bestehenden JSON-Datei (--retranslate-json).""" """Workflow: Re-Übersetzung einer bestehenden JSON-Datei (--retranslate-json)."""
print("=== Re-Übersetzung JSON-Datei ===\n") print("=== Re-Übersetzung JSON-Datei ===\n")
@@ -2241,7 +2287,7 @@ def _run_retranslate(
print(f"JSON-Datei: {json_file}") print(f"JSON-Datei: {json_file}")
print(f"Sprachen: {', '.join(translation_languages)}") print(f"Sprachen: {', '.join(translation_languages)}")
retranslate_json_file(json_file, translation_dicts_all, translation_languages) retranslate_json_file(json_file, translation_dicts_all, translation_languages, transfer_terms_all)
print("\nVerarbeitung erfolgreich abgeschlossen.") print("\nVerarbeitung erfolgreich abgeschlossen.")
sys.exit(0) sys.exit(0)
@@ -2451,10 +2497,12 @@ def main() -> None:
# Übersetzungs-Configs laden: {LANG: (multi, trigramme, bigramme, single), ...} # Übersetzungs-Configs laden: {LANG: (multi, trigramme, bigramme, single), ...}
translation_dicts_all = {} translation_dicts_all = {}
transfer_terms_all = {}
if translation_languages: if translation_languages:
translation_dir = check_environment_var('PROJECT_TRANSLATION') translation_dir = check_environment_var('PROJECT_TRANSLATION')
for lang in translation_languages: for lang in translation_languages:
translation_dicts_all[lang] = load_translation_config(lang, translation_dir) translation_dicts_all[lang] = load_translation_config(lang, translation_dir)
transfer_terms_all[lang] = load_transfer_terms(lang, translation_dir)
# Für backwards compatibility: bei genau einer Sprache flaches Dict bereitstellen # Für backwards compatibility: bei genau einer Sprache flaches Dict bereitstellen
translation_dicts = translation_dicts_all.get(translation_languages[0]) if len(translation_languages) == 1 else None translation_dicts = translation_dicts_all.get(translation_languages[0]) if len(translation_languages) == 1 else None
@@ -2464,11 +2512,11 @@ def main() -> None:
# Workflow delegieren # Workflow delegieren
if args.retranslate_json: if args.retranslate_json:
_run_retranslate(args, work_dir, translation_dicts_all, translation_languages) _run_retranslate(args, work_dir, translation_dicts_all, translation_languages, transfer_terms_all)
if args.retranslate_txt: if args.retranslate_txt:
_run_retranslate_txt(args, work_dir, translation_dicts_all, translation_languages, _run_retranslate_txt(args, work_dir, translation_dicts_all, translation_languages,
wildcard_patterns, regex_patterns) wildcard_patterns, regex_patterns, transfer_terms_all)
if args.todxf: if args.todxf:
_run_todxf(args, work_dir, translation_languages, wildcard_patterns, regex_patterns, _run_todxf(args, work_dir, translation_languages, wildcard_patterns, regex_patterns,