HundM05-Testfall und DXF-Analyse-Skripte
- lib/dxf_scan_components.py: erkennt ILS-/Omniflo-Komponenten in fremden
Projektzeichnungen und schreibt sie im Testdaten-JSON-Schema aus
- lib/dxf_abbild.py: getreue Abbildung der Bauteile einer Fremdzeichnung
(Attribute, Weltkoordinaten, Unterkomponenten) ohne Interpretation
- tests/test_hundm05.{lsp,py}, testdata/hundm05.json, conftest-Fixtures und
alltests.json-Eintrag fuer den Kreisel-Abschnitt aus ST500592_05.dxf
- menu: TEST_HUNDM05 im Testmenue, Connection_Insert/Edit in SSG_LIB.cui
- Doku: hartkodierte Pfade durch (getenv "DXFMAKRO") ersetzt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,7 +50,7 @@ Menue einmalig in BricsCAD geladen wurde (siehe unten: "Menue einrichten").
|
||||
|
||||
Alternativ per LISP-Kommandozeile in BricsCAD:
|
||||
```lisp
|
||||
(command "_.MENULOAD" "C:/10-Develop/gitrepos/dxfmakros/menu/SSG_LIB.mnu")
|
||||
(command "_.MENULOAD" (strcat (getenv "DXFMAKRO") "/menu/SSG_LIB.mnu"))
|
||||
```
|
||||
|
||||
Das Menue bleibt dauerhaft registriert. Nach einem Update der `.mnu`-Datei
|
||||
@@ -157,6 +157,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM
|
||||
| `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Aufruf: `python export_sivas.py <export_raw.json> <data_dir> <output.csv>` |
|
||||
| `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Aufruf: `python export_csv.py <export_raw.json> <data_dir> <output.csv>` |
|
||||
| `split_block_library.py` | Teilt `ils_library.dxf` in einzelne DXF-Dateien pro Block auf (mit Abhaengigkeiten). Export-Prefixe: Vario_, Staustrecke_, _3D_AS_, _3D_ES_ |
|
||||
| `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `tests/testdata/object_data.md` — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/hundm05.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Aufruf: `python dxf_scan_components.py <dxf> <data_dir> <output.json> [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf |
|
||||
|
||||
### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ alle Feature-Module). Details: siehe `../CLAUDE.md` Abschnitt "Lademechanismus".
|
||||
|
||||
**Manuell laden (Notfall / Entwicklung):**
|
||||
```lisp
|
||||
(load "C:/10-Develop/gitrepos/dxfmakros/Lisp/ssg_load.lsp")
|
||||
(load (strcat (getenv "DXFMAKRO") "/Lisp/ssg_load.lsp"))
|
||||
```
|
||||
`ssg_load.lsp` laedt alle Module in der richtigen Reihenfolge ohne Menueabhaengigkeit.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,333 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
dxf_abbild.py - Getreue Abbildung der Bauteile einer Fremdzeichnung als JSON.
|
||||
|
||||
Abgrenzung zu lib/dxf_scan_components.py: dieses Skript beschreibt, WAS in der
|
||||
Zeichnung steht - nicht, wie unsere Makros es nachbauen sollen. Es interpretiert
|
||||
so wenig wie moeglich und schreibt alles mit, was im DXF wirklich vorhanden ist:
|
||||
Blockname, Layer, Weltposition, Rotation, Skalierung, Extrusionsrichtung,
|
||||
saemtliche Blockattribute und die verschachtelten Unterkomponenten. Wo eine
|
||||
Angabe nicht im DXF steht, steht sie auch nicht in der Ausgabe - es wird nichts
|
||||
geraten und nichts mit Defaults aufgefuellt.
|
||||
|
||||
Der Typ eines Bauteils kommt bevorzugt aus den Attributen (BESCHR, BEZEICHNUNG,
|
||||
VERW), nicht aus dem Blocknamen: in ST500592_05.dxf tragen 15730 INSERTs
|
||||
Attribute mit 80 verschiedenen Tags, darunter Klartext wie
|
||||
BESCHR="TEF-Profil-Bogen innen 67,5 Grad R400". Das ist belastbarer als jede
|
||||
Blocknamen-Heuristik.
|
||||
|
||||
Was das DXF NICHT enthaelt und was hier darum fehlt:
|
||||
- Flussrichtung/Pfeile: kein Blockname enthaelt Pfeil/arrow/flow/Richtung,
|
||||
die Pfeile sind gezeichnete Geometrie (SOLID/Polylinie). Nicht ableitbar.
|
||||
- Explizite Verbindungsgraphen: die Zeichnung hat 0 GROUP-Objekte. Die
|
||||
vorhandenen XDATA sind Fremd-Metadaten (TeklaStructures, CSTABVIEWER).
|
||||
Was an Beziehung drinsteckt, steckt in den Kennzeichnungs-Strings und wird
|
||||
unter "beziehungen" als GELESENER Text abgebildet, nicht als Topologie.
|
||||
- Zeichnungsgeometrie (LINE/ARC/LWPOLYLINE): bewusst nicht erfasst.
|
||||
|
||||
Aufruf:
|
||||
python dxf_abbild.py <dxf> <output.json> [--fenster X0 Y0 X1 Y1]
|
||||
[--max-tiefe N] [--ohne-geometrie-container]
|
||||
|
||||
Beispiel:
|
||||
python lib/dxf_abbild.py results/ST500592_05.dxf results/hundm05_abbild.json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ezdxf.math import Matrix44
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from dxf_scan_components import ( # noqa: E402
|
||||
RUND, familie_von, insert_matrix, lese_inserts, im_fenster)
|
||||
|
||||
PREFIX = "[dxf_abbild]"
|
||||
|
||||
# Attribut-Tags, die ein Bauteil identifizieren. Reihenfolge = Aussagekraft.
|
||||
ID_TAGS = ["ID", "ARTINR", "OriginalName", "KENNZEICHNUNG", "POSITION",
|
||||
"DETAILS", "IO"]
|
||||
|
||||
# Attribut-Tags, aus denen sich der Bauteiltyp im Klartext ergibt.
|
||||
TYP_TAGS = ["BESCHR", "BEZEICHNUNG", "VERW"]
|
||||
|
||||
# Attribut-Tags, die eine Zugehoerigkeit ausdruecken (Gruppe, Steuerung,
|
||||
# Anlagenkennzeichen). Werden als gelesener Text uebernommen.
|
||||
BEZUG_TAGS = ["KENNZEICHNUNG", "GRUPPE", "SPS", "AUFLOESE", "ETIKETTE"]
|
||||
|
||||
# Blocknamen, die reine Geometrie-Container sind (Volumenkoerper-Huellen aus
|
||||
# dem CAD-Import). Sie tragen keine Bauteilinformation, blaehen die Ausgabe aber
|
||||
# stark auf - mit --ohne-geometrie-container werden sie nur gezaehlt.
|
||||
GEOMETRIE_CONTAINER = re.compile(r"^(sgBrepBlock__|\*[A-Z])")
|
||||
|
||||
# EPLAN-Kennzeichnung: =Anlage +Ort -Betriebsmittel
|
||||
KENNZ_RE = re.compile(r"(?:=([^+\-=]+))?(?:\+([^+\-=]+))?(?:-(.+))?$")
|
||||
|
||||
# Verweis in Klartext-Bezeichnungen, z.B. "in Separator (ILS-CV M2111)"
|
||||
VERWEIS_RE = re.compile(r"\(([^)]+)\)")
|
||||
|
||||
|
||||
def identifikatoren(attribs):
|
||||
"""Alle vorhandenen Identifikatoren eines Bauteils - nur echte Werte."""
|
||||
ids = {}
|
||||
for tag in ID_TAGS:
|
||||
wert = attribs.get(tag)
|
||||
if wert not in (None, ""):
|
||||
ids[tag] = wert
|
||||
return ids
|
||||
|
||||
|
||||
def typ_von(attribs, blockname):
|
||||
"""Bauteiltyp ermitteln.
|
||||
|
||||
Rueckgabe: (typ, quelle). "quelle" nennt das Attribut, aus dem der Typ
|
||||
stammt, bzw. "blockname" als Rueckfallebene - damit im JSON sichtbar
|
||||
bleibt, wie belastbar die Angabe ist.
|
||||
"""
|
||||
for tag in TYP_TAGS:
|
||||
wert = attribs.get(tag)
|
||||
if wert not in (None, ""):
|
||||
return wert, tag
|
||||
return blockname, "blockname"
|
||||
|
||||
|
||||
def kennzeichnung_teile(wert):
|
||||
"""=Anlage +Ort -Betriebsmittel aufschluesseln, soweit vorhanden."""
|
||||
m = KENNZ_RE.match(wert.strip())
|
||||
if not m:
|
||||
return {}
|
||||
teile = {}
|
||||
for name, gruppe in (("anlage", 1), ("ort", 2), ("betriebsmittel", 3)):
|
||||
if m.group(gruppe):
|
||||
teile[name] = m.group(gruppe).strip()
|
||||
return teile
|
||||
|
||||
|
||||
def beziehungen(attribs):
|
||||
"""Zugehoerigkeiten, die als Text in den Attributen stehen.
|
||||
|
||||
Bewusst KEINE Topologie: die Zeichnung enthaelt keinen Verbindungsgraphen
|
||||
(0 GROUP-Objekte, XDATA nur von Fremd-Anwendungen). Hier steht nur, was
|
||||
wirklich geschrieben ist - die Auswertung bleibt dem Leser.
|
||||
"""
|
||||
bez = {}
|
||||
for tag in BEZUG_TAGS:
|
||||
wert = attribs.get(tag)
|
||||
if wert not in (None, ""):
|
||||
bez[tag] = wert
|
||||
kennz = attribs.get("KENNZEICHNUNG")
|
||||
if kennz:
|
||||
teile = kennzeichnung_teile(kennz)
|
||||
if teile:
|
||||
bez["kennzeichnung_teile"] = teile
|
||||
# Verweis auf ein benanntes Foerdermittel, z.B. "(ILS-CV M2111)"
|
||||
for tag in ("BEZEICHNUNG", "BESCHR"):
|
||||
wert = attribs.get(tag)
|
||||
if wert:
|
||||
m = VERWEIS_RE.search(wert)
|
||||
if m:
|
||||
bez["verweis"] = m.group(1).strip()
|
||||
break
|
||||
return bez
|
||||
|
||||
|
||||
def welt_daten(rec, mutter, block_base):
|
||||
"""Weltposition, Rotation und Gesamtmatrix eines INSERT."""
|
||||
mat = insert_matrix(rec, block_base) @ mutter
|
||||
ursprung = mat.transform((0.0, 0.0, 0.0))
|
||||
xachse = mat.transform((1.0, 0.0, 0.0)) - ursprung
|
||||
rot = math.degrees(math.atan2(xachse.y, xachse.x)) % 360.0
|
||||
return mat, ursprung, rot
|
||||
|
||||
|
||||
def element_dict(rec, ursprung, rot, tiefe):
|
||||
"""Einen INSERT als Abbild-Eintrag beschreiben."""
|
||||
attribs = dict(rec["attribs"])
|
||||
typ, typ_quelle = typ_von(attribs, rec["name"])
|
||||
eintrag = collections.OrderedDict()
|
||||
eintrag["block"] = rec["name"]
|
||||
eintrag["layer"] = rec["layer"] or ""
|
||||
eintrag["typ"] = typ
|
||||
eintrag["typ_quelle"] = typ_quelle
|
||||
eintrag["position"] = [round(ursprung.x, RUND) + 0.0,
|
||||
round(ursprung.y, RUND) + 0.0,
|
||||
round(ursprung.z, RUND) + 0.0]
|
||||
eintrag["rotation"] = round(rot, RUND) + 0.0
|
||||
if [round(s, 6) for s in rec["s"]] != [1.0, 1.0, 1.0]:
|
||||
eintrag["skalierung"] = [round(s, 6) for s in rec["s"]]
|
||||
if [round(v, 6) for v in rec["ext"]] != [0.0, 0.0, 1.0]:
|
||||
# Nur wenn abweichend: die Extrusionsrichtung erklaert, warum ein
|
||||
# Bauteil gekippt/gespiegelt im Raum liegt (13,7 % der INSERTs).
|
||||
eintrag["extrusion"] = [round(v, 6) for v in rec["ext"]]
|
||||
ids = identifikatoren(attribs)
|
||||
if ids:
|
||||
eintrag["ids"] = ids
|
||||
bez = beziehungen(attribs)
|
||||
if bez:
|
||||
eintrag["beziehungen"] = bez
|
||||
if attribs:
|
||||
eintrag["attribute"] = attribs
|
||||
fam = familie_von(rec["name"])
|
||||
if fam:
|
||||
# Unsere eigene Klassifikation - klar als solche gekennzeichnet, damit
|
||||
# sie nicht mit den Angaben aus der Zeichnung verwechselt wird.
|
||||
eintrag["ssg_familie"] = fam
|
||||
eintrag["tiefe"] = tiefe
|
||||
return eintrag
|
||||
|
||||
|
||||
def baue_abbild(ms_inserts, block_inserts, block_base, fenster=None,
|
||||
max_tiefe=12, ohne_container=False):
|
||||
"""Alle Modelspace-INSERTs als Abbild-Baum beschreiben.
|
||||
|
||||
Rueckgabe: (elemente, statistik). Jedes Element der obersten Ebene enthaelt
|
||||
seine aufgeloesten Unterkomponenten unter "unterkomponenten".
|
||||
"""
|
||||
stat = collections.Counter()
|
||||
|
||||
def kinder_von(name, mat, tiefe, pfad):
|
||||
"""Verschachtelte INSERTs eines Blocks rekursiv beschreiben."""
|
||||
if tiefe > max_tiefe:
|
||||
return []
|
||||
ergebnis = []
|
||||
for rec in block_inserts.get(name, []):
|
||||
if not rec["name"]:
|
||||
continue
|
||||
if ohne_container and GEOMETRIE_CONTAINER.match(rec["name"]):
|
||||
stat["container_uebersprungen"] += 1
|
||||
continue
|
||||
kind_mat, ursprung, rot = welt_daten(rec, mat, block_base)
|
||||
eintrag = element_dict(rec, ursprung, rot, tiefe)
|
||||
stat["unterkomponenten"] += 1
|
||||
if rec["name"] not in pfad:
|
||||
enkel = kinder_von(rec["name"], kind_mat, tiefe + 1,
|
||||
pfad | {rec["name"]})
|
||||
if enkel:
|
||||
eintrag["unterkomponenten"] = enkel
|
||||
ergebnis.append(eintrag)
|
||||
return ergebnis
|
||||
|
||||
elemente = []
|
||||
for nr, rec in enumerate(ms_inserts, start=1):
|
||||
if not rec["name"]:
|
||||
continue
|
||||
mat, ursprung, rot = welt_daten(rec, Matrix44(), block_base)
|
||||
if not im_fenster(ursprung.x, ursprung.y, fenster):
|
||||
stat["ausserhalb"] += 1
|
||||
continue
|
||||
if ohne_container and GEOMETRIE_CONTAINER.match(rec["name"]):
|
||||
stat["container_uebersprungen"] += 1
|
||||
continue
|
||||
eintrag = element_dict(rec, ursprung, rot, 0)
|
||||
eintrag["nr"] = nr
|
||||
eintrag.move_to_end("nr", last=False)
|
||||
kinder = kinder_von(rec["name"], mat, 1, frozenset({rec["name"]}))
|
||||
if kinder:
|
||||
eintrag["unterkomponenten"] = kinder
|
||||
elemente.append(eintrag)
|
||||
stat["elemente"] += 1
|
||||
if rec["attribs"]:
|
||||
stat["mit_attributen"] += 1
|
||||
return elemente, stat
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Schreibt die Bauteile einer Fremdzeichnung als getreue "
|
||||
"JSON-Abbildung (keine Bau-Anweisung).")
|
||||
parser.add_argument("dxf", help="Quellzeichnung (binaeres DXF)")
|
||||
parser.add_argument("output", help="Ziel-JSON")
|
||||
parser.add_argument("--fenster", nargs=4, type=float,
|
||||
metavar=("X0", "Y0", "X1", "Y1"),
|
||||
help="Nur Elemente in diesem XY-Fenster (mm)")
|
||||
parser.add_argument("--max-tiefe", type=int, default=12,
|
||||
help="Maximale Verschachtelungstiefe (Default 12)")
|
||||
parser.add_argument("--ohne-geometrie-container", action="store_true",
|
||||
help="Volumenkoerper-Huellen (sgBrepBlock_*, *A/*B/...) "
|
||||
"nur zaehlen, nicht ausgeben")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isfile(args.dxf):
|
||||
print("%s FEHLER: DXF nicht gefunden: %s" % (PREFIX, args.dxf))
|
||||
sys.exit(1)
|
||||
|
||||
fenster = None
|
||||
if args.fenster:
|
||||
x0, y0, x1, y1 = args.fenster
|
||||
fenster = (min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1))
|
||||
|
||||
groesse = os.path.getsize(args.dxf) / (1024.0 * 1024.0)
|
||||
print("%s lese %s (%.1f MB) ..." % (PREFIX, args.dxf, groesse))
|
||||
ms_inserts, block_inserts, block_base, statistik = lese_inserts(args.dxf)
|
||||
print("%s %d INSERTs im Modelspace, %d INSERTs gesamt, %d mit gekippter OCS"
|
||||
% (PREFIX, len(ms_inserts), statistik["inserts"],
|
||||
statistik["ocs_gekippt"]))
|
||||
|
||||
elemente, stat = baue_abbild(ms_inserts, block_inserts, block_base,
|
||||
fenster=fenster, max_tiefe=args.max_tiefe,
|
||||
ohne_container=args.ohne_geometrie_container)
|
||||
|
||||
# Kennzahlen, die belegen, wie belastbar die Typangaben sind
|
||||
quellen = collections.Counter()
|
||||
layer = collections.Counter()
|
||||
|
||||
def zaehle(liste):
|
||||
for e in liste:
|
||||
quellen[e["typ_quelle"]] += 1
|
||||
layer[e["layer"]] += 1
|
||||
zaehle(e.get("unterkomponenten", []))
|
||||
|
||||
zaehle(elemente)
|
||||
|
||||
abbild = collections.OrderedDict()
|
||||
abbild["quelle"] = collections.OrderedDict([
|
||||
("datei", os.path.basename(args.dxf)),
|
||||
("groesse_mb", round(groesse, 1)),
|
||||
("inserts_gesamt", statistik["inserts"]),
|
||||
("inserts_modelspace", len(ms_inserts)),
|
||||
("inserts_gekippte_ocs", statistik["ocs_gekippt"]),
|
||||
("fenster", list(fenster) if fenster else None),
|
||||
("hinweis", "Getreue Abbildung der Bauteile. Enthaelt KEINE "
|
||||
"Flussrichtung (im DXF nicht vorhanden), keinen "
|
||||
"Verbindungsgraphen (0 GROUP-Objekte) und keine "
|
||||
"Zeichnungsgeometrie (bewusst ausgelassen). Felder mit "
|
||||
"typ_quelle=blockname sind NICHT durch ein Attribut "
|
||||
"belegt."),
|
||||
])
|
||||
abbild["kennzahlen"] = collections.OrderedDict([
|
||||
("elemente_oberste_ebene", stat["elemente"]),
|
||||
("unterkomponenten", stat["unterkomponenten"]),
|
||||
("mit_attributen_oberste_ebene", stat["mit_attributen"]),
|
||||
("typ_quellen", dict(quellen.most_common())),
|
||||
("layer_top15", dict(layer.most_common(15))),
|
||||
])
|
||||
abbild["elemente"] = elemente
|
||||
|
||||
ziel_dir = os.path.dirname(os.path.abspath(args.output))
|
||||
if ziel_dir and not os.path.isdir(ziel_dir):
|
||||
os.makedirs(ziel_dir)
|
||||
with open(args.output, "w", encoding="utf-8") as fh:
|
||||
json.dump(abbild, fh, ensure_ascii=False, indent=2)
|
||||
fh.write("\n")
|
||||
|
||||
mb = os.path.getsize(args.output) / (1024.0 * 1024.0)
|
||||
print("%s %d Elemente (oberste Ebene) + %d Unterkomponenten -> %s (%.1f MB)"
|
||||
% (PREFIX, stat["elemente"], stat["unterkomponenten"], args.output,
|
||||
mb))
|
||||
if stat["ausserhalb"]:
|
||||
print("%s %d Elemente ausserhalb des Fensters ausgelassen"
|
||||
% (PREFIX, stat["ausserhalb"]))
|
||||
if stat["container_uebersprungen"]:
|
||||
print("%s %d Geometrie-Container uebersprungen"
|
||||
% (PREFIX, stat["container_uebersprungen"]))
|
||||
print("%s Typ-Quellen: %s" % (PREFIX, dict(quellen.most_common())))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
+110
-52
@@ -639,132 +639,156 @@
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00134">
|
||||
<Macro>
|
||||
<Name>Verbindung einfuegen</Name>
|
||||
<Command>^C^C(ssg-ensure "Connection_Edit") CONNECTION_INSERT</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00135">
|
||||
<Macro>
|
||||
<Name>Verbindung bearbeiten</Name>
|
||||
<Command>^C^C(ssg-ensure "Connection_Edit") CONNECTION_EDIT</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00138">
|
||||
<Macro>
|
||||
<Name>Export Sivas</Name>
|
||||
<Command>^C^C(ssg-ensure "count_sep_scan") ZAEHLE_SEP_SCAN (ssg-ensure "export") EXPORTSIVAS</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00135">
|
||||
<MenuMacro UID="MM_00139">
|
||||
<Macro>
|
||||
<Name>Export CSV</Name>
|
||||
<Command>^C^C(ssg-ensure "export") EXPORTCSV</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00137">
|
||||
<MenuMacro UID="MM_00141">
|
||||
<Macro>
|
||||
<Name>Umschalten 2D und 3D</Name>
|
||||
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_SWITCH</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00138">
|
||||
<MenuMacro UID="MM_00142">
|
||||
<Macro>
|
||||
<Name>Alle Bloecke auf 2D</Name>
|
||||
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_2D</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00139">
|
||||
<MenuMacro UID="MM_00143">
|
||||
<Macro>
|
||||
<Name>Alle Bloecke auf 3D</Name>
|
||||
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_3D</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00140">
|
||||
<MenuMacro UID="MM_00144">
|
||||
<Macro>
|
||||
<Name>Sprache</Name>
|
||||
<Command>^C^C(ssg-ensure "SSG_LIB_Commands") SSG_SPRACHE</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00143">
|
||||
<MenuMacro UID="MM_00147">
|
||||
<Macro>
|
||||
<Name>Alle Tests ausfuehren</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) SSG_RUN_ALL_TESTS</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00145">
|
||||
<MenuMacro UID="MM_00149">
|
||||
<Macro>
|
||||
<Name>Kreisel</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) TEST_KREISEL</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00146">
|
||||
<MenuMacro UID="MM_00150">
|
||||
<Macro>
|
||||
<Name>Foerderer</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_foerderer.lsp")) TEST_FOERDERER</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00147">
|
||||
<MenuMacro UID="MM_00151">
|
||||
<Macro>
|
||||
<Name>Foerderer-Linienzug</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) TEST_LINIENZUG</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00148">
|
||||
<MenuMacro UID="MM_00152">
|
||||
<Macro>
|
||||
<Name>Omniflo</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) TEST_OMNIFLO</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00149">
|
||||
<MenuMacro UID="MM_00153">
|
||||
<Macro>
|
||||
<Name>Omniflo Strecke</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo_strecke.lsp")) TEST_OMNIFLO_STRECKE</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00150">
|
||||
<MenuMacro UID="MM_00154">
|
||||
<Macro>
|
||||
<Name>Gefaellestrecke</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) TEST_GEFAELLESTRECKE</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00151">
|
||||
<MenuMacro UID="MM_00155">
|
||||
<Macro>
|
||||
<Name>Mubea Gesamtmodell</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) TEST_MUBEA</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00152">
|
||||
<MenuMacro UID="MM_00156">
|
||||
<Macro>
|
||||
<Name>HundM05 Kreisel-Abschnitt</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) TEST_HUNDM05</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00157">
|
||||
<Macro>
|
||||
<Name>KS 2D/3D pruefen (CSV)</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00153">
|
||||
<MenuMacro UID="MM_00158">
|
||||
<Macro>
|
||||
<Name>Export CSV/Sivas ALL</Name>
|
||||
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00165">
|
||||
<MenuMacro UID="MM_00170">
|
||||
<Macro>
|
||||
<Name>Kreisel bearbeiten</Name>
|
||||
<Command>(ssg-ensure "KreiselInsert") KreiselEdit</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00166">
|
||||
<MenuMacro UID="MM_00171">
|
||||
<Macro>
|
||||
<Name>Omniflo Element bearbeiten</Name>
|
||||
<Command>(ssg-ensure "OmniModulInsert") OMNI_EDIT</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00167">
|
||||
<MenuMacro UID="MM_00172">
|
||||
<Macro>
|
||||
<Name>Gefaellestrecke bearbeiten</Name>
|
||||
<Command>(ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00168">
|
||||
<MenuMacro UID="MM_00173">
|
||||
<Macro>
|
||||
<Name>VarioFoerderer bearbeiten</Name>
|
||||
<Command>(ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00169">
|
||||
<MenuMacro UID="MM_00174">
|
||||
<Macro>
|
||||
<Name>TRO-Marker bearbeiten</Name>
|
||||
<Command>(ssg-ensure "Tro_Edit") TRO_EDIT</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00171">
|
||||
<MenuMacro UID="MM_00175">
|
||||
<Macro>
|
||||
<Name>Verbindung bearbeiten</Name>
|
||||
<Command>(ssg-ensure "Connection_Edit") CONNECTION_EDIT</Command>
|
||||
</Macro>
|
||||
</MenuMacro>
|
||||
<MenuMacro UID="MM_00177">
|
||||
<Macro>
|
||||
<Name>SSG Bearbeiten (Auto)</Name>
|
||||
<Command>(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT</Command>
|
||||
@@ -783,8 +807,9 @@
|
||||
<PopMenuRef pUID="PM_00047"/>
|
||||
<PopMenuRef pUID="PM_00129"/>
|
||||
<PopMenuRef pUID="PM_00133"/>
|
||||
<PopMenuRef pUID="PM_00136"/>
|
||||
<PopMenuRef pUID="PM_00142"/>
|
||||
<PopMenuRef pUID="PM_00137"/>
|
||||
<PopMenuRef pUID="PM_00140"/>
|
||||
<PopMenuRef pUID="PM_00146"/>
|
||||
</PopMenu>
|
||||
<PopMenu UID="PM_00005">
|
||||
<Name>ILS</Name>
|
||||
@@ -1484,44 +1509,65 @@
|
||||
</PopMenuItem>
|
||||
</PopMenu>
|
||||
<PopMenu UID="PM_00133">
|
||||
<Name>Export</Name>
|
||||
<Name>Connections</Name>
|
||||
<PopMenuItem>
|
||||
<NameRef>Export Sivas</NameRef>
|
||||
<NameRef>Verbindung einfuegen</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00134"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Export CSV</NameRef>
|
||||
<NameRef>Verbindung bearbeiten</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00135"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
</PopMenu>
|
||||
<PopMenu UID="PM_00136">
|
||||
<Name>Konfiguration</Name>
|
||||
<PopMenuItem>
|
||||
<NameRef>Umschalten 2D und 3D</NameRef>
|
||||
<NameRef>Bearbeiten (Auto)</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00137"/>
|
||||
<MacroRef MenuMacroID="MM_00132"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
</PopMenu>
|
||||
<PopMenu UID="PM_00137">
|
||||
<Name>Export</Name>
|
||||
<PopMenuItem>
|
||||
<NameRef>Alle Bloecke auf 2D</NameRef>
|
||||
<NameRef>Export Sivas</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00138"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Alle Bloecke auf 3D</NameRef>
|
||||
<NameRef>Export CSV</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00139"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
</PopMenu>
|
||||
<PopMenu UID="PM_00140">
|
||||
<Name>Konfiguration</Name>
|
||||
<PopMenuItem>
|
||||
<NameRef>Umschalten 2D und 3D</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00141"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Alle Bloecke auf 2D</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00142"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Alle Bloecke auf 3D</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00143"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Sprache</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00140"/>
|
||||
<MacroRef MenuMacroID="MM_00144"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
@@ -1531,108 +1577,120 @@
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
</PopMenu>
|
||||
<PopMenu UID="PM_00142">
|
||||
<PopMenu UID="PM_00146">
|
||||
<Name>Tests</Name>
|
||||
<PopMenuItem>
|
||||
<NameRef>Alle Tests ausfuehren</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00143"/>
|
||||
<MacroRef MenuMacroID="MM_00147"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem IsSeparator="true"/>
|
||||
<PopMenuItem>
|
||||
<NameRef>Kreisel</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00145"/>
|
||||
<MacroRef MenuMacroID="MM_00149"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Foerderer</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00146"/>
|
||||
<MacroRef MenuMacroID="MM_00150"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Foerderer-Linienzug</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00147"/>
|
||||
<MacroRef MenuMacroID="MM_00151"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Omniflo</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00148"/>
|
||||
<MacroRef MenuMacroID="MM_00152"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Omniflo Strecke</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00149"/>
|
||||
<MacroRef MenuMacroID="MM_00153"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Gefaellestrecke</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00150"/>
|
||||
<MacroRef MenuMacroID="MM_00154"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Mubea Gesamtmodell</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00151"/>
|
||||
<MacroRef MenuMacroID="MM_00155"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>HundM05 Kreisel-Abschnitt</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00156"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>KS 2D/3D pruefen (CSV)</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00152"/>
|
||||
<MacroRef MenuMacroID="MM_00157"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Export CSV/Sivas ALL</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00153"/>
|
||||
<MacroRef MenuMacroID="MM_00158"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
</PopMenu>
|
||||
<PopMenu UID="PM_00164">
|
||||
<PopMenu UID="PM_00169">
|
||||
<Alias>POP501</Alias>
|
||||
<Name>SSG_LIB Edit Context</Name>
|
||||
<PopMenuItem>
|
||||
<NameRef>Kreisel bearbeiten</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00165"/>
|
||||
<MacroRef MenuMacroID="MM_00170"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Omniflo Element bearbeiten</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00166"/>
|
||||
<MacroRef MenuMacroID="MM_00171"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Gefaellestrecke bearbeiten</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00167"/>
|
||||
<MacroRef MenuMacroID="MM_00172"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>VarioFoerderer bearbeiten</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00168"/>
|
||||
<MacroRef MenuMacroID="MM_00173"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>TRO-Marker bearbeiten</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00169"/>
|
||||
<MacroRef MenuMacroID="MM_00174"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem>
|
||||
<NameRef>Verbindung bearbeiten</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00175"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
<PopMenuItem IsSeparator="true"/>
|
||||
<PopMenuItem>
|
||||
<NameRef>SSG Bearbeiten (Auto)</NameRef>
|
||||
<MenuItem>
|
||||
<MacroRef MenuMacroID="MM_00171"/>
|
||||
<MacroRef MenuMacroID="MM_00177"/>
|
||||
</MenuItem>
|
||||
</PopMenuItem>
|
||||
</PopMenu>
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
[Omniflo Strecke]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo_strecke.lsp")) TEST_OMNIFLO_STRECKE
|
||||
[Gefaellestrecke]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) TEST_GEFAELLESTRECKE
|
||||
[Mubea Gesamtmodell]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) TEST_MUBEA
|
||||
[HundM05 Kreisel-Abschnitt]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) TEST_HUNDM05
|
||||
[KS 2D/3D pruefen (CSV)]^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN
|
||||
[<-Export CSV/Sivas ALL]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL
|
||||
|
||||
|
||||
+2
-1
@@ -5,5 +5,6 @@
|
||||
{ "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" },
|
||||
{ "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" },
|
||||
{ "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" },
|
||||
{ "name": "mubea", "save": "dxf", "module": "KreiselInsert" }
|
||||
{ "name": "mubea", "save": "dxf", "module": "KreiselInsert" },
|
||||
{ "name": "hundm05", "save": "dxf", "module": "KreiselInsert" }
|
||||
]
|
||||
|
||||
@@ -187,3 +187,37 @@ def mubea_dxf():
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("mubea_tests.dxf nicht vorhanden - TESTRUN (save:dxf) in BricsCAD ausfuehren")
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
|
||||
# --- HundM05 Fixtures ---
|
||||
|
||||
@pytest.fixture
|
||||
def hundm05_testdata():
|
||||
"""Laedt die HundM05-Testdefinitionen aus testdata/hundm05.json.
|
||||
|
||||
Erzeugt von lib/dxf_scan_components.py aus results/ST500592_05.dxf
|
||||
(Fenster 216000 144000 228000 156000, Kreisel-Abschnitt).
|
||||
"""
|
||||
path = os.path.join(_testdata_dir(), "hundm05.json")
|
||||
return _load_json(path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hundm05_results():
|
||||
"""Laedt die HundM05-Ergebnisse aus output/hundm05_results.json."""
|
||||
path = os.path.join(_output_dir(), "hundm05_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("hundm05_results.json nicht vorhanden - TEST_HUNDM05 in BricsCAD ausfuehren")
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("hundm05_results.json ist leer")
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hundm05_dxf():
|
||||
"""Laedt die erzeugte HundM05-DXF aus output/hundm05_tests.dxf."""
|
||||
path = os.path.join(_output_dir(), "hundm05_tests.dxf")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("hundm05_tests.dxf nicht vorhanden - TESTRUN (save:dxf) in BricsCAD ausfuehren")
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
@@ -0,0 +1,601 @@
|
||||
;; ============================================================
|
||||
;; test_hundm05.lsp - Integrationstest: baut den Kreisel-Abschnitt der
|
||||
;; Anlage HundM aus der Fremdzeichnung ST500592_05.dxf nach.
|
||||
;;
|
||||
;; Liest tests/testdata/hundm05.json (erzeugt von lib/dxf_scan_components.py
|
||||
;; mit --fenster 216000 144000 228000 156000) und baut in EINER Zeichnung:
|
||||
;; - Kreisel (Eintraege mit "id", OHNE "type") Block KREISEL_*
|
||||
;; "function": "connect" -> kreisel-connect-script (Start-/
|
||||
;; Endpunkt, abstand+rotation werden intern berechnet)
|
||||
;; "function": "insert" -> kreisel-insert-script
|
||||
;; - Vario (test_id "VF_*") Block VF_* via variofoerderer-einfuegen
|
||||
;; - Gefaelle (test_id "GF_*") Block GF_* via gefaellestrecke-einfuegen
|
||||
;; - Gerade (test_id "GERADE_*") waagrechte Foerderstrecke via
|
||||
;; insert-hz-scaled-block-by-ks (Staustrecke_SP_1000_mm)
|
||||
;; - Omniflo (Eintraege mit "type") - NICHT gebaut, siehe hundm05:build-omniflo
|
||||
;; - Sensoren (Eintraege mit "block") via _.INSERT
|
||||
;;
|
||||
;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"):
|
||||
;; tests/output/hundm05_tests.dxf
|
||||
;; tests/output/hundm05_results.json (via hundm05:export-results)
|
||||
;;
|
||||
;; Unterschiede zu tests/test_mubea.lsp - warum eine eigene Datei und kein
|
||||
;; gemeinsamer Treiber:
|
||||
;; 1. Der Kreisel kommt hier als "connect" (zwei Punkte aus dem DXF), bei
|
||||
;; Mubea als "insert" (Punkt + abstand + rotation von Hand).
|
||||
;; 2. Die VarioFoerderer nennen ihren Startpunkt "startpunkt" (Liste, wie
|
||||
;; object_data.md 3.1) statt x/y/z, und sie haben kein "L_GF1" - der
|
||||
;; L_GF-Split ist hier "gleichmaessig" (Default nach object_data.md).
|
||||
;; 3. Es gibt den Eintragstyp "gerade", den mubea.json nicht kennt.
|
||||
;; 4. Eintraege mit "type" (Omniflo) muessen VOR "id" abgefragt werden, weil
|
||||
;; sie beides tragen - bei Mubea gab es diesen Fall nicht.
|
||||
;; test_mubea.lsp bleibt dadurch unangetastet und die bestehende Suite
|
||||
;; unveraendert lauffaehig.
|
||||
;;
|
||||
;; Voraussetzungen:
|
||||
;; - SSG_LIB geladen (KreiselInsert, VarioFoerderer, Gefaellestrecke,
|
||||
;; SSG_LIB_Commands, ssg_core, vf_core)
|
||||
;; - Umgebungsvariable DXFMAKRO gesetzt
|
||||
;;
|
||||
;; Aufruf in BricsCAD:
|
||||
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp"))
|
||||
;; TEST_HUNDM05
|
||||
;; ============================================================
|
||||
|
||||
|
||||
;; --- Ergebnis eines Bau-Schritts als JSON-String erzeugen ---
|
||||
(defun hundm05:result-json (test-id kind status block-name block-handle insert-point attribs /
|
||||
json tag val first)
|
||||
(setq json (strcat " {\n"
|
||||
" \"test_id\": \"" test-id "\",\n"
|
||||
" \"kind\": \"" kind "\",\n"
|
||||
" \"status\": \"" status "\",\n"
|
||||
" \"block_name\": \"" (if block-name block-name "") "\",\n"
|
||||
" \"block_handle\": \"" (if block-handle block-handle "") "\",\n"
|
||||
" \"insert_point\": ["
|
||||
(if insert-point
|
||||
(strcat (rtos (car insert-point) 2 1) ", "
|
||||
(rtos (cadr insert-point) 2 1) ", "
|
||||
(rtos (if (caddr insert-point) (caddr insert-point) 0.0) 2 1))
|
||||
"0.0, 0.0, 0.0")
|
||||
"],\n"
|
||||
" \"actual_attributes\": {"))
|
||||
(setq first T)
|
||||
(foreach att attribs
|
||||
(setq tag (car att) val (cdr att))
|
||||
(if (not first) (setq json (strcat json ",")))
|
||||
(setq json (strcat json "\n \"" tag "\": \"" (if val val "") "\""))
|
||||
(setq first nil))
|
||||
(setq json (strcat json "\n }\n }"))
|
||||
json
|
||||
)
|
||||
|
||||
|
||||
;; --- Aus einer Block-ENAME ein Ergebnis-JSON erzeugen ---
|
||||
(defun hundm05:ent-json (test-id kind ent / ed)
|
||||
(if ent
|
||||
(progn
|
||||
(setq ed (entget ent))
|
||||
(hundm05:result-json test-id kind "executed"
|
||||
(cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed))
|
||||
(ssg-attrib-read ent)))
|
||||
(hundm05:result-json test-id kind "failed" nil nil nil nil))
|
||||
)
|
||||
|
||||
|
||||
;; --- Letzten INSERT mit Blocknamen-Praefix (rueckwaerts) suchen ---
|
||||
(defun hundm05:last-insert-prefix (pref / ent found ed bn)
|
||||
(setq ent (entlast))
|
||||
(while (and ent (null found))
|
||||
(setq ed (entget ent) bn (cdr (assoc 2 ed)))
|
||||
(if (and (= (cdr (assoc 0 ed)) "INSERT") bn
|
||||
(>= (strlen bn) (strlen pref))
|
||||
(= (substr bn 1 (strlen pref)) pref))
|
||||
(setq found ent))
|
||||
(setq ent (entprev ent)))
|
||||
found
|
||||
)
|
||||
|
||||
|
||||
;; --- Zahl aus JSON lesen, mit Default statt Fehler bei fehlendem Feld ---
|
||||
;; (float nil) bricht in AutoLISP ab. hundm05.json laesst - anders als
|
||||
;; mubea.json - bewusst Felder weg (z.B. "winkel" und "L_GF1" beim Vario, damit
|
||||
;; der Solver selbst waehlt), darum hier grundsaetzlich abgesichert lesen.
|
||||
(defun hundm05:num (e key default / v)
|
||||
(setq v (ssg-val e key))
|
||||
(if (numberp v) (float v) default)
|
||||
)
|
||||
|
||||
|
||||
;; --- Punkt-Liste aus JSON lesen ("startpunkt"/"start_mm": [x, y, z]) ---
|
||||
;; Liefert NIEMALS einen Ersatzpunkt. Ein Ursprungs-Fallback (0 0 0) waere hier
|
||||
;; fatal: er baut die Anlage sichtbar, aber komplett an der falschen Stelle -
|
||||
;; genau das passiert, wenn das Array in der JSON ueber mehrere Zeilen
|
||||
;; umgebrochen ist (siehe hundm05:startpunkt).
|
||||
(defun hundm05:pt (e key / v)
|
||||
(setq v (ssg-val e key))
|
||||
(if (and v (listp v) (>= (length v) 3)) (mapcar 'float v) nil)
|
||||
)
|
||||
|
||||
|
||||
;; --- Startpunkt eines Eintrags lesen: Array-Feld, sonst flache x/y/z ---
|
||||
;; Rueckgabe: Punkt oder nil. Bei nil MUSS der Aufrufer den Eintrag als
|
||||
;; "failed" melden und darf nichts bauen.
|
||||
;;
|
||||
;; Haeufigste Ursache fuer nil: ssg-parse-json-array (ssg_core.lsp) liest die
|
||||
;; JSON ZEILENWEISE, und ssg-cfg-parse-array braucht das ganze Array in einer
|
||||
;; Zeile. Ein von json.dump(indent=2) umgebrochenes Array
|
||||
;; "start_mm": [
|
||||
;; 219540.85,
|
||||
;; ...
|
||||
;; ]
|
||||
;; liefert nil. Die Ausgabe von lib/dxf_scan_components.py zieht Zahlen-Arrays
|
||||
;; darum wieder auf eine Zeile (arrays_einzeilig).
|
||||
(defun hundm05:startpunkt (e feld / p)
|
||||
(setq p (hundm05:pt e feld))
|
||||
;; Ersatzweise flache x/y/z-Felder zulassen (Schema wie mubea.json)
|
||||
(if (and (null p) (numberp (ssg-val e "x")))
|
||||
(setq p (list (hundm05:num e "x" 0.0) (hundm05:num e "y" 0.0)
|
||||
(hundm05:num e "z" 0.0))))
|
||||
p
|
||||
)
|
||||
|
||||
|
||||
;; --- Meldung fuer einen nicht lesbaren Startpunkt ---
|
||||
(defun hundm05:kein-startpunkt (tid feld)
|
||||
(princ (strcat "\n FEHLER: " tid " - Feld \"" feld "\" nicht lesbar."
|
||||
" Steht das Array in EINER Zeile? ssg-cfg-parse-array kann"
|
||||
" keine umgebrochenen Arrays lesen. Nichts gebaut."))
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; Bau-Funktionen je Elementtyp
|
||||
;; ============================================================
|
||||
|
||||
;; --- Kreisel: "connect" (Start-/Endpunkt) oder "insert" (Punkt+abstand) ---
|
||||
;; Der Scanner liefert fuer AN8/SP8-Paare "connect": beide Stationen stehen im
|
||||
;; DXF, abstand und rotation berechnet kreisel-connect-script daraus selbst
|
||||
;; (abstand = Distanz - *kreisel-durchmesser*). "insert" bleibt fuer von Hand
|
||||
;; gepflegte Eintraege moeglich (Schema wie mubea.json).
|
||||
(defun hundm05:build-kreisel (e / id fn typ hoehe ptStart ptEnd x y z abstand rotation ent)
|
||||
(setq id (ssg-val e "id")
|
||||
fn (ssg-val e "function")
|
||||
typ (ssg-val e "typ")
|
||||
hoehe (ssg-val e "hoehe"))
|
||||
(if (null typ) (setq typ "STANDARD"))
|
||||
(if (numberp hoehe) (setq hoehe (float hoehe)) (setq hoehe nil))
|
||||
|
||||
(if (= fn "connect")
|
||||
(progn
|
||||
(setq ptStart (list (hundm05:num e "start_x" 0.0)
|
||||
(hundm05:num e "start_y" 0.0)
|
||||
(hundm05:num e "start_z" 0.0)))
|
||||
(setq ptEnd (list (hundm05:num e "end_x" 0.0)
|
||||
(hundm05:num e "end_y" 0.0)
|
||||
(hundm05:num e "end_z" 0.0)))
|
||||
(princ (strcat "\n [KREISEL] " id " connect ("
|
||||
(rtos (car ptStart) 2 0) "," (rtos (cadr ptStart) 2 0) ") -> ("
|
||||
(rtos (car ptEnd) 2 0) "," (rtos (cadr ptEnd) 2 0) ")"
|
||||
" H=" (if hoehe (rtos hoehe 2 0) "-")))
|
||||
(setq ent (kreisel-connect-script ptStart ptEnd typ hoehe)))
|
||||
(progn
|
||||
(setq x (hundm05:num e "x" 0.0)
|
||||
y (hundm05:num e "y" 0.0)
|
||||
z (hundm05:num e "z" 0.0)
|
||||
abstand (hundm05:num e "abstand" *kreisel-default-laenge*)
|
||||
rotation (hundm05:num e "rotation" 0.0))
|
||||
(princ (strcat "\n [KREISEL] " id " insert (" (rtos x 2 0) "," (rtos y 2 0)
|
||||
"," (rtos z 2 0) ") A=" (rtos abstand 2 0)
|
||||
" R=" (rtos rotation 2 1)))
|
||||
(setq ent (kreisel-insert-script (list x y z) abstand rotation typ
|
||||
(if hoehe hoehe z)))))
|
||||
(hundm05:ent-json id "kreisel" ent)
|
||||
)
|
||||
|
||||
|
||||
;; --- Vario (VF_*) ---
|
||||
;; Wie mubea:build-vario-one, aber ohne "winkel"-Zwang und ohne L_GF1-Ziel:
|
||||
;; hundm05.json gibt nur deltaL/deltaH/richtung/seite/hz vor, den Bogenwinkel
|
||||
;; waehlt berechne-alle-winkel selbst (kleinster gueltiger Kandidat) und der
|
||||
;; L_GF-Split ist "gleichmaessig" (beide Haelften L_GF/2, Default nach
|
||||
;; object_data.md 3.1). Ist "L_GF1" doch gesetzt, wird es als Vorlauf-Ziel
|
||||
;; verwendet - dann verhaelt sich der Treiber wie der Mubea-Treiber.
|
||||
(defun hundm05:build-vario (e / tid typ richtung deltaL deltaH seite hz startpunkt
|
||||
gf1-ziel ergebnis best-winkel L_GF L_VF gf1 gf2
|
||||
hoehe-von hoehe-bis lastEnt ent vf-nummer)
|
||||
(setq tid (ssg-val e "test_id")
|
||||
typ (ssg-val e "typ")
|
||||
richtung (ssg-val e "richtung")
|
||||
deltaL (hundm05:num e "deltaL" 0.0)
|
||||
deltaH (hundm05:num e "deltaH" 0.0)
|
||||
seite (ssg-val e "seite")
|
||||
hz (hundm05:num e "hz" 0.0)
|
||||
gf1-ziel (ssg-val e "L_GF1"))
|
||||
(if (or (null typ) (= typ "")) (setq typ "standard"))
|
||||
(if (or (null seite) (= seite "")) (setq seite "rechts"))
|
||||
(if (or (null richtung) (= richtung "")) (setq richtung "Auf"))
|
||||
(setq startpunkt (hundm05:startpunkt e "startpunkt"))
|
||||
(if (null startpunkt) (hundm05:kein-startpunkt tid "startpunkt"))
|
||||
|
||||
(princ (strcat "\n [VARIO] " tid " (" typ ") -> deltaL=" (rtos deltaL 2 0)
|
||||
" deltaH=" (rtos deltaH 2 0) " " richtung
|
||||
" hz=" (rtos hz 2 0)))
|
||||
|
||||
(setq ergebnis
|
||||
(if (= typ "etage")
|
||||
(berechne-etage deltaL deltaH richtung seite)
|
||||
(berechne-alle-winkel deltaL deltaH richtung nil)))
|
||||
(setq best-winkel (car ergebnis) L_GF (cadr ergebnis) L_VF (caddr ergebnis))
|
||||
|
||||
;; startpunkt MUSS mit in die Bedingung: ohne ihn wuerde am Ursprung gebaut.
|
||||
(if (and startpunkt best-winkel L_GF L_VF (> L_GF 0) (> L_VF 0))
|
||||
(progn
|
||||
;; L_GF-Split: mit L_GF1-Vorgabe wie bei Mubea (Vorlauf-Ziel), sonst
|
||||
;; gleichmaessig auf Vor- und Nachlauf verteilt.
|
||||
(if (numberp gf1-ziel)
|
||||
(setq gf1 (min (float gf1-ziel) L_GF))
|
||||
(setq gf1 (/ L_GF 2.0)))
|
||||
(setq gf2 (max 0.0 (- L_GF gf1)))
|
||||
(princ (strcat " -> Winkel=" (itoa best-winkel) " L_GF=" (rtos L_GF 2 1)
|
||||
" L_VF=" (rtos L_VF 2 1) " L_GF1=" (rtos gf1 2 1)
|
||||
" L_GF2=" (rtos gf2 2 1)))
|
||||
|
||||
;; Absolute Hoehen eintragen. hoehe-von/hoehe-bis gehen NUR in Label und
|
||||
;; Attribute (MONTAGEHOEHE_m, HOEHE_VON_mm - vf_core.lsp), die Geometrie
|
||||
;; kommt aus startpunkt. test_mubea.lsp uebergibt hier 0 und traegt damit
|
||||
;; RELATIVE Hoehen ein; fuer diese Zeichnung stehen die echten Z-Werte im
|
||||
;; DXF, also werden sie auch verwendet - sonst exportiert EXPORTCSV eine
|
||||
;; falsche Montagehoehe (bei VF_HundM05_11 waeren es 2.914 m statt
|
||||
;; -1.028 m).
|
||||
(setq hoehe-von (caddr startpunkt))
|
||||
(setq hoehe-bis (+ hoehe-von (if (= richtung "Auf") deltaH (- deltaH))))
|
||||
(setq lastEnt (vf-lastent-ohne-attribute))
|
||||
(if (= typ "etage")
|
||||
(etage-foerderanlage-einfuegen deltaL deltaH richtung best-winkel gf1 gf2 L_VF startpunkt seite hz)
|
||||
(variofoerderer-einfuegen deltaL deltaH richtung best-winkel gf1 gf2 L_VF startpunkt seite hz))
|
||||
;; Blocknummer wie in der Produktion ueber vf-next-number holen (NICHT
|
||||
;; fest auf 1) - sonst trifft das _.BLOCK des zweiten Foerderers auf einen
|
||||
;; schon vorhandenen Namen, BricsCAD fragt "Neu definieren?" und
|
||||
;; verschluckt die feste Antwortfolge (siehe test_mubea.lsp).
|
||||
(setq vf-nummer (vf-next-number))
|
||||
(setq ent (vf-block-erstellen typ seite vf-nummer 0 hoehe-von hoehe-bis
|
||||
deltaH deltaL L_VF gf1 gf2 richtung best-winkel
|
||||
startpunkt lastEnt hz "0" "Schoenenberger Geruest"))
|
||||
(if (or (null ent) (/= (type ent) 'ENAME))
|
||||
(setq ent (hundm05:last-insert-prefix "VF_")))
|
||||
;; SSG_VF_EDIT-XDATA schreiben, sonst verweigert VARIOFOERDERER_EDIT beim
|
||||
;; Doppelklick die Bearbeitung (vfs-edit-keine-xdata).
|
||||
(if (and ent (= (type ent) 'ENAME) (member typ '("standard" "etage")))
|
||||
(vf-edit-xdata-schreiben ent typ hz))
|
||||
(hundm05:ent-json tid "vario" ent))
|
||||
(progn
|
||||
;; Ursache benennen, nicht pauschal "nicht loesbar" melden: ein fehlender
|
||||
;; Startpunkt hat nichts mit dem Winkel-Solver zu tun, und eine falsche
|
||||
;; Diagnose schickt die Fehlersuche in die falsche Richtung.
|
||||
(if (null startpunkt)
|
||||
(princ (strcat "\n FEHLER: " tid " nicht gebaut - Startpunkt fehlt"
|
||||
" (siehe Meldung oben)."))
|
||||
(princ (strcat "\n FEHLER: " tid " deltaL=" (rtos deltaL 2 0)
|
||||
" deltaH=" (rtos deltaH 2 0) " nicht loesbar"
|
||||
" (kein gueltiges L_GF/L_VF).")))
|
||||
(hundm05:result-json tid "vario" "failed" nil nil startpunkt nil)))
|
||||
)
|
||||
|
||||
|
||||
;; --- Gefaellestrecke: Init-Konflikt umgehen -----------------------------
|
||||
;; gefaellestrecke-einfuegen prueft "(if (not *lib-initialized*)
|
||||
;; (gf-init-bibliothek))" - dasselbe Flag, das init-bibliothek (Vario) fuer
|
||||
;; SEINE eigenen aus-dx/ein-dx setzt. Wird zuerst ein Vario gebaut, kaeme
|
||||
;; gf-init-bibliothek nie mehr zum Zug und aus-dx/ein-dx blieben die
|
||||
;; Vario-Werte. Darum einmal je Lauf erzwingen (wie in test_mubea.lsp).
|
||||
(defun hundm05:ensure-gf-init ( / )
|
||||
(if (not *hundm05-gf-init-done*)
|
||||
(progn
|
||||
(setq *lib-initialized* nil)
|
||||
(gf-init-bibliothek)
|
||||
(setq *lib-initialized* t)
|
||||
(setq *hundm05-gf-init-done* T)))
|
||||
)
|
||||
|
||||
|
||||
;; --- Eine Gefaellestrecke bauen (Modus 1) ---
|
||||
;; Aufbau ausschliesslich aus Anfangspunkt und horizontaler Gesamtlaenge
|
||||
;; deltaL - KEIN Endpunkt (wie Produktion/gefaellestrecke.dcl Modus 1, siehe
|
||||
;; object_data.md 2.1). L_stau aus deltaL abgeleitet, Formel identisch zu
|
||||
;; test_gefaellestrecke.lsp/test_mubea.lsp.
|
||||
(defun hundm05:build-gf (e / tid start-mm deltaL hz as-seite es-seite as-winkel
|
||||
es-winkel winkel)
|
||||
(setq tid (ssg-val e "test_id")
|
||||
start-mm (hundm05:startpunkt e "start_mm")
|
||||
deltaL (hundm05:num e "deltaL_mm" 0.0)
|
||||
hz (hundm05:num e "hz_grad" 0.0)
|
||||
as-seite (ssg-val e "as_seite")
|
||||
es-seite (ssg-val e "es_seite")
|
||||
as-winkel(ssg-val e "as_winkel")
|
||||
es-winkel(ssg-val e "es_winkel")
|
||||
winkel (hundm05:num e "winkel_grad" 3.0))
|
||||
(if (or (null as-seite) (= as-seite "")) (setq as-seite "links"))
|
||||
(if (or (null es-seite) (= es-seite "")) (setq es-seite "links"))
|
||||
(if (or (null as-winkel) (= as-winkel "")) (setq as-winkel "90"))
|
||||
(if (or (null es-winkel) (= es-winkel "")) (setq es-winkel "90"))
|
||||
|
||||
(if (null start-mm)
|
||||
(progn
|
||||
(hundm05:kein-startpunkt tid "start_mm")
|
||||
(hundm05:result-json tid "gefaellestrecke" "failed" nil nil nil nil))
|
||||
(hundm05:build-gf-punkt tid start-mm deltaL hz as-seite es-seite
|
||||
as-winkel es-winkel winkel))
|
||||
)
|
||||
|
||||
|
||||
;; --- Gefaellestrecke mit gepruefter Startkoordinate bauen ---
|
||||
(defun hundm05:build-gf-punkt (tid start-mm deltaL hz as-seite es-seite
|
||||
as-winkel es-winkel winkel / rad sep-x L_stau ent)
|
||||
(hundm05:ensure-gf-init)
|
||||
;; Element-Masse zur gewaehlten AS/ES-Variante setzen, damit aus-dx/ein-dx
|
||||
;; zur Variante (90/30 Grad) passen; danach auf 90 zuruecksetzen.
|
||||
(vf-set-as-masse as-winkel as-seite)
|
||||
(vf-set-es-masse es-winkel es-seite)
|
||||
(setq rad (* winkel (/ pi 180.0)))
|
||||
(setq sep-x (* 300.0 (cos rad)))
|
||||
(setq L_stau (/ (- deltaL (float aus-dx) (float ein-dx) sep-x) (cos rad)))
|
||||
|
||||
(princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0)
|
||||
"," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0)
|
||||
") hz=" (rtos hz 2 0) " deltaL=" (rtos deltaL 2 0)
|
||||
" winkel=" (rtos winkel 2 2) " L_stau=" (rtos L_stau 2 1)))
|
||||
|
||||
(if (<= L_stau 0.0)
|
||||
(progn
|
||||
(princ (strcat " -> uebersprungen: L_stau <= 0 (deltaL " (rtos deltaL 2 0)
|
||||
" zu kurz fuer AS/ES-Elemente + Separator)"))
|
||||
(vf-set-as-masse "90" as-seite)
|
||||
(vf-set-es-masse "90" es-seite)
|
||||
(hundm05:result-json tid "gefaellestrecke" "failed" nil nil start-mm nil))
|
||||
(progn
|
||||
(setq ent (car (gefaellestrecke-einfuegen
|
||||
L_stau winkel start-mm as-seite es-seite hz
|
||||
deltaL as-winkel es-winkel "0" "Schoenenberger Geruest")))
|
||||
(vf-set-as-masse "90" as-seite)
|
||||
(vf-set-es-masse "90" es-seite)
|
||||
(hundm05:ent-json tid "gefaellestrecke" ent)))
|
||||
)
|
||||
|
||||
|
||||
;; --- Waagrechte Foerderstrecke (GERADE_*) ---
|
||||
;; Antrieb/Umlenk-Paare ohne Hoehendifferenz sind keine VarioFoerderer (deren
|
||||
;; Winkel-Solver braucht eine Hoehendifferenz, siehe GERADE_MAX_DELTA_H in
|
||||
;; lib/dxf_scan_components.py). Gebaut wird mit derselben Produktions-Primitive,
|
||||
;; die der Etage-Foerderer fuer seine waagrechten Abschnitte nutzt:
|
||||
;; insert-hz-scaled-block-by-ks skaliert Staustrecke_SP_1000_mm auf die Laenge
|
||||
;; und richtet sie an hz aus (vert-winkel 0 = waagrecht).
|
||||
;; Rueckgabe der Primitive ist der Ausgangspunkt, nicht die ENAME - der Block
|
||||
;; wird darum ueber (entlast) eingesammelt.
|
||||
(defun hundm05:build-gerade (e / tid laenge hz startpunkt blk ent)
|
||||
(setq tid (ssg-val e "test_id")
|
||||
laenge (hundm05:num e "laenge" 0.0)
|
||||
hz (hundm05:num e "hz" 0.0)
|
||||
startpunkt (hundm05:startpunkt e "startpunkt"))
|
||||
(if (null startpunkt)
|
||||
(hundm05:kein-startpunkt tid "startpunkt")
|
||||
(princ (strcat "\n [GERADE] " tid " -> Start=(" (rtos (car startpunkt) 2 0)
|
||||
"," (rtos (cadr startpunkt) 2 0) "," (rtos (caddr startpunkt) 2 0)
|
||||
") laenge=" (rtos laenge 2 0) " hz=" (rtos hz 2 0))))
|
||||
(if (or (null startpunkt) (<= laenge 0.0))
|
||||
(progn
|
||||
(if startpunkt (princ " -> uebersprungen: laenge <= 0"))
|
||||
(hundm05:result-json tid "gerade" "failed" nil nil startpunkt nil))
|
||||
(progn
|
||||
;; Block VORHER laden und pruefen: insert-hz-scaled-block-by-ks ruft bei
|
||||
;; fehlendem Block (exit) auf und wuerde den gesamten Testlauf abbrechen.
|
||||
(setq blk (ssg-ils-block-laden "Staustrecke_SP_1000_mm"))
|
||||
(if (null blk)
|
||||
(progn
|
||||
(princ " -> uebersprungen: Block Staustrecke_SP_1000_mm nicht ladbar")
|
||||
(hundm05:result-json tid "gerade" "failed" "Staustrecke_SP_1000_mm"
|
||||
nil startpunkt nil))
|
||||
(progn
|
||||
(insert-hz-scaled-block-by-ks "Staustrecke_SP_1000_mm"
|
||||
startpunkt laenge hz 0)
|
||||
(setq ent (entlast))
|
||||
(if (and ent (atoms-family 1 '("ssg-id-generate")))
|
||||
(ssg-id-generate ent))
|
||||
(hundm05:ent-json tid "gerade" ent)))))
|
||||
)
|
||||
|
||||
|
||||
;; --- Omniflo (Eintraege mit "type": "bogen"/"weiche") ---
|
||||
;; BEWUSST nicht gebaut. omni:insert-dxf (Lisp/OmniModulInsert.lsp) holt den
|
||||
;; Einfuegepunkt zwingend per (command "_.INSERT" dxf-pfad pause ...) - es gibt
|
||||
;; in Produktion keinen Aufrufpfad, der X/Y programmgesteuert uebergibt (siehe
|
||||
;; object_data.md 4.1). Ein JSON-Treiber muesste den _.INSERT selbst nachbauen.
|
||||
;; Im gewaehlten Abschnitt (Kreisel-Bereich) kommt kein einziger Omniflo-Eintrag
|
||||
;; vor, darum wird hier nichts auf Verdacht implementiert, was der Testlauf nie
|
||||
;; ausfuehren koennte. Der eigene Zweig ist trotzdem noetig: Omniflo-Eintraege
|
||||
;; tragen ein "id"-Feld und wuerden sonst als Kreisel gebaut werden.
|
||||
(defun hundm05:build-omniflo (e / tid)
|
||||
(setq tid (cond ((ssg-val e "id")) ((ssg-val e "sivasnr")) ("OMNIFLO")))
|
||||
(princ (strcat "\n [OMNIFLO] " tid " -> uebersprungen (nicht scriptbar, "
|
||||
"siehe object_data.md 4.1)"))
|
||||
(hundm05:result-json tid "omniflo" "unsupported"
|
||||
(ssg-val e "sivasnr") nil
|
||||
(list (hundm05:num e "x" 0.0) (hundm05:num e "y" 0.0)
|
||||
(hundm05:num e "z" 0.0))
|
||||
nil)
|
||||
)
|
||||
|
||||
|
||||
;; --- Ein einzelner Sensor/Block: Blockdefinition laden, dann per Name einfuegen ---
|
||||
;; Wie mubea:build-separator-one. Der in JSON genannte Block wird ueber
|
||||
;; ssg-ils-block-laden vorab definiert (loest 2D/3D-Suffix auf); schlaegt das
|
||||
;; fehl, wird auf Separator_SP zurueckgefallen - genau das greift bei den
|
||||
;; Blocknamen "S-LP"/"S-SP", die es in data/ils/ nicht gibt.
|
||||
(defun hundm05:build-sensor (e idx / blockname x y z rot tid eff n pt)
|
||||
(setq blockname (ssg-val e "block")
|
||||
x (hundm05:num e "x" 0.0)
|
||||
y (hundm05:num e "y" 0.0)
|
||||
z (hundm05:num e "z" 0.0)
|
||||
rot (hundm05:num e "rotation" 0.0)
|
||||
tid (cond ((ssg-val e "test_id"))
|
||||
((strcat "SENSOR_" (itoa idx)))))
|
||||
(setq pt (list x y z))
|
||||
(princ (strcat "\n [SENSOR] " tid " '" (if blockname blockname "?")
|
||||
"' -> (" (rtos x 2 0) "," (rtos y 2 0) "," (rtos z 2 0)
|
||||
") R=" (rtos rot 2 0)))
|
||||
;; NICHT (and blockname (ssg-ils-block-laden ...)) verwenden: AutoLISPs AND
|
||||
;; liefert T statt des letzten Wertes, das _.INSERT wuerde dann einen Block
|
||||
;; namens T einfuegen (siehe Kommentar in test_mubea.lsp).
|
||||
(setq eff (if blockname (ssg-ils-block-laden blockname)))
|
||||
(if (null eff) (setq eff (ssg-ils-block-laden "Separator_SP")))
|
||||
(if eff
|
||||
(progn
|
||||
(command "_.INSERT" eff pt "" "" rot)
|
||||
;; Verbleibende Prompts GEZAEHLT mit Enter abfangen - ein Prompt, der die
|
||||
;; Leer-Antwort ablehnt, haelt CMDACTIVE dauerhaft > 0 und eine ungebremste
|
||||
;; Schleife wuerde BricsCAD haengen lassen.
|
||||
(setq n 0)
|
||||
(while (and (> (getvar "CMDACTIVE") 0) (< n 5))
|
||||
(command "")
|
||||
(setq n (1+ n)))
|
||||
(if (> (getvar "CMDACTIVE") 0)
|
||||
(progn
|
||||
(command)
|
||||
(princ (strcat "\n WARNUNG: INSERT von '" eff
|
||||
"' nicht sauber beendet - abgebrochen."))))
|
||||
(if (atoms-family 1 '("ssg-id-generate"))
|
||||
(ssg-id-generate (entlast)))
|
||||
(hundm05:ent-json tid "sensor" (entlast)))
|
||||
(progn
|
||||
;; Kein Block gefunden -> sichtbaren Platzhalter zeichnen
|
||||
(if (not (tblsearch "LAYER" "HUNDM05_SENSOR"))
|
||||
(entmake (list '(0 . "LAYER") '(2 . "HUNDM05_SENSOR") '(70 . 0) '(62 . 1))))
|
||||
(entmake (list '(0 . "CIRCLE") (cons 10 pt) (cons 40 150.0)
|
||||
'(8 . "HUNDM05_SENSOR")))
|
||||
(princ " (Block fehlt - Platzhalter)")
|
||||
(hundm05:result-json tid "sensor" "placeholder"
|
||||
(if blockname blockname "Separator_SP") nil pt nil)))
|
||||
)
|
||||
|
||||
|
||||
;; --- JSON-Export der Ergebnisse ---
|
||||
(defun hundm05:export-results (tests-out-dir / out-json f first)
|
||||
(if (null *hundm05-test-results*)
|
||||
(princ "\n Keine HundM05-Ergebnisse vorhanden.")
|
||||
(progn
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setq out-json (strcat tests-out-dir "/hundm05_results.json"))
|
||||
(setq f (open out-json "w"))
|
||||
(if f
|
||||
(progn
|
||||
(write-line "[" f)
|
||||
(setq first T)
|
||||
(foreach r *hundm05-test-results*
|
||||
(if (not first) (write-line "," f))
|
||||
(write-line r f)
|
||||
(setq first nil))
|
||||
(write-line "]" f)
|
||||
(close f)
|
||||
(princ (strcat "\n Ergebnisse: " out-json)))
|
||||
(princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!")))))
|
||||
)
|
||||
|
||||
|
||||
;; ============================================================
|
||||
;; C:TEST_HUNDM05 - Hauptbefehl
|
||||
;; ============================================================
|
||||
(defun c:TEST_HUNDM05 ( / json-datei daten eintrag tid res r
|
||||
results-list sensor-idx anz-ok anz-fehler)
|
||||
(dbgopen "hundm05_id.dbg" "DXFM_LOG")
|
||||
|
||||
;; Benoetigte Feature-Module laden
|
||||
(ssg-ensure "KreiselInsert")
|
||||
(ssg-ensure "VarioFoerderer")
|
||||
(ssg-ensure "Gefaellestrecke")
|
||||
(ssg-ensure "SSG_LIB_Commands")
|
||||
|
||||
(ssg-start "TEST_HUNDM05" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA")))
|
||||
(setvar "OSMODE" 0)
|
||||
(setvar "ATTREQ" 0)
|
||||
(setvar "ATTDIA" 0)
|
||||
|
||||
;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren - fuer Vario+GF noetig
|
||||
(if (or (not *lib-initialized*) (null bogen-auf))
|
||||
(init-bibliothek))
|
||||
|
||||
;; Cache fuer den GF-Init-Workaround pro Lauf zuruecksetzen
|
||||
(setq *hundm05-gf-init-done* nil)
|
||||
|
||||
;; Testdaten laden
|
||||
(setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json"))
|
||||
(if (not (findfile json-datei))
|
||||
(progn
|
||||
(princ (strcat "\n[TEST_HUNDM05] FEHLER: " json-datei " nicht gefunden!"))
|
||||
(ssg-end)
|
||||
(dbgclose)
|
||||
(exit)))
|
||||
(setq daten (ssg-load-json json-datei))
|
||||
(if (null daten)
|
||||
(progn
|
||||
(princ "\n[TEST_HUNDM05] FEHLER: Testdaten konnten nicht geladen werden.")
|
||||
(ssg-end)
|
||||
(dbgclose)
|
||||
(exit)))
|
||||
|
||||
(princ "\n\n================================================================")
|
||||
(princ "\n TEST_HUNDM05 - HundM Kreisel-Abschnitt (ST500592_05.dxf)")
|
||||
(princ (strcat "\n " (itoa (length daten)) " Eintraege geladen"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
(setq results-list '() sensor-idx 0 anz-ok 0 anz-fehler 0)
|
||||
|
||||
(foreach eintrag daten
|
||||
(setq tid (ssg-val eintrag "test_id"))
|
||||
(setq res
|
||||
(cond
|
||||
;; Sensor/Insert: hat "block"
|
||||
((ssg-val eintrag "block")
|
||||
(setq sensor-idx (1+ sensor-idx))
|
||||
(hundm05:build-sensor eintrag sensor-idx))
|
||||
;; Omniflo VOR Kreisel pruefen: diese Eintraege tragen "id" UND "type"
|
||||
((ssg-val eintrag "type")
|
||||
(hundm05:build-omniflo eintrag))
|
||||
;; Kreisel: hat "id" (ohne "type")
|
||||
((ssg-val eintrag "id")
|
||||
(hundm05:build-kreisel eintrag))
|
||||
;; Vario: test_id "VF_*"
|
||||
((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "VF_"))
|
||||
(hundm05:build-vario eintrag))
|
||||
;; Gefaellestrecke: test_id "GF_*"
|
||||
((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "GF_"))
|
||||
(hundm05:build-gf eintrag))
|
||||
;; Waagrechte Strecke: test_id "GERADE_*"
|
||||
((and tid (>= (strlen tid) 7) (= (substr tid 1 7) "GERADE_"))
|
||||
(hundm05:build-gerade eintrag))
|
||||
(T
|
||||
(princ "\n WARNUNG: Unbekannter Eintrag uebersprungen")
|
||||
nil)))
|
||||
;; Alle Builder liefern hier je einen String (keine Templates mit "anzahl"
|
||||
;; wie bei Mubea) - der Vollstaendigkeit halber trotzdem Listen zulassen.
|
||||
(if res
|
||||
(progn
|
||||
(if (= (type res) 'STR) (setq res (list res)))
|
||||
(foreach r res
|
||||
(setq results-list (cons r results-list))
|
||||
(if (or (vl-string-search "\"executed\"" r)
|
||||
(vl-string-search "\"placeholder\"" r)
|
||||
(vl-string-search "\"unsupported\"" r))
|
||||
(setq anz-ok (1+ anz-ok))
|
||||
(setq anz-fehler (1+ anz-fehler)))))))
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, " (itoa anz-fehler) " Fehler"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
(setq *hundm05-test-results* (reverse results-list))
|
||||
|
||||
(ssg-end)
|
||||
(princ "\n TEST_HUNDM05 abgeschlossen.")
|
||||
(princ)
|
||||
(dbgclose)
|
||||
)
|
||||
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_hundm05.py - Validiert den HundM-Kreisel-Abschnitt.
|
||||
|
||||
Prueft die von TEST_HUNDM05 (test_hundm05.lsp) erzeugten Ergebnisse:
|
||||
1. Alle Eintraege aus hundm05.json wurden verarbeitet (results.json)
|
||||
2. Statuswerte (executed / placeholder fuer Sensoren / unsupported Omniflo)
|
||||
3. Kreisel-Attribute (HOEHE, KREISELART, Block-Praefix)
|
||||
4. Vario: VF_-Block, L_GF gleichmaessig verteilt (kein L_GF1 in den Daten)
|
||||
5. DXF-Geometrie: Block-Anzahlen je Typ (ezdxf)
|
||||
|
||||
Die Testdaten stammen aus lib/dxf_scan_components.py (Fenster
|
||||
216000 144000 228000 156000 der Zeichnung results/ST500592_05.dxf) und nicht
|
||||
aus Handarbeit - anders als bei mubea.json.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Klassifikation der hundm05.json-Eintraege
|
||||
# ============================================================
|
||||
# Reihenfolge wie im Dispatcher von test_hundm05.lsp: Omniflo VOR Kreisel,
|
||||
# weil Omniflo-Eintraege sowohl "id" als auch "type" tragen.
|
||||
|
||||
def _is_sensor(item):
|
||||
return "block" in item
|
||||
|
||||
|
||||
def _is_omniflo(item):
|
||||
return "type" in item and "block" not in item
|
||||
|
||||
|
||||
def _is_kreisel(item):
|
||||
return "id" in item and "type" not in item and "block" not in item
|
||||
|
||||
|
||||
def _hat_prefix(item, prefix):
|
||||
return str(item.get("test_id", "")).startswith(prefix)
|
||||
|
||||
|
||||
def _is_vario(item):
|
||||
return _hat_prefix(item, "VF_")
|
||||
|
||||
|
||||
def _is_gefaelle(item):
|
||||
return _hat_prefix(item, "GF_")
|
||||
|
||||
|
||||
def _is_gerade(item):
|
||||
return _hat_prefix(item, "GERADE_")
|
||||
|
||||
|
||||
def _expected_counts(testdata):
|
||||
return {
|
||||
"kreisel": sum(1 for t in testdata if _is_kreisel(t)),
|
||||
"vario": sum(1 for t in testdata if _is_vario(t)),
|
||||
"gefaellestrecke": sum(1 for t in testdata if _is_gefaelle(t)),
|
||||
"gerade": sum(1 for t in testdata if _is_gerade(t)),
|
||||
"sensor": sum(1 for t in testdata if _is_sensor(t)),
|
||||
"omniflo": sum(1 for t in testdata if _is_omniflo(t)),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Testdaten-Pruefung (hundm05.json selbst)
|
||||
# ============================================================
|
||||
|
||||
class TestHundM05Testdaten:
|
||||
|
||||
def test_jeder_eintrag_klassifizierbar(self, hundm05_testdata):
|
||||
"""Kein Eintrag darf durch alle Zweige des Dispatchers fallen."""
|
||||
for i, t in enumerate(hundm05_testdata):
|
||||
erkannt = (_is_sensor(t) or _is_omniflo(t) or _is_kreisel(t)
|
||||
or _is_vario(t) or _is_gefaelle(t) or _is_gerade(t))
|
||||
assert erkannt, \
|
||||
f"Eintrag {i} ist keinem Typ zuzuordnen: {sorted(t)}"
|
||||
|
||||
def test_kreisel_vorhanden(self, hundm05_testdata):
|
||||
"""Der Abschnitt wurde wegen des Kreisels gewaehlt - er muss drin sein."""
|
||||
kreisel = [t for t in hundm05_testdata if _is_kreisel(t)]
|
||||
assert kreisel, "kein Kreisel in hundm05.json"
|
||||
|
||||
def test_vario_hat_hoehendifferenz(self, hundm05_testdata):
|
||||
"""VarioFoerderer brauchen deltaH > 0 - der Winkel-Solver findet sonst
|
||||
keinen Bogenwinkel. Waagrechte Paare gehoeren als 'gerade' ins JSON
|
||||
(siehe GERADE_MAX_DELTA_H in lib/dxf_scan_components.py)."""
|
||||
for t in hundm05_testdata:
|
||||
if _is_vario(t):
|
||||
assert float(t.get("deltaH", 0)) > 0, \
|
||||
f'{t["test_id"]}: deltaH={t.get("deltaH")} - gehoert als ' \
|
||||
f'"gerade" klassifiziert'
|
||||
|
||||
def test_gerade_hat_laenge(self, hundm05_testdata):
|
||||
for t in hundm05_testdata:
|
||||
if _is_gerade(t):
|
||||
assert float(t.get("laenge", 0)) > 0, \
|
||||
f'{t["test_id"]}: laenge={t.get("laenge")}'
|
||||
|
||||
def test_arrays_einzeilig(self):
|
||||
"""Jedes Array muss komplett in EINER Zeile stehen.
|
||||
|
||||
ssg-parse-json-array (Lisp/ssg_core.lsp) liest die Datei zeilenweise,
|
||||
und ssg-cfg-parse-array braucht das ganze Array in einem String. Ein
|
||||
von json.dump(indent=2) umgebrochenes Array wird zu nil - der Treiber
|
||||
baut das Element dann nicht an seiner Position. Das ist genau der
|
||||
Fehler, der die Anlage einmal komplett am Ursprung aufgebaut hat, und
|
||||
er ist in der Zeichnung nicht als Datenfehler zu erkennen.
|
||||
"""
|
||||
import os
|
||||
pfad = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"testdata", "hundm05.json")
|
||||
with open(pfad, encoding="utf-8") as fh:
|
||||
for nr, zeile in enumerate(fh, start=1):
|
||||
offen = zeile.count("[") - zeile.count("]")
|
||||
# Die aeusserste Klammer des Dokuments darf allein stehen.
|
||||
if zeile.strip() in ("[", "]"):
|
||||
continue
|
||||
assert offen == 0, \
|
||||
f"Zeile {nr}: Array ueber mehrere Zeilen umgebrochen - " \
|
||||
f"ssg-cfg-parse-array kann das nicht lesen: {zeile.strip()!r}"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Ergebnis-Pruefung (results.json vs. testdata)
|
||||
# ============================================================
|
||||
|
||||
class TestHundM05Results:
|
||||
|
||||
def test_alle_verarbeitet(self, hundm05_testdata, hundm05_results):
|
||||
"""Je Kind muss die erwartete Anzahl an Ergebnissen vorliegen."""
|
||||
expected = _expected_counts(hundm05_testdata)
|
||||
got = {}
|
||||
for r in hundm05_results:
|
||||
got[r["kind"]] = got.get(r["kind"], 0) + 1
|
||||
for kind, n in expected.items():
|
||||
assert got.get(kind, 0) == n, \
|
||||
f'{kind}: erwartet {n}, verarbeitet {got.get(kind, 0)}'
|
||||
|
||||
def test_status_ok(self, hundm05_results):
|
||||
"""Sensoren duerfen 'placeholder', Omniflo ist 'unsupported'
|
||||
(omni:insert-dxf ist nicht scriptbar, siehe object_data.md 4.1),
|
||||
alles andere muss 'executed' sein."""
|
||||
for r in hundm05_results:
|
||||
if r["kind"] == "sensor":
|
||||
erlaubt = ("executed", "placeholder")
|
||||
elif r["kind"] == "omniflo":
|
||||
erlaubt = ("unsupported",)
|
||||
else:
|
||||
erlaubt = ("executed",)
|
||||
assert r["status"] in erlaubt, \
|
||||
f'{r["test_id"]} ({r["kind"]}): Status={r["status"]}, ' \
|
||||
f'erlaubt {erlaubt}'
|
||||
|
||||
def test_kreisel_attribute(self, hundm05_testdata, hundm05_results):
|
||||
"""Kreisel: KREISELART und HOEHE stimmen mit der Vorgabe, Blockname
|
||||
traegt das Praefix KREISEL_. HOEHE wird von kreisel-connect-script aus
|
||||
'hoehe' bzw. dem Z des Startpunkts gebildet (rtos ... 2 0, also
|
||||
ganzzahlig)."""
|
||||
by_id = {r["test_id"]: r for r in hundm05_results}
|
||||
for t in hundm05_testdata:
|
||||
if not _is_kreisel(t):
|
||||
continue
|
||||
r = by_id.get(t["id"])
|
||||
assert r, f'Kreisel {t["id"]} fehlt im Ergebnis'
|
||||
assert r["block_name"].startswith("KREISEL_"), \
|
||||
f'{t["id"]}: Block "{r["block_name"]}" ohne Praefix KREISEL_'
|
||||
assert r["actual_attributes"].get("KREISELART") == \
|
||||
t.get("typ", "STANDARD"), \
|
||||
f'{t["id"]}: KREISELART ist=' \
|
||||
f'{r["actual_attributes"].get("KREISELART")}'
|
||||
soll = t.get("hoehe", t.get("start_z", 0.0))
|
||||
assert r["actual_attributes"].get("HOEHE") == str(int(round(soll))), \
|
||||
f'{t["id"]}: HOEHE ist={r["actual_attributes"].get("HOEHE")}, ' \
|
||||
f'erwartet={int(round(soll))}'
|
||||
|
||||
def test_kreisel_abstand(self, hundm05_testdata, hundm05_results):
|
||||
"""Bei 'connect' berechnet kreisel-connect-script den Abstand als
|
||||
Distanz(Start,Ende) - Kreiseldurchmesser (800mm). Der Block muss also
|
||||
auf dem Startpunkt sitzen."""
|
||||
by_id = {r["test_id"]: r for r in hundm05_results}
|
||||
for t in hundm05_testdata:
|
||||
if not _is_kreisel(t) or t.get("function") != "connect":
|
||||
continue
|
||||
r = by_id.get(t["id"])
|
||||
assert r, f'Kreisel {t["id"]} fehlt im Ergebnis'
|
||||
ip = r["insert_point"]
|
||||
assert abs(ip[0] - t["start_x"]) < 1.0, \
|
||||
f'{t["id"]}: Einfuegepunkt X {ip[0]} != start_x {t["start_x"]}'
|
||||
assert abs(ip[1] - t["start_y"]) < 1.0, \
|
||||
f'{t["id"]}: Einfuegepunkt Y {ip[1]} != start_y {t["start_y"]}'
|
||||
|
||||
def test_vario_block_und_split(self, hundm05_testdata, hundm05_results):
|
||||
"""Vario: VF_-Block, und ohne L_GF1-Vorgabe wird L_GF gleichmaessig auf
|
||||
Vor- und Nachlauf verteilt (Default nach object_data.md 3.1)."""
|
||||
by_id = {r["test_id"]: r for r in hundm05_results}
|
||||
for t in hundm05_testdata:
|
||||
if not _is_vario(t):
|
||||
continue
|
||||
r = by_id.get(t["test_id"])
|
||||
assert r, f'Vario {t["test_id"]} fehlt im Ergebnis'
|
||||
assert r["block_name"].startswith("VF_"), \
|
||||
f'{t["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block'
|
||||
attrs = r["actual_attributes"]
|
||||
if "L_GF1" in t or "L_GF_m" not in attrs:
|
||||
continue
|
||||
teile = attrs["L_GF_m"].split(",")
|
||||
assert len(teile) == 2, \
|
||||
f'{t["test_id"]}: L_GF_m Format "{attrs["L_GF_m"]}"'
|
||||
vorne, hinten = float(teile[0]), float(teile[1])
|
||||
assert abs(vorne - hinten) < 0.002, \
|
||||
f'{t["test_id"]}: L_GF nicht gleichmaessig verteilt ' \
|
||||
f'({vorne} / {hinten})'
|
||||
|
||||
def test_gefaelle_block(self, hundm05_testdata, hundm05_results):
|
||||
by_id = {r["test_id"]: r for r in hundm05_results}
|
||||
for t in hundm05_testdata:
|
||||
if not _is_gefaelle(t):
|
||||
continue
|
||||
r = by_id.get(t["test_id"])
|
||||
assert r, f'Gefaellestrecke {t["test_id"]} fehlt im Ergebnis'
|
||||
assert r["block_name"].startswith("GF_"), \
|
||||
f'{t["test_id"]}: Block "{r["block_name"]}" ist kein GF_-Block'
|
||||
|
||||
|
||||
# ============================================================
|
||||
# DXF Geometrie-Pruefung (ezdxf)
|
||||
# ============================================================
|
||||
|
||||
class TestHundM05Geometry:
|
||||
|
||||
def _counts(self, dxf):
|
||||
c = {}
|
||||
for e in dxf.modelspace():
|
||||
if e.dxftype() == "INSERT":
|
||||
c[e.dxf.name] = c.get(e.dxf.name, 0) + 1
|
||||
return c
|
||||
|
||||
def test_block_anzahlen(self, hundm05_dxf, hundm05_testdata):
|
||||
"""Anzahl der KREISEL_/VF_/GF_-Bloecke >= Vorgabe aus hundm05.json."""
|
||||
c = self._counts(hundm05_dxf)
|
||||
exp = _expected_counts(hundm05_testdata)
|
||||
n_kr = sum(v for n, v in c.items() if n.startswith("KREISEL_"))
|
||||
n_vf = sum(v for n, v in c.items() if n.startswith("VF_"))
|
||||
n_gf = sum(v for n, v in c.items() if n.startswith("GF_"))
|
||||
assert n_kr >= exp["kreisel"], f"KREISEL_: {n_kr} < {exp['kreisel']}"
|
||||
assert n_vf >= exp["vario"], f"VF_: {n_vf} < {exp['vario']}"
|
||||
assert n_gf >= exp["gefaellestrecke"], \
|
||||
f"GF_: {n_gf} < {exp['gefaellestrecke']}"
|
||||
|
||||
def test_handles_im_modelspace(self, hundm05_dxf, hundm05_results):
|
||||
"""Jedes gebaute Block-Handle (executed) liegt im Modelspace."""
|
||||
handles = {e.dxf.handle for e in hundm05_dxf.modelspace()
|
||||
if e.dxftype() == "INSERT"}
|
||||
for r in hundm05_results:
|
||||
if r["status"] == "executed" and r["block_handle"]:
|
||||
assert r["block_handle"] in handles, \
|
||||
f'{r["test_id"]}: Handle {r["block_handle"]} nicht im ' \
|
||||
f'Modelspace'
|
||||
Vendored
+573
@@ -0,0 +1,573 @@
|
||||
[
|
||||
{
|
||||
"id": "Kreisel_HundM05_01",
|
||||
"function": "connect",
|
||||
"start_x": 216120.19,
|
||||
"start_y": 151070.0,
|
||||
"start_z": 0.0,
|
||||
"end_x": 239701.9,
|
||||
"end_y": 151070.0,
|
||||
"end_z": 0.0,
|
||||
"typ": "STANDARD",
|
||||
"hoehe": 0.0,
|
||||
"comment": "aus DXF: AN8 (Layer 1ANTR-800) -> SP8 (Layer 1SP-800), Abstand 23581.71; daraus folgt produktionsseitig abstand = 23581.71 - 800.0 = 22781.71 und rotation = 0.00 Grad. ACHTUNG hoehe: AN8 und SP8 sind in dieser Zeichnung flache 2D-Symbole (nur Linien/Kreis/Text auf Z=0) und auf Z=0 eingefuegt - die Einfuegehoehe des Kreisels steht NICHT im DXF und muss hier von Hand gesetzt werden (vgl. mubea.json, wo z ebenfalls Handarbeit ist)."
|
||||
},
|
||||
{
|
||||
"test_id": "GF_HundM05_04",
|
||||
"modus": 1,
|
||||
"deltaL_mm": 2094.45,
|
||||
"winkel_grad": 48.21,
|
||||
"hz_grad": 125.48,
|
||||
"start_mm": [219540.85, 152832.91, 2342.96],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links",
|
||||
"as_winkel": "90",
|
||||
"es_winkel": "90",
|
||||
"comment": "aus DXF: 3D_FF_Antrieb_rechts -> Umlenkung FF (Layer _3D_Fingerförderer / z-3), XY-Abstand 2094.45, Neigung 48.21 Grad; Gefaellestrecke, da fallend und Neigung >= 3 Grad. as_/es_seite und -winkel sind Defaults (im DXF nicht ableitbar)."
|
||||
},
|
||||
{
|
||||
"test_id": "VF_HundM05_05",
|
||||
"typ": "standard",
|
||||
"richtung": "Auf",
|
||||
"deltaL": 1694.19,
|
||||
"deltaH": 2057.58,
|
||||
"startpunkt": [219542.27, 153175.18, 0.0],
|
||||
"seite": "rechts",
|
||||
"hz": 135.86,
|
||||
"comment": "aus DXF: Antrieb FF -> 3D_FF_Umlenk_rechts (Layer z-3 / _3D_Fingerförderer), XY-Abstand 1694.19, Neigung 50.53 Grad; VarioFoerderer. 'winkel' bewusst nicht gesetzt - der Solver waehlt den kleinsten gueltigen Kandidaten. 'seite' ist Default (im DXF nicht ableitbar)."
|
||||
},
|
||||
{
|
||||
"test_id": "GERADE_HundM05_08",
|
||||
"typ": "gerade",
|
||||
"laenge": 3305.26,
|
||||
"hz": 143.35,
|
||||
"startpunkt": [223150.89, 152953.65, 0.0],
|
||||
"endpunkt": [220499.01, 154926.53, 0.0],
|
||||
"comment": "aus DXF: Antrieb FF -> Umlenkung FF (Layer z-3 / z-3), XY-Abstand 3305.26, Neigung 0.00 Grad; waagrechte Foerderstrecke (Hoehendifferenz 0.00 mm < 10 mm) - KEIN VarioFoerderer, da dessen Winkel-Solver eine Hoehendifferenz braucht."
|
||||
},
|
||||
{
|
||||
"test_id": "GF_HundM05_09",
|
||||
"modus": 1,
|
||||
"deltaL_mm": 3817.56,
|
||||
"winkel_grad": 6.75,
|
||||
"hz_grad": 107.51,
|
||||
"start_mm": [223151.48, 152603.13, 2337.66],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links",
|
||||
"as_winkel": "90",
|
||||
"es_winkel": "90",
|
||||
"comment": "aus DXF: 3D_FF_Antrieb_rechts -> 3D_FF_Umlenk_rechts (Layer _3D_Fingerförderer / _3D_Fingerförderer), XY-Abstand 3817.56, Neigung 6.75 Grad; Gefaellestrecke, da fallend und Neigung >= 3 Grad. as_/es_seite und -winkel sind Defaults (im DXF nicht ableitbar)."
|
||||
},
|
||||
{
|
||||
"test_id": "GERADE_HundM05_10",
|
||||
"typ": "gerade",
|
||||
"laenge": 11940.15,
|
||||
"hz": 33.61,
|
||||
"startpunkt": [225207.36, 149949.84, 0.0],
|
||||
"endpunkt": [235151.11, 156559.61, -3.45],
|
||||
"comment": "aus DXF: Antrieb FF -> Umlenkung FF (Layer z-3 / z-3), XY-Abstand 11940.15, Neigung 0.02 Grad; waagrechte Foerderstrecke (Hoehendifferenz 3.45 mm < 10 mm) - KEIN VarioFoerderer, da dessen Winkel-Solver eine Hoehendifferenz braucht."
|
||||
},
|
||||
{
|
||||
"test_id": "VF_HundM05_11",
|
||||
"typ": "standard",
|
||||
"richtung": "Auf",
|
||||
"deltaL": 11769.21,
|
||||
"deltaH": 5827.59,
|
||||
"startpunkt": [225207.48, 149949.9, -3941.61],
|
||||
"seite": "rechts",
|
||||
"hz": 32.34,
|
||||
"comment": "aus DXF: Antrieb FF -> 3D_FF_Umlenk_rechts (Layer z-3 / _3D_Fingerförderer), XY-Abstand 11769.21, Neigung 26.34 Grad; VarioFoerderer. 'winkel' bewusst nicht gesetzt - der Solver waehlt den kleinsten gueltigen Kandidaten. 'seite' ist Default (im DXF nicht ableitbar)."
|
||||
},
|
||||
{
|
||||
"test_id": "VF_HundM05_12",
|
||||
"typ": "standard",
|
||||
"richtung": "Auf",
|
||||
"deltaL": 3450.94,
|
||||
"deltaH": 1885.98,
|
||||
"startpunkt": [227298.92, 152942.18, 0.0],
|
||||
"seite": "rechts",
|
||||
"hz": 106.79,
|
||||
"comment": "aus DXF: Antrieb FF -> 3D_FF_Umlenk_rechts (Layer z-3 / _3D_Fingerförderer), XY-Abstand 3450.94, Neigung 28.66 Grad; VarioFoerderer. 'winkel' bewusst nicht gesetzt - der Solver waehlt den kleinsten gueltigen Kandidaten. 'seite' ist Default (im DXF nicht ableitbar)."
|
||||
},
|
||||
{
|
||||
"test_id": "GF_HundM05_13",
|
||||
"modus": 1,
|
||||
"deltaL_mm": 4078.06,
|
||||
"winkel_grad": 29.82,
|
||||
"hz_grad": 104.2,
|
||||
"start_mm": [227300.62, 152605.32, 2337.74],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links",
|
||||
"as_winkel": "90",
|
||||
"es_winkel": "90",
|
||||
"comment": "aus DXF: 3D_FF_Antrieb_rechts -> Umlenkung FF (Layer _3D_Fingerförderer / z-3), XY-Abstand 4078.06, Neigung 29.82 Grad; Gefaellestrecke, da fallend und Neigung >= 3 Grad. as_/es_seite und -winkel sind Defaults (im DXF nicht ableitbar)."
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Motorstation_500mm_rechts",
|
||||
"x": 224346.52,
|
||||
"y": 149549.9,
|
||||
"z": 21677.04,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'Antrieb FF', Layer 'z-3', auf Makro-Block 'Vario_Motorstation_500mm_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Motorstation_500mm_rechts",
|
||||
"x": 223998.19,
|
||||
"y": 149549.84,
|
||||
"z": 2384.8,
|
||||
"rotation": 180.0,
|
||||
"comment": "aus DXF: Block '3D_FF_Antrieb_rechts', Layer '_3D_Fingerförderer', auf Makro-Block 'Vario_Motorstation_500mm_rechts' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Motorstation_500mm_rechts",
|
||||
"x": 224346.4,
|
||||
"y": 149549.84,
|
||||
"z": 0.0,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'Antrieb FF', Layer 'z-3', auf Makro-Block 'Vario_Motorstation_500mm_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Kurve_rechts_30_TEF_aussen",
|
||||
"x": 219480.78,
|
||||
"y": 153174.98,
|
||||
"z": 2230.86,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block '3D_FF_Bogen_außen_30°', Layer '_3D_Fingerförderer', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Kurve_rechts_30_TEF_innen",
|
||||
"x": 220438.78,
|
||||
"y": 154981.88,
|
||||
"z": 1726.02,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block '3D_FF_Bogen_innen_30°', Layer '_3D_Fingerförderer', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_innen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Kurve_rechts_30_TEF_aussen",
|
||||
"x": 219535.71,
|
||||
"y": 153172.45,
|
||||
"z": 2349.22,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Kurve_rechts_30_TEF_aussen",
|
||||
"x": 220500.01,
|
||||
"y": 154973.55,
|
||||
"z": 1850.0,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Kurve_rechts_30_TEF_aussen",
|
||||
"x": 222776.13,
|
||||
"y": 149549.84,
|
||||
"z": 2335.65,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Vario_Kurve_rechts_30_TEF_aussen",
|
||||
"x": 223249.77,
|
||||
"y": 149949.81,
|
||||
"z": 2360.47,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "AS_Element_90_rechts",
|
||||
"x": 217765.98,
|
||||
"y": 151472.13,
|
||||
"z": 2099.94,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'Ausschleuselement_V3', Layer '_3D_ILS_AE', auf Makro-Block 'AS_Element_90_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Winkel=90, Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "AS_Element_90_rechts",
|
||||
"x": 221440.62,
|
||||
"y": 151470.0,
|
||||
"z": 2067.0,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'Ausschleuselement_V3', Layer '_3D_ILS_AE', auf Makro-Block 'AS_Element_90_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Winkel=90, Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "AS_Element_90_rechts",
|
||||
"x": 225739.77,
|
||||
"y": 151470.0,
|
||||
"z": 2067.0,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'Ausschleuselement_V3', Layer '_3D_ILS_AE', auf Makro-Block 'AS_Element_90_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Winkel=90, Seite=rechts - bei Bedarf hier im JSON korrigieren"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 216525.22,
|
||||
"y": 150670.0,
|
||||
"z": 0.0,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 217622.88,
|
||||
"y": 151470.0,
|
||||
"z": 0.0,
|
||||
"rotation": 180.0,
|
||||
"comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 221298.51,
|
||||
"y": 151470.0,
|
||||
"z": 0.0,
|
||||
"rotation": 180.0,
|
||||
"comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 224908.15,
|
||||
"y": 150670.0,
|
||||
"z": 0.0,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 225508.15,
|
||||
"y": 150670.0,
|
||||
"z": 0.0,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 225597.66,
|
||||
"y": 151470.0,
|
||||
"z": 0.0,
|
||||
"rotation": 180.0,
|
||||
"comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 218325.21,
|
||||
"y": 154009.54,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 219542.38,
|
||||
"y": 151961.76,
|
||||
"z": 0.0,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 220498.95,
|
||||
"y": 155645.73,
|
||||
"z": 0.09,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 220498.95,
|
||||
"y": 155953.39,
|
||||
"z": 0.09,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 222000.85,
|
||||
"y": 155698.7,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 222153.05,
|
||||
"y": 149875.67,
|
||||
"z": 0.0,
|
||||
"rotation": 330.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 223150.84,
|
||||
"y": 151972.72,
|
||||
"z": -7.54,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 223617.86,
|
||||
"y": 149949.84,
|
||||
"z": 0.0,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 226300.0,
|
||||
"y": 155718.69,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-SP",
|
||||
"x": 227300.01,
|
||||
"y": 151981.61,
|
||||
"z": -7.54,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 217622.88,
|
||||
"y": 151190.0,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 220218.95,
|
||||
"y": 155609.84,
|
||||
"z": 0.09,
|
||||
"rotation": 180.0,
|
||||
"comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 221298.51,
|
||||
"y": 151190.0,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 222013.05,
|
||||
"y": 149633.18,
|
||||
"z": 0.0,
|
||||
"rotation": 240.0,
|
||||
"comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 223617.86,
|
||||
"y": 149669.84,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 224882.1,
|
||||
"y": 150392.28,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 225597.66,
|
||||
"y": 151190.0,
|
||||
"z": 0.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 218326.39,
|
||||
"y": 153912.84,
|
||||
"z": 2066.06,
|
||||
"rotation": 0.0,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 219544.61,
|
||||
"y": 152086.0,
|
||||
"z": 2318.68,
|
||||
"rotation": 180.29,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 220500.0,
|
||||
"y": 155763.99,
|
||||
"z": 1876.16,
|
||||
"rotation": 180.0,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 222001.54,
|
||||
"y": 155600.71,
|
||||
"z": 1923.14,
|
||||
"rotation": 359.97,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 222001.69,
|
||||
"y": 155900.21,
|
||||
"z": 1905.78,
|
||||
"rotation": 359.97,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 222201.22,
|
||||
"y": 149847.53,
|
||||
"z": 2318.04,
|
||||
"rotation": 60.49,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 223149.87,
|
||||
"y": 152086.02,
|
||||
"z": 2318.68,
|
||||
"rotation": 180.29,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 223747.95,
|
||||
"y": 149950.46,
|
||||
"z": 2386.66,
|
||||
"rotation": 90.81,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 226300.69,
|
||||
"y": 155600.71,
|
||||
"z": 1923.14,
|
||||
"rotation": 359.97,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 226300.84,
|
||||
"y": 155900.21,
|
||||
"z": 1905.78,
|
||||
"rotation": 359.97,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "Separator_SP",
|
||||
"x": 227298.05,
|
||||
"y": 152086.02,
|
||||
"z": 2318.68,
|
||||
"rotation": 180.29,
|
||||
"comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 216630.12,
|
||||
"y": 150670.0,
|
||||
"z": 2167.0,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 217517.98,
|
||||
"y": 151470.0,
|
||||
"z": 2167.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 221193.61,
|
||||
"y": 151470.0,
|
||||
"z": 2167.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 225013.06,
|
||||
"y": 150670.0,
|
||||
"z": 2167.0,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 225492.76,
|
||||
"y": 151470.0,
|
||||
"z": 2167.0,
|
||||
"rotation": 270.0,
|
||||
"comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
},
|
||||
{
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 225613.06,
|
||||
"y": 150670.0,
|
||||
"z": 2167.0,
|
||||
"rotation": 90.0,
|
||||
"comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter"
|
||||
}
|
||||
]
|
||||
Vendored
+37
-7
@@ -52,7 +52,7 @@
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 400,
|
||||
"y": -3815,
|
||||
"y": -4038,
|
||||
"z": 1467,
|
||||
"rotation": 90,
|
||||
"assigned_to": "Kreisel_links"
|
||||
@@ -62,7 +62,7 @@
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 400,
|
||||
"y": -3925,
|
||||
"y": -4083,
|
||||
"z": 1467,
|
||||
"rotation": 0,
|
||||
"assigned_to": "Kreisel_links"
|
||||
@@ -83,7 +83,7 @@
|
||||
"block": "Scanner",
|
||||
"x": 10310,
|
||||
"y": -610,
|
||||
"z": 1467,
|
||||
"z": 1941,
|
||||
"rotation": 0,
|
||||
"assigned_to": "Kreisel_rechts"
|
||||
},
|
||||
@@ -264,13 +264,23 @@
|
||||
"rotation": 180,
|
||||
"assigned_to": "Kreisel_unten"
|
||||
},
|
||||
{
|
||||
"comment": "Scanner unterer Kreisel",
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 1415,
|
||||
"y": -5100,
|
||||
"z": 1467,
|
||||
"rotation": 270,
|
||||
"assigned_to": "Kreisel_unten"
|
||||
},
|
||||
{
|
||||
"comment": "Separator S-LP #11 Y-Abgleich mit Gefaellestrecken (Sep-Y=8777, GF_Mubea_04-Y=9126, diff=349mm, enger Treffer)",
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 9510,
|
||||
"y": 13649,
|
||||
"z": 1467,
|
||||
"y": 14334,
|
||||
"z": 1941,
|
||||
"rotation": 270,
|
||||
"assigned_to": "Kreisel_rechts"
|
||||
},
|
||||
@@ -293,13 +303,33 @@
|
||||
"z": 1941,
|
||||
"rotation": 90,
|
||||
"assigned_to": "Kreisel_rechts"
|
||||
},
|
||||
{
|
||||
"comment": "Separator Eingang 3 Rechter_Kreisel",
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 10310,
|
||||
"y": -5073,
|
||||
"z": 1941,
|
||||
"rotation": 90,
|
||||
"assigned_to": "Kreisel_rechts"
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 8725,
|
||||
"y": -4743,
|
||||
"z": 1941,
|
||||
"rotation": 180,
|
||||
"assigned_to": "GF_Mubea"
|
||||
},
|
||||
{
|
||||
"comment": "Separator S-LP #8 Y-Abgleich mit Gefaellestrecken (Sep-Y=-4823, GF_Mubea_20-Y=-3673, diff=1150mm, naechstgelegene Reihe, kein enger Treffer)",
|
||||
"function": "insert",
|
||||
"block": "S-LP",
|
||||
"x": 9510,
|
||||
"y": -3887,
|
||||
"y": -3636,
|
||||
"z": 1941,
|
||||
"rotation": 270,
|
||||
"assigned_to": "GF_Mubea_20"
|
||||
@@ -309,7 +339,7 @@
|
||||
"function": "insert",
|
||||
"block": "Scanner",
|
||||
"x": 9510,
|
||||
"y": -3787,
|
||||
"y": -3557,
|
||||
"z": 1941,
|
||||
"rotation": 0,
|
||||
"assigned_to": "Kreisel_rechts"
|
||||
|
||||
Vendored
+48
-1
@@ -49,6 +49,7 @@ Grad). Richtung `hz`/`hz_grad`: 0=Ost(+X), 90=Nord(+Y), 180=West(-X),
|
||||
| 4.3 | Omniflo TEF/Zubehoer | `omni:insert-dxf` (fester Sivasnr) | Position ja, Punkt interaktiv | 1 Punkt (nur via Wrapper) | [#43](#43-omniflo-tefzubehoer) |
|
||||
| 4.4 | Omniflo TV_*/APBW_* | — (DUMMY) | **nein** | — | [#44](#44-omniflo-tv_-apbw_) |
|
||||
| 5 | Separator/Scanner/Sensor | `ils-insert-sensor (fname)` + `_.INSERT` | **nein** (Schleife) | 1 Punkt `x,y,z` (nur via Wrapper) | [#5](#5-separatorscannersensor) |
|
||||
| 6 | Waagrechte Foerderstrecke (`gerade`) | `insert-hz-scaled-block-by-ks (blockname startpunkt laenge hz vert)` | **ja** | 1 Punkt `startpunkt` (+ `endpunkt` als Doku) | [#6](#6-waagrechte-foerderstrecke-gerade) |
|
||||
|
||||
Legende Scriptbar-Spalte:
|
||||
- **ja** — direkt per JSON aufrufbar.
|
||||
@@ -541,7 +542,53 @@ Vollstaendiges JSON (wie in `mubea.json`s Separator-Eintraegen):
|
||||
|
||||
---
|
||||
|
||||
## 6. Zusammenfassung: scriptbar vs. nicht scriptbar
|
||||
## 6. Waagrechte Foerderstrecke (`gerade`)
|
||||
|
||||
Produktionsfunktion:
|
||||
`insert-hz-scaled-block-by-ks (blockname startpunkt laenge hz-winkel vert-winkel)`
|
||||
(`Lisp/vf_etage.lsp`) — dieselbe Primitive, mit der der Etage-Foerderer seine
|
||||
waagrechten Abschnitte baut. Skaliert `Staustrecke_SP_1000_mm` auf `laenge`
|
||||
(Massstab = `laenge / 1000`) und richtet sie ueber `KS_EIN`/`KS_AUS` am
|
||||
`startpunkt` aus.
|
||||
|
||||
Dieser Typ entstand beim Nachbau von `results/ST500592_05.dxf`: ein
|
||||
Antriebs-/Umlenkstation-Paar **ohne** Hoehendifferenz ist fachlich kein
|
||||
VarioFoerderer. Dessen Solver (`berechne-standard` →
|
||||
`berechne-alle-winkel`) muss einen Bogenwinkel aus der Kandidatenliste
|
||||
3…51 Grad waehlen, was bei `deltaH ≈ 0` nicht aufgeht — der Eintrag waere
|
||||
unbaubar. `lib/dxf_scan_components.py` trennt solche Paare darum ueber
|
||||
`GERADE_MAX_DELTA_H` (10 mm) ab und gibt sie als `gerade` aus.
|
||||
|
||||
| Feld | Typ | Pflicht | Bedeutung | Default |
|
||||
|---|---|---|---|---|
|
||||
| `test_id` | string | **ja** | Kennung, Praefix `GERADE_` (steuert den Dispatcher) | - |
|
||||
| `typ` | string | nein | `"gerade"` (Dokumentation, nicht ausgewertet) | - |
|
||||
| `laenge` | real (mm) | **ja** | horizontale Laenge; Massstab der Staustrecke | - |
|
||||
| `hz` | real (Grad) | nein | Fahrtrichtung in der XY-Ebene | 0.0 |
|
||||
| `startpunkt` | [x,y,z] (mm) | **ja** | Anfangspunkt (`KS_EIN` wird hierhin gelegt) | - |
|
||||
| `endpunkt` | [x,y,z] (mm) | nein | Endpunkt — reine Dokumentation/Herkunft, **kein** Parameter; die Geometrie folgt aus `startpunkt` + `laenge` + `hz` | - |
|
||||
|
||||
`vert-winkel` wird vom Treiber fest auf `0` gesetzt (waagrecht). Die Funktion
|
||||
gibt den **Ausgangspunkt** zurueck, nicht die Entity — ein Treiber holt den
|
||||
Block ueber `(entlast)`. Bei fehlender Blockdatei ruft sie `(exit)` auf und
|
||||
wuerde einen Testlauf abbrechen, darum vorher mit `ssg-ils-block-laden`
|
||||
pruefen (so macht es `hundm05:build-gerade` in `tests/test_hundm05.lsp`).
|
||||
|
||||
Vollstaendiges JSON (wie in `hundm05.json`):
|
||||
```json
|
||||
{
|
||||
"test_id": "GERADE_HundM05_08",
|
||||
"typ": "gerade",
|
||||
"laenge": 3305.26,
|
||||
"hz": 143.35,
|
||||
"startpunkt": [223150.89, 152953.65, 0.0],
|
||||
"endpunkt": [220499.01, 154926.53, 0.0]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Zusammenfassung: scriptbar vs. nicht scriptbar
|
||||
|
||||
| Element | Produktionsfunktion | Scriptbar? |
|
||||
|---|---|---|
|
||||
|
||||
Reference in New Issue
Block a user