Add Connections to SSG_LIB, reusing the TRO mechanisms

A connection is a CONNECTION_ARROW block sitting at the midpoint of an arrow
between two TRO markers. It stores the relationship as attributes rather than by
position:

    TRO11  ---------->  TRO14
    UID 0062            UID 0065

Same architecture as the TRO feature, nothing new introduced:

  Blockname CONNECTION_*  ->  c:CONNECTION_EDIT (Lisp/Connection_Edit.lsp)
                          ->  dcl/connection_edit.dcl

The double-click and EATTEDIT hooks already route every INSERT through
SSG_BLOCKEDIT, so this is one more branch in that dispatcher - placed before the
TRO branch since both come from the same annotation run and the patterns must not
overlap. DCL path from DXFM_DCL, ssg-start/ssg-end, ssg-attrib-read and
ssg-attrib-set-on, all text via ssg-text/ssg-textf with 16 new keys in both
language files. Loading is lazy: not preloaded in the MNL, pulled by the menu
macros and the dispatcher with (ssg-ensure "Connection_Edit"), with a fallback to
native EATTEDIT if the module is missing.

CONNECTION_INSERT picks two TRO markers and draws the connection; the new
internal number comes from ssg-id-max/ssg-id-format, so it stays inside the
SSG_LIB number space. It refuses when the block definition is absent - the shape
comes from tro_annotate.py, this command does not invent one.

Menu: SSG_LIB > Connections with Verbindung einfuegen, Verbindung bearbeiten and
Bearbeiten (Auto), plus an entry in the POP501 edit context menu.

On the internal id: the markers keep their visible ID (TRO11) untouched - the
existing TRO_INSERT/tro-naechste-id read it and the request was explicit about
not changing it. The permanent unique number therefore lives in a separate
attribute UID, in the same four-digit space as ssg_id and the CSV TeileId.
Putting it into ID was tried and reverted: IDSCHECK treats ID as numeric and
would have overwritten TRO11 with a number while correcting duplicates.
Connections reference the UIDs, so the relationship survives a renamed or moved
marker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-19 15:09:10 +02:00
parent dc2b0b8900
commit e91608d7cc
8 changed files with 1713 additions and 1343 deletions
+254
View File
@@ -0,0 +1,254 @@
;; ============================================================
;; CONNECTION_EDIT.LSP - Verbindungen zwischen TROs
;;
;; Eine Verbindung ist ein Block CONNECTION_ARROW in der Mitte eines Pfeils
;; zwischen zwei TRO-Markern. Sie haelt die Beziehung ueber die *internen* IDs
;; der beiden TROs fest, nicht ueber die Lage im Plan:
;;
;; TRO11 ----------> TRO14
;; ID 0062 ID 0065
;;
;; Attribute des Verbindungsblocks:
;; ID unsichtbar - eindeutige interne Nummer (ID-Raum von SSG_LIB)
;; FROM_ID unsichtbar - interne ID der Quelle
;; TO_ID unsichtbar - interne ID des Ziels
;; FROM_TRO sichtbar - sprechender Name der Quelle
;; TO_TRO sichtbar - sprechender Name des Ziels
;; KIND unsichtbar - flow | colocated
;;
;; Warum interne IDs: der sprechende Name darf sich aendern (TRO_EDIT), die
;; interne ID nicht. Ein DXF-Export kann die Beziehung damit eindeutig
;; rekonstruieren, auch wenn Marker verschoben oder umbenannt wurden.
;;
;; Befehle:
;; CONNECTION_INSERT - zwei TRO-Marker waehlen, Verbindung einzeichnen
;; CONNECTION_EDIT - Quelle/Ziel einer bestehenden Verbindung aendern
;;
;; Aufruf:
;; - Doppelklick auf die Verbindung -> ***DOUBLECLICK [INSERT] -> SSG_BLOCKEDIT
;; - EATTEDIT -> c:EATTEDIT -> SSG_BLOCKEDIT
;; - Menue SSG_LIB > Connections
;; Der Dispatcher SSG_BLOCKEDIT leitet Blocknamen "CONNECTION_*" hierher.
;;
;; Setzt ssg_core.lsp (ssg-start/ssg-end, ssg-attrib-*), ssg_lang.lsp und
;; ssg_id.lsp (ssg-id-max, ssg-id-format) voraus.
;; ============================================================
(setq *connection-block* "CONNECTION_ARROW")
;; ------------------------------------------------------------
;; Attribut eines Blocks lesen ("" wenn nicht vorhanden)
;; ------------------------------------------------------------
(defun con-attrib (ent tag / wert)
(setq wert (cdr (assoc tag (ssg-attrib-read ent))))
(if wert wert "")
)
;; ------------------------------------------------------------
;; Einen TRO-Marker waehlen; liefert (ename interne-ID sprechender-Name)
;; oder nil bei Abbruch bzw. falschem Block.
;; ------------------------------------------------------------
(defun con-pick-tro (prompt-key / sel ent bname)
(setq sel (entsel (ssg-text prompt-key)))
(if (null sel)
nil
(progn
(setq ent (car sel)
bname (cdr (assoc 2 (entget ent))))
(if (not (wcmatch bname "TRO_*"))
(progn (alert (ssg-textf "con-kein-tro" (list bname))) nil)
(list ent (con-attrib ent "ID") (con-attrib ent "TRO_ID"))
)
)
)
)
;; ------------------------------------------------------------
;; C:CONNECTION_INSERT - Verbindung zwischen zwei TRO-Markern einzeichnen
;;
;; Zeichnet Schaft und Spitze auf den Fluss-Layer und setzt den
;; Verbindungsblock in die Mitte, ausgerichtet entlang des Pfeils.
;; ------------------------------------------------------------
(defun c:CONNECTION_INSERT ( / von nach p1 p2 dx dy laenge winkel mitte
kopf ent-neu neue-id layer-name)
(ssg-start "CONNECTION_INSERT" '(("OSMODE") ("ATTREQ") ("ATTDIA") ("CLAYER")))
(setvar "OSMODE" 0)
(if (not (tblsearch "BLOCK" *connection-block*))
(progn
(alert (ssg-textf "con-block-fehlt" (list *connection-block*)))
(ssg-end)
(exit)
)
)
(setq von (con-pick-tro "con-quelle-waehlen"))
(if (null von) (progn (ssg-end) (exit)))
(setq nach (con-pick-tro "con-ziel-waehlen"))
(if (null nach) (progn (ssg-end) (exit)))
(if (= (car von) (car nach))
(progn (alert (ssg-text "con-gleicher-block")) (ssg-end) (exit))
)
(setq p1 (cdr (assoc 10 (entget (car von))))
p2 (cdr (assoc 10 (entget (car nach)))))
(setq dx (- (car p2) (car p1))
dy (- (cadr p2) (cadr p1))
laenge (distance p1 p2))
(if (<= laenge 1e-6)
(progn (alert (ssg-text "con-gleicher-punkt")) (ssg-end) (exit))
)
(setq winkel (atan dy dx)
mitte (list (/ (+ (car p1) (car p2)) 2.0)
(/ (+ (cadr p1) (cadr p2)) 2.0)
0.0))
;; Layer wie die uebrigen Verbindungen der Beschriftung
(setq layer-name "TRO_FLOW")
(ssg-make-layer layer-name "5" T)
;; Schaft
(entmake (list (cons 0 "LWPOLYLINE")
(cons 100 "AcDbEntity")
(cons 8 layer-name)
(cons 100 "AcDbPolyline")
(cons 90 2)
(cons 70 0)
(cons 10 (list (car p1) (cadr p1)))
(cons 10 (list (car p2) (cadr p2)))))
;; Verbindungsblock in die Mitte, entlang des Pfeils gedreht
(setvar "ATTREQ" 0)
(setvar "ATTDIA" 0)
(command "_.-INSERT" *connection-block* mitte "" "" (angtos winkel 0 8))
(setq ent-neu (entlast))
;; Eindeutige interne ID im ID-Raum von SSG_LIB
(setq neue-id (ssg-id-format (1+ (ssg-id-max))))
(ssg-attrib-set-on ent-neu
(list (cons "ID" neue-id)
(cons "FROM_ID" (cadr von))
(cons "TO_ID" (cadr nach))
(cons "FROM_TRO" (caddr von))
(cons "TO_TRO" (caddr nach))
(cons "KIND" "flow")))
(entupd ent-neu)
(princ (ssg-textf "con-eingefuegt"
(list (caddr von) (caddr nach) neue-id)))
(ssg-end)
(princ)
)
;; ------------------------------------------------------------
;; C:CONNECTION_EDIT - Quelle/Ziel einer Verbindung aendern
;; ------------------------------------------------------------
(defun c:CONNECTION_EDIT ( / ss ent ed bname
alt-from-id alt-to-id alt-from alt-to alt-kind
neu-from-id neu-to-id neu-from neu-to
dcl-pfad dat ergebnis)
;; Implied Selection VOR ssg-start pruefen (ssg-start hebt Selektion auf)
(setq ss (ssget "I"))
(if (and ss (= (sslength ss) 1)
(= (cdr (assoc 0 (entget (ssname ss 0)))) "INSERT"))
(setq ent (ssname ss 0))
)
(ssg-start "CONNECTION_EDIT" nil)
(if (null ent)
(progn
(princ (ssg-text "cmd-block-auswaehlen"))
(setq ss (ssget ":S" '((0 . "INSERT"))))
(if ss (setq ent (ssname ss 0)))
)
)
(if (null ent)
(progn (princ (ssg-text "cmd-kein-block-ausgewaehlt")) (ssg-end) (exit))
)
(setq ed (entget ent)
bname (cdr (assoc 2 ed)))
(if (not (wcmatch bname "CONNECTION_*"))
(progn
(alert (ssg-textf "con-keine-verbindung" (list bname)))
(ssg-end)
(exit)
)
)
(setq alt-from-id (con-attrib ent "FROM_ID")
alt-to-id (con-attrib ent "TO_ID")
alt-from (con-attrib ent "FROM_TRO")
alt-to (con-attrib ent "TO_TRO")
alt-kind (con-attrib ent "KIND"))
(setq dcl-pfad (strcat (getenv "DXFM_DCL") "/connection_edit.dcl"))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "connection_edit" dat))
(progn
(alert (ssg-textf "con-dialog-fehlt" (list dcl-pfad)))
(ssg-end)
(exit)
)
)
(set_tile "blockname" (ssg-textf "con-block" (list bname (con-attrib ent "ID"))))
(set_tile "from_id" alt-from-id)
(set_tile "from_tro" alt-from)
(set_tile "to_id" alt-to-id)
(set_tile "to_tro" alt-to)
(set_tile "richtung" (ssg-textf "con-richtung" (list alt-from alt-to)))
(set_tile "hinweis" (ssg-text "con-hinweis"))
(setq neu-from-id alt-from-id neu-to-id alt-to-id
neu-from alt-from neu-to alt-to)
(action_tile "from_id" "(setq neu-from-id (get_tile \"from_id\"))")
(action_tile "to_id" "(setq neu-to-id (get_tile \"to_id\"))")
(action_tile "from_tro" "(setq neu-from (get_tile \"from_tro\"))")
(action_tile "to_tro" "(setq neu-to (get_tile \"to_tro\"))")
(action_tile "accept"
(strcat "(setq neu-from-id (get_tile \"from_id\")"
" neu-to-id (get_tile \"to_id\")"
" neu-from (get_tile \"from_tro\")"
" neu-to (get_tile \"to_tro\"))"
"(done_dialog 1)"))
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(if (or (= (vl-string-trim " " neu-from-id) "")
(= (vl-string-trim " " neu-to-id) ""))
(alert (ssg-text "con-id-leer"))
(if (= neu-from-id neu-to-id)
(alert (ssg-text "con-gleiche-id"))
(progn
(ssg-attrib-set-on ent
(list (cons "FROM_ID" neu-from-id)
(cons "TO_ID" neu-to-id)
(cons "FROM_TRO" neu-from)
(cons "TO_TRO" neu-to)
(cons "KIND" (if (= alt-kind "") "flow" alt-kind))))
(entupd ent)
(princ (ssg-textf "con-gespeichert" (list neu-from neu-to)))
)
)
)
)
(ssg-end)
(princ)
)
(prompt "\nConnection_Edit.lsp geladen - Befehle: CONNECTION_INSERT, CONNECTION_EDIT")
(princ)
+17
View File
@@ -439,6 +439,23 @@
(c:VARIOFOERDERER_EDIT) (c:VARIOFOERDERER_EDIT)
) )
;; Verbindungen (CONNECTION_*) -> CONNECTION_EDIT (DCL-Dialog)
;; Vor der TRO-Pruefung, da beide aus der Beschriftung von sps_skel stammen
;; und sich die Namensmuster nicht ueberschneiden duerfen.
((wcmatch bname "CONNECTION_*")
(princ (ssg-textf "cmd-connection-bearbeiten" (list bname)))
(if (and (null *ssg-Connection_Edit-loaded*) (atoms-family 1 '("SSG-ENSURE")))
(ssg-ensure "Connection_Edit")
)
(if (atoms-family 1 '("C:CONNECTION_EDIT"))
(c:CONNECTION_EDIT)
(progn
(princ (ssg-textf "tro-edit-modul-fehlt" (list "Connection_Edit.lsp")))
(command "_.EATTEDIT")
)
)
)
;; TRO-Marker (TRO_SYM_<Typ>) -> TRO_EDIT (DCL-Dialog) ;; TRO-Marker (TRO_SYM_<Typ>) -> TRO_EDIT (DCL-Dialog)
;; Die Marker kommen aus sps_skel (lib/tro_annotate.py) und tragen die ;; Die Marker kommen aus sps_skel (lib/tro_annotate.py) und tragen die
;; Attribute ID und TYPE. Modul bei Bedarf nachladen, damit der Doppelklick ;; Attribute ID und TYPE. Modul bei Bedarf nachladen, damit der Doppelklick
+5 -3
View File
@@ -245,7 +245,7 @@
;; C:TRO_EDIT - Dialog fuer einen TRO-Marker ;; C:TRO_EDIT - Dialog fuer einen TRO-Marker
;; ------------------------------------------------------------ ;; ------------------------------------------------------------
(defun c:TRO_EDIT ( / ss ent ed bname attribs (defun c:TRO_EDIT ( / ss ent ed bname attribs
alt-id alt-type alt-fb alt-id alt-type alt-fb int-id
neu-id neu-type neu-id neu-type
liste idx dcl-pfad dat ergebnis) liste idx dcl-pfad dat ergebnis)
@@ -284,7 +284,8 @@
(setq attribs (ssg-attrib-read ent) (setq attribs (ssg-attrib-read ent)
alt-id (cdr (assoc "ID" attribs)) alt-id (cdr (assoc "ID" attribs))
alt-type (cdr (assoc "TYPE" attribs)) alt-type (cdr (assoc "TYPE" attribs))
alt-fb (cdr (assoc "FB_BLOCK" attribs))) alt-fb (cdr (assoc "FB_BLOCK" attribs))
int-id (cdr (assoc "UID" attribs)))
(if (null alt-id) (if (null alt-id)
(progn (progn
@@ -310,7 +311,8 @@
idx (tro-index alt-type liste)) idx (tro-index alt-type liste))
(set_tile "blockname" (ssg-textf "tro-edit-block" (list bname))) (set_tile "blockname" (ssg-textf "tro-edit-block" (list bname)))
(set_tile "fb" (ssg-textf "tro-edit-fb" (list (if alt-fb alt-fb "-")))) (set_tile "fb" (ssg-textf "tro-edit-fb"
(list (if alt-fb alt-fb "-") (if int-id int-id "-"))))
(set_tile "hinweis" (ssg-text "tro-edit-hinweis")) (set_tile "hinweis" (ssg-text "tro-edit-hinweis"))
(set_tile "id" alt-id) (set_tile "id" alt-id)
(start_list "type") (start_list "type")
+1
View File
@@ -35,6 +35,7 @@
(load (strcat base-path "tests.lsp") "tests.lsp nicht gefunden") (load (strcat base-path "tests.lsp") "tests.lsp nicht gefunden")
(load (strcat base-path "OmniModulInsert.lsp") "OmniModulInsert.lsp nicht gefunden") (load (strcat base-path "OmniModulInsert.lsp") "OmniModulInsert.lsp nicht gefunden")
(load (strcat base-path "Tro_Edit.lsp") "Tro_Edit.lsp nicht gefunden") (load (strcat base-path "Tro_Edit.lsp") "Tro_Edit.lsp nicht gefunden")
(load (strcat base-path "Connection_Edit.lsp") "Connection_Edit.lsp nicht gefunden")
(load (strcat base-path "SSG_LIB_Commands.lsp") "SSG_LIB_Commands.lsp nicht gefunden") (load (strcat base-path "SSG_LIB_Commands.lsp") "SSG_LIB_Commands.lsp nicht gefunden")
(prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, KreiselInsert, VarioFoerderer, Gefaellestrecke, export, tests, OmniModulInsert, SSG_LIB_Commands") (prompt "\nSSG-Bibliotheken geladen: ssg_core, ssg_lang, ssg_dbg, ssg_dialog, ssg_layer, KreiselInsert, VarioFoerderer, Gefaellestrecke, export, tests, OmniModulInsert, SSG_LIB_Commands")
(princ) (princ)
+59
View File
@@ -0,0 +1,59 @@
// ============================================================
// connection_edit.dcl - Verbindung zwischen zwei TROs bearbeiten
// Aendert Quelle und Ziel eines CONNECTION_*-Blocks. Gearbeitet wird
// mit den internen IDs; die sprechenden Namen stehen daneben.
// Aufruf ueber c:CONNECTION_EDIT (Lisp/Connection_Edit.lsp).
// ============================================================
connection_edit : dialog {
label = "Verbindung bearbeiten";
: column {
: text {
key = "blockname";
label = "";
}
: boxed_column {
label = "Quelle";
: edit_box {
key = "from_id";
label = "Interne ID:";
edit_width = 12;
}
: edit_box {
key = "from_tro";
label = "TRO-Kennung:";
edit_width = 24;
}
}
: boxed_column {
label = "Ziel";
: edit_box {
key = "to_id";
label = "Interne ID:";
edit_width = 12;
}
: edit_box {
key = "to_tro";
label = "TRO-Kennung:";
edit_width = 24;
}
}
: text {
key = "richtung";
label = "";
}
: text {
key = "hinweis";
label = "";
}
}
ok_cancel;
}
+686 -670
View File
File diff suppressed because it is too large Load Diff
+686 -670
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -130,6 +130,10 @@
[Marker einfuegen]^C^C(ssg-ensure "Tro_Edit") TRO_INSERT [Marker einfuegen]^C^C(ssg-ensure "Tro_Edit") TRO_INSERT
[Marker bearbeiten]^C^C(ssg-ensure "Tro_Edit") TRO_EDIT [Marker bearbeiten]^C^C(ssg-ensure "Tro_Edit") TRO_EDIT
[<-Bearbeiten (Auto)]^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT [<-Bearbeiten (Auto)]^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT
[->Connections]
[Verbindung einfuegen]^C^C(ssg-ensure "Connection_Edit") CONNECTION_INSERT
[Verbindung bearbeiten]^C^C(ssg-ensure "Connection_Edit") CONNECTION_EDIT
[<-Bearbeiten (Auto)]^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT
[->Export] [->Export]
[Export Sivas]^C^C(ssg-ensure "count_sep_scan") ZAEHLE_SEP_SCAN (ssg-ensure "export") EXPORTSIVAS [Export Sivas]^C^C(ssg-ensure "count_sep_scan") ZAEHLE_SEP_SCAN (ssg-ensure "export") EXPORTSIVAS
[<-Export CSV]^C^C(ssg-ensure "export") EXPORTCSV [<-Export CSV]^C^C(ssg-ensure "export") EXPORTCSV
@@ -167,5 +171,6 @@
[Gefaellestrecke bearbeiten](ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT [Gefaellestrecke bearbeiten](ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT
[VarioFoerderer bearbeiten](ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT [VarioFoerderer bearbeiten](ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT
[TRO-Marker bearbeiten](ssg-ensure "Tro_Edit") TRO_EDIT [TRO-Marker bearbeiten](ssg-ensure "Tro_Edit") TRO_EDIT
[Verbindung bearbeiten](ssg-ensure "Connection_Edit") CONNECTION_EDIT
[--] [--]
[SSG Bearbeiten (Auto)](ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT [SSG Bearbeiten (Auto)](ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT