Export: Insertpoint (24-Zeichen KOS) und K1-K4 (12-Zeichen Position) je Block

csv:block-to-json ergaenzt fuer jeden Block einen Insertpoint-String
(Position+Rotation als Z-Quaternion, csv:kos-encode) sowie K1-K4 als
reine Positions-Strings (csv:trans-encode, 0.1 mm). KS_EIN/KS_AUS werden
auf K1/K2 gemappt, falls keine echten K1/K2 vorhanden sind. Neue CSV-
Spalten Insertpoint;K1;K2;K3;K4 in export_csv.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 14:38:08 +02:00
parent f51b421e23
commit 52264f1531
2 changed files with 206 additions and 6 deletions
+168 -1
View File
@@ -90,6 +90,158 @@
)
)
;; ============================================================
;; KOS-KOMPRIMIERUNG (Position + Rotation als 24-Zeichen Base64-String)
;; ============================================================
;; Kodiert Position (x,y,z in mm) und Rotation (als Quaternion qx,qy,qz,qw)
;; verlustarm (24 Bit Fixed-Point je Wert) in einen 24-Zeichen Base64-String.
;; Ported aus einer interaktiven Referenz-Routine (c:ExportKOS), hier als
;; reine Funktion fuer den automatischen Export nutzbar. Alle Elemente in
;; diesem Projekt werden ausschliesslich um die Z-Achse gedreht (siehe
;; DREHUNG-Konvention weiter unten), daher genuegt eine Halbwinkel-Quaternion
;; fuer eine reine Z-Rotation (csv:z-angle-to-quat) -- keine vollstaendige
;; Rotationsmatrix/Quaternion-Extraktion noetig.
(setq *csv-b64-chars* "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
;; --- Wert auf [min-val, max-val] begrenzen ---
(defun csv:clamp (val min-val max-val)
(max min-val (min val max-val))
)
;; --- Rotationswinkel (Grad, reine Z-Drehung) -> Quaternion (qx qy qz qw) ---
(defun csv:z-angle-to-quat (deg / rad)
(setq rad (/ (* deg pi) 180.0))
(list 0.0 0.0 (sin (/ rad 2.0)) (cos (/ rad 2.0)))
)
;; --- Liste vorzeichenbehafteter 24-Bit-Integer -> Base64 (je 4 Zeichen) ---
;; Gemeinsamer Kodierer fuer csv:kos-encode und csv:trans-encode.
(defun csv:b64-encode-ints (int-list / b64-str val)
(setq b64-str "")
(foreach val int-list
(if (< val 0) (setq val (+ val 16777216))) ; Zweierkomplement (24 Bit)
(setq b64-str (strcat b64-str
(substr *csv-b64-chars* (1+ (lsh val -18)) 1)
(substr *csv-b64-chars* (1+ (logand (lsh val -12) 63)) 1)
(substr *csv-b64-chars* (1+ (logand (lsh val -6) 63)) 1)
(substr *csv-b64-chars* (1+ (logand val 63)) 1)
))
)
b64-str
)
;; --- Position (mm) + Quaternion -> 24-Zeichen Base64-String ---
;; Fixed-Point: x,y,z (Faktor 1000 = mm) und qx,qy,qz werden je auf 24 Bit
;; gerundet (6 Werte * 4 Zeichen = 24 Zeichen). qw wird nicht mitkodiert,
;; sondern nur zur Vorzeichen-Normierung verwendet (q und -q sind dieselbe
;; Rotation). Fuer den Insertpoint (volle Lage), siehe csv:trans-encode fuer
;; reine Positionen (K1-K4).
(defun csv:kos-encode (x y z qx qy qz qw)
(if (< qw 0.0)
(setq qx (- qx) qy (- qy) qz (- qz))
)
(csv:b64-encode-ints (list
(fix (csv:clamp (* x 1000.0) -8388607 8388607))
(fix (csv:clamp (* y 1000.0) -8388607 8388607))
(fix (csv:clamp (* z 1000.0) -8388607 8388607))
(fix (csv:clamp (* qx 8388607.0) -8388607 8388607))
(fix (csv:clamp (* qy 8388607.0) -8388607 8388607))
(fix (csv:clamp (* qz 8388607.0) -8388607 8388607))
))
)
;; --- Position (nur x,y,z) -> 12-Zeichen Base64-String ---
;; Fixed-Point Faktor 10.0 (0.1 mm Genauigkeit, Bereich +-838 m), 3 Werte *
;; 4 Zeichen = 12 Zeichen. Fuer die Koordinatensysteme K1-K4 (nur Position,
;; keine Rotation). Ported aus der Referenz-Routine c:ExportTrans.
(defun csv:trans-encode (x y z)
(csv:b64-encode-ints (list
(fix (csv:clamp (* x 10.0) -8388607 8388607))
(fix (csv:clamp (* y 10.0) -8388607 8388607))
(fix (csv:clamp (* z 10.0) -8388607 8388607))
))
)
;; --- Lokalen Punkt (relativ zum Block-Ursprung) unter Beruecksichtigung der
;; Eltern-Rotation (reine Z-Drehung um den Einfuegepunkt) in Weltkoordinaten
;; umrechnen. Rueckgabe: (x y z) ---
(defun csv:local-to-world (parent-pt parent-rot-rad local-pt / c s lx ly lz pz)
(setq c (cos parent-rot-rad) s (sin parent-rot-rad))
(setq lx (car local-pt) ly (cadr local-pt))
(setq lz (if (caddr local-pt) (caddr local-pt) 0.0))
(setq pz (if (caddr parent-pt) (caddr parent-pt) 0.0))
(list
(+ (car parent-pt) (- (* lx c) (* ly s)))
(+ (cadr parent-pt) (+ (* lx s) (* ly c)))
(+ pz lz)
)
)
;; --- K1-K4-Koordinatensysteme eines Blocks als Positions-Strings lesen ---
;; ename = Entity-Name des platzierten INSERT (Omniflo Bogen/Weiche/Gerade).
;; pt/rot-rad = bereits ermittelte Welt-Position/-Rotation (radiant) des
;; Eltern-INSERT (siehe csv:block-to-json), keine erneute Abfrage.
;; Sucht in der Block-DEFINITION (wie omni:get-block-hoehe) nach direkten
;; Sub-INSERTs "K1".."K4" und rechnet deren lokale Position ueber die
;; Eltern-Rotation (reine Z-Drehung) in Weltkoordinaten um. Es wird nur die
;; POSITION kodiert (12-Zeichen-String, csv:trans-encode) - keine Rotation.
;; Zusaetzlich werden Strecken-Koordinatensysteme KS_EIN->K1 und KS_AUS->K2
;; (inkl. der KSYS_-Varianten, siehe ks-normalize-name in vf_core.lsp) gemappt,
;; sofern kein echter K1/K2 am Block vorhanden ist (echte K-Bloecke haben
;; Vorrang: sie werden vorne angehaengt, csv:k-kos liest per assoc den ersten).
;; Rueckgabe: Assoc-Liste (("K1" . "<pos-string>") ("K2" . ...) ...), nur
;; fuer tatsaechlich vorhandene K-Bloecke (nicht jedes Element hat alle 4).
(defun csv:get-k-kos-strings (ename pt rot-rad / ed bname blk-tbl sub-ent sub-ed
sub-bname sub-pt world-pt target is-real result)
(setq ed (entget ename))
(setq bname (cdr (assoc 2 ed)))
(setq blk-tbl (tblsearch "BLOCK" bname))
(setq result nil)
(if blk-tbl
(progn
(setq sub-ent (entnext (cdr (assoc -2 blk-tbl))))
(while sub-ent
(setq sub-ed (entget sub-ent))
(if (= (cdr (assoc 0 sub-ed)) "INSERT")
(progn
(setq sub-bname (strcase (cdr (assoc 2 sub-ed))))
;; Ziel-Slot bestimmen: echte K1-K4 direkt, KS_EIN/KS_AUS gemappt.
(setq is-real (wcmatch sub-bname "K1,K2,K3,K4"))
(setq target
(cond
(is-real sub-bname)
((or (= sub-bname "KS_EIN") (= sub-bname "KSYS_EIN")) "K1")
((or (= sub-bname "KS_AUS") (= sub-bname "KSYS_AUS")) "K2")
(t nil)))
;; Echte K-Bloecke immer aufnehmen (vorne = Vorrang bei assoc);
;; KS-abgeleitete nur, wenn der Slot noch nicht belegt ist.
(if (and target (or is-real (not (assoc target result))))
(progn
(setq sub-pt (cdr (assoc 10 sub-ed)))
(setq world-pt (csv:local-to-world pt rot-rad sub-pt))
(setq result (cons
(cons target
(csv:trans-encode
(car world-pt) (cadr world-pt) (caddr world-pt)))
result))
)
)
)
)
(setq sub-ent (entnext sub-ent))
)
)
)
result
)
;; --- Wert aus csv:get-k-kos-strings nach Name lesen, "" falls nicht vorhanden ---
(defun csv:k-kos (k-list name / found)
(setq found (assoc name k-list))
(if found (cdr found) "")
)
;; --- Mindesthoehe der Bounding-Box (Z-Ausdehnung), aus cfg/export.cfg ---
;; [Boundingbox] bb_minimum_height_mm, Default 1 (mm) falls nicht gesetzt.
;; Verhindert dz=0 bei flachen 2D-Bloecken (Draufsicht ohne Z-Ausdehnung).
@@ -166,7 +318,12 @@
;; --- Einen INSERT-Block als JSON-Objekt schreiben ---
;; include-bbox = T -> zusaetzlich Bounding-Box (Mitte + Ausdehnung) ermitteln
;; und als "bbox"-Objekt anhaengen (nur fuer EXPORTCSV, nicht fuer EXPORTSIVAS).
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation handle attribs bbox bbox-json)
;; "insertpoint" (KOS-String von Position+Rotation des Blocks selbst) und
;; "k1".."k4" (KOS-Strings der Koordinatensysteme im Block, siehe
;; csv:get-k-kos-strings) werden immer geschrieben, unabhaengig von
;; include-bbox -- Kodierung ueber csv:kos-encode (siehe dort).
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation handle attribs bbox bbox-json
insert-quat insert-kos k-list)
(setq ed (entget ename))
(setq blk-name (cdr (assoc 2 ed)))
(setq layer (cdr (assoc 8 ed)))
@@ -175,6 +332,11 @@
(setq handle (cdr (assoc 5 ed)))
(if (null rotation) (setq rotation 0.0))
(setq attribs (csv:read-attribs ename))
(setq insert-quat (csv:z-angle-to-quat (* (/ rotation pi) 180.0)))
(setq insert-kos (csv:kos-encode
(car pt) (cadr pt) (if (caddr pt) (caddr pt) 0.0)
(nth 0 insert-quat) (nth 1 insert-quat) (nth 2 insert-quat) (nth 3 insert-quat)))
(setq k-list (csv:get-k-kos-strings ename pt rotation))
(setq bbox-json "")
(if include-bbox
(progn
@@ -204,6 +366,11 @@
",\"z\":" (rtos (if (caddr pt) (caddr pt) 0.0) 2 4)
",\"rotation\":" (rtos (* (/ rotation pi) 180.0) 2 4)
",\"attribs\":" attribs
",\"insertpoint\":\"" insert-kos "\""
",\"k1\":\"" (csv:k-kos k-list "K1") "\""
",\"k2\":\"" (csv:k-kos k-list "K2") "\""
",\"k3\":\"" (csv:k-kos k-list "K3") "\""
",\"k4\":\"" (csv:k-kos k-list "K4") "\""
bbox-json
"}"
)
+38 -5
View File
@@ -11,12 +11,18 @@ Aufruf:
python export_csv.py <export_raw.json> <data_dir> <output.csv>
CSV-Format:
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;Nachbarn;Fehler;Merkmale
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;
Insertpoint;K1;K2;K3;K4;Nachbarn;Fehler;Merkmale
Position und Boundingbox stammen aus der von export.lsp (csv:get-bbox, per
vla-getboundingbox) ermittelten Bounding-Box je Block:
Position = Mittenkoordinate x, y, z
Boundingbox = Ausdehnung (Laenge, Breite, Hoehe) in x, y, z
Insertpoint = KOS-String (24-Zeichen Base64, Position+Rotation als Quaternion,
siehe csv:kos-encode in export.lsp) des Block-Einfuegepunkts selbst.
K1-K4 = Positions-Strings der im Block enthaltenen Koordinatensysteme K1..K4
(12-Zeichen Base64, nur Position ohne Rotation, siehe csv:trans-encode;
nur bei Omniflo Bogen/Weiche/Gerade vorhanden, sonst leer).
Planquadrat wird aus der x/y-Koordinate des Blocks berechnet, siehe
export_planquadrat.py ([Planquadrate] in cfg/export.cfg).
Nachbarn = kommaseparierte TeileId-Liste ueberschneidender Elemente (Bounding-
@@ -34,7 +40,10 @@ Omniflo-Elemente brauchen mindestens einen Partner, Gefaellestrecke/
Foerderer/Strecke-Modul mindestens zwei (siehe export_neighbors.py
MIN_PARTNER).
Position, Boundingbox, Planquadrat, Nachbarn und Fehler sind nur fuer
EXPORTCSV vorhanden (nicht EXPORTSIVAS) - siehe csv:run-export.
EXPORTCSV vorhanden (nicht EXPORTSIVAS) - siehe csv:run-export. Insertpoint/
K1-K4 stehen zwar in export_raw.json fuer jeden Export zur Verfuegung (siehe
csv:block-to-json), werden aber ebenfalls nur von diesem Skript (EXPORTCSV)
als CSV-Spalten ausgegeben.
"""
import json
@@ -243,21 +252,34 @@ def build_kreisel_merkmale(block):
# ---------------------------------------------------------------------------
def bbox_columns(block):
"""Liest die von export.lsp (csv:get-bbox) ermittelte Bounding-Box.
"""Liest die von export.lsp (csv:get-bbox) ermittelte Bounding-Box, sowie
die KOS-Strings von Insertpoint und K1-K4 (csv:kos-encode/csv:block-to-json).
Rueckgabe: dict mit "position" (Mittenkoordinate x,y,z) und "boundingbox"
(Ausdehnung x,y,z) als Strings, oder leere Strings falls kein bbox-Feld
vorhanden ist (z.B. EXPORTSIVAS oder die synthetische Omniflo-Sum-Zeile).
"insertpoint" ist ein 24-Zeichen Base64-KOS-String (Position + Rotation
als Quaternion, csv:kos-encode); "k1".."k4" sind 12-Zeichen Positions-
Strings (nur Position, csv:trans-encode) - leer falls das jeweilige
Koordinatensystem am Block nicht existiert.
"_bbox" enthaelt zusaetzlich die rohen Zahlenwerte fuer die Nachbarschafts-
erkennung (siehe export_neighbors.py), wird nicht in die CSV geschrieben.
"""
kos_cols = {
"insertpoint": block.get("insertpoint", ""),
"k1": block.get("k1", ""),
"k2": block.get("k2", ""),
"k3": block.get("k3", ""),
"k4": block.get("k4", ""),
}
bbox = block.get("bbox")
if not bbox:
return {"position": "", "boundingbox": "", "_bbox": None}
return {"position": "", "boundingbox": "", "_bbox": None, **kos_cols}
return {
"position": f'{bbox.get("cx", 0):.2f}, {bbox.get("cy", 0):.2f}, {bbox.get("cz", 0):.2f}',
"boundingbox": f'{bbox.get("dx", 0):.2f}, {bbox.get("dy", 0):.2f}, {bbox.get("dz", 0):.2f}',
"_bbox": bbox,
**kos_cols,
}
@@ -468,6 +490,11 @@ def process_blocks(blocks, lookup):
len_ap110_mm, len_ap60_mm),
"position": "",
"boundingbox": "",
"insertpoint": "",
"k1": "",
"k2": "",
"k3": "",
"k4": "",
})
neighbor_ids = compute_neighbor_ids(
@@ -495,6 +522,11 @@ def format_csv_line(item):
f';1'
f';"{csv_quote(item["position"])}"'
f';"{csv_quote(item["boundingbox"])}"'
f';"{csv_quote(item["insertpoint"])}"'
f';"{csv_quote(item["k1"])}"'
f';"{csv_quote(item["k2"])}"'
f';"{csv_quote(item["k3"])}"'
f';"{csv_quote(item["k4"])}"'
f';"{csv_quote(item["nachbarn"])}"'
f';"{csv_quote(item["fehler"])}"'
f';{merkmale_json}'
@@ -528,7 +560,8 @@ def main():
items = process_blocks(blocks, lookup)
header = "Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;Nachbarn;Fehler;Merkmale"
header = ("Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;"
"Insertpoint;K1;K2;K3;K4;Nachbarn;Fehler;Merkmale")
with open(output_csv, "w", encoding="utf-8") as f:
f.write(header + "\n")
for item in items: