diff --git a/doc/kommandos.md b/doc/kommandos.md index 5419396..4da84b0 100644 --- a/doc/kommandos.md +++ b/doc/kommandos.md @@ -197,6 +197,7 @@ Aus dem Menue "Tests" bzw. der Kommandozeile; Details in `doc/LispTests.md`. | `TEST_HUNDM05` (`_2D`/`_3D`) | HundM05-Anlage aus der Spec bauen | | `TEST_MUBEA` | Mubea-Nachbau aus `tests/testdata/mubea.json` | | `TEST_DEMATIC_ARVATO` | Dematic/Arvato-Nachbau (Flowpuffer/Kreisel) | +| `TEST_SHOWROOM_ILS` | ILS-3.0-Showroom-Nachbau (Kreisel, Gefaellestrecken; Hoehen noch Platzhalter) | --- diff --git a/lib/dxf_scan_components.py b/lib/dxf_scan_components.py index 5b179de..7863a95 100644 --- a/lib/dxf_scan_components.py +++ b/lib/dxf_scan_components.py @@ -153,6 +153,15 @@ GF_LINIE_MIN_DZ = 100.0 # rechts), nicht der kleinste Abstand. GF_AS_MAX_ENTLANG = 2000.0 GF_AS_MAX_QUER = 700.0 +# Wie weit (mm) das AS/ES VOR dem Linienende liegen darf, also schon auf der +# Kreisel-Seite. In Dematic_arvato liegt es innerhalb der Linie; im +# Showroom_ILS_3_0-Plan endet die Staustrecke 305 mm vor dem Element, das auf +# der Kreisel-Kette sitzt. Kleiner als das Linienraster quer zur Kette +# (Nachbar-GF der Gegenrichtung lag dort 1200 mm entfernt). +GF_AS_MAX_VOR = 800.0 +# Toleranz (mm) fuer zusammenstossende Linienenden beim Zusammenfassen +# zerstueckelter Staustrecken (linien_zusammenfassen). +GF_LINIE_STOSS_TOL = 1.0 # Groesste Luecke (mm) zwischen Linienende und Kreisel-Kette, bis zu der die # Linie an diesen Kreisel angeschlossen wird. Gezeichnet ist nur die @@ -303,8 +312,9 @@ FAMILIEN = [ ("omniflo_weiche", ["Weiche *", "Weichen-TEF*"]), ("bogen", ["3D_ILS_Bogen_*", "3D_FF_Bogen_*", "TEF_Bogen_*", "tef30*", "tef90*"]), + # "AE-D"/"EE-R": Kurznamen der aelteren ILS-Plaene (Showroom_ILS_3_0.dxf) ("schleuselement", ["Ausschleuselement*", "Einschleuselement*", - "EE_Neu*", "AE_Neu*"]), + "EE_Neu*", "AE_Neu*", "AE-*", "EE-*"]), ("sensor", ["S-LP", "S-SP", "SCAN", "SCAN_*", "Scanner*", "Separator*", "Stopper*", "Stopperschleuse*"]), ] @@ -399,12 +409,12 @@ def mappe_block(bname): geraten) # Aus-/Einschleuselement -> AS_Element / ES_Element - if "ausschleus" in low or low.startswith("ae_"): + if "ausschleus" in low or low.startswith(("ae_", "ae-")): winkel, geschaetzt = winkel_aus_name(bname, DEFAULT_SCHLEUS_WINKEL) if geschaetzt: geraten.append("Winkel=%d" % winkel) return "AS_Element_%d_%s" % (winkel, seite_aus()), geraten - if "einschleus" in low or low.startswith("ee_"): + if "einschleus" in low or low.startswith(("ee_", "ee-")): winkel, geschaetzt = winkel_aus_name(bname, DEFAULT_SCHLEUS_WINKEL) if geschaetzt: geraten.append("Winkel=%d" % winkel) @@ -1047,17 +1057,25 @@ def kreisel_paare_bilden(an_liste, sp_liste): 19 m daneben gepaart). Zusaetzlich muessen beide auf einer gemeinsamen Achse liegen (Querversatz <= KREISEL_MAX_QUER). + Danach Tausch-Nachbesserung auf minimale Abstandssumme (siehe unten). + Rueckgabe: (paare [(an, sp, abstand)] nach AN-Position sortiert, AN8 ohne Partner, SP8 ohne Partner) """ + def abstand(an, sp): + """Mittenabstand, oder None wenn nicht auf gemeinsamer Achse.""" + dx = abs(sp["x"] - an["x"]) + dy = abs(sp["y"] - an["y"]) + if min(dx, dy) > KREISEL_MAX_QUER: + return None + return math.hypot(dx, dy) + kandidaten = [] for ia, an in enumerate(an_liste): for isp, sp in enumerate(sp_liste): - dx = abs(sp["x"] - an["x"]) - dy = abs(sp["y"] - an["y"]) - if min(dx, dy) > KREISEL_MAX_QUER: - continue - kandidaten.append((math.hypot(dx, dy), ia, isp)) + d = abstand(an, sp) + if d is not None: + kandidaten.append((d, ia, isp)) kandidaten.sort() an_frei = set(range(len(an_liste))) sp_frei = set(range(len(sp_liste))) @@ -1066,7 +1084,29 @@ def kreisel_paare_bilden(an_liste, sp_liste): if ia in an_frei and isp in sp_frei: an_frei.discard(ia) sp_frei.discard(isp) - paare.append((an_liste[ia], sp_liste[isp], dist)) + paare.append([an_liste[ia], sp_liste[isp], dist]) + + # Nachbesserung: zwei Paare tauschen ihre SP8, solange die Summe der + # Abstaende dadurch kleiner wird. "Kuerzestes Paar zuerst" allein + # scheitert an zwei Kreiseln auf DERSELBEN Achse: die Luecke zwischen + # SP8 des einen und AN8 des anderen ist kuerzer als beide Kreisel und + # wird zuerst gepaart (Showroom_ILS_3_0: 800 + 12832 statt 9033 + 3000). + # Die echte Zuordnung hat immer die kleinere Summe - jede vertauschte + # Paarung ueberspannt mindestens eine Luecke doppelt. + verbessert = True + while verbessert: + verbessert = False + for i in range(len(paare)): + for j in range(i + 1, len(paare)): + a1, s1, d1 = paare[i] + a2, s2, d2 = paare[j] + n1, n2 = abstand(a1, s2), abstand(a2, s1) + if n1 is None or n2 is None or n1 + n2 >= d1 + d2 - 1e-6: + continue + paare[i] = [a1, s2, n1] + paare[j] = [a2, s1, n2] + verbessert = True + paare = [tuple(p) for p in paare] paare.sort(key=lambda p: (p[0]["x"], p[0]["y"])) return (paare, [an_liste[i] for i in sorted(an_frei)], @@ -1320,6 +1360,10 @@ def as_am_ende(p, richtung, as_liste, belegt): auf dieser Seite liegen - das trennt ihn vom Nachbar-AS der Linie daneben. Bereits vergebene AS (belegt = Menge von id()) zaehlen nicht. + Funktioniert genauso fuer Einschleuselemente am unteren Ende (es_liste in + gf_eintraege_aus_linien), dann mit der Richtung vom unteren Ende in die + Linie hinein. + Rueckgabe: (treffer, zielblock, geraten) oder None. """ beste = None @@ -1329,7 +1373,8 @@ def as_am_ende(p, richtung, as_liste, belegt): 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): + if not (-GF_AS_MAX_VOR <= 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 []): @@ -1376,8 +1421,78 @@ def kreisel_anschluss(p, richtung, kreisel): return beste +def linien_zusammenfassen(linien): + """Kollineare, aneinanderstossende Linien desselben Layers vereinen. + + Eine Staustrecke ist im Plan oft in Stuecke zerlegt (Showroom_ILS_3_0: + vier LINEs je GF, an den Separatoren getrennt). Als Einzelstuecke fehlte + jedem das AS am einen oder das ES am anderen Ende. Vereint wird nur bei + gleicher Richtung (in 3D) und Stoss innerhalb GF_LINIE_STOSS_TOL. + """ + def richtung(ln): + d = [ln["b"][i] - ln["a"][i] for i in range(3)] + n = math.sqrt(sum(c * c for c in d)) + return [c / n for c in d] if n else None + + def kollinear(r1, r2): + kreuz = (r1[1] * r2[2] - r1[2] * r2[1], r1[2] * r2[0] - r1[0] * r2[2], + r1[0] * r2[1] - r1[1] * r2[0]) + # 1e-3 rad (~0.06 Grad): die Plaene sind nicht exakt gezeichnet - + # im Showroom-Plan sitzt das Separatorstueck 0,3 mm neben der Achse, + # das verkippt das bis dahin vereinte Stueck um ~2.6e-4. + return math.sqrt(sum(c * c for c in kreuz)) < 1e-3 + + def gleich(p, q): + return math.dist(p, q) <= GF_LINIE_STOSS_TOL + + rest = [{"layer": ln["layer"], "a": list(ln["a"]), "b": list(ln["b"])} + for ln in linien] + fertig = [] + while rest: + akt = rest.pop(0) + gewachsen = True + while gewachsen: + gewachsen = False + ra = richtung(akt) + for ln in rest: + rb = richtung(ln) + if (ln["layer"] != akt["layer"] or ra is None or rb is None + or not kollinear(ra, rb)): + continue + # Die vier moeglichen Stoesse; das Ergebnis behaelt die + # beiden aeusseren Enden. + for p_akt, q_akt in (("a", "b"), ("b", "a")): + for p_ln, q_ln in (("a", "b"), ("b", "a")): + if gleich(akt[p_akt], ln[p_ln]): + akt = {"layer": akt["layer"], "a": akt[q_akt], + "b": ln[q_ln]} + gewachsen = True + break + if gewachsen: + break + if gewachsen: + rest.remove(ln) + break + fertig.append(akt) + return fertig + + +def seite_aus_lage(t, p, r): + """Seite eines AS/ES aus seiner Lage zur GF-Fahrtrichtung. + + t = Element, p = Punkt auf der Achse, r = Fahrtrichtung der GF (bergab). + Links der Fahrtrichtung -> "links", sonst "rechts" - dieselbe Zuordnung + wie in Dematic_arvato ("Ausschleuselement links" liegt links der + Fahrtrichtung, siehe as_am_ende). Nur fuer Bloecke ohne Seite im Namen + ("AE-D"/"EE-R"); gespiegelte Inserts landen dadurch automatisch auf der + Gegenseite. + """ + vx, vy = t["x"] - p[0], t["y"] - p[1] + return "links" if r[0] * vy - r[1] * vx > 0 else "rechts" + + def gf_eintraege_aus_linien(linien, as_liste, kreisel=None, sep_liste=None, - winkel_default=3.0): + winkel_default=3.0, es_liste=None, flach=False): """Gefaellestrecken (Modus 1, Schema 2.1) aus Staustrecken-Linien. Eine Gefaellestrecke laeuft immer bergab: Start ist das HOEHERE @@ -1407,15 +1522,35 @@ def gf_eintraege_aus_linien(linien, as_liste, kreisel=None, sep_liste=None, die Gefaellestrecke selbst mit, als Einzel-Insert stuenden sie doppelt in der Zeichnung (und im Sivas-Export). Der AS eines uebersprungenen Steigfoerderers bleibt unverbraucht. + + es_liste: Einschleuselemente; eines am unteren Ende liefert es_seite/ + es_winkel und wird ebenfalls verbraucht. Ohne es_liste bleiben beide + Defaults (Dematic_arvato hat kein ES im Plan). + + flach: 2D-Plan ohne Hoehen (alle Linien waagrecht, z.B. + Showroom_ILS_3_0). Dann ist eine waagrechte Linie eine GF, wenn genau an + EINEM Ende ein Ausschleuselement sitzt - dieses Ende ist oben. Ohne flach + werden waagrechte Linien wie bisher uebergangen. """ eintraege, hinweise = [], [] belegt = set() waagrecht = 0 kandidaten = [] - for ln in linien: + for ln in linien_zusammenfassen(linien): a, b = ln["a"], ln["b"] if abs(a[2] - b[2]) < GF_LINIE_MIN_DZ: - waagrecht += 1 + lxy = math.hypot(b[0] - a[0], b[1] - a[1]) + if not (flach and lxy > 0.0): + waagrecht += 1 + continue + r_ab = ((b[0] - a[0]) / lxy, (b[1] - a[1]) / lxy) + as_a = as_am_ende(a, r_ab, as_liste, belegt) + as_b = as_am_ende(b, (-r_ab[0], -r_ab[1]), as_liste, belegt) + if (as_a is None) == (as_b is None): + waagrecht += 1 + continue + hoch, tief = (a, b) if as_a is not None else (b, a) + kandidaten.append((hoch, tief, lxy, ln["layer"])) continue hoch, tief = (a, b) if a[2] > b[2] else (b, a) lxy = math.hypot(tief[0] - hoch[0], tief[1] - hoch[1]) @@ -1495,6 +1630,13 @@ def gf_eintraege_aus_linien(linien, as_liste, kreisel=None, sep_liste=None, belegt.add(id(t)) teile = block.split("_") # AS_Element__ eintrag["as_winkel"], eintrag["as_seite"] = teile[2], teile[3] + if "Seite=%s" % DEFAULT_SEITE in geraten: + eintrag["as_seite"] = seite_aus_lage(t, hoch, r) + geraten = [g for g in geraten if not g.startswith("Seite=")] + block = "AS_Element_%s_%s" % (eintrag["as_winkel"], + eintrag["as_seite"]) + kommentar += ("; as_seite %s aus der Lage zur Fahrtrichtung" + % eintrag["as_seite"]) kommentar += ("; AS aus Block '%s' bei (%.2f, %.2f) -> %s, als " "Einzel-Insert entfernt (baut die GF selbst)" % (t["block"], t["x"], t["y"], block)) @@ -1520,7 +1662,28 @@ def gf_eintraege_aus_linien(linien, as_liste, kreisel=None, sep_liste=None, 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)." + unten = (as_am_ende(tief, (-r[0], -r[1]), es_liste, belegt) + if es_liste else None) + if unten is not None: + t, block, geraten = unten + belegt.add(id(t)) + teile = block.split("_") # ES_Element__ + eintrag["es_winkel"], eintrag["es_seite"] = teile[2], teile[3] + if "Seite=%s" % DEFAULT_SEITE in geraten: + eintrag["es_seite"] = seite_aus_lage(t, tief, r) + geraten = [g for g in geraten if not g.startswith("Seite=")] + block = "ES_Element_%s_%s" % (eintrag["es_winkel"], + eintrag["es_seite"]) + kommentar += ("; es_seite %s aus der Lage zur Fahrtrichtung" + % eintrag["es_seite"]) + kommentar += ("; ES 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) + kommentar += "." + else: + kommentar += "; es_seite/es_winkel sind Defaults (kein ES im DXF)." eintrag["comment"] = kommentar eintraege.append(eintrag) @@ -1532,12 +1695,20 @@ def gf_eintraege_aus_linien(linien, as_liste, kreisel=None, sep_liste=None, def baue_eintraege(treffer, defaults, boegen, weichen, - bekannte_bloecke=None, linien=None, kreisel_hoehen=None): + bekannte_bloecke=None, linien=None, kreisel_hoehen=None, + kreisel_achsen=None, flach=False): """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. + + kreisel_achsen: Liste (x1, y1, x2, y2) vom Anwender - Radmitten eines + Kreisels ohne AN8/SP8 im Plan (Showroom_ILS_3_0: Schleifen, die an beiden + Enden mit Weichenboegen schliessen). Wird als STANDARD-Kreisel + angenaehert, x1/y1 gilt als AN8-Seite. + + flach: siehe gf_eintraege_aus_linien. """ nach_familie = {} for t in treffer: @@ -1555,6 +1726,12 @@ def baue_eintraege(treffer, defaults, boegen, weichen, for an in an_rest: hinweise.append("AN8 bei (%.2f, %.2f) ohne SP8-Partner" % (an["x"], an["y"])) eintraege.append(sensor_eintrag(an, bekannte_bloecke, hinweise, treffer)) + for x1, y1, x2, y2 in kreisel_achsen or []: + an = {"block": "(manuell AN8)", "layer": "--kreisel-achse", + "x": x1, "y": y1, "z": 0.0, "darstellung": "2D"} + sp = {"block": "(manuell SP8)", "layer": "--kreisel-achse", + "x": x2, "y": y2, "z": 0.0, "darstellung": "2D"} + kreisel_paare.append((an, sp, math.hypot(x2 - x1, y2 - y1))) for i, (an, sp, dist) in enumerate(kreisel_paare, start=1): rotation = math.degrees(math.atan2(sp["y"] - an["y"], sp["x"] - an["x"])) % 360.0 # AN8/SP8 sind flache 2D-Symbole auf Z=0 - die Einbauhoehe steht nicht @@ -1632,9 +1809,12 @@ def baue_eintraege(treffer, defaults, boegen, weichen, if linien: as_liste = [t for t in nach_familie.get("schleuselement", []) if mappe_block(t["block"])[0].startswith("AS_Element")] + es_liste = [t for t in nach_familie.get("schleuselement", []) + if mappe_block(t["block"])[0].startswith("ES_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)) + defaults.get("gefaelle", {}).get("default_winkel", 3.0), + es_liste, flach) eintraege.extend(gf) hinweise.extend(gf_hinweise) for familie in ("schleuselement", "sensor"): @@ -1873,6 +2053,17 @@ def main(): "(wie im Ausgabe-JSON); gilt auch fuer Start/Ende " "der daran anschliessenden Gefaellestrecken. " "Mehrfach angebbar") + parser.add_argument("--kreisel-achse", nargs=4, type=float, + action="append", metavar=("X1", "Y1", "X2", "Y2"), + help="Kreisel ohne AN8/SP8 im Plan (z.B. Schleife mit " + "Weichenboegen an beiden Enden): Radmitten der " + "beiden Enden, X1/Y1 = AN8-Seite. Wird als " + "STANDARD-Kreisel angenaehert. Mehrfach angebbar") + parser.add_argument("--flach", action="store_true", + help="2D-Plan ohne Hoehen: waagrechte Staustrecken-" + "Linien mit Ausschleuselement an genau einem " + "Ende als Gefaellestrecke ausgeben (Hoehen dann " + "ueber --kreisel-hoehe)") parser.add_argument("--projekt", help="Projektkennung in den erzeugten IDs " "(Default: Name der DXF-Datei ohne Endung)") @@ -1937,7 +2128,8 @@ def main(): eintraege, hinweise = baue_eintraege(treffer, defaults, boegen, weichen, bekannte_bloecke, linien, - args.kreisel_hoehe) + args.kreisel_hoehe, args.kreisel_achse, + args.flach) if args.ohne_hoehen: print("%s --ohne-hoehen: Hoehe in %d Eintraegen auf 0 gesetzt" % (PREFIX, hoehen_entfernen(eintraege))) diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index 50aeee5..467be1c 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -741,48 +741,54 @@ + + ILS 3.0 Showroom (Vorlage) + ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS) + + + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + Kreisel bearbeiten (ssg-ensure "KreiselInsert") KreiselEdit - + Omniflo Element bearbeiten (ssg-ensure "OmniModulInsert") OMNI_EDIT - + Gefaellestrecke bearbeiten (ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT - + VarioFoerderer bearbeiten (ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT - + TRO-Marker bearbeiten (ssg-ensure "Tro_Edit") TRO_EDIT - + Verbindung bearbeiten (ssg-ensure "Connection_Edit") CONNECTION_EDIT - + SSG Bearbeiten (Auto) (ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT @@ -1640,56 +1646,62 @@ - KS 2D/3D pruefen (CSV) + ILS 3.0 Showroom (Vorlage) + + KS 2D/3D pruefen (CSV) + + + + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - + Omniflo Element bearbeiten - + Gefaellestrecke bearbeiten - + VarioFoerderer bearbeiten - + TRO-Marker bearbeiten - + Verbindung bearbeiten - + SSG Bearbeiten (Auto) - + diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 703b508..7b7ed54 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -155,6 +155,7 @@ [Mubea Gesamtmodell]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) (c:TEST_MUBEA) [HundM05 Kreisel-Abschnitt]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) (c:TEST_HUNDM05) [Dematic/Arvato Flowpuffer (Vorlage)]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) (c:TEST_DEMATIC_ARVATO) +[ILS 3.0 Showroom (Vorlage)]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS) [<-KS 2D/3D pruefen (CSV)]^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN ***DOUBLECLICK diff --git a/tests/alltests.json b/tests/alltests.json index f427384..d0caa14 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -11,5 +11,6 @@ { "name": "mubea", "save": "dxf", "module": "KreiselInsert" }, { "name": "hundm05", "save": "dxf", "module": "VarioFoerderer" }, { "name": "dematic_arvato", "save": "dxf", "module": "VarioFoerderer", "disabled": true }, - { "name": "hm_recformat", "save": "dxf", "module": "VarioFoerderer", "disabled": true } + { "name": "hm_recformat", "save": "dxf", "module": "VarioFoerderer", "disabled": true }, + { "name": "showroom_ils", "save": "dxf", "module": "VarioFoerderer", "disabled": true } ] diff --git a/tests/test_dematic_arvato.lsp b/tests/test_dematic_arvato.lsp index 6fcb57c..3583bec 100644 --- a/tests/test_dematic_arvato.lsp +++ b/tests/test_dematic_arvato.lsp @@ -255,25 +255,27 @@ "fehler_text" (vl-catch-all-error-message res))) res)) -(defun c:TEST_DEMATIC_ARVATO ( / datei daten out zaehler st) - (setq datei (dematic_arvato:datei)) +;; Alle Eintraege einer Nachbau-Datei bauen und die Records zurueckgeben +;; (nil bei Fehler vor dem Bau). Gemeinsamer Lauf fuer jeden Treiber mit +;; diesem Eintragsschema - auch tests/test_showroom_ils.lsp. +;; befehl = Name fuer Meldungen/ssg-start, titel = Kopfzeile, datei = JSON +(defun dematic_arvato:bau-datei (befehl titel datei / daten out records zaehler st) (princ "\n\n================================================================") - (princ "\n TEST_DEMATIC_ARVATO - Abschnitt Flowpuffer/Kreisel") + (princ (strcat "\n " befehl " - " titel)) (princ (strcat "\n " datei)) (princ "\n================================================================") (cond ((null (car (atoms-family 1 '("VSP-BAU-JOURNAL")))) - (princ "\n[TEST_DEMATIC_ARVATO] FEHLER: vf_spec.lsp nicht geladen" - " (VarioFoerderer laden).") - (setq *dematic-arvato-records* nil)) + (princ (strcat "\n[" befehl "] FEHLER: vf_spec.lsp nicht geladen" + " (VarioFoerderer laden).")) + nil) ((null (findfile datei)) - (princ (strcat "\n[TEST_DEMATIC_ARVATO] FEHLER: " datei " nicht gefunden!")) - (setq *dematic-arvato-records* nil)) + (princ (strcat "\n[" befehl "] FEHLER: " datei " nicht gefunden!")) + nil) (T (setq daten (ssg-json-laden datei)) (princ (strcat "\n " (itoa (length daten)) " Eintraege geladen")) - (ssg-start "TEST_DEMATIC_ARVATO" - '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) + (ssg-start befehl '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) (setvar "OSMODE" 0) (setvar "ATTREQ" 0) (setvar "ATTDIA" 0) @@ -285,10 +287,10 @@ (foreach obj daten (setq out (cons (dematic_arvato:bau-eintrag obj) out))) (ssg-end) - (setq *dematic-arvato-records* (reverse out)) + (setq records (reverse out)) ;; Zusammenfassung je Status (setq zaehler '()) - (foreach r *dematic-arvato-records* + (foreach r records (setq st (cond ((ssg-val r "status")) ("?"))) (if (assoc st zaehler) (setq zaehler (subst (cons st (1+ (cdr (assoc st zaehler)))) @@ -298,7 +300,13 @@ (foreach z (reverse zaehler) (princ (strcat "\n " (car z) ": " (itoa (cdr z))))) (princ "\n================================================================") - (command "_.ZOOM" "_E"))) + (command "_.ZOOM" "_E") + records))) + +(defun c:TEST_DEMATIC_ARVATO () + (setq *dematic-arvato-records* + (dematic_arvato:bau-datei "TEST_DEMATIC_ARVATO" "Abschnitt Flowpuffer/Kreisel" + (dematic_arvato:datei))) (princ)) ;; Ergebnis-Export (Konvention :export-results fuer test_run_all.lsp) diff --git a/tests/test_dxf_scan_gf.py b/tests/test_dxf_scan_gf.py index 65de3c1..25a9c0f 100644 --- a/tests/test_dxf_scan_gf.py +++ b/tests/test_dxf_scan_gf.py @@ -166,3 +166,97 @@ def test_ohne_as_defaults_und_hinweis(): assert eintraege[0]["hz_grad"] == 0.0 assert eintraege[0]["as_seite"] == dsc.DEFAULT_SEITE assert any("ohne Ausschleuselement" in h for h in hinweise) + + +# --- Showroom_ILS_3_0.dxf: flacher 2D-Plan, Werte dort gemessen ------------- + +def _station(x, y): + return {"x": x, "y": y} + + +def test_kreisel_paare_zwei_kreisel_auf_einer_achse(): + # x=26998: AN8 y=3466 / SP8 y=12499 und AN8 y=13299 / SP8 y=16299. Die + # Luecke SP8(12499)-AN8(13299) = 800 ist das kuerzeste Paar - ohne + # Tausch-Nachbesserung kamen 800 + 12833 statt 9033 + 3000 heraus. + an = [_station(26998.0, 3466.4), _station(26998.0, 13298.9)] + sp = [_station(26998.0, 16298.9), _station(26998.0, 12498.9)] + paare, an_rest, sp_rest = dsc.kreisel_paare_bilden(an, sp) + assert an_rest == [] and sp_rest == [] + abstaende = sorted(round(p[2], 1) for p in paare) + assert abstaende == [3000.0, 9032.5] + + +def test_zerstueckelte_linie_wird_vereint(): + # GF bei x=34006 in vier Stuecken, das Separatorstueck 0,3 mm daneben + stuecke = [ + _linie((34005.93, 13893.90, 0.0), (34005.93, 14193.90, 0.0)), + _linie((34005.93, 15043.90, 0.0), (34005.93, 15593.90, 0.0)), + _linie((34006.24, 14744.31, 0.0), (34006.24, 15044.31, 0.0)), + _linie((34005.93, 14193.90, 0.0), (34005.93, 14743.90, 0.0)), + ] + vereint = dsc.linien_zusammenfassen(stuecke) + assert len(vereint) == 1 + ys = sorted([vereint[0]["a"][1], vereint[0]["b"][1]]) + assert ys == pytest.approx([13893.90, 15593.90], abs=0.01) + + +def test_parallele_nachbarlinie_wird_nicht_vereint(): + linien = [_linie((0.0, 0.0, 0.0), (0.0, 1000.0, 0.0)), + _linie((800.0, 1000.0, 0.0), (800.0, 2000.0, 0.0))] + assert len(dsc.linien_zusammenfassen(linien)) == 2 + + +def _kreisel_d(): + # Schleife oben (Kette unten y=15898.9), Radmitten per --kreisel-achse + return {"id": "K_D", "a": (27797.93, 16298.9), "u": (1.0, 0.0), + "laenge": 8908.0, "r": 400.0, "z_anwender": None} + + +def _kreisel_e(): + # Schleife Mitte (Kette oben y=13493.9) + return {"id": "K_E", "a": (28772.93, 13093.9), "u": (1.0, 0.0), + "laenge": 6533.0, "r": 400.0, "z_anwender": None} + + +def test_flacher_plan_richtung_und_seiten_aus_as_es(): + # Waagrechte Linie; AE-D sitzt 305 mm VOR dem oberen Ende auf der + # Kreisel-Kette, 180 mm links der Fahrtrichtung (Fahrt -y, links = +x). + # EE-R am unteren Ende 212 mm rechts davon. + linie = _linie((30455.93, 13893.9, 0.0), (30455.93, 15593.9, 0.0)) + ae = {"familie": "schleuselement", "block": "AE-D", + "x": 30636.27, "y": 15898.9, "z": 0.0, "rot": 270.0} + ee = {"familie": "schleuselement", "block": "EE-R", + "x": 30243.76, "y": 13493.9, "z": 0.0, "rot": 90.0} + eintraege, belegt, _ = dsc.gf_eintraege_aus_linien( + [linie], [ae], [_kreisel_d(), _kreisel_e()], es_liste=[ee], flach=True) + assert len(eintraege) == 1 + gf = eintraege[0] + assert gf["hz_grad"] == 270.0 + assert gf["start_mm"][:2] == [30455.93, 15898.9] + assert gf["deltaL_mm"] == pytest.approx(2405.0, abs=0.01) + assert (gf["as_seite"], gf["es_seite"]) == ("links", "rechts") + assert (gf["kreisel_start"], gf["kreisel_ende"]) == ("K_D", "K_E") + assert belegt == {id(ae), id(ee)} + + +def test_flacher_plan_ohne_flach_schalter_keine_gf(): + linie = _linie((30455.93, 13893.9, 0.0), (30455.93, 15593.9, 0.0)) + ae = {"familie": "schleuselement", "block": "AE-D", + "x": 30636.27, "y": 15898.9, "z": 0.0, "rot": 270.0} + eintraege, _, _ = dsc.gf_eintraege_aus_linien([linie], [ae]) + assert eintraege == [] + + +def test_flacher_plan_ohne_as_keine_gf(): + # Staulinie am Kreisel B (x=36348) - kein AS an einem Ende + linie = _linie((36347.6, 11313.9, 0.0), (36347.6, 14913.9, 0.0)) + eintraege, _, hinweise = dsc.gf_eintraege_aus_linien([linie], [], flach=True) + assert eintraege == [] + assert any("waagrechte" in h for h in hinweise) + + +def test_kurznamen_ae_ee(): + assert dsc.familie_von("AE-D") == "schleuselement" + assert dsc.familie_von("EE-R") == "schleuselement" + assert dsc.mappe_block("AE-D")[0].startswith("AS_Element_90_") + assert dsc.mappe_block("EE-R")[0].startswith("ES_Element_90_") diff --git a/tests/test_showroom_ils.lsp b/tests/test_showroom_ils.lsp new file mode 100644 index 0000000..8e20f8a --- /dev/null +++ b/tests/test_showroom_ils.lsp @@ -0,0 +1,58 @@ +;; ============================================================ +;; test_showroom_ils.lsp - Nachbau des ILS-3.0-Showrooms. +;; +;; Datenquelle: tests/testdata/showroom_ils.json, erzeugt aus der +;; Fremdzeichnung (flacher 2D-Plan, keine Hoehen): +;; python lib/dxf_scan_components.py \ +;; C:/develop/sps_skel/data/Showroom_ILS_3_0.dxf data \ +;; tests/testdata/showroom_ils.json --projekt Showroom \ +;; --flach --ohne-hoehen \ +;; --kreisel-achse 27797.93 16298.90 36705.93 16298.90 \ +;; --kreisel-achse 28772.93 13093.90 35305.93 13093.90 +;; +;; Inhalt: 6 Kreisel, 11 Gefaellestrecken (Modus 1), Separatoren/Scanner. +;; - Kreisel_Showroom_05/06 sind Schleifen ohne AN8/SP8 im Plan (an beiden +;; Enden Weichenboegen) - per --kreisel-achse als STANDARD-Kreisel +;; angenaehert (Radmitten = Mittelpunkte der PIN-Kreise). +;; - Weichen (WB-*/WM-*/WL-*/WR-*) werden bewusst NICHT nachgebaut. +;; - HOEHEN FEHLEN NOCH: alle z = 0, die GF laufen mit dem Platzhalter +;; winkel_grad 3.0. Sobald die Kreisel-Hoehen bekannt sind, den Scanner +;; mit --kreisel-hoehe X Y Z je Kreisel neu laufen lassen - dann bekommen +;; die GF start_mm.z/hoehe_ende_mm und der Treiber rechnet den Winkel. +;; +;; Keine eigene Bau-Logik: Eintragsschema und Lauf wie TEST_DEMATIC_ARVATO +;; (dematic_arvato:bau-datei aus tests/test_dematic_arvato.lsp). +;; +;; Aufruf in BricsCAD: +;; (load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) +;; TEST_SHOWROOM_ILS +;; ============================================================ + +(if (not (boundp '*showroom-ils-records*)) (setq *showroom-ils-records* nil)) + +;; Beim LADEN holen (siehe test_dematic_arvato.lsp: per load definierte +;; Funktionen sind erst nach Rueckkehr zum Top-Level sichtbar). +(if (null (car (atoms-family 1 '("DEMATIC_ARVATO:BAU-DATEI")))) + (load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp"))) + +(defun showroom_ils:datei () + (strcat (getenv "DXFMAKRO") "/tests/testdata/showroom_ils.json")) + +(defun c:TEST_SHOWROOM_ILS () + (setq *showroom-ils-records* + (dematic_arvato:bau-datei "TEST_SHOWROOM_ILS" "ILS 3.0 Showroom" + (showroom_ils:datei))) + (princ)) + +;; Ergebnis-Export (Konvention :export-results fuer test_run_all.lsp) +(defun showroom_ils:export-results (out-dir / pfad) + (if (null *showroom-ils-records*) + (princ "\n Keine Showroom-Ergebnisse vorhanden.") + (progn + (vl-mkdir out-dir) + (setq pfad (strcat out-dir "/showroom_ils_results.json")) + (vsp-results-schreiben *showroom-ils-records* pfad))) + (princ)) + +(princ "\nTEST_SHOWROOM_ILS geladen. Aufruf: TEST_SHOWROOM_ILS") +(princ) diff --git a/tests/testdata/showroom_ils.json b/tests/testdata/showroom_ils.json new file mode 100644 index 0000000..81e9af7 --- /dev/null +++ b/tests/testdata/showroom_ils.json @@ -0,0 +1,410 @@ +[ + { + "id": "Kreisel_Showroom_01", + "function": "insert", + "x": 26997.93, + "y": 3066.4, + "z": 0.0, + "abstand": 9032.5, + "rotation": 90.0, + "typ": "STANDARD", + "darstellung": "2D", + "comment": "aus DXF: Radmitte AN8 (Layer 1ANTR-800) (26997.93, 3466.40) -> SP8 (Layer 1SP-800) (26997.93, 12498.90), Mittenabstand 9032.50, rotation 90.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=11039.9 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. [Hoehe entfernt: --ohne-hoehen]", + "hoehe_unbekannt": true + }, + { + "id": "Kreisel_Showroom_02", + "function": "insert", + "x": 26997.93, + "y": 12898.9, + "z": 0.0, + "abstand": 3000.0, + "rotation": 90.0, + "typ": "STANDARD", + "darstellung": "2D", + "comment": "aus DXF: Radmitte AN8 (Layer 1ANTR-800) (26997.93, 13298.90) -> SP8 (Layer 1SP-800) (26997.93, 16298.90), Mittenabstand 3000.00, rotation 90.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=11039.9 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. [Hoehe entfernt: --ohne-hoehen]", + "hoehe_unbekannt": true + }, + { + "id": "Kreisel_Showroom_03", + "function": "insert", + "x": 37105.93, + "y": 9888.9, + "z": 0.0, + "abstand": 7200.0, + "rotation": 180.0, + "typ": "STANDARD", + "darstellung": "2D", + "comment": "aus DXF: Radmitte AN8 (Layer 1ANTR-800) (36705.93, 9888.90) -> SP8 (Layer 1SP-800) (29505.93, 9888.90), Mittenabstand 7200.00, rotation 180.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=11039.9 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. [Hoehe entfernt: --ohne-hoehen]", + "hoehe_unbekannt": true + }, + { + "id": "Kreisel_Showroom_04", + "function": "insert", + "x": 36705.93, + "y": 10288.9, + "z": 0.0, + "abstand": 4810.0, + "rotation": 90.0, + "typ": "STANDARD", + "darstellung": "2D", + "comment": "aus DXF: Radmitte AN8 (Layer 1ANTR-800) (36705.93, 10688.90) -> SP8 (Layer 1SP-800) (36705.93, 15498.90), Mittenabstand 4810.00, rotation 90.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=11039.9 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe. [Hoehe entfernt: --ohne-hoehen]", + "hoehe_unbekannt": true + }, + { + "id": "Kreisel_Showroom_05", + "function": "insert", + "x": 27397.93, + "y": 16298.9, + "z": 0.0, + "abstand": 8908.0, + "rotation": 0.0, + "typ": "STANDARD", + "darstellung": "2D", + "comment": "aus DXF: Radmitte (manuell AN8) (Layer --kreisel-achse) (27797.93, 16298.90) -> (manuell SP8) (Layer --kreisel-achse) (36705.93, 16298.90), Mittenabstand 8908.00, rotation 0.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=0.0 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe.", + "hoehe_unbekannt": true + }, + { + "id": "Kreisel_Showroom_06", + "function": "insert", + "x": 28372.93, + "y": 13093.9, + "z": 0.0, + "abstand": 6533.0, + "rotation": 0.0, + "typ": "STANDARD", + "darstellung": "2D", + "comment": "aus DXF: Radmitte (manuell AN8) (Layer --kreisel-achse) (28772.93, 13093.90) -> (manuell SP8) (Layer --kreisel-achse) (35305.93, 13093.90), Mittenabstand 6533.00, rotation 0.00 Grad; x/y = AN8-Mitte minus Radius. ACHTUNG hoehe=0.0 ist NICHT belegt: AN8/SP8 sind flache 2D-Symbole auf Z=0 und nur 0 3D-Nachbarn in 3000 mm - zu wenige fuer eine belegbare Hoehe.", + "hoehe_unbekannt": true + }, + { + "test_id": "GF_Showroom_01", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [30455.93, 15898.9, 0.0], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (30455.93, 15593.90) -> (30455.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (30636.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (30456, 13994), S-SP bei (30456, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (30243.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_02", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [31255.93, 12693.9, 0.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (31255.93, 12388.90) -> (31255.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (31075.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (31256, 10789), S-SP bei (31256, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (31468.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_03", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [31255.93, 15898.9, 0.0], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (31255.93, 15593.90) -> (31255.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (31436.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (31256, 13994), S-SP bei (31256, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (31043.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_04", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [32055.93, 12693.9, 0.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (32055.93, 12388.90) -> (32055.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (31875.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (32056, 10789), S-SP bei (32056, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (32268.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_05", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [32055.93, 15898.9, 0.0], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (32055.93, 15593.90) -> (32055.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (32236.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (32056, 13994), S-SP bei (32056, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (31843.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_06", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [33205.93, 12693.9, 0.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (33205.93, 12388.90) -> (33205.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (33025.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (33206, 10789), S-SP bei (33206, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (33418.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_07", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [33205.93, 15898.9, 0.0], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (33205.93, 15593.90) -> (33205.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (33386.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (33206, 13994), S-SP bei (33206, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (32993.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_08", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34005.93, 12693.9, 0.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (34005.93, 12388.90) -> (34005.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (33825.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34006, 10789), S-SP bei (34006, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (34218.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_09", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34005.93, 15898.9, 0.0], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (34005.93, 15593.90) -> (34005.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (34186.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34006, 13994), S-SP bei (34006, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (33793.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_10", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34805.93, 12693.9, 0.0], + "as_seite": "rechts", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_06", + "kreisel_ende": "Kreisel_Showroom_03", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (34805.93, 12388.90) -> (34805.93, 10688.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_03 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite rechts aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (34625.60, 12693.90) -> AS_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34806, 10789), S-SP bei (34806, 11602); es_seite links aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (35018.11, 10288.90) -> ES_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "test_id": "GF_Showroom_11", + "modus": 1, + "deltaL_mm": 2405.0, + "hz_grad": 270.0, + "start_mm": [34805.93, 15898.9, 0.0], + "as_seite": "links", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "kreisel_start": "Kreisel_Showroom_05", + "kreisel_ende": "Kreisel_Showroom_06", + "winkel_grad": 3.0, + "hoehe_unbekannt": true, + "comment": "aus DXF: LINE auf Layer '6-SP' (34805.93, 15593.90) -> (34805.93, 13893.90), Laenge XY 1700.00; am oberen Ende um 305.0 mm bis zur Kette von Kreisel_Showroom_05 verlaengert; am unteren Ende um 400.0 mm bis zur Kette von Kreisel_Showroom_06 verlaengert; Hoehen unbekannt - winkel_grad 3.0 ist Platzhalter; as_seite links aus der Lage zur Fahrtrichtung; AS aus Block 'AE-D' bei (34986.27, 15898.90) -> AS_Element_90_links, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90; Separator auf der Achse entfernt (die GF baut ihren eigenen): S-SP bei (34806, 13994), S-SP bei (34806, 14807); es_seite rechts aus der Lage zur Fahrtrichtung; ES aus Block 'EE-R' bei (34593.76, 13493.90) -> ES_Element_90_rechts, als Einzel-Insert entfernt (baut die GF selbst) - geraten: Winkel=90." + }, + { + "function": "insert", + "block": "S-LP", + "x": 26597.93, + "y": 11973.9, + "z": 0.0, + "rotation": 270.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 26597.93, + "y": 13982.92, + "z": 0.0, + "rotation": 90.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer 'TGW_HB_2OG-AM_5'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 27397.93, + "y": 5450.07, + "z": 0.0, + "rotation": 90.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer 'TGW_HB_2OG-AM_5'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 27397.93, + "y": 15773.9, + "z": 0.0, + "rotation": 270.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 29397.93, + "y": 13493.9, + "z": 0.0, + "rotation": 0.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 30343.39, + "y": 9488.9, + "z": 0.0, + "rotation": 0.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 32083.74, + "y": 9488.9, + "z": 0.0, + "rotation": 0.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 35905.93, + "y": 10288.9, + "z": 0.0, + "rotation": 180.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "S-LP", + "x": 37105.93, + "y": 14978.9, + "z": 0.0, + "rotation": 270.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "Scanner", + "x": 29398.73, + "y": 13176.71, + "z": 0.0, + "rotation": 270.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "Scanner", + "x": 30343.39, + "y": 9208.9, + "z": 0.0, + "rotation": 270.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "Scanner", + "x": 32083.74, + "y": 9208.9, + "z": 0.0, + "rotation": 270.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "Scanner", + "x": 35905.93, + "y": 10008.9, + "z": 0.0, + "rotation": 270.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + }, + { + "function": "insert", + "block": "Scanner", + "x": 36825.93, + "y": 14978.9, + "z": 0.0, + "rotation": 180.0, + "darstellung": "2D", + "hoehe_unbekannt": true, + "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet; 2D-Schema ohne 3D-Zwilling - z ist keine Einbauhoehe (hoehe_unbekannt) [Hoehe entfernt: --ohne-hoehen]" + } +]