Zum Inhalt springen

OpenSCAD Benutzerhandbuch/Benutzerdefiniert

Aus Wikibooks

Benutzerdefinierte Funktionen und Module

[Bearbeiten]

Du kannst die OpenSCAD-Sprache erweitern, indem du eigene Funktionen und Module definierst. Das ermöglicht es dir, Code-Abschnitte zu gruppieren und mit unterschiedlichen Werten wiederverwendbar zu machen. Gut gewählte Namen helfen außerdem, deinen Code verständlich zu halten.

  • Funktionen berechnen und geben Werte zurück.
  • Module führen Aktionen aus (z. B. Geometrie erzeugen), geben aber nichts zurück.

OpenSCAD berechnet Variablenwerte zur Kompilierzeit, nicht zur Laufzeit. Innerhalb eines Gültigkeitsbereichs gilt stets die letzte Zuweisung – auch in tieferen Ebenen. Denk besser an „überschreibbare Konstanten“ statt an klassische Variablen.

Für jeden Aufruf von Funktionen oder Modulen erstellt OpenSCAD eine eigene Kopie mit eigenem Gültigkeitsbereich – alle Werte sind darin fest und unabhängig.

Namen sind Groß-/Kleinschreibung-sensitiv: "test()" und "TEST()" sind zwei verschiedene Module/Funktionen.

Gültigkeitsbereich (Scope)

[Bearbeiten]

Module und Funktionen können innerhalb eines Moduls definiert werden – dann sind sie nur dort sichtbar.

Beispiel: Eine Parabel wird lokal definiert und geplottet:

function parabola(f,x) = (1/(4*f)) * x*x; 

module plotParabola(f, wide, steps=1) {
  function y(x) = parabola(f,x);        // nur hier sichtbar
  module plot(x,y) {                    // nur hier sichtbar
    translate([x,y]) circle(1,$fn=12);
  }
  xAxis = [-wide/2 : steps : wide/2];
  for (x = xAxis) plot(x, y(x));
}

color("red")  plotParabola(10, 100, 5);
color("blue") plotParabola(4,  60,  2);

Die Funktion "y()" und das Modul "plot()" können nicht außerhalb von "plotParabola" aufgerufen werden.

Funktionen

[Bearbeiten]

Funktionen berechnen aus Eingabewerten einen neuen Wert (auch Vektoren!).

Syntax:

function name(parameter = default) = ausdruck;
  • Name: Nur Buchstaben, Ziffern, Unterstrich ("[a-zA-Z0-9_]")
  • Parameter: Optional mit Standardwert
  • Ausdruck: Beliebiger berechenbarer Wert

Nutzung

[Bearbeiten]

Funktionen werden wie Werte behandelt – kein Semikolon am Ende!

Beispiel 1:

function func0() = 5;
function func1(x=3) = 2*x+1;
function func2() = [1,2,3,4];
function func3(y=7) = (y==7) ? 5 : 2;
function func4(p0,p1,p2,p3) = [p0,p1,p2,p3];

echo(func0());            // 5
a = func1();              // 7
b = func1(5);             // 11
echo(func2());            // [1,2,3,4]
echo(func3(2), func3());  // 2, 5

z = func4(func0(), func1(), func2(), func3());
// → [5, 7, [1,2,3,4], 5]

translate([0, -4*func0(), 0])
  cube([func0(), 2*func0(), func0()]);
// → translate([0,-20,0]) cube([5,10,5]);

Beispiel 2: Dynamischer Bereich für "for"-Schleifen

function steps(start, no_steps, end) =
  [start : (end-start)/(no_steps-1) : end];

echo(steps(10, 3, 5));   // [10, 7.5, 5]
echo(steps(0, 5, 5));    // [0, 1.25, 2.5, 3.75, 5]

Beispiel 3: Rhomboid als Funktion

Beispiel 3
function rhomboid(x=1, y=1, angle=90) =
  [[0,0], [x,0],
   [x + x*cos(angle)/sin(angle), y],
   [x*cos(angle)/sin(angle), y]];

v1 = rhomboid(10,10,35);
polygon(v1);

Im Vergleich dazu als Modul:

module parallelogram(x=1,y=1,angle=90) {
  polygon([[0,0],[x,0],
           [x+x*cos(angle)/sin(angle),y],
           [x*cos(angle)/sin(angle),y]]);
}
parallelogram(10,10,35);

"let" in Funktionen

[Bearbeiten]

Seit 2015.03 kannst du mit "let" Zwischenwerte in Funktionen speichern:

function get_square_triangle_perimeter(p1, p2) =
  let(hypotenuse = sqrt(p1*p1 + p2*p2))
    p1 + p2 + hypotenuse;

Rekursive Funktionen

[Bearbeiten]

Rekursion ist möglich – aber mit Begrenzung (einige Tausend Aufrufe). Nutze den Bedingungsoperator ("? :") zum Abbruch.

Einfache Rekursion (kein Tail-Call):

function add_up_to(n) = (n == 0) ? 0 : n + add_up_to(n-1);

Tail-rekursiv (effizienter, bis zu 1 Mio. Aufrufe):

function add_up_to(n, sum=0) =
  (n == 0) ? sum : add_up_to(n-1, sum+n);

echo(sum = add_up_to(100000)); // → 5.00005e+009

Funktionsliterale (Lambdas)

[Bearbeiten]

Vorlage:Requires

Du kannst anonyme Funktionen definieren und wie Werte behandeln:

func = function(x) x * x;
echo(func(5)); // → 25

// Funktion, die eine Funktion zurückgibt:
a = 1;
selector = function(which)
  which == "add" ? function(x) x + x + a
                 : function(x) x * x + a;

echo(selector("add")(5)); // → 11
echo(selector("mul")(5)); // → 26

Überschreiben eingebauter Funktionen

[Bearbeiten]

Möglich – aber Vorsicht! Die Definition wird vor der Auswertung verarbeitet:

echo(sin(1));           // → true
function sin(x) = true;
echo(sin(1));           // → true

Module

[Bearbeiten]

Module definieren entweder Objekte oder Operatoren (mit "children()"). Sobald definiert, verhalten sie sich wie Sprachbefehle.

Syntax:

module name(parameter = default) { ... }
  • Name: Nur "[a-zA-Z0-9_]"
  • Parameter: Optional mit Standardwert
  • Aktionen: Fast alles erlaubt – auch verschachtelte Module/Funktionen (nur lokal sichtbar)

Variablen existieren nur innerhalb des Modulaufrufs – Rückgabewerte gibt es nicht.

Objekt-Module

[Bearbeiten]

Erzeugen Geometrie. Beim Aufruf enden sie mit Semikolon.

Beispiel 1: Farbbalken

Farbbalken
ColorBreak = [[0,""], [20,"lime"], [40,"greenyellow"], [60,"yellow"], [75,"LightCoral"], [200,"red"]];
Expense = [16,20,25,85,52,63,45];

module ColorBar(value, period, range) {
  RangeHi = ColorBreak[range][0];
  RangeLo = ColorBreak[range-1][0];
  color(ColorBreak[range][1])
  translate([10*period, 0, RangeLo])
    if (value > RangeHi) cube([5,2,RangeHi-RangeLo]);
    else if (value > RangeLo) cube([5,2,value-RangeLo]);
}

module ShowColorBars(values) {
  for (month = [0:len(values)-1], range = [1:len(ColorBreak)-1])
    ColorBar(values[month], month, range);
}

translate([-30,-20,0]) ShowColorBars(Expense);

Beispiel 2: Haus mit verschiedenen Dächern

Haus
module house(roof="flat", paint=[1,0,0]) {
  color(paint)
  if (roof=="flat") {
    translate([0,-1,0]) cube();
  } else if (roof=="pitched") {
    rotate([90,0,0]) linear_extrude(height=1)
      polygon(points=[[0,0],[0,1],[0.5,1.5],[1,1],[1,0]]);
  } else if (roof=="domical") {
    translate([0,-1,0]) {
      translate([0.5,0.5,1]) sphere(r=0.5,$fn=20);
      cube();
    }
  }
}

house();
translate([2,0,0]) house("pitched");
translate([4,0,0]) house("domical",[0,1,0]);
// ... weitere Varianten

Operator-Module

[Bearbeiten]

Nutzen "children()", um Transformationen auf übergebene Objekte anzuwenden. Kein Semikolon beim Aufruf!

Grundlagen:

  • "children()" → alle Kindobjekte
  • "children(i)" → ein bestimmtes Kind (Index 0 bis "$children-1")
  • "children([start:end])" → Bereich auswählen

Beispiel: Eigenes Transformationsmodul

module move(x=0,y=0,z=0,rx=0,ry=0,rz=0) {
  translate([x,y,z]) rotate([rx,ry,rz]) children();
}

move(10) cube(10,true);
move(z=7.07, ry=45) cube(10,true);

Beispiel: Nur erstes Kind mehrfach nutzen

module lineup(num, space) {
  for (i = [0:num-1])
    translate([space*i, 0, 0]) children(0);
}

lineup(5, 65) { sphere(30); cube(35); } // Nur die Kugel wird 5× platziert

Beispiel: Jedes Kind separat bearbeiten

Separate Aktion pro Kind
module SeparateChildren(space) {
  for (i = [0:$children-1])
    translate([i*space, 0, 0]) { children(i); text(str(i)); }
}

SeparateChildren(-20) {
  cube(5);              // 0
  sphere(5);            // 1
  translate([0,20,0]) { // 2 (als ein Kind!)
    cube(5); sphere(5);
  }
  cylinder(15);         // 3
  cube(8,true);         // 4
}

Veralteter Befehl: Bis 2013.06 hieß es "child()" – heute durch "children()" ersetzt:

  • "child()" → "children(0)"
  • "child(x)" → "children(x)"

Weitere Beispiele

[Bearbeiten]

Objekte:

module arrow(){
  cylinder(10);
  cube([4,.5,3],true);
  cube([.5,4,3],true);
  translate([0,0,10]) cylinder(4,2,0,true);
}

module cannon(){
  difference(){
    union(){ sphere(10); cylinder(40,10,8); }
    cylinder(41,4,4);
  }
}

Operatoren:

Rotierende Cluster
module aim(elevation, azimuth=0) {
  rotate([0,0,azimuth])
    rotate([0,90-elevation,0]) children(0);
}

module RotaryCluster(radius=30, number=8) {
  for (azimuth = [0:360/number:359])
    rotate([0,0,azimuth])
      translate([radius,0,0]) {
        children();
        translate([40,0,30]) text(str(azimuth));
      }
}

RotaryCluster(200,7) color("lightgreen") aim(15){cannon();base();}

Rekursive Module

[Bearbeiten]

Module können sich selbst aufrufen – aber ohne Tail-Call-Optimierung! Halte die Rekursionstiefe niedrig (< 7), sonst wird’s langsam.

Einfacher rekursiver Baum
module simple_tree(size, dna, n) {   
  if (n > 0) {
    // Stamm
    cylinder(r1=size/10, r2=size/12, h=size, $fn=24);
    // Äste
    translate([0,0,size])
      for(bd = dna) {
        angx = bd[0]; angz = bd[1]; scal = bd[2];
        rotate([angx,0,angz])
          simple_tree(scal*size, dna, n-1);
      }
  } else {
    // Blätter
    color("green")
    scale([1,1,3])
      translate([0,0,size/6])
        rotate([90,0,0])
          cylinder(r=size/6, h=size/10);
  }
}

dna = [ [12, 80, 0.85], [55, 0, 0.6], 
        [62,125, 0.6], [57,-125,0.6] ];
simple_tree(50, dna, 5);
      1. Überschreiben eingebauter Module ###

Möglich – z. B. um Standardparameter zu ändern:

module sphere() { square(); } // Jetzt erzeugt sphere() ein Quadrat!
sphere();

Sinnvoller Einsatz: Ersetze 3D-Primitive durch extrudierte 2D-Formen mit zusätzlichen Optionen.