OpenSCAD Benutzerhandbuch/Tipps und Tricks
Ein Hinweis zur Lizenz
[Bearbeiten]Alle Code-Schnipsel auf dieser Seite darfst du frei verwenden – ohne Namensnennung und für jeden Zweck. Du kannst davon ausgehen, dass jeder hier gezeigte Code gemeinfrei ist oder unter der CC0-Lizenz steht. Das ändert aber nicht die übliche Lizenz des gesamten Wikibooks oder Handbuchs.
Daten
[Bearbeiten]Werte aus einer Liste umwandeln
[Bearbeiten]// Diese Funktion bildet Eingabewerte x auf Ausgabewerte ab.
// Im Beispiel wird floor() genutzt, um Gleitkommazahlen
// in ganze Zahlen umzuwandeln.
function map(x) = floor(x);
input = [58.9339, 22.9263, 19.2073, 17.8002, 40.4922, 19.7331, 38.9541, 28.9327, 18.2059, 75.5965];
// Mit einem List-Comprehension-Ausdruck rufst du die map()-Funktion
// für jeden Wert der Eingabeliste auf und packst das Ergebnis
// in die Ausgabeliste.
output = [ for (x = input) map(x) ];
echo(output);
// ECHO: [58, 22, 19, 17, 40, 19, 38, 28, 18, 75]
Werte in einer Liste filtern
[Bearbeiten]// Diese Funktion legt fest, ob ein Wert x in die gefilterte Liste kommt.
// Im Beispiel werden alle geraden Zahlen größer oder gleich 6 ausgewählt.
function condition(x) = (x >= 6) && (x % 2 == 0);
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
// Mit einem List-Comprehension-Ausdruck prüfst du jeden Wert
// und nimmst ihn nur auf, wenn condition(x) true liefert.
output = [ for (x = input) if (condition(x)) x ];
echo(output);
// ECHO: [6, 8]
Alle Werte in einer Liste addieren
[Bearbeiten]// Eine einfache rekursive Funktion, die alle Werte einer Liste addiert.
// Dank der Endrekursion wird intern eine Schleife verwendet,
// sodass kein Stack-Überlauf entsteht.
function add(v, i = 0, r = 0) = i < len(v) ? add(v, i + 1, r + v[i]) : r;
input = [2, 3, 5, 8, 10, 12];
output = add(input);
echo(output);
// ECHO: 40
//------------------ add2 -----------------------
// Noch einfacher: eine nicht-rekursive Version mit Matrix-Multiplikation
function add2(v) = [for(p=v) 1]*v;
echo(add2(input));
// ECHO: 40
// add2 funktioniert auch mit Listen von Vektoren
input2 = [ [2, 3] , [5, 8] , [10, 12] ];
echo(add2(input2));
// ECHO: [17, 23]
echo(add(input2));
// ECHO: undef // Warum?
//----------------- add3 --------------------------
// Mit etwas mehr Code kann add() auch beliebige homogene Listenstrukturen verarbeiten
function add3(v, i = 0, r) =
i < len(v) ?
i == 0 ?
add3(v, 1, v[0]) :
add3(v, i + 1, r + v[i]) :
r;
input3 = [ [[1], 1] , [[1], 2] , [[1], 3] ];
input4 = [ 10, [[1], 1] , [[1], 2] , [[1], 3] ];
echo(add3(input3));
// ECHO: [[3], 6]
echo(add2(input3));
// ECHO: undef // input3 ist keine Liste von Vektoren
echo(add3(input4));
// ECHO: undef // input4 ist keine homogene Liste
Kumulative Summe
[Bearbeiten]// Erstelle eine kumulative Summenfunktion mit C-artigem Generator
values = [1,2,65,1,4];
cumsum = [ for (a=0, b=values[0]; a < len(values); a= a+1, b=b+values[a]) b];
// Vermeidet Warnung: "WARNING: undefined operation (number + undefined) ..."
cumsum2 = [ for (a=0, b=values[0]; a < len(values); a= a+1, b=b+(values[a]==undef?0:values[a])) b];
echo(cumsum);
// ECHO: [1, 3, 68, 69, 73]
echo(cumsum2);
// ECHO: [1, 3, 68, 69, 73]
Anzahl der Werte zählen, die eine Bedingung erfüllen
[Bearbeiten]// Dieselbe condition()-Funktion wie oben
function condition(x) = (x >= 6) && (x % 2 == 0);
input = [3, 3.3, 4, 4.1, 4.8, 5, 6, 6.3, 7, 8];
// Filtere die Liste und zähle die Elemente mit len()
output = len([ for (x = input) if (condition(x)) x ]);
echo(output);
// ECHO: 2
Index des größten Werts in einer Liste finden
[Bearbeiten]// Finde den Index des maximalen Werts in einer Liste
function index_max(l) = search(max(l), l)[0];
input = [ 6.3, 4, 4.1, 8, 7, 3, 3.3, 4.8, 5, 6];
echo(index_max(input));
// Prüfe es
echo(input[index_max(input)] == max(input));
// ECHO: 3
// ECHO: true
Vorsicht mit „undef“
[Bearbeiten]Die meisten ungültigen Operationen in OpenSCAD liefern undef. Manche geben nan zurück. Das Programm läuft trotzdem weiter – aber undef-Werte können später zu unvorhersehbarem Verhalten führen. Wird bei einem Funktionsaufruf ein Argument weggelassen, bekommt es automatisch den Wert undef. Um das zu vermeiden, kannst du optionale Argumente mit Standardwerten versehen.
// Addiere 'a' zu jedem Element der Liste 'L'
function incrementBy(L, a) = [ for(x=L) x+a ];
// Dasselbe, aber mit Standardwert a=1
function incrementByWithDefault(L, a=1) = [ for(x=L) x+a ];
echo(incrementBy= incrementBy([1,2,3],2));
echo(incrementByWithDefault= incrementByWithDefault([1,2,3],2));
echo(incrementBy= incrementBy([1,2,3]));
echo(incrementByWithDefault= incrementByWithDefault([1,2,3]));
// ECHO: incrementBy= [3, 4, 5]
// ECHO: incrementByWithDefault= [3, 4, 5]
// ECHO: incrementBy= [undef, undef, undef]
// ECHO: incrementByWithDefault= [2, 3, 4]
Manchmal hängt der Standardwert von anderen Parametern ab – dann hilft eine bedingte Zuweisung:
// Gibt den Teil der Liste 'list' von Index 'from' bis 'to' zurück
function sublist(list, from=0, to) =
let( end = (to==undef ? len(list)-1 : to) )
[ for(i=[from:end]) list[i] ];
echo(s0= sublist(["a", "b", "c", "d"]) ); // from=0, end=3
echo(s1= sublist(["a", "b", "c", "d"], 1, 2) ); // from=1, end=2
echo(s2= sublist(["a", "b", "c", "d"], 1)); // from=1, end=3
echo(s3= sublist(["a", "b", "c", "d"], to=2) ); // from=0, end=2
// ECHO: s0 = ["a", "b", "c", "d"]
// ECHO: s1 = ["b", "c"]
// ECHO: s2 = ["b", "c", "d"]
// ECHO: s3 = ["a", "b", "c"]
Die Funktion sublist() liefert unerwünschte Ergebnisse, wenn from > to (und wirft eine Warnung). Eine einfache Lösung: gib in dem Fall die leere Liste [] zurück:
// Gibt [] zurück, wenn 'from > to'
function sublist2(list, from=0, to) =
from<=to ?
let( end = (to==undef ? len(list)-1 : to) )
[ for(i=[from:end]) list[i] ] :
[];
echo(s1= sublist2(["a", "b", "c", "d"], 3, 1));
echo(s2= sublist2(["a", "b", "c", "d"], 1));
echo(s3= sublist2(["a", "b", "c", "d"], to=2));
// ECHO: s1 = []
// ECHO: s2 = []
// ECHO: s3 = ["a", "b", "c"]
Das Ergebnis s2 ist leer, weil to==undef ist und der Vergleich from <= to zu false ausgewertet wird – der Standardwert geht verloren.
Lösung: Drehe die Bedingung einfach um:
function sublist3(list, from=0, to) =
from>to ?
[] :
let( end = to==undef ? len(list)-1 : to )
[ for(i=[from:end]) list[i] ] ;
echo(s1=sublist3(["a", "b", "c", "d"], 3, 1));
echo(s2=sublist3(["a", "b", "c", "d"], 1));
echo(s3=sublist3(["a", "b", "c", "d"], to=2));
// ECHO: s1 = []
// ECHO: s2 = ["b", "c", "d"]
// ECHO: s3 = ["a", "b", "c"]
Jetzt wird bei undefiniertem to der erste Test false, und der let()-Block wird ausgeführt. Mit geschickten Abfragen kannst du so souverän mit undef umgehen.
Geometrie
[Bearbeiten]Zylinder übereinander stapeln
[Bearbeiten]
// Definiere die Abmessungen der Zylinder: erst Radius, dann Höhe.
// Alle Zylinder sollen übereinander gestapelt werden (mit 1 Einheit Abstand).
sizes = [ [ 26, 3 ], [ 20, 5 ], [ 11, 8 ], [ 5, 10 ], [ 2, 13 ] ];
// Lösung mit einem rekursiven Modul, das bei jedem Aufruf
// ein neues Koordinatensystem nach oben verschiebt.
module translated_cylinder(size_vector, idx = 0) {
if (idx < len(size_vector)) {
radius = size_vector[idx][0];
height = size_vector[idx][1];
// Aktuellen Zylinder erzeugen
cylinder(r = radius, h = height + 0.01);
// Rekursiver Aufruf: nächster Zylinder um aktuelle Höhe nach oben verschieben
translate([0, 0, height - 0.01]) {
translated_cylinder(size_vector, idx + 1);
}
}
}
// Stapel erzeugen
translated_cylinder(sizes);
Minimal-Rotationsproblem
[Bearbeiten]In 2D gibt es (bis auf Spezialfälle) nur zwei Rotationen, um einen Vektor an einen anderen auszurichten. In 3D sind es unendlich viele – aber nur eine hat den kleinsten Rotationswinkel. Die folgende Funktion berechnet die Transformationsmatrix dafür (vereinfacht aus Oskar Lindes `sweep.scad`):
// Gibt den normierten Vektor von v zurück. Fehlschlag bei v=[0,0,0].
function unit(v) = norm(v)>0 ? v/norm(v) : undef;
// Transponiert eine rechteckige Matrix
function transpose(m) =
[ for(j=[0:len(m[0])-1]) [ for(i=[0:len(m)-1]) m[i][j] ] ];
// Einheitsmatrix der Größe n
function identity(n) = [for(i=[0:n-1]) [for(j=[0:n-1]) i==j ? 1 : 0] ];
// Berechnet die Rotation mit minimalem Winkel von a nach b
// Funktioniert nicht, wenn a und b genau entgegengesetzt sind.
function rotate_from_to(a,b) =
let( axis = unit(cross(a,b)) )
axis*axis >= 0.99 ?
transpose([unit(b), axis, cross(axis, unit(b))]) *
[unit(a), axis, cross(axis, unit(a))] :
identity(3);
„Linien“ in OpenSCAD zeichnen
[Bearbeiten]
// Nutzt die Minimalrotation: Zeichnet einen dünnen Zylinder zwischen p0 und p1
module line(p0, p1, diameter=1) {
v = p1-p0;
translate(p0)
multmatrix(rotate_from_to([0,0,1],v))
cylinder(d=diameter, h=norm(v), $fn=4);
}
// Polygonpunkte für einen Knoten generieren
knot = [ for(i=[0:2:360])
[ (19*cos(3*i) + 40)*cos(2*i),
(19*cos(3*i) + 40)*sin(2*i),
19*sin(3*i) ] ];
// Jedes Segment einzeln zeichnen
for(i=[1:len(knot)-1])
line(knot[i-1], knot[i], diameter=5);
// Achtung: Das Rendern dauert sehr lange – nur zur Vorschau nutzen!
Einen alternativen Ansatz findest du unter Hilfe zur Rotationsregel.
Hull-Kette
[Bearbeiten]Mit Schleifen kannst du komplexe Formen durch wiederholtes Erzeugen von `hull`-Segmenten bauen – selbst mit wenigen Parametern. Im folgenden Beispiel läuft die Schleife von `i=1` bis `i=18` und erzeugt Paare wie `[1,2], [2,3], …, [18,19]`:
for (i=[1:18]){
j=i+1;
hull(){
translate([0,0,i])
cylinder(.1,d1=10*sin(i*9),d2=0);
translate([0,0,j])
cylinder(.1,d1=10*sin(j*9),d2=0);
}
}
Text in einen vorgegebenen Bereich einpassen
[Bearbeiten]OpenSCAD kann die tatsächliche Größe eines text()-Objekts nicht abfragen. Aber du kannst sie grob abschätzen und mit resize() in einen bekannten Bereich skalieren – unter der Annahme, dass die Breite entscheidend ist.

// Zwei Zufallszahlen zwischen 10 und 30
r = rands(10, 30, 2);
width = r[1];
length = 3 * r[0];
difference() {
// Rahmen
linear_extrude(2, center = true)
square([length + 4, width + 4], center = true);
// Aussparung für den Text
linear_extrude(2)
square([length + 2, width + 2], center = true);
// Text einpassen (nach Breite skaliert)
color("green")
linear_extrude(1.5, center = true, convexity = 4)
resize([length, 0], auto = true)
text("Text goes here!", valign = "center", halign = "center");
}
Gespiegeltes Objekt mit Original behalten
[Bearbeiten]Das mirror()-Modul transformiert nur – es erzeugt kein zusätzliches Objekt. Mit children() kannst du aber leicht ein eigenes Modul schreiben, das beides zeigt:

// Eigenes mirror-Modul: Original + Spiegelung
module mirror_copy(v = [1, 0, 0]) {
children();
mirror(v) children();
}
// Beispielobjekt
module object() {
translate([5, 5, 0]) {
difference() {
cube(10);
cylinder(r = 8, h = 30, center = true);
}
}
}
// Zuerst an X-, dann an Y-Achse spiegeln
mirror_copy([0, 1, 0])
mirror_copy()
object();
Teile in einem Raster anordnen
[Bearbeiten]Ein praktisches Modul, um Kindobjekte in einem regelmäßigen Gitter anzuordnen:

// Ordnet Kinder in einem rechteckigen Raster an
// spacing = Abstand zwischen Ursprüngen, n = Anzahl entlang X
module arrange(spacing=50, n=5) {
nparts = $children;
for(i=[0:1:n-1], j=[0:nparts/n])
if (i+n*j < nparts)
translate([spacing*(i+1), spacing*j, 0])
children(i+n*j);
}
arrange(spacing=30,n=3) {
sphere(r=20,$fn=8);
sphere(r=20,$fn=10);
cube(30,center=true);
sphere(r=20,$fn=14);
sphere(r=20,$fn=16);
sphere(r=20,$fn=18);
cylinder(r=15,h=30);
sphere(r=20,$fn=22);
}
Achtung: Folgendes funktioniert **nicht**:
arrange() for(i=[8:16]) sphere(15, $fn=i);
Denn die for-Schleife macht intern eine implizite union() – es entsteht nur ein einziges Kindobjekt.
Polygone verrunden
[Bearbeiten]Polygone lassen sich mit dem offset()-Operator auf verschiedene Weise verrunden:

p = [ [0,0], [10,0], [10,10], [5,5], [0,10]];
polygon(p);
// Spitze Ecken verrunden und vergrößern
translate([-15, 0])
offset(1,$fn=24) polygon(p);
// Einkerbungen glätten und verkleinern
translate([-30, 0])
offset(-1,$fn=24) polygon(p);
// Einkerbungen glätten, Maße erhalten
translate([15, 0])
offset(-1,$fn=24) offset(1,$fn=24) polygon(p);
// Spitze Ecken verrunden, Maße erhalten
translate([30, 0])
offset(1,$fn=24) offset(-1,$fn=24) polygon(p);
// Alles verrunden, Maße erhalten
translate([45, 0])
offset(-1,$fn=24) offset(1,$fn=24)
offset(1,$fn=24) offset(-1,$fn=24) polygon(p);
3D-Kanten verrunden („Filleting“)
[Bearbeiten]Das 3D-Pendant zum Verrunden von Polygonen. Da es keinen 3D-offset() gibt, nutzt man stattdessen minkowski():

difference(){
offset_3d(2) offset_3d(-2) // Außenkanten verrunden
offset_3d(-4) offset_3d(4) // Innenkanten verrunden
basic_model();
// Loch ohne Verrundung
translate([0,0,10])
cylinder(r=18,h=50);
}
// Einfacheres Beispiel für negativen Offset
* offset_3d(-4)difference(){
cube(50,center=true);
cube(50,center=false);
}
module basic_model(){
cylinder(r=25,h=55,$fn=6); // $fn=6 für schnellere Berechnung
cube([80,80,10], center=true);
}
module offset_3d(r=1, size=1000) {
n = $fn==undef ? 12: $fn;
if(r==0) children();
else
if( r>0 )
minkowski(convexity=5){
children();
sphere(r, $fn=n);
}
else {
size2 = size*[1,1,1];
size1 = size2*2;
difference(){
cube(size2, center=true);
minkowski(convexity=5){
difference(){
cube(size1, center=true);
children();
}
sphere(-r, $fn=n);
}
}
}
}
Achtung: Das ist extrem rechenintensiv! Jeder minkowski()-Aufruf erhöht die Eckenzahl – jede weitere Stufe dauert länger.
Bounding Box berechnen
[Bearbeiten]OpenSCAD bietet keine direkte Möglichkeit, die Bounding Box eines Objekts abzufragen. Aber du kannst ihr Volumen berechnen: Projiziere das Modell auf jede Achse, bilde daraus „Stäbe“ und kombiniere sie per minkowski().
module bbox() {
// Näherung der Projektion auf X-Achse
module xProjection()
translate([0,1/2,-1/2])
linear_extrude(1)
hull()
projection()
rotate([90,0,0])
linear_extrude(1)
projection() children();
// Bounding Box mit Offset 1 in alle Richtungen
module bbx()
minkowski() {
xProjection() children(); // X
rotate(-90) // Y
xProjection() rotate(90) children();
rotate([0,-90,0]) // Z
xProjection() rotate([0,90,0]) children();
}
// Um 1 Einheit in alle Richtungen verkleinern
module shrink()
intersection() {
translate([ 1, 1, 1]) children();
translate([-1,-1,-1]) children();
}
shrink() bbx() children();
}

So sieht die (transparente) Bounding Box eines roten Modells aus:
module model()
color("red")
union() {
sphere(10);
translate([15,10,5]) cube(10);
}
model();
%bbox() model();
Die Würfel im `offset_3d`-Beispiel könnten durch diese echte Bounding Box ersetzt werden – dann bräuchtest du das künstliche `size`-Argument nicht mehr.

Als Anwendungsbeispiel: Damit kannst du Features um beliebigen Text platzieren, ohne dessen Größe zu kennen. Hier wird eine quadratische Grundplatte mit zwei Bohrungen an den Textenden erzeugt – alles mit festen Abständen:
my_string = "Demo text";
module BasePlate(margin) {
minkowski() {
translate(-margin) square(2*margin);
projection() bbox() linear_extrude(1) children();
}
}
module TextThing() {
text(my_string, halign="center", valign="center");
}
hole_size = 3;
margwidth = 2;
linear_extrude(1)
difference() {
BasePlate([2*(hole_size+margwidth), margwidth]) TextThing();
offset(hole_size) {
difference() {
scale([1.001, 1])
resize([-1, 0.001])
BasePlate([hole_size+margwidth, margwidth]) TextThing();
BasePlate([hole_size+margwidth, margwidth]) TextThing();
}
}
}
linear_extrude(2) TextThing();
Höhenkarte aus Daten
[Bearbeiten]Das eingebaute surface()-Modul erzeugt 3D-Höhenkarten – aber nur aus externen Textdateien. Dieses Modul macht dasselbe direkt aus einer Matrix im Code:

data = [ for(a=[0:10:360])
[ for(b=[0:10:360])
cos(a-b)+4*sin(a+b)+(a+b)/40 ]
];
surfaceData(data, center=true);
cube();
// Wie surface(), aber mit Matrix statt Datei
module surfaceData(M, center=false, convexity=10){
n = len(M);
m = len(M[0]);
miz = min([for(Mi=M) min(Mi)]);
minz = miz< 0? miz-1 : -1;
ctr = center ? [-(m-1)/2, -(n-1)/2, 0]: [0,0,0];
points = [
for(i=[0:n-1])for(j=[0:m-1]) [j, i, M[i][j]] +ctr,
[ 0, 0, minz ] + ctr,
[ m-1, 0, minz ] + ctr,
[ m-1, n-1, minz ] + ctr,
[ 0, n-1, minz ] + ctr,
for(i=[0:n-1])for(j=[0:m-1])
let( med = i==n-1 || j==m-1 ? 0:
(M[i][j]+M[i+1][j]+M[i+1][j+1]+M[i][j+1])/4 )
[j+0.5, i+0.5, med] + ctr
];
faces = [
for(i=[0:n-2])
for(j=[i*m:i*m+m-2])
each [ [ j+1, j, j+n*m+4 ],
[ j, j+m, j+n*m+4 ],
[ j+m, j+m+1, j+n*m+4 ],
[ j+m+1, j+1, j+n*m+4 ] ] ,
[ for(i=[0:m-1]) i, n*m+1, n*m ],
[ for(i=[m-1:-1:0]) -m+i+n*m, n*m+3, n*m+2 ],
[ for(i=[n-1:-1:0]) i*m, n*m, n*m+3 ],
[ for(i=[0:n-1]) i*m+m-1, n*m+2, n*m+1 ],
[n*m, n*m+1, n*m+2, n*m+3 ]
];
polyhedron(points, faces, convexity);
}
Gugelhupf-Kuchen
[Bearbeiten]// Gugelhupf-Nachbildung – handtellergroß (10 cm × 5 cm)
// Parameter
cake_radius = 50; // 10 cm Durchmesser
cake_height = 50; // 5 cm hoch
center_hole_radius = 20;
ridge_count = 12;
ridge_depth = 5;
/* [Hidden] */
$fa=1;$fs=0.5;
// Hauptform
module bundtCake() {
difference() {
// Äußerer Kuchenkörper
union() {
for (i = [0:360/ridge_count:360]) {
rotate([0,0,i])
translate([cake_radius - ridge_depth, 0, 0])
cylinder(h = cake_height, r = ridge_depth);
}
cylinder(h = cake_height, r = cake_radius - ridge_depth);
}
// Mittelloch
translate([0,0,-1])
cylinder(h = cake_height + 2, r = center_hole_radius);
}
}
bundtCake();
Zeichenketten
[Bearbeiten]Zahl aus numerischem String (Dezimal oder Hex)
[Bearbeiten]Wandelt eine Zahl im String-Format in eine Ganzzahl um (ursprünglich „String to Decimal“, später Hex hinzugefügt).
Beispiel: `echo(s2d("314159")/100000);` → `ECHO: 3.14159`
function s2d(h="0",base=10,i=-1) =
// Wandelt einen String aus Dezimal- oder Hex-Ziffern in eine Dezimalzahl um
(i == -1)
? s2d(h,base,i=len(h)-1)
: (i == 0)
? _chkBase(_d2n(h[0]),base)
: _chkBase(_d2n(h[i]),base) + base*s2d(h,base,i-1);
function _chkBase(n,b) =
(n>=b)
? (0/0) // 0/0 = nan
: n;
function _d2n(digitStr) =
// Einzelnes Zeichen (0–9, a–f, A–F) in Zahl umwandeln
(digitStr == undef
|| len(digitStr) == undef
|| len(digitStr) != 1)
? (0/0)
: _d2nV()[search(digitStr,_d2nV(),1,0)[0]][1];
function _d2nV()=
// Vektor für Zeichen-zu-Zahl-Umwandlung
[ ["0",0],["1",1],["2",2],["3",3],["4",4],
["5",5],["6",6],["7",7],["8",8],["9",9],
["a",10],["b",11],["c",12],
["d",13],["e",14],["f",15],
["A",10],["B",11],["C",12],
["D",13],["E",14],["F",15]
];
Debugging
[Bearbeiten]Debug-Tap-Funktion
[Bearbeiten]Ähnlich Rubys „Tap“-Funktion: Gibt das Objekt zurück, aber schreibt es vorher in die Konsole – aber nur, wenn `$_debug` wahr ist.
Beispiel: Bei `$_debug = true` zeigt `x = debugTap(2 * 2, "Solution is: ");` → `ECHO: Solution is: 4`
function debugTap(o, s) = let(
nothing = [ for (i = [1:1]) if ($_debug) echo(str(s, ": ", o)) ]) o;
// Benutzung
// Hinweis: parseArgsToString() verbindet alle Argumente zu einem hübschen String
$_debug = true;
// Quadriert 'x'
function foo(x) =
let(
fnName = "foo",
args = [x]
)
debugTap(x * x, str(fnName, parseArgsToString(args)));
x = 2;
y = foo(x);
echo(str("x: ", x, " y: ", y));
// Konsole:
// ECHO: "foo(2): 4"
// ECHO: "x: 2 y: 4"