Zum Inhalt springen

Phyton Script Custom für 2D Ausrichten und Verteilen


nfedl

Frage

Geschrieben

Hallo Ihr Meister der Programmierung. (ich kenn mich leider gar nicht aus und versuche es derzeit mit ChatGPT)

Ich hätte gerne eine bessere Version von "2D Ausrichten und Verteilen" und zwar so, dass ich ein  Toolset mit folgenden Buttons bekomme:

  • Ausrichten: Linksbündig, Rechtsbündig, Mitte, Oben ausgerichtet, unten ausgerichtet,
  • gleichzeitig ein zweite Option auswählbar mit: Verteilen Abstand horizontal bzw. auch vertikal

Danke für euren Input!

image.thumb.png.6a533e75a0d0a7027055d1f128a78ae6.png

 

 

import vs

# ---------- IDs ----------
BTN_ALIGN_LEFT     = 101
BTN_ALIGN_RIGHT    = 102
BTN_ALIGN_UP       = 103
BTN_ALIGN_DOWN     = 104
BTN_ALIGN_CENTER_X = 105
BTN_ALIGN_CENTER_Y = 106
BTN_DIST_H         = 201
BTN_DIST_V         = 202
BTN_DIST_H_SPACED  = 301
BTN_DIST_V_SPACED  = 302

# ---------- Hilfsfunktionen ----------
def _normalize_bbox(b):
    try:
        if isinstance(b[0], (list, tuple)):
            x1,y1 = b[0]
            x2,y2 = b[1]
        else:
            x1,y1,x2,y2 = b
    except Exception:
        x1,y1,x2,y2 = 0,0,0,0
    return float(x1), float(y1), float(x2), float(y2)

def get_selected_objects():
    h = vs.FSActLayer()
    objs = []
    while h:
        x1,y1,x2,y2 = _normalize_bbox(vs.GetBBox(h))
        objs.append((h, x1, y1, x2, y2))
        h = vs.NextSObj(h)
    return objs

# ---------- Ausrichten ----------
def align_left():     move_selected('x','min')
def align_right():    move_selected('x','max')
def align_up():       move_selected('y','max')
def align_down():     move_selected('y','min')
def align_center_x(): move_selected('x','mid')
def align_center_y(): move_selected('y','mid')

def move_selected(axis, mode):
    objs = get_selected_objects()
    if not objs: return vs.AlrtDialog("Keine Objekte ausgewählt.")
    if axis=='x':
        if mode=='min': ref = min(o[1] for o in objs)
        elif mode=='max': ref = max(o[3] for o in objs)
        elif mode=='mid': ref = sum((o[1]+o[3])/2 for o in objs)/len(objs)
        for h,x1,y1,x2,y2 in objs:
            dx = ref - ((x1+x2)/2 if mode=='mid' else (x1 if mode=='min' else x2))
            vs.HMove(h, dx, 0)
    else:
        if mode=='min': ref = min(o[2] for o in objs)
        elif mode=='max': ref = max(o[4] for o in objs)
        elif mode=='mid': ref = sum((o[2]+o[4])/2 for o in objs)/len(objs)
        for h,x1,y1,x2,y2 in objs:
            dy = ref - ((y1+y2)/2 if mode=='mid' else (y1 if mode=='min' else y2))
            vs.HMove(h, 0, dy)

# ---------- Verteilen ----------
def distribute_horizontal():
    objs = get_selected_objects()
    if len(objs)<3: return vs.AlrtDialog("Mindestens 3 Objekte auswählen.")
    objs.sort(key=lambda o: (o[1]+o[3])/2)
    left = (objs[0][1]+objs[0][3])/2
    right = (objs[-1][1]+objs[-1][3])/2
    step = (right-left)/(len(objs)-1)
    for i,(h,x1,y1,x2,y2) in enumerate(objs):
        dx = (left + i*step) - (x1+x2)/2
        vs.HMove(h, dx, 0)

def distribute_vertical():
    objs = get_selected_objects()
    if len(objs)<3: return vs.AlrtDialog("Mindestens 3 Objekte auswählen.")
    objs.sort(key=lambda o: (o[2]+o[4])/2)
    bottom = (objs[0][2]+objs[0][4])/2
    top = (objs[-1][2]+objs[-1][4])/2
    step = (top-bottom)/(len(objs)-1)
    for i,(h,x1,y1,x2,y2) in enumerate(objs):
        dy = (bottom + i*step) - (y1+y2)/2
        vs.HMove(h, 0, dy)

# Gleichmäßiger Abstand zwischen Objekten
def distribute_horizontal_spaced():
    objs = get_selected_objects()
    if len(objs)<2: return vs.AlrtDialog("Mindestens 2 Objekte auswählen.")
    objs.sort(key=lambda o: o[1])
    left = min(o[1] for o in objs)
    right = max(o[3] for o in objs)
    total_width = sum(o[3]-o[1] for o in objs)
    spacing = (right-left-total_width)/(len(objs)-1)
    pos = left
    for h,x1,y1,x2,y2 in objs:
        dx = pos - x1
        vs.HMove(h, dx, 0)
        pos += (x2-x1) + spacing

def distribute_vertical_spaced():
    objs = get_selected_objects()
    if len(objs)<2: return vs.AlrtDialog("Mindestens 2 Objekte auswählen.")
    objs.sort(key=lambda o: o[2])
    bottom = min(o[2] for o in objs)
    top = max(o[4] for o in objs)
    total_height = sum(o[4]-o[2] for o in objs)
    spacing = (top-bottom-total_height)/(len(objs)-1)
    pos = bottom
    for h,x1,y1,x2,y2 in objs:
        dy = pos - y1
        vs.HMove(h, 0, dy)
        pos += (y2-y1) + spacing

# ---------- Dialog-Handler ----------
def dialog_handler(item, data):
    mapping = {
        BTN_ALIGN_LEFT: align_left,
        BTN_ALIGN_RIGHT: align_right,
        BTN_ALIGN_UP: align_up,
        BTN_ALIGN_DOWN: align_down,
        BTN_ALIGN_CENTER_X: align_center_x,
        BTN_ALIGN_CENTER_Y: align_center_y,
        BTN_DIST_H: distribute_horizontal,
        BTN_DIST_V: distribute_vertical,
        BTN_DIST_H_SPACED: distribute_horizontal_spaced,
        BTN_DIST_V_SPACED: distribute_vertical_spaced
    }
    if item in mapping: mapping[item]()
    return item

# ---------- Dialog mit Untermenü ----------
def run_dialog():
    dlg = vs.CreateLayout("2D Ausrichten & Verteilen", False, "Schließen", "Abbrechen")

    # Untermenü Ausrichten
    vs.CreateStaticText(dlg, 10, "Ausrichten:", -1)
    vs.CreatePushButton(dlg, BTN_ALIGN_LEFT, "← Links")
    vs.CreatePushButton(dlg, BTN_ALIGN_RIGHT, "→ Rechts")
    vs.CreatePushButton(dlg, BTN_ALIGN_UP, "↑ Oben")
    vs.CreatePushButton(dlg, BTN_ALIGN_DOWN, "↓ Unten")
    vs.CreatePushButton(dlg, BTN_ALIGN_CENTER_X, "↔ Mitte X")
    vs.CreatePushButton(dlg, BTN_ALIGN_CENTER_Y, "↕ Mitte Y")

    # Untermenü Verteilen
    vs.CreateStaticText(dlg, 20, "Verteilen:", -1)
    vs.CreatePushButton(dlg, BTN_DIST_H, "⇄ Horizontal")
    vs.CreatePushButton(dlg, BTN_DIST_V, "⇅ Vertikal")
    vs.CreatePushButton(dlg, BTN_DIST_H_SPACED, "⇄ Gleichmäßig H")
    vs.CreatePushButton(dlg, BTN_DIST_V_SPACED, "⇅ Gleichmäßig V")

    # Layout: vertikal angeordnet
    vs.SetFirstLayoutItem(dlg, 10)
    vs.SetBelowItem(dlg, 10, BTN_ALIGN_LEFT, 0, 0)
    vs.SetBelowItem(dlg, BTN_ALIGN_LEFT, BTN_ALIGN_RIGHT, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_RIGHT, BTN_ALIGN_UP, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_UP, BTN_ALIGN_DOWN, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_DOWN, BTN_ALIGN_CENTER_X, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_CENTER_X, BTN_ALIGN_CENTER_Y, 0, 5)

    vs.SetBelowItem(dlg, BTN_ALIGN_CENTER_Y, 20, 0, 15) # Verteilen Überschrift
    vs.SetBelowItem(dlg, 20, BTN_DIST_H, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_H, BTN_DIST_V, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_V, BTN_DIST_H_SPACED, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_H_SPACED, BTN_DIST_V_SPACED, 0, 5)

    vs.RunLayoutDialog(dlg, dialog_handler)

# ---------- Hauptausführung ----------
if __name__ == "__main__":
    run_dialog()
 

Nikolaus Fedl

VW 2025 Designer, Win 11, i9, 32GB,  www.fedl.eu; www.gartenplanung-fedl.at; www.freiraumarchitektur.at; www.schattenbild.at; www.gartenarchitekten.at

5 Antworten auf diese Frage

Empfohlene Beiträge

Geschrieben (bearbeitet)

Salü nfedl,

vor 10 Minuten schrieb nfedl:

Hallo Ihr Meister der Programmierung. (ich kenn mich leider gar nicht aus und versuche es derzeit mit ChatGPT)

Ich hätte gerne eine bessere Version von "2D Ausrichten und Verteilen" und zwar so, dass ich ein  Toolset mit folgenden Buttons bekomme:

  • Ausrichten: Linksbündig, Rechtsbündig, Mitte, Oben ausgerichtet, unten ausgerichtet,
  • gleichzeitig ein zweite Option auswählbar mit: Verteilen Abstand horizontal bzw. auch vertikal

Danke für euren Input!

 

Wie hakt es? So wie ich getestet habe, funktioniert es doch schon ziemlich gut?

Gruss KroVex

Bearbeitet von KroVex

CADNODE.ch

Unabhängige CAD-Beratung & Support für Planende: persönlich, zuverlässig und pragmatisch – Workflows, die nicht nur gut klingen, sondern auch funktionieren.

Have you tried to turn it off and on again?
Vectorworks | Architektur | macOS/Windows

Geschrieben

Hier sonst eine leicht angepasste Version, damit die Aktionen nicht erst nach dem Verlassen des Dialoges ausgeführt werden, sondern sobald du die Buttons drückst:

Erforderliche Änderungen:

Ergänzt: vs.ReDrawAll() -> erzwingt eine Neuzeichnung der Zeichnung trotz laufendem Dialog.
Und vs.Yield() ->  damit das Redraw auch tatsächlich abgearbeitet wird (je nach Plattform/Version kann ReDrawAll allein nicht reichen).

 

import vs

# ---------- IDs ----------
BTN_ALIGN_LEFT     = 101
BTN_ALIGN_RIGHT    = 102
BTN_ALIGN_UP       = 103
BTN_ALIGN_DOWN     = 104
BTN_ALIGN_CENTER_X = 105
BTN_ALIGN_CENTER_Y = 106
BTN_DIST_H         = 201
BTN_DIST_V         = 202
BTN_DIST_H_SPACED  = 301
BTN_DIST_V_SPACED  = 302

# ---------- Hilfsfunktionen ----------
def _normalize_bbox(b):
    try:
        if isinstance(b[0], (list, tuple)):
            x1, y1 = b[0]
            x2, y2 = b[1]
        else:
            x1, y1, x2, y2 = b
    except Exception:
        x1, y1, x2, y2 = 0, 0, 0, 0
    return float(x1), float(y1), float(x2), float(y2)

def get_selected_objects():
    h = vs.FSActLayer()
    objs = []
    while h:
        x1, y1, x2, y2 = _normalize_bbox(vs.GetBBox(h))
        objs.append((h, x1, y1, x2, y2))
        h = vs.NextSObj(h)
    return objs

# ---------- Ausrichten ----------
def align_left():     move_selected('x', 'min')
def align_right():    move_selected('x', 'max')
def align_up():       move_selected('y', 'max')
def align_down():     move_selected('y', 'min')
def align_center_x(): move_selected('x', 'mid')
def align_center_y(): move_selected('y', 'mid')

def move_selected(axis, mode):
    objs = get_selected_objects()
    if not objs:
        vs.AlrtDialog("Keine Objekte ausgewählt.")
        return

    if axis == 'x':
        if mode == 'min':
            ref = min(o[1] for o in objs)
        elif mode == 'max':
            ref = max(o[3] for o in objs)
        elif mode == 'mid':
            ref = sum((o[1] + o[3]) / 2 for o in objs) / len(objs)
        for h, x1, y1, x2, y2 in objs:
            dx = ref - ((x1 + x2) / 2 if mode == 'mid' else (x1 if mode == 'min' else x2))
            vs.HMove(h, dx, 0)
    else:
        if mode == 'min':
            ref = min(o[2] for o in objs)
        elif mode == 'max':
            ref = max(o[4] for o in objs)
        elif mode == 'mid':
            ref = sum((o[2] + o[4]) / 2 for o in objs) / len(objs)
        for h, x1, y1, x2, y2 in objs:
            dy = ref - ((y1 + y2) / 2 if mode == 'mid' else (y1 if mode == 'min' else y2))
            vs.HMove(h, 0, dy)

# ---------- Verteilen ----------
def distribute_horizontal():
    objs = get_selected_objects()
    if len(objs) < 3:
        vs.AlrtDialog("Mindestens 3 Objekte auswählen.")
        return
    objs.sort(key=lambda o: (o[1] + o[3]) / 2)
    left  = (objs[0][1] + objs[0][3]) / 2
    right = (objs[-1][1] + objs[-1][3]) / 2
    step  = (right - left) / (len(objs) - 1)
    for i, (h, x1, y1, x2, y2) in enumerate(objs):
        dx = (left + i * step) - (x1 + x2) / 2
        vs.HMove(h, dx, 0)

def distribute_vertical():
    objs = get_selected_objects()
    if len(objs) < 3:
        vs.AlrtDialog("Mindestens 3 Objekte auswählen.")
        return
    objs.sort(key=lambda o: (o[2] + o[4]) / 2)
    bottom = (objs[0][2] + objs[0][4]) / 2
    top    = (objs[-1][2] + objs[-1][4]) / 2
    step   = (top - bottom) / (len(objs) - 1)
    for i, (h, x1, y1, x2, y2) in enumerate(objs):
        dy = (bottom + i * step) - (y1 + y2) / 2
        vs.HMove(h, 0, dy)

# Gleichmäßiger Abstand zwischen Objekten
def distribute_horizontal_spaced():
    objs = get_selected_objects()
    if len(objs) < 2:
        vs.AlrtDialog("Mindestens 2 Objekte auswählen.")
        return
    objs.sort(key=lambda o: o[1])
    left  = min(o[1] for o in objs)
    right = max(o[3] for o in objs)
    total_width = sum(o[3] - o[1] for o in objs)
    spacing = (right - left - total_width) / (len(objs) - 1)
    pos = left
    for h, x1, y1, x2, y2 in objs:
        dx = pos - x1
        vs.HMove(h, dx, 0)
        pos += (x2 - x1) + spacing

def distribute_vertical_spaced():
    objs = get_selected_objects()
    if len(objs) < 2:
        vs.AlrtDialog("Mindestens 2 Objekte auswählen.")
        return
    objs.sort(key=lambda o: o[2])
    bottom = min(o[2] for o in objs)
    top    = max(o[4] for o in objs)
    total_height = sum(o[4] - o[2] for o in objs)
    spacing = (top - bottom - total_height) / (len(objs) - 1)
    pos = bottom
    for h, x1, y1, x2, y2 in objs:
        dy = pos - y1
        vs.HMove(h, 0, dy)
        pos += (y2 - y1) + spacing

# ---------- Dialog-Handler ----------
def dialog_handler(item, data):
    mapping = {
        BTN_ALIGN_LEFT: align_left,
        BTN_ALIGN_RIGHT: align_right,
        BTN_ALIGN_UP: align_up,
        BTN_ALIGN_DOWN: align_down,
        BTN_ALIGN_CENTER_X: align_center_x,
        BTN_ALIGN_CENTER_Y: align_center_y,
        BTN_DIST_H: distribute_horizontal,
        BTN_DIST_V: distribute_vertical,
        BTN_DIST_H_SPACED: distribute_horizontal_spaced,
        BTN_DIST_V_SPACED: distribute_vertical_spaced
    }
    if item in mapping:
        mapping[item]()   # Aktion sofort ausführen
        # Bildschirm sofort aktualisieren (falls verfügbar)
        try:
            vs.ReDrawAll()
            vs.Yield()
        except:
            pass
        return 0          # Dialog offen lassen
    return item            # OK/Abbrechen verhalten sich normal

# ---------- Dialog mit Untermenü ----------
def run_dialog():
    dlg = vs.CreateLayout("2D Ausrichten & Verteilen", False, "Schließen", "Abbrechen")
    # Untermenü Ausrichten
    vs.CreateStaticText(dlg, 10, "Ausrichten:", -1)
    vs.CreatePushButton(dlg, BTN_ALIGN_LEFT, "← Links")
    vs.CreatePushButton(dlg, BTN_ALIGN_RIGHT, "→ Rechts")
    vs.CreatePushButton(dlg, BTN_ALIGN_UP, "↑ Oben")
    vs.CreatePushButton(dlg, BTN_ALIGN_DOWN, "↓ Unten")
    vs.CreatePushButton(dlg, BTN_ALIGN_CENTER_X, "↔ Mitte X")
    vs.CreatePushButton(dlg, BTN_ALIGN_CENTER_Y, "↕ Mitte Y")
    # Untermenü Verteilen
    vs.CreateStaticText(dlg, 20, "Verteilen:", -1)
    vs.CreatePushButton(dlg, BTN_DIST_H, "⇄ Horizontal")
    vs.CreatePushButton(dlg, BTN_DIST_V, "⇅ Vertikal")
    vs.CreatePushButton(dlg, BTN_DIST_H_SPACED, "⇄ Gleichmäßig H")
    vs.CreatePushButton(dlg, BTN_DIST_V_SPACED, "⇅ Gleichmäßig V")
    # Layout: vertikal angeordnet
    vs.SetFirstLayoutItem(dlg, 10)
    vs.SetBelowItem(dlg, 10, BTN_ALIGN_LEFT, 0, 0)
    vs.SetBelowItem(dlg, BTN_ALIGN_LEFT, BTN_ALIGN_RIGHT, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_RIGHT, BTN_ALIGN_UP, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_UP, BTN_ALIGN_DOWN, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_DOWN, BTN_ALIGN_CENTER_X, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_CENTER_X, BTN_ALIGN_CENTER_Y, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_CENTER_Y, 20, 0, 15)  # Verteilen Überschrift
    vs.SetBelowItem(dlg, 20, BTN_DIST_H, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_H, BTN_DIST_V, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_V, BTN_DIST_H_SPACED, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_H_SPACED, BTN_DIST_V_SPACED, 0, 5)
    vs.RunLayoutDialog(dlg, dialog_handler)

# ---------- Hauptausführung ----------
if __name__ == "__main__":
    run_dialog()

 

Gruss KroVex

  • Like 1

CADNODE.ch

Unabhängige CAD-Beratung & Support für Planende: persönlich, zuverlässig und pragmatisch – Workflows, die nicht nur gut klingen, sondern auch funktionieren.

Have you tried to turn it off and on again?
Vectorworks | Architektur | macOS/Windows

Geschrieben


vs.Yield() Interessant. Aber ich denke, das hat der Kollege GPT erfunden, weil es plausibel wäre, ein Script zu unterbrechen um dem UI die Kontrolle zu geben im Dialog loop, während das Script läuft. 

Und da vs.Yield() nicht definiert ist (gibt es nicht), kommt eine Fehlermeldung die man mit try: except: dann überspringt. Da könnte man auch vs.Superdupergeheimfuntion() aufrufen im try-except, es würde genau gleich funktionieren.

Aber ja, es wäre spannend ein vs.Yield() zu haben.

Ich habe bis jetzt immer diese Funktionen betrachtet.
 

vs.RegisterDialogForTimerEvents(dialogID, timerDelayInMilliseconds)
vs.DeregisterDialogFromTimerEvents(dialogID)


 Wenn ich wollte, dass im Plan sich die Ansicht ändern kann oder ein vs.ReDrawAll() aufrufe. Aber hier scheint es auch ohne das zu klappen. Vielleicht ist das TimerEvents vielleicht auch nur eine placebo im vs Python.

  • Like 2

Macbook m3 Max 48 GB | Ryzen 3950x 64GB NVIDIA rtx3090

Geschrieben
vor 14 Stunden schrieb KroVex:

Hier sonst eine leicht angepasste Version, damit die Aktionen nicht erst nach dem Verlassen des Dialoges ausgeführt werden, sondern sobald du die Buttons drückst:

Erforderliche Änderungen:

Ergänzt: vs.ReDrawAll() -> erzwingt eine Neuzeichnung der Zeichnung trotz laufendem Dialog.
Und vs.Yield() ->  damit das Redraw auch tatsächlich abgearbeitet wird (je nach Plattform/Version kann ReDrawAll allein nicht reichen).

 

import vs

# ---------- IDs ----------
BTN_ALIGN_LEFT     = 101
BTN_ALIGN_RIGHT    = 102
BTN_ALIGN_UP       = 103
BTN_ALIGN_DOWN     = 104
BTN_ALIGN_CENTER_X = 105
BTN_ALIGN_CENTER_Y = 106
BTN_DIST_H         = 201
BTN_DIST_V         = 202
BTN_DIST_H_SPACED  = 301
BTN_DIST_V_SPACED  = 302

# ---------- Hilfsfunktionen ----------
def _normalize_bbox(b):
    try:
        if isinstance(b[0], (list, tuple)):
            x1, y1 = b[0]
            x2, y2 = b[1]
        else:
            x1, y1, x2, y2 = b
    except Exception:
        x1, y1, x2, y2 = 0, 0, 0, 0
    return float(x1), float(y1), float(x2), float(y2)

def get_selected_objects():
    h = vs.FSActLayer()
    objs = []
    while h:
        x1, y1, x2, y2 = _normalize_bbox(vs.GetBBox(h))
        objs.append((h, x1, y1, x2, y2))
        h = vs.NextSObj(h)
    return objs

# ---------- Ausrichten ----------
def align_left():     move_selected('x', 'min')
def align_right():    move_selected('x', 'max')
def align_up():       move_selected('y', 'max')
def align_down():     move_selected('y', 'min')
def align_center_x(): move_selected('x', 'mid')
def align_center_y(): move_selected('y', 'mid')

def move_selected(axis, mode):
    objs = get_selected_objects()
    if not objs:
        vs.AlrtDialog("Keine Objekte ausgewählt.")
        return

    if axis == 'x':
        if mode == 'min':
            ref = min(o[1] for o in objs)
        elif mode == 'max':
            ref = max(o[3] for o in objs)
        elif mode == 'mid':
            ref = sum((o[1] + o[3]) / 2 for o in objs) / len(objs)
        for h, x1, y1, x2, y2 in objs:
            dx = ref - ((x1 + x2) / 2 if mode == 'mid' else (x1 if mode == 'min' else x2))
            vs.HMove(h, dx, 0)
    else:
        if mode == 'min':
            ref = min(o[2] for o in objs)
        elif mode == 'max':
            ref = max(o[4] for o in objs)
        elif mode == 'mid':
            ref = sum((o[2] + o[4]) / 2 for o in objs) / len(objs)
        for h, x1, y1, x2, y2 in objs:
            dy = ref - ((y1 + y2) / 2 if mode == 'mid' else (y1 if mode == 'min' else y2))
            vs.HMove(h, 0, dy)

# ---------- Verteilen ----------
def distribute_horizontal():
    objs = get_selected_objects()
    if len(objs) < 3:
        vs.AlrtDialog("Mindestens 3 Objekte auswählen.")
        return
    objs.sort(key=lambda o: (o[1] + o[3]) / 2)
    left  = (objs[0][1] + objs[0][3]) / 2
    right = (objs[-1][1] + objs[-1][3]) / 2
    step  = (right - left) / (len(objs) - 1)
    for i, (h, x1, y1, x2, y2) in enumerate(objs):
        dx = (left + i * step) - (x1 + x2) / 2
        vs.HMove(h, dx, 0)

def distribute_vertical():
    objs = get_selected_objects()
    if len(objs) < 3:
        vs.AlrtDialog("Mindestens 3 Objekte auswählen.")
        return
    objs.sort(key=lambda o: (o[2] + o[4]) / 2)
    bottom = (objs[0][2] + objs[0][4]) / 2
    top    = (objs[-1][2] + objs[-1][4]) / 2
    step   = (top - bottom) / (len(objs) - 1)
    for i, (h, x1, y1, x2, y2) in enumerate(objs):
        dy = (bottom + i * step) - (y1 + y2) / 2
        vs.HMove(h, 0, dy)

# Gleichmäßiger Abstand zwischen Objekten
def distribute_horizontal_spaced():
    objs = get_selected_objects()
    if len(objs) < 2:
        vs.AlrtDialog("Mindestens 2 Objekte auswählen.")
        return
    objs.sort(key=lambda o: o[1])
    left  = min(o[1] for o in objs)
    right = max(o[3] for o in objs)
    total_width = sum(o[3] - o[1] for o in objs)
    spacing = (right - left - total_width) / (len(objs) - 1)
    pos = left
    for h, x1, y1, x2, y2 in objs:
        dx = pos - x1
        vs.HMove(h, dx, 0)
        pos += (x2 - x1) + spacing

def distribute_vertical_spaced():
    objs = get_selected_objects()
    if len(objs) < 2:
        vs.AlrtDialog("Mindestens 2 Objekte auswählen.")
        return
    objs.sort(key=lambda o: o[2])
    bottom = min(o[2] for o in objs)
    top    = max(o[4] for o in objs)
    total_height = sum(o[4] - o[2] for o in objs)
    spacing = (top - bottom - total_height) / (len(objs) - 1)
    pos = bottom
    for h, x1, y1, x2, y2 in objs:
        dy = pos - y1
        vs.HMove(h, 0, dy)
        pos += (y2 - y1) + spacing

# ---------- Dialog-Handler ----------
def dialog_handler(item, data):
    mapping = {
        BTN_ALIGN_LEFT: align_left,
        BTN_ALIGN_RIGHT: align_right,
        BTN_ALIGN_UP: align_up,
        BTN_ALIGN_DOWN: align_down,
        BTN_ALIGN_CENTER_X: align_center_x,
        BTN_ALIGN_CENTER_Y: align_center_y,
        BTN_DIST_H: distribute_horizontal,
        BTN_DIST_V: distribute_vertical,
        BTN_DIST_H_SPACED: distribute_horizontal_spaced,
        BTN_DIST_V_SPACED: distribute_vertical_spaced
    }
    if item in mapping:
        mapping[item]()   # Aktion sofort ausführen
        # Bildschirm sofort aktualisieren (falls verfügbar)
        try:
            vs.ReDrawAll()
            vs.Yield()
        except:
            pass
        return 0          # Dialog offen lassen
    return item            # OK/Abbrechen verhalten sich normal

# ---------- Dialog mit Untermenü ----------
def run_dialog():
    dlg = vs.CreateLayout("2D Ausrichten & Verteilen", False, "Schließen", "Abbrechen")
    # Untermenü Ausrichten
    vs.CreateStaticText(dlg, 10, "Ausrichten:", -1)
    vs.CreatePushButton(dlg, BTN_ALIGN_LEFT, "← Links")
    vs.CreatePushButton(dlg, BTN_ALIGN_RIGHT, "→ Rechts")
    vs.CreatePushButton(dlg, BTN_ALIGN_UP, "↑ Oben")
    vs.CreatePushButton(dlg, BTN_ALIGN_DOWN, "↓ Unten")
    vs.CreatePushButton(dlg, BTN_ALIGN_CENTER_X, "↔ Mitte X")
    vs.CreatePushButton(dlg, BTN_ALIGN_CENTER_Y, "↕ Mitte Y")
    # Untermenü Verteilen
    vs.CreateStaticText(dlg, 20, "Verteilen:", -1)
    vs.CreatePushButton(dlg, BTN_DIST_H, "⇄ Horizontal")
    vs.CreatePushButton(dlg, BTN_DIST_V, "⇅ Vertikal")
    vs.CreatePushButton(dlg, BTN_DIST_H_SPACED, "⇄ Gleichmäßig H")
    vs.CreatePushButton(dlg, BTN_DIST_V_SPACED, "⇅ Gleichmäßig V")
    # Layout: vertikal angeordnet
    vs.SetFirstLayoutItem(dlg, 10)
    vs.SetBelowItem(dlg, 10, BTN_ALIGN_LEFT, 0, 0)
    vs.SetBelowItem(dlg, BTN_ALIGN_LEFT, BTN_ALIGN_RIGHT, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_RIGHT, BTN_ALIGN_UP, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_UP, BTN_ALIGN_DOWN, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_DOWN, BTN_ALIGN_CENTER_X, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_CENTER_X, BTN_ALIGN_CENTER_Y, 0, 5)
    vs.SetBelowItem(dlg, BTN_ALIGN_CENTER_Y, 20, 0, 15)  # Verteilen Überschrift
    vs.SetBelowItem(dlg, 20, BTN_DIST_H, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_H, BTN_DIST_V, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_V, BTN_DIST_H_SPACED, 0, 5)
    vs.SetBelowItem(dlg, BTN_DIST_H_SPACED, BTN_DIST_V_SPACED, 0, 5)
    vs.RunLayoutDialog(dlg, dialog_handler)

# ---------- Hauptausführung ----------
if __name__ == "__main__":
    run_dialog()

 

Gruss KroVex

Jawohl KroVex. Bin immer wieder begeistert wie man so einen Code verstehen kann ,-). 

Jetzt wäre es super, wenn man diese Buttons direkt in der Toolpalette haben könnte!? und direkt ausrichten/verteilen kann (ohne Umwege)

image.png.904f108f0c8430af1cd6b15ee50c5929.png

Nikolaus Fedl

VW 2025 Designer, Win 11, i9, 32GB,  www.fedl.eu; www.gartenplanung-fedl.at; www.freiraumarchitektur.at; www.schattenbild.at; www.gartenarchitekten.at

Geschrieben
vor 19 Stunden schrieb Dominique Corpataux:

vs.Yield() Interessant. Aber ich denke, das hat der Kollege GPT erfunden, weil es plausibel wäre, ein Script zu unterbrechen um dem UI die Kontrolle zu geben im Dialog loop, während das Script läuft. 

Ertappt 😄 
Es gäbe zwar ein Yield, aber für die Progressbar: 

 vs.ProgressDlgYield(count)

PS: Ich persönlich empfinde es legitim, den "Kollegen GPT" zu verwenden, sofern man den Anspruch hat daraus zu lernen und das Verstandene auch selber zu schreiben  - und nicht nur sich einfach blind Sachen generieren lässt, die man nicht versteht/verstehen will. Vor der GPT-Ära habe ich nebst dem Lernen via try und error, mir auch Bücher zu dem Thema gekauft und anhand dessen versucht zu lernen - aber die sind halt immer sehr theoretisch und linear aufgebaut, was mir oft schnell die Lust geraubt hat.

Mit GPT zu lernen finde ich hingegen viel intuitiver und zielorientierter - ich kann anhand Projekten lernen, auf die ich jetzt gerade Bock habe und nicht auf irgendwelche Beispielprojekte aus irgend einem Tutorial. Ich kann alles selber schreiben was ich kann und verstehe, aber kann mir Ergänzungen / Verbesserungen vorschlagen und diese ausführlich erklären lassen. Und für die Erklärungen kann ich selber das Niveau bestimmen - manchmal verstehe ich etwas abstraktes sofort und manchmal soll er es mir anhand Äpfel und Birnen erklären😅

 

Und in Bezug zur Vectorworks-Programmierung: Hier ist es, wie @Dominique Corpataux gerade an meiner gestrigen Faulheit schön hervorgehoben hat, oftmals ein Bullshit-Generator. Basic-Python ist selten ein Problem, aber die Logik und Eigenheiten, sowie die vs-Funktionen von Vectorworks kennt es oftmals nicht oder erfindet sehr viel. Mindestens die VS-Funktionen sollte man immer hier  gegen prüfen und ggf. auch die Int-Forumssuche verwenden.

Back to Topic:

vor 6 Stunden schrieb nfedl:

Jetzt wäre es super, wenn man diese Buttons direkt in der Toolpalette haben könnte!?

Für dass kannst du 50% deines Codes wieder löschen. 😅 Die ganzen Dialog-Funktionen braucht es dafür nicht, sondern nur deine Hilfsfunktion, sowie deine einzelnen Befehle - aber aufgeteilt: Jeder Button-Befehl = 1 einzelnes Script.
Z.B. für Align_Left:

# ---------- Hilfsfunktionen ----------
def _normalize_bbox(b):
    try:
        if isinstance(b[0], (list, tuple)):
            x1, y1 = b[0]
            x2, y2 = b[1]
        else:
            x1, y1, x2, y2 = b
    except Exception:
        x1, y1, x2, y2 = 0, 0, 0, 0
    return float(x1), float(y1), float(x2), float(y2)

def get_selected_objects():
    h = vs.FSActLayer()
    objs = []
    while h:
        x1, y1, x2, y2 = _normalize_bbox(vs.GetBBox(h))
        objs.append((h, x1, y1, x2, y2))
        h = vs.NextSObj(h)
    return objs
    
# ---------- Einer deiner Button-Befehle ------    
def align_left():
    objs = get_selected_objects()
    ref = min(o[1] for o in objs)
    if not objs:
        vs.AlrtDialog("Keine Objekte ausgewählt.")
        return

    for h, x1, y1, x2, y2 in objs:
        dx = ref - x1
        vs.HMove(h, dx, 0)
        
align_left()

 

Dann kannst du unter Menü Extras -> Plug-Ins -> Plug-In Manager, für jeden Button-Befehl ein (Script-)Werkzeug anlegen:
image.thumb.png.9f6a1e58381ec31cb302c9ef54786bb9.png

 

Und über "Code..." deine Code-Snippets hinzufügen:
image.thumb.png.dab9772cd600ca34eb64f3979e85dd5c.png

 

Diese Werkzeuge findest du dann unter "Arbeitsumgebung anpassen" und kannst dir eine eigene Tool-Palette anlegen:
image.thumb.png.f80c5e127e910bf72525afe7e63749b0.png

 

(Optional -> gleich noch Tastenkürzel bestimmen.)

 

Gruss KroVex

  • Like 2

CADNODE.ch

Unabhängige CAD-Beratung & Support für Planende: persönlich, zuverlässig und pragmatisch – Workflows, die nicht nur gut klingen, sondern auch funktionieren.

Have you tried to turn it off and on again?
Vectorworks | Architektur | macOS/Windows

Erstelle ein Benutzerkonto oder melde Dich an, um zu kommentieren

Du musst ein Benutzerkonto haben, um einen Kommentar verfassen zu können

Benutzerkonto erstellen

Neues Benutzerkonto für unsere Community erstellen. Es ist einfach!

Neues Benutzerkonto erstellen

Anmelden

Du hast bereits ein Benutzerkonto? Melde Dich hier an.

Jetzt anmelden
  • Forenstatistik

    • Themen insgesamt
      26,3Tsd
    • Beiträge insgesamt
      136,7Tsd
×
×
  • Neu erstellen...