Wiener ULF Straßenbahn – Vom Modell zum animierten Rig in Blender
Die Wiener ULF (Ultra Low Floor) Straßenbahn gehört zum Stadtbild Wiens wie der Stephansdom. Als 3D-Künstler war es für mich nur eine Frage der Zeit, bis ich mich an dieses Fahrzeug wage. Was als Modellierungsprojekt begann, wurde schnell zu einer technischen Herausforderung – denn eine Straßenbahn zu modellieren ist das eine, sie realistisch auf Schienen fahren zu lassen das andere.
Das Modell
Der ULF ist kein gewöhnliches Fahrzeug. Er besteht aus mehreren starren Segmenten, die über Portale (Gelenke) miteinander verbunden sind. Die Räder sitzen nicht unter den Wagenkästen, sondern in den Portalen selbst – ein einzigartiges Konstruktionsmerkmal, das den ultra-niedrigen Einstieg ermöglicht.
Für das 3D-Modell habe ich mich an der Linie 25 Richtung Floridsdorf orientiert. Die Segmente sind als Instanzen aufgebaut, was Arbeitsspeicher spart und Änderungen an einem Segment automatisch auf alle gleichen Teile überträgt. Die Fahrerkabinen vorne und hinten sind eigenständige Objekte mit individuellen Details: Anzeigetafeln, Scheinwerfer, Wagennummer 727 und das Wiener-Linien-Branding.
Die Herausforderung: Animation entlang eines Pfades
Ein stehendes Modell sieht nett aus, aber eine Straßenbahn muss fahren. Und nicht nur geradeaus – sie muss sich in Kurven realistisch verhalten. Die starren Wagenkästen dürfen sich nicht verbiegen, sondern nur an den Gelenkpunkten knicken. Genau wie beim echten ULF.
Ich habe mehrere Ansätze durchprobiert, bevor ich eine stabile Lösung gefunden habe.
Versuch 1: Spline IK
Der naheliegendste Ansatz in Blender. Eine Bone-Kette mit Spline IK Constraint entlang einer Curve – klingt einfach, funktioniert für organische Dinge wie Tentakel oder Schlangen. Aber für starre Straßenbahnsegmente ist Spline IK das falsche Werkzeug. Es verbiegt die gesamte Kette gleichmäßig, anstatt nur an den Gelenken zu knicken. Und sobald man Follow Path dazunimmt, um die Bahn entlang der Curve zu bewegen, überschreiben sich die beiden Systeme gegenseitig.
Versuch 2: Spline IK + Follow Path + Driver
Gemini schlug vor, die Curve-Länge per Driver dynamisch auszulesen und damit die Abstände zu berechnen. Klingt elegant – scheitert aber an einer fundamentalen Einschränkung von Blender: calc_length() ist eine Python-Funktion und kann nicht in Drivern aufgerufen werden. Der Driver bleibt rot, egal was man tut.
Versuch 3: Geometry Nodes
Ein moderner Ansatz: Resample Curve, Curve to Points, Instance on Points. Funktioniert grundsätzlich, aber der ULF hat unterschiedlich lange Segmente. Ein gleichmäßiges Resampling passt nicht, und die nötige Komplexität für variable Abstände steht in keinem Verhältnis zum Nutzen.
Die Lösung: Empty-Kette mit dynamischen Abständen
Nach drei gescheiterten Versuchen und Hilfe aus dem deutschen Blender-Forum, von ChatGPT, Gemini und schließlich Claude AI habe ich einen sauberen Ansatz gefunden, der stabil funktioniert.
Das Prinzip
Bewegung und Biegung werden komplett getrennt:
Empties als Führungspunkte sitzen an jedem Drehpunkt der Straßenbahn – also dort, wo die Räder in den Portalen sitzen. Jedes Empty hat einen Follow Path Constraint mit aktivierter Fixed Position auf der Strecken-Curve.
Nur ein Empty wird animiert. Das vorderste Empty (ULF_Pivot.Front) bekommt Keyframes auf den Offset Factor. Das ist das „Gaspedal“ für die ganze Bahn.
Alle anderen Empties folgen per Driver. Jedes weitere Empty liest den Offset Factor des ersten und addiert seinen eigenen Abstand dazu. Die Formel:
ziel + (ABSTAND_IN_METERN / laenge)
Dabei ist ziel der Offset Factor des ersten Empties und laenge die Gesamtlänge der Curve.
Bones spannen sich von Empty zu Empty. Jeder Bone bekommt Copy Location auf das vordere Empty und Damped Track auf das hintere. Die Mesh-Segmente hängen per Bone Parenting an den Bones und bleiben komplett starr.
Das Python-Script
Die Curve-Länge wird über ein kleines Python-Script automatisch als Custom Property auf die Curve geschrieben. So passen sich die Abstände an, wenn man die Curve verlängert, verkürzt oder umformt.
import bpy
def update_curve_length(scene, depsgraph):
pivot = bpy.data.objects.get("ULF_Pivot.Front")
if not pivot:
return
for con in pivot.constraints:
if con.type == 'FOLLOW_PATH' and con.target:
length = con.target.data.splines[0].calc_length()
old_curve = bpy.data.objects.get("ULF_Track")
if old_curve:
old_curve["curve_length"] = length
con.target["curve_length"] = length
break
bpy.app.handlers.depsgraph_update_post.append(update_curve_length)
update_curve_length(None, None)
Das Script erkennt die Curve automatisch über den Follow Path Constraint – egal wie sie heißt. Man kann die Bahn in jede Szene einfügen und auf eine beliebige Curve umstellen.
Die Struktur im Detail
Der ULF hat 4 echte Knickpunkte. Vorne und hinten sind jeweils drei Segmente (Fahrerkabine plus zwei angrenzende Teile) starr miteinander verbunden. Das ergibt:
- 6 Empties an den Drehpunkten (vordere Räder, 4 Knickpunkte, hintere Räder)
- 5 Bones von Drehpunkt zu Drehpunkt
- Verbindungsstücke an den Knickpunkten, über Hilfs-Empties mit Copy Location und Copy Rotation positioniert
Nutzung in eigenen Szenen
Die Straßenbahn lässt sich per Append in jede Blender-Szene einfügen:
- File → Append → die Tram-Datei → Collection → ULF_Tram
- Eine Bezier Curve als Strecke erstellen oder eine vorhandene nutzen
- Bei allen ULF_Pivot Empties den Follow Path Target auf die neue Curve ändern
- Das Script ausführen
- Offset Factor von ULF_Pivot.Front animieren – die ganze Bahn folgt
Hinweis zu Blender-Versionen
In Blender 5.0.1 hat der Driver die Custom Property nicht korrekt gelesen (Value blieb auf 0). In Blender 5.1 funktioniert es problemlos. Ich empfehle daher Blender 5.1 oder neuer.
Verfügbarkeit
Das Modell ist auf zwei Marktplätzen erhältlich:
- Superhive (ehemals Blender Market): https://superhivemarket.com/products/vienna-ulf-tram–rigged–animated
- Fab (von Epic Games): https://www.fab.com/de/listings/4ba055b6-c444-4fa7-8fef-1311569533d0
🎉 Launch-Angebot: Bis Ende April gibt es die Straßenbahn auf Superhive zum halben Preis.
Beide Versionen kommen komplett mit Rig, Drivern und dem Python-Script. Keine externen Addons nötig.