[ADD] lib/dxf_anlage_export.py: gebaute und von Hand nachbearbeitete Anlage aus der Zeichnung zurueck in Nachbau-Testdaten (Kreisel, GF, VF standard/etage, VF-Linienzug als unveraendertes Journal, Einzelbloecke) - tests/testdata/dematic_arvato.json jetzt aus Dematic_TST.dxf; [ADD] dxf_scan_components.py: Gefaellestrecken aus Staustrecken-LINEs (Layer 6-SP), Start/Ende auf die Kreisel-Ketten verlaengert, AS-Element und Separator auf der Achse werden verbraucht, Steigfoerderer erkannt, --ohne-hoehen und --kreisel-hoehe; [ADD] vf_spec.lsp: Replay-Kern vsp-bau-journal aus vsp-bau-aus-spec herausgeloest (auch fuer aufgezeichnete Journale); [CHANGE] TEST_DEMATIC_ARVATO baut GF, VF standard/etage und Linienzug-Journale (mubea:gf-bauen/-vario-bauen wiederverwendet); [FIX] GF-Winkel aus Kreisel-Hoehen: AS/ES-Abfall mit falschem Vorzeichen an gf-winkel-berechnen uebergeben (3.79 statt ~2.49 Grad, GF endete 258 mm zu tief); [ADD] Tests test_dxf_scan_gf.py, test_dxf_anlage_export.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 13:35:46 +02:00
parent 2f9cb7eaed
commit ba20d63f63
9 changed files with 2024 additions and 1328 deletions
+413 -3
View File
@@ -11,6 +11,11 @@ also so, dass die Anlage per JSON-Treiber mit den Produktionsmakros
nachgebaut werden kann (Vorbild: tests/testdata/mubea.json, das bisher von
Hand aus einem DXF abgeleitet wurde).
Gefaellestrecken, die nicht als Blockpaar, sondern als 3D-LINE auf einem
Staustrecken-Layer gezeichnet sind (GF_LINIE_LAYER), werden in einem zweiten
Lesedurchgang erkannt und als Modus-1-Eintraege (Schema 2.1) ausgegeben,
siehe gf_eintraege_aus_linien.
Aufruf:
python dxf_scan_components.py <dxf> <data_dir> <output.json>
[--fenster X0 Y0 X1 Y1]
@@ -128,6 +133,43 @@ ACHSE_MAX_QUER = 1500.0
# der Antriebsstationen, ohne benachbarte Bauteile zu verwechseln.
ZWILLING_MAX_ABSTAND = 1500.0
# --- Gefaellestrecken aus Staustrecken-Linien -------------------------------
# Manche Zeichnungen (Dematic_arvato_reduced.dxf) fuehren die Gefaellestrecke
# nicht als Blockpaar, sondern als einfache 3D-LINE auf einem Staustrecken-
# Layer vom hohen zum tiefen Kreisel (dort: 27 Linien auf "6-SP", 10654 mm
# lang, 370 mm Gefaelle). Nur Modelspace-LINEs; Muster wie FAMILIEN.
GF_LINIE_LAYER = ["6-SP"]
# Mindest-Hoehendifferenz (mm), ab der eine Staustrecken-Linie als Gefaelle
# gilt. Darunter ist sie eine waagrechte Strecke - die liegen auf denselben
# Layern (Rueckfuehrungen, Kreisel-Verbindungen) und sind keine GF.
GF_LINIE_MIN_DZ = 100.0
# Suchbereich fuer das Ausschleuselement am oberen Linienende, gemessen im
# Linien-KS: "entlang" ab Linienstart in Gefaellerichtung, "quer" seitlich.
# In Dematic_arvato_reduced.dxf sitzt es 1158 mm entlang und 467 mm quer -
# der Nachbar-AS der Linie daneben (800 mm Raster) liegt nur 433 mm quer auf
# der ANDEREN Seite. Darum entscheidet die Seite aus dem Blocknamen (links/
# rechts), nicht der kleinste Abstand.
GF_AS_MAX_ENTLANG = 2000.0
GF_AS_MAX_QUER = 700.0
# Groesste Luecke (mm) zwischen Linienende und Kreisel-Kette, bis zu der die
# Linie an diesen Kreisel angeschlossen wird. Gezeichnet ist nur die
# Staustrecke: oben fehlt das Ausschleuselement (~430 mm), unten das
# Einschleuselement (~480 mm) bis zur Kette.
GF_KREISEL_MAX_LUECKE = 1500.0
# Separatoren auf der GF-Achse: die Gefaellestrecke baut ihren eigenen
# (Staustrecke_Separator_SP_300_mm), der gezeichnete waere doppelt. Gilt fuer
# Vorkommen bis GF_SEP_MAX_QUER (mm) neben der Achse.
GF_SEP_MUSTER = ["S-SP", "S-LP", "Separator*"]
GF_SEP_MAX_QUER = 300.0
# Abstand (mm), bis zu dem eine --kreisel-hoehe X Y Z dem Kreisel mit diesem
# Einfuegepunkt (Eintrag x/y) zugeordnet wird.
KREISEL_HOEHE_TOL = 50.0
# ---------------------------------------------------------------------------
# Hilfsfunktionen
@@ -593,6 +635,62 @@ def lese_inserts(dxf_path):
return ms_inserts, block_inserts, block_base, statistik, block_info
def lese_linien(dxf_path, layer_muster):
"""Liest alle Modelspace-LINEs auf den Layern aus layer_muster.
Eigener Durchlauf statt Erweiterung von lese_inserts: dessen Rueckgabe
nutzt auch lib/dxf_abbild.py, und die Linien braucht nur die
Gefaellestrecken-Erkennung. Kostet einen zweiten Lesedurchgang.
Rueckgabe: Liste {"layer", "a": [x, y, z], "b": [x, y, z]} in WCS
(eine LINE ist immer in WCS angegeben, keine OCS-Umrechnung noetig).
"""
linien = []
section = None
warte_auf_sectionname = False
rec = None
def ablegen(r):
if r and r["ps"] != 1 and matches_any(r["layer"] or "", layer_muster):
linien.append({"layer": r["layer"], "a": r["a"], "b": r["b"]})
fh = io.open(dxf_path, "rb")
try:
mm = mmap.mmap(fh.fileno(), 0, access=mmap.ACCESS_READ)
except ValueError:
fh.close()
raise
try:
for code, value in iter_tags(mm):
if code == 0:
ablegen(rec)
rec = None
if value == "SECTION":
warte_auf_sectionname = True
elif value == "ENDSEC":
section = None
elif value == "LINE" and section == "ENTITIES":
rec = {"layer": None, "a": [0.0, 0.0, 0.0],
"b": [0.0, 0.0, 0.0], "ps": 0}
elif code == 2 and warte_auf_sectionname:
section = value
warte_auf_sectionname = False
elif rec is not None:
if code == 8:
rec["layer"] = value
elif code in (10, 20, 30):
rec["a"][{10: 0, 20: 1, 30: 2}[code]] = value
elif code in (11, 21, 31):
rec["b"][{11: 0, 21: 1, 31: 2}[code]] = value
elif code == 67:
rec["ps"] = value
ablegen(rec)
finally:
mm.close()
fh.close()
return linien
def insert_matrix(rec, block_base):
"""Transformationsmatrix eines INSERT (inkl. Basispunkt des Zielblocks).
@@ -1213,15 +1311,242 @@ def sensor_eintrag(t, bekannte_bloecke=None, hinweise=None, treffer=None):
}
def as_am_ende(p, richtung, as_liste, belegt):
"""Ausschleuselement an einem Linienende suchen.
p = Linienende (x, y), richtung = Einheitsvektor (x, y) von dort in die
Linie hinein. Gesucht wird im Linien-KS (siehe GF_AS_MAX_ENTLANG/-QUER);
"quer" ist positiv LINKS der Richtung. Ein AS mit Seite im Blocknamen muss
auf dieser Seite liegen - das trennt ihn vom Nachbar-AS der Linie daneben.
Bereits vergebene AS (belegt = Menge von id()) zaehlen nicht.
Rueckgabe: (treffer, zielblock, geraten) oder None.
"""
beste = None
for t in as_liste:
if id(t) in belegt:
continue
vx, vy = t["x"] - p[0], t["y"] - p[1]
entlang = vx * richtung[0] + vy * richtung[1]
quer = richtung[0] * vy - richtung[1] * vx
if not (0.0 <= entlang <= GF_AS_MAX_ENTLANG and abs(quer) <= GF_AS_MAX_QUER):
continue
block, geraten = mappe_block(t["block"])
if "Seite=%s" % DEFAULT_SEITE not in (geraten or []):
if block.endswith("_links") and quer < 0:
continue
if block.endswith("_rechts") and quer > 0:
continue
if beste is None or abs(quer) < beste[0]:
beste = (abs(quer), t, block, geraten or [])
return beste[1:] if beste else None
def kreisel_anschluss(p, richtung, kreisel):
"""Schnittpunkt einer GF-Achse mit der naechsten Kreisel-Kette.
p = Linienende (x, y), richtung = Einheitsvektor (x, y) der GF-Achse.
Je Kreisel (siehe kreisel_geo in baue_eintraege: Achse von der AN8- zur
SP8-Radmitte, Kette im Abstand r beidseits) wird die Kette auf der Seite
von p genommen und die Achse bis dorthin verlaengert bzw. gekuerzt. Nur
Kreisel, an deren Laenge p liegt und deren Kette hoechstens
GF_KREISEL_MAX_LUECKE entfernt ist; bei mehreren gewinnt die kleinste
Verschiebung.
Rueckgabe: (kreisel, punkt (x, y), verschiebung entlang richtung) oder None.
"""
beste = None
for k in kreisel:
ux, uy = k["u"]
nx, ny = -uy, ux
vx, vy = p[0] - k["a"][0], p[1] - k["a"][1]
t = vx * ux + vy * uy
q = vx * nx + vy * ny
if not (-k["r"] <= t <= k["laenge"] + k["r"]):
continue
if abs(abs(q) - k["r"]) > GF_KREISEL_MAX_LUECKE:
continue
dn = richtung[0] * nx + richtung[1] * ny
if abs(dn) < 1e-6: # GF parallel zum Kreisel
continue
kette = k["r"] if q >= 0 else -k["r"]
lam = (kette - q) / dn
if beste is None or abs(lam) < abs(beste[2]):
beste = (k, (p[0] + lam * richtung[0], p[1] + lam * richtung[1]), lam)
return beste
def gf_eintraege_aus_linien(linien, as_liste, kreisel=None, sep_liste=None,
winkel_default=3.0):
"""Gefaellestrecken (Modus 1, Schema 2.1) aus Staustrecken-Linien.
Eine Gefaellestrecke laeuft immer bergab: Start ist das HOEHERE
Linienende, unabhaengig von der Zeichenrichtung. Das Ausschleuselement
gehoert an dieses obere Ende. Sitzt es dagegen am UNTEREN Ende, faehrt
die Strecke vom tiefen Kreisel hinauf - das ist ein Steigfoerderer und
keine GF (Dematic_arvato_reduced.dxf: 3 solche Linien); sie wird
gemeldet und uebersprungen.
Anschluss an die Kreisel: gezeichnet ist nur die Staustrecke. Das Makro
legt den Ursprung des AS-Elements auf start_mm und dessen Einlauf
(KS_EIN) seitlich daneben, auf gleicher Hoehe der Achse; ebenso liegt der
Auslauf des ES-Elements neben dessen Ursprung, und deltaL_mm reicht von
AS- bis ES-Ursprung (gemessen an GF_Mubea: 9510 -> 399.2 = 9110.8). Damit
beide die Kreisel-Kette treffen, werden Start und Ende auf die jeweils
naechste Kette geschoben (kreisel_anschluss). Ohne Kreisel in Reichweite
bleibt das Linienende stehen und es gibt einen Hinweis.
Hoehen: tragen beide Kreisel eine Anwender-Hoehe ("z_anwender"), wird
start_mm.z die des oberen und hoehe_ende_mm die des unteren Kreisels -
den Winkel rechnet dann der LISP-Treiber mit gf-winkel-berechnen
(dieselbe Naeherung wie Modus 3, mit den Elementmassen der aktuellen
2D/3D-Bibliothek). Sonst ist winkel_default ein Platzhalter.
Rueckgabe: (eintraege, verbrauchte ids, hinweise). Verbraucht werden das
AS am oberen Ende und Separatoren auf der Achse (sep_liste) - beides baut
die Gefaellestrecke selbst mit, als Einzel-Insert stuenden sie doppelt in
der Zeichnung (und im Sivas-Export). Der AS eines uebersprungenen
Steigfoerderers bleibt unverbraucht.
"""
eintraege, hinweise = [], []
belegt = set()
waagrecht = 0
kandidaten = []
for ln in linien:
a, b = ln["a"], ln["b"]
if abs(a[2] - b[2]) < GF_LINIE_MIN_DZ:
waagrecht += 1
continue
hoch, tief = (a, b) if a[2] > b[2] else (b, a)
lxy = math.hypot(tief[0] - hoch[0], tief[1] - hoch[1])
if lxy <= 0.0:
continue
kandidaten.append((hoch, tief, lxy, ln["layer"]))
kandidaten.sort(key=lambda k: (round(k[0][0], RUND), round(k[0][1], RUND)))
for hoch, tief, lxy, layer in kandidaten:
r = ((tief[0] - hoch[0]) / lxy, (tief[1] - hoch[1]) / lxy)
oben = as_am_ende(hoch, r, as_liste, belegt)
if oben is None:
unten = as_am_ende(tief, (-r[0], -r[1]), as_liste, belegt)
if unten is not None:
# AS NICHT verbrauchen: er gehoert zum Steigfoerderer und
# bleibt als Einzel-Insert in der Ausgabe stehen.
hinweise.append(
"Staustrecken-Linie (%.0f, %.0f, %.0f) -> (%.0f, %.0f, "
"%.0f): Ausschleuselement am UNTEREN Ende - Steigfoerderer, "
"keine Gefaellestrecke, uebersprungen"
% (tief[0], tief[1], tief[2], hoch[0], hoch[1], hoch[2]))
continue
tid = "GF_%s_%02d" % (PROJEKT, len(eintraege) + 1)
kommentar = ("aus DXF: LINE auf Layer '%s' (%.2f, %.2f) -> (%.2f, "
"%.2f), Laenge XY %.2f"
% (layer, hoch[0], hoch[1], tief[0], tief[1], lxy))
# Start/Ende auf die Kreisel-Ketten schieben
start, ende = (hoch[0], hoch[1]), (tief[0], tief[1])
k_start = k_ende = None
for wo, p, rr in (("oberen", hoch, r), ("unteren", tief, (-r[0], -r[1]))):
ans = kreisel_anschluss(p, rr, kreisel or [])
if ans is None:
hinweise.append("%s: kein Kreisel am %s Ende - Linienende "
"bleibt stehen" % (tid, wo))
kommentar += "; am %s Ende kein Kreisel in Reichweite" % wo
continue
k, punkt, lam = ans
kommentar += ("; am %s Ende um %.1f mm bis zur Kette von %s "
"verlaengert" % (wo, -lam, k["id"]))
if wo == "oberen":
start, k_start = punkt, k
else:
ende, k_ende = punkt, k
laenge = (ende[0] - start[0]) * r[0] + (ende[1] - start[1]) * r[1]
eintrag = {
"test_id": tid,
"modus": 1,
"deltaL_mm": round(laenge, RUND),
"hz_grad": round(math.degrees(math.atan2(r[1], r[0])) % 360.0, RUND) + 0.0,
"start_mm": [round(start[0], RUND) + 0.0, round(start[1], RUND) + 0.0, 0.0],
"as_seite": DEFAULT_SEITE,
"es_seite": "links",
"as_winkel": str(DEFAULT_SCHLEUS_WINKEL),
"es_winkel": str(DEFAULT_SCHLEUS_WINKEL),
}
if k_start:
eintrag["kreisel_start"] = k_start["id"]
if k_ende:
eintrag["kreisel_ende"] = k_ende["id"]
if (k_start and k_ende and k_start.get("z_anwender") is not None
and k_ende.get("z_anwender") is not None):
eintrag["start_mm"][2] = float(k_start["z_anwender"])
eintrag["hoehe_ende_mm"] = float(k_ende["z_anwender"])
eintrag["hoehe_quelle"] = "Anwender"
kommentar += ("; Hoehen vom Anwender (Kreisel %s / %s), den Winkel "
"rechnet der Treiber" % (k_start["id"], k_ende["id"]))
else:
eintrag["winkel_grad"] = winkel_default
eintrag["hoehe_unbekannt"] = True
kommentar += ("; Hoehen unbekannt - winkel_grad %.1f ist Platzhalter"
% winkel_default)
if oben is not None:
t, block, geraten = oben
belegt.add(id(t))
teile = block.split("_") # AS_Element_<winkel>_<seite>
eintrag["as_winkel"], eintrag["as_seite"] = teile[2], teile[3]
kommentar += ("; AS aus Block '%s' bei (%.2f, %.2f) -> %s, als "
"Einzel-Insert entfernt (baut die GF selbst)"
% (t["block"], t["x"], t["y"], block))
if geraten:
kommentar += " - geraten: %s" % ", ".join(geraten)
else:
kommentar += ("; KEIN Ausschleuselement am oberen Ende gefunden - "
"as_seite/as_winkel sind Defaults")
hinweise.append("%s ohne Ausschleuselement am oberen Ende" % tid)
# Separatoren auf der Achse zwischen Start und Ende
weg = []
for s in sep_liste or []:
if id(s) in belegt or not matches_any(s["block"], GF_SEP_MUSTER):
continue
vx, vy = s["x"] - start[0], s["y"] - start[1]
entlang = vx * r[0] + vy * r[1]
quer = r[0] * vy - r[1] * vx
if 0.0 <= entlang <= laenge and abs(quer) <= GF_SEP_MAX_QUER:
belegt.add(id(s))
weg.append("%s bei (%.0f, %.0f)" % (s["block"], s["x"], s["y"]))
if weg:
kommentar += ("; Separator auf der Achse entfernt (die GF baut "
"ihren eigenen): %s" % ", ".join(weg))
kommentar += "; es_seite/es_winkel sind Defaults (kein ES im DXF)."
eintrag["comment"] = kommentar
eintraege.append(eintrag)
if waagrecht:
hinweise.append("%d waagrechte Staustrecken-Linie(n) auf %s - keine "
"Gefaellestrecke, nicht ausgegeben"
% (waagrecht, "/".join(GF_LINIE_LAYER)))
return eintraege, belegt, hinweise
def baue_eintraege(treffer, defaults, boegen, weichen,
bekannte_bloecke=None):
"""Erkannte Vorkommen auf die Schemata aus object_data.md abbilden."""
bekannte_bloecke=None, linien=None, kreisel_hoehen=None):
"""Erkannte Vorkommen auf die Schemata aus object_data.md abbilden.
kreisel_hoehen: Liste (x, y, z) vom Anwender - z gilt fuer den Kreisel,
dessen Einfuegepunkt (Eintrag x/y) hoechstens KREISEL_HOEHE_TOL entfernt
liegt, und fuer die Gefaellestrecken, die an ihm anschliessen.
"""
nach_familie = {}
for t in treffer:
nach_familie.setdefault(t["familie"], []).append(t)
eintraege = []
hinweise = []
kreisel_geo = [] # Achse/Kette je Kreisel fuer kreisel_anschluss
offene_hoehen = list(kreisel_hoehen or [])
# --- Kreisel: AN8 (Antriebsstation) + SP8 (Spannstation) --------------
durchmesser = defaults.get("kreisel", {}).get("durchmesser", 800.0)
@@ -1275,10 +1600,47 @@ def baue_eintraege(treffer, defaults, boegen, weichen,
sp["block"], sp["layer"], sp["x"], sp["y"], dist,
rotation)) + hoehe_hinweis,
})
kreisel = eintraege[-1]
for h in offene_hoehen:
if math.hypot(h[0] - kreisel["x"], h[1] - kreisel["y"]) <= KREISEL_HOEHE_TOL:
kreisel["z"] = float(h[2])
kreisel["hoehe_quelle"] = "Anwender"
kreisel["comment"] += " Hoehe %.1f vom Anwender." % h[2]
offene_hoehen.remove(h)
break
kreisel_geo.append({
"id": kreisel["id"],
"a": (an["x"], an["y"]),
"u": (math.cos(richtung), math.sin(richtung)),
"laenge": dist,
"r": radius,
"z_anwender": kreisel["z"] if kreisel.get("hoehe_quelle") else None,
})
for h in offene_hoehen:
hinweise.append("Kreisel-Hoehe %.1f bei (%.2f, %.2f): kein Kreisel-"
"Einfuegepunkt in %g mm - nicht verwendet"
% (h[2], h[0], h[1], KREISEL_HOEHE_TOL))
for sp in sp_liste:
hinweise.append("SP8 bei (%.2f, %.2f) ohne AN8-Partner" % (sp["x"], sp["y"]))
eintraege.append(sensor_eintrag(sp, bekannte_bloecke, hinweise, treffer))
# --- Gefaellestrecken aus Staustrecken-Linien ---------------------------
# Nach den Kreiseln (Anschluss an deren Ketten) und vor dem Rest-Block:
# sie entnehmen ihre Ausschleuselemente und Separatoren aus den Familien
# (siehe gf_eintraege_aus_linien), der Rest-Block darf diese nicht mehr
# als Einzel-Insert ausgeben.
if linien:
as_liste = [t for t in nach_familie.get("schleuselement", [])
if mappe_block(t["block"])[0].startswith("AS_Element")]
gf, belegt, gf_hinweise = gf_eintraege_aus_linien(
linien, as_liste, kreisel_geo, nach_familie.get("sensor", []),
defaults.get("gefaelle", {}).get("default_winkel", 3.0))
eintraege.extend(gf)
hinweise.extend(gf_hinweise)
for familie in ("schleuselement", "sensor"):
nach_familie[familie] = [t for t in nach_familie.get(familie, [])
if id(t) not in belegt]
# --- Foerderer: Antriebs- und Umlenkstation paaren ---------------------
gf_winkel_min = defaults.get("gefaelle", {}).get("default_winkel", 3.0)
paare, rest_a, rest_u = paare_bilden(nach_familie.get("foerderer_antrieb", []),
@@ -1400,6 +1762,37 @@ def filtere_fenster(eintraege, fenster):
return behalten, len(eintraege) - len(behalten), ohne_punkte
def hoehen_entfernen(eintraege):
"""Alle Hoehen auf 0 setzen, ausser den vom Anwender vorgegebenen.
Fuer Zeichnungen, deren Z-Werte (noch) nicht belastbar sind: lieber
gar keine Hoehe als eine, die nach Einbauhoehe aussieht und keine ist.
Ausgenommen sind Eintraege mit hoehe_quelle "Anwender" (--kreisel-hoehe
und die daran anschliessenden Gefaellestrecken).
Rueckgabe: Anzahl der geaenderten Eintraege.
"""
n = 0
for e in eintraege:
if e.get("hoehe_quelle") == "Anwender":
continue
geaendert = False
for feld in ("z", "hoehe"):
if feld in e and e[feld] != 0.0:
e[feld] = 0.0
geaendert = True
for feld in ("startpunkt", "start_mm", "endpunkt"):
p = e.get(feld)
if isinstance(p, list) and len(p) >= 3 and p[2] != 0.0:
p[2] = 0.0
geaendert = True
if any(f in e for f in ("z", "hoehe", "startpunkt", "start_mm")):
e["hoehe_unbekannt"] = True
if geaendert:
e["comment"] = e.get("comment", "") + " [Hoehe entfernt: --ohne-hoehen]"
n += 1
return n
def arrays_einzeilig(text):
"""Zahlen-Arrays im JSON-Text auf eine Zeile ziehen.
@@ -1471,6 +1864,15 @@ def main():
help="Nur Bauteile in diesem XY-Fenster (mm) "
"ausgeben - fuer den Nachbau eines einzelnen "
"Anlagenabschnitts statt der ganzen Zeichnung")
parser.add_argument("--ohne-hoehen", action="store_true",
help="Alle Hoehen auf 0 setzen (Z der Zeichnung nicht "
"belastbar); ausgenommen --kreisel-hoehe")
parser.add_argument("--kreisel-hoehe", nargs=3, type=float, action="append",
metavar=("X", "Y", "Z"),
help="Hoehe Z fuer den Kreisel mit Einfuegepunkt X/Y "
"(wie im Ausgabe-JSON); gilt auch fuer Start/Ende "
"der daran anschliessenden Gefaellestrecken. "
"Mehrfach angebbar")
parser.add_argument("--projekt",
help="Projektkennung in den erzeugten IDs "
"(Default: Name der DXF-Datei ohne Endung)")
@@ -1529,8 +1931,16 @@ def main():
dim_stat["attribute_uebernommen"], dim_stat["nur_2d"],
dim_stat["nur_3d"], len(treffer)))
linien = lese_linien(dxf_path, GF_LINIE_LAYER)
print("%s %d Staustrecken-Linien auf %s" % (PREFIX, len(linien),
"/".join(GF_LINIE_LAYER)))
eintraege, hinweise = baue_eintraege(treffer, defaults, boegen, weichen,
bekannte_bloecke)
bekannte_bloecke, linien,
args.kreisel_hoehe)
if args.ohne_hoehen:
print("%s --ohne-hoehen: Hoehe in %d Eintraegen auf 0 gesetzt"
% (PREFIX, hoehen_entfernen(eintraege)))
if fenster:
vorher = len(eintraege)