11. Wiederholungen mit for()
11.1 Rubik Würfel
Ein Rubik-Würfel besteht in der Basisform aus 3 * 3 * 3 Würfeln. Wenn wir so etwas mit OpenSCAD schreiben wollen, gibt es als erste Variante das Ausschreiben mit 27 cube()-Befehlen und den entsprechenden Verschiebungen. Mithilfe von Konstanten geht es jedoch ohne große Rechnerei.
Beschreiben wir zunächst nur die ersten drei Würfel: Der erste Würfel befindet sich bei 0,0,0. Der zweite Würfel ist dann um lang + delta in der x-Achse verschoben, und der dritte Würfel wird um lang + delta + lang + delta verschoben.
lang=20;
delta=2;
translate([0,0,0])
cube([lang,lang,lang]);
translate([lang+delta,0,0])
cube([lang,lang,lang]);
translate([lang+delta+lang+delta,0,0])
cube([lang,lang,lang]);
Wir merken schon: Da müsste es doch eine einfachere Variante geben, um solche Wiederholungen zu machen?

11.1.1 Wie funktioniert eine Wiederholung mit "for() {...}"
Ja, die gibt es! Mit der for()-Schleife kann ein Code-Abschnitt eine gewünschte Anzahl von Durchläufen ausführen.
lang=20;
delta=2;
for (i = [0:1:2]) {
translate([i*(lang+delta),0,0])
cube([lang,lang,lang]);
}
Was steht da genau? In der for()-Klammer wird eine Variable i definiert. Warum plötzlich eine Variable? Die Variable in der for()-Klammer ändert bei jedem Durchgang ihren Wert! Die drei Zahlen in der Klammer definieren, wie sich die Variable i verhalten soll.
- Der erste Wert sagt, mit welchem Wert i starten soll – hier mit dem Wert 0.
- Der zweite Wert sagt, um wie viel sich i pro Runde erhöhen soll – hier pro Runde um den Wert 1.
Der dritte Wert sagt, bis zu welchem Wert hochgerechnet und der Code abgearbeitet werden soll – hier bis zum Wert 2.
Der erste Gedanke ist: „Es sind aber 3 Würfel, nicht 2!“ Ja, schon – aber es beginnt bei Runde 0, dann folgt 1, dann 2. Somit sind es insgesamt 3 Runden.
In der ersten Runde ist die x-Verschiebung 0 * (lang + delta), somit wird der erste Würfel um 0 verschoben. Beim zweiten Würfel ist es dann 1 * (lang + delta) und beim dritten 2 * (lang + delta).

11.1.2 Wie weiter?
Wie wird nun die zweite Reihe erzeugt? Eine Möglichkeit wäre, den Code zu kopieren und zusätzlich eine Verschiebung in y-Richtung einzubauen.
for (i = [0:1:2]) {
translate([i*(lang+delta),lang+delta,0])
cube([lang,lang,lang]);
}
Und dann noch eine – und auch in z-Richtung.

Warum nicht eine Schleife in einer Schleife verwenden?
Das machen wir! Allerdings benennen wir die Variablen anders: Statt i heißt die erste Variable nun x, und die zweite heißt y.
for (x = [0:1:2]) {
for (y = [0:1:2]) {
translate([x*(lang+delta),y*(lang+delta),0])
cube([lang,lang,lang]);
}
}
Obwohl unser Code nur ein translate() und ein cube() enthält, werden bereits 9 Würfel erzeugt.

Wir ahnen es schon: Auch in z-Richtung funktioniert es mit einer for()-Schleife.
for (x = [0:1:2]) {
for (y = [0:1:2]) {
for (z = [0:1:2]) {
translate([x*(lang+delta),y*(lang+delta),z*(lang+delta)])
cube([lang,lang,lang]);
}
}
}
Mit so wenig Code entstehen bereits viele Würfel. Jetzt können wir die Konstanten lang und delta variieren und damit spielen.

Wenn auch die Anzahl der Würfel als Konstante hinterlegt wird, kann im Nu ein riesengroßer Rubik-Würfel erzeugt werden.
Ich habe dazu eine Konstante n = 4 definiert und alle [0, 1, 2] durch [0, 1,n] ersetzt.

Bei einem Rubik-Würfel müssten alle Seiten unterschiedliche Farben haben. Das geht leider mit einem einfachen cube() nicht. Jeder Würfel müsste stattdessen aus einzelnen Flächen bestehen, die jeweils mit der entsprechenden Farbe eingefärbt sind.
11.2 Rad mit Speichen
Eine for()-Schleife kann bei allen Befehlen, die Werte annehmen, angewendet werden. Versuchen wir uns doch einmal an einem Rad mit Speichen. Das Ganze soll zunächst nicht allzu kompliziert sein. Das gedruckte Rad, in zwei Hälften zusammengeleimt, sieht aus wie das Rad eines Leiterwagens.
Warum in zwei Teilen? Die Speichen des Rades sollen rund sein. Wird das Rad geteilt, hat jede Hälfte einen Halbbogen der Speiche, und das Drucken funktioniert so problemlos.

Das Code-Beispiel wird Schritt für Schritt aufgebaut und zwischendurch immer wieder angepasst. Dabei kommt es immer wieder zu Überraschungen, wie OpenSCAD reagiert und welche Fehlermeldungen angezeigt werden.
11.2.1 Die Felge
Als Erstes definieren wir nur die Felge und erstellen sie. Da es hier wieder um Kreise geht, darf $fn = 60 nicht vergessen werden. Mit einer Auflösung von 60 erhalten wir bereits ordentliche Kreise. Die Konstanten für Kreise beginnen hier immer mit r_. Die Dicke der halben Felge wird mit der Konstanten felge definiert, die Höhe des halben Rades mit z_hoch.
$fn=60;
r_felge=80; // Radius des Rades
felge=3; // Dicke der Felge
z_hoch=5; // Höhe des Rades
translate([0,0,0])
linear_extrude(z_hoch)
circle(r_felge);
Der erste Schritt ist gemacht.

Aus dieser Scheibe schneiden wir nun die Felge heraus. Das Herausschneiden erfolgt immer mit difference(). Vor der bestehenden Scheibe fügen wir difference() { ein, und darunter kommt die zweite Scheibe, die um felge = 3 kleiner ist. Wichtig: Am Ende von difference() { muss auch ein } stehen, sonst gibt OpenSCAD eine Fehlermeldung aus.
// ganze Felge
difference() {
translate([0,0,0])
linear_extrude(z_hoch)
circle(r_felge);
translate([0,0,-0.1])
linear_extrude(z_hoch+0.2)
circle(r_felge-felge);
}
Im Bild sehen wir die beiden { } von difference(). Wird eine Klammer selektiert, erscheinen beide Klammern grün eingefärbt. So erkennen wir, welche Klammer zu welcher gehört. Das Einrücken der Code-Zeilen hilft dabei, eine klare Struktur zu erhalten – je länger der Code, desto hilfreicher ist das.

11.2.2 Die Nabe
Im Zentrum des Rades benötigen wir eine Nabe mit einem Loch. Dazu definieren wir ganz am Ende des Codes, nach der schließenden Klammer } von difference(), weitere Konstanten.
r_nabe=5; // Radius Nabe
r_loch=2; // Radius Loch in der Nabe
translate([0,0,0])
linear_extrude(z_hoch)
circle(r_nabe);
Auf dem Bild sehen wir gerade noch die letzten Linien der Felge. Gleich anschließend folgen die beiden neuen Konstanten. Die Konstante r_loch wird zu diesem Zeitpunkt noch nicht benötigt.

Damit wir in die Nabe ein Loch „bohren“ können, brauchen wir wieder ein difference() { ... } um den bestehenden Code der Nabe.
r_nabe=5; // Radius Nabe
r_loch=2; // Radius Loch in der Nabe
difference() {
translate([0,0,0])
linear_extrude(z_hoch)
circle(r_nabe);
translate([0,0,-0.1])
linear_extrude(z_hoch+0.2)
circle(r_loch);
}
Natürlich hätten wir von Anfang an die Nabe und das Loch mit difference() erstellen können. Aber auf diese Weise sehen wir besser, wie sich das Ganze Schritt für Schritt entwickelt.
Damit das Loch sichtbar wird, muss man ordentlich hineinzoomen.

11.2.3 Die Speichen
Jetzt kommen die Speichen, die zylindrisch sein sollen. Am Ende wird das Rad in zwei Hälften „geschnitten“, damit die Speichen problemlos gedruckt werden können.
Zuerst erstellen wir eine einzelne Speiche. Für ihre Größe legen wir eine Konstante r_speichen = 2; fest.
r_speichen=2; // Radius Speichen
translate([0,0,0])
linear_extrude(r_felge)
circle(r_speichen);
Der neue Code wird ebenfalls ganz am Ende geschrieben. Damit wir das Ergebnis sehen, muss die Ansicht vermutlich erneut gezoomt und gedreht werden.

Natürlich müssen die Speichen noch geneigt werden. Sie können entweder in x- oder y-Richtung gedreht werden. Im Beispiel geschieht dies in y-Richtung mit rotate([0, 90, 0]). Das translate([0, 0, 0]) ist eigentlich optional, erlaubt uns aber, das Rad jederzeit im Raum zu positionieren.
translate([0,0,0])
rotate([0,90,0])
linear_extrude(r_felge)
circle(r_speichen);
Das rotate() kommt nach dem translate(), aber vor dem linear_extrude(). Ansonsten gibt das System eine Fehlermeldung aus.

Jetzt kommen wir wieder zur for()-Schleife. Zunächst legen wir die Anzahl der Speichen als Konstante fest.
Der Radius der Speiche ist bereits durch r_speichen = 2 definiert, und n = 10 bestimmt die Anzahl der Speichen.
Damit die Speichen gleichmäßig über den Kreis verteilt sind, berechnen wir den Drehwinkel pro Speiche: Ein Kreis hat 360°, bei 10 Speichen ergibt das delta = 360 / n;, also 36°. Mit echo(delta); lässt sich dies überprüfen.
r_speichen=2; // Radius Speichen
n=10; // Anzahl Speichen
delta=360/n; // Kreisaufteilung
echo(delta);

11.2.4 Multiplizieren der Speichen im Kreis
Nun bauen wir um den bestehenden Speichen-Code eine for()-Schleife. Die Variable i (bei Zählern wird häufig i als Name verwendet) sorgt dafür, dass jede neue Speiche um i * delta um das Zentrum gedreht wird.
for (i = [0:1:n]) {
translate([0,0,0])
rotate([0,90,i*delta])
linear_extrude(r_felge)
circle(r_speichen);
}
Jubel – unser Rad ist fertig! Beim genauen Hinsehen entdecken wir jedoch noch ein paar kleine Problemchen.

Die Schleife beginnt bei i = 0 und läuft bis 10. 0 * delta ergibt 0, 10 * delta ergibt 360. So werden im Beispiel 11 Speichen erzeugt, wobei die 11. Speiche genau auf der ersten (0.) Speiche liegt.
Einfach n = 9 zu setzen funktioniert nicht, da die erste Speiche dann wieder an der Position der letzten Speiche der Schleife stünde.
Deshalb definieren wir die Schleife mit n-1; alles andere bleibt unverändert.
for (i = [0:1:n-1]) {
...
...
}
Das Resultat entspricht dem vorherigen Bild, jedoch ohne die doppelten Speichen.

11.2.5 Umbauen des gesamten Codes
Beim genaueren Betrachten des Rades aus allen Blickwinkeln erkennen wir zwei weitere Probleme.

1. Das Loch wurde durch die Speichen wieder „zugekleistert“.
2.
Die Speichen müssen noch halbiert werden.
Schauen wir uns das Gerüst unseres Codes genauer an. Dabei habe ich den eigentlichen Code der Abschnitte durch ... ersetzt.
- Zuoberst: die ersten Konstanten
- Anschließend: die // gesamte Felge mit einem difference() { ... }
- Gefolgt von: die // gesamte Nabe mit einem difference() { ... } inklusive der nötigen Konstanten
- Am Ende: // die Speichen
$fn=60;
r_felge=80; // Radius des Rades
felge=3; // Dicke der Felge
z_hoch=5; // Höhe des Rades
// ganze Felge
difference() {
...
}
// ganze Nabe
r_nabe=5; // Radius Nabe
r_loch=2; // Radius Loch in der Nabe
difference() {
...
}
// Speichen
r_speichen=2; // Radius Speichen
n=10; // Anzahl Speichen
delta=360/n; // Kreisaufteilung
for (i = [0:1:n-1]) {
...
}
Die Speichen werden nach der Erstellung der Nabe (mit Loch) eingefügt und verstopfen so das bereits erstellte Loch. Was passiert, wenn wir zuerst die Speichen und danach die Nabe erstellen?
Im Beispiel habe ich die Code-Blöcke getauscht – alles andere bleibt unverändert.

Das Loch sollte ganz am Ende über den gesamten Code erzeugt werden!
Damit Nabe und Speichen als ein zusammenhängendes Objekt erkannt werden, benötigen wir ein union() um die beiden Abschnitte.
Tipp: Das Einrücken des Codes ist ganz einfach. Den gewünschten Bereich markieren und mit der TAB-Taste einrücken. Mit SHIFT + TAB kann in die Gegenrichtung eingerückt werden.
union() {
// ganze Nabe
r_nabe=5; // Radius Nabe
r_loch=2; // Radius Loch in der Nabe
difference() {
translate([0,0,0])
linear_extrude(z_hoch)
circle(r_nabe);
translate([0,0,-0.1])
linear_extrude(z_hoch+0.2)
circle(r_loch);
}
// Speichen
r_speichen=2; // Radius Speichen
n=10; // Anzahl Speichen
delta=360/n; // Kreisaufteilung
for (i = [0:1:n-1]) {
translate([0,0,0])
rotate([0,90,i*delta])
linear_extrude(r_felge)
circle(r_speichen);
}
}
Das Resultat bleibt unverändert, da das Loch (im Bild markiert) noch innerhalb des Abschnitts „ganze Nabe“ liegt. Um korrekt zu wirken, muss das Loch erst nach der union() erzeugt werden.

Damit das Loch korrekt aus diesem union() erstellt wird, kann das difference() im Nabenbereich gelöscht und stattdessen um das union() gelegt werden.
PS: Nicht gleich F5 drücken – erst weiterlesen.
difference() {
union() {
// ganze Nabe
r_nabe=5; // Radius Nabe
r_loch=2; // Radius Loch in der Nabe
translate([0,0,0])
linear_extrude(z_hoch)
circle(r_nabe);
// Speichen
r_speichen=2; // Radius Speichen
n=10; // Anzahl Speichen
delta=360/n; // Kreisaufteilung
for (i = [0:1:n-1]) {
translate([0,0,0])
rotate([0,90,i*delta])
linear_extrude(r_felge)
circle(r_speichen);
}
}
// Loch
translate([0,0,-0.1])
linear_extrude(z_hoch+0.2)
circle(r_loch);
}
Zur Kontrolle kann die { von difference() angeklickt werden. Die letzte Klammer muss dabei grün aufleuchten. Dasselbe gilt für die { bei union(): So sehen wir, dass das // Loch außerhalb von union(), aber innerhalb von difference() liegt.
Soweit ist alles supi – doch nach einem F5 erscheint eine Fehlermeldung im Konsolen-Fenster.

Das Programm kennt r_loch plötzlich nicht mehr! Dabei steht die Konstante ja weiter oben, direkt nach r_nabe. Warum wird sie nicht erkannt?
Dieses Problem hat mich beim ersten Mal auch überrascht. Ich musste erst lernen, dass in OpenSCAD eine neu definierte Konstante innerhalb eines { ... }-Blocks nur innerhalb dieses Blocks und der darin verschachtelten Blöcke gültig ist.
difference() {
a=10;
// hier ist a gültig
...
union() {
b=10;
// hier ist a und b gültig
...
}
// hier ist a gültig
// b ist nicht mehr gültig
...
}
// hier a und b nicht mehr gültig
...
Somit verschieben wir die r_loch Konstante an den Anfang des Codes.

Erstes Problem gelöst. Nun zum Problem 2 mit den Speichen die halbiert werden müssen.

Am besten erstellen wir ein difference() über das gesamte Rad unterhalb der z = 0-Linie. Dazu reicht eine Scheibe, die größer als das Rad und dicker als die halbe Speiche ist.
Die Felge (1) muss dafür in den bereits bestehenden union()-Bereich verschoben werden. Anschließend kann bei (2) die Scheibe vom gesamten union()-Block herausgeschnitten werden.

Das würde dann so aussehen:

Und schon wieder viele Warnungen… Linie 42: r_speichen ist nicht bekannt. Nun wissen wir aber: Die Konstante muss aus dem { ... }-Block herausgenommen werden. Am besten wird sie gleich an den Anfang des Codes gesetzt.

Meist ist es am besten, wenn Konstanten im „Header“, also ganz oben, definiert werden. So sind sie überall gültig.
11.2.6 Rendern und drucken
Wenn das Rad nun mit den aktuellen Maßen in den Drucker geladen wird, sehen wir, dass es doch sehr groß geraten ist. Ein Radius von 80 mm ergibt einen Durchmesser von 160 mm, dabei sollte der Durchmesser eigentlich nur 80 mm betragen.
Nun könnten wir in OpenSCAD alle Werte halbieren, oder wir können das gesamte Teil im Drucker einfach auf 50 % skalieren.

Nun noch duplizieren und ausdrucken.

Das Rad kann natürlich auch noch detaillierter und aufwendiger gestaltet werden.