Modéliser un objet 3D avec OpenSCAD

et Inkscape (un peu)

Introduction


OpenSCAD n'est pas un modeleur graphique, mais un langage :


Exemples :
cube([2,3,4]);
difference(){
  cube(30, center=true);
  sphere(20);
}
translate([0,0,30]) {
  cylinder(h=40, r=10);
}

Fondements d'OpenSCAD


  • Primitives géométriques
    • 2D (rectangles, cercles...)
    • 3D (parallélépipèdes, sphères...)
  • Transformations
    • Translation
    • Rotation
    • Redimensionnement
    • Déformation
  • Opérations dites de "CSG" ("Constructive Solid Geometry")
    • union, différence, intersection...
  • Fonctions
    • arithmétiques : addition, soustraction...
    • trigonométriques : cosinus, sinus, tangente...
    • autres : texte, racine carrée, max, min...
  • Grands classiques de la programmation :
    • Fonctions, itérateurs, variables

Avantages & inconvénients


Avantages

  • Toutes plateformes : Windows + Mac + Linux
  • Grande précision (saisie de valeurs numériques)
  • Très adapté si vous avez une expérience en programmation !
  • Export de fichiers STL (= format utilisé pour l'impression 3D)
  • Réutilisation de modules
  • Conception paramétrique

Inconvénients

  • Moins visuel qu'un modeleur
  • Très addictif ! :)

Installation

  1. Télécharger OpenSCAD
  2. Installer le logiciel
  3. Lancer OpenSCAD

Premier contact


  • A gauche : la zone de saisie du code
  • En haut à droite, la zone de prévisualisation
  • En bas à droite, la console qui affichera les messages (erreurs...)

Quelques réglages

  • Allez dans le menu Edit > Preferences
  • Sélectionnez l'onglet Advanced
  • Cochez "Allow to open multiple documents"
  • Dans l'onglet Features, activez le module "text()". NB : si votre ordinateur est sous Linux, vous devez compiler vous-même OpenSCAD en ajoutant "CONFIG += experimental" après la commande "qmake" pour pouvoir bénéficier du module text.


Ceci permettra d'ouvrir plusieurs fenêtres OpenSCAD en parallèle et d'utiliser directement du texte dans vos objets.

Space: the final frontier


  • Unité : logiciel agnostique, considérez qu'il s'agit de millimètres
  • Déplacement de la scène/caméra: maintien du clic droit
  • Zoom : molette de la souris
  • Rotation : maintien du clic gauche, haut/bas tourne autour d'un axe, droite/gauche autour d'un autre. A chaque clic les 2 axes les plus perpendiculaires à l'axe de la caméra sont sélectionnés.
  • Perdu dans l'espace ? Menu View > Reset view

Vos doigts vous brûlent ?

Saisissez ceci dans la zone de saisie du code, puis appuyez sur la touche F5 ou F6 pour générer l'objet :

cube(size=20);
translate([40,0,0]) cube(size=30);

Bravo, sans le savoir vous avez :

  • utilisé (deux fois) la "primitive" cube,
  • passé un "paramètre" size avec des "valeurs" différentes,
  • déplacé l'un des cubes grâce à l'opération de "translation",
  • appris que chaque appel à une primitive se terminait par un ";"

Vous maîtrisez donc déjà plusieurs concepts cruciaux !

Quelle progression fulgurante ! :)

Objet #1 : un arbre

Un premier objet très simple, pour découvrir les bases d'OpenSCAD

Les commentaires

Prenez l'habitude de commenter vos programmes pour en faciliter la relecture plusieurs mois après, ou par quelqu'un d'autre.

Afin qu'OpenSCAD fasse la distinction entre les portions qu'il doit interpréter, ou ignorer, une syntaxe précise doit être utilisée :

  • Un commentaire sur une seule ligne doit être précédé par deux "slashes",
  • Un commentaire sur plusieurs lignes doit être entouré par "/*" et "*/".

Exemple :

// Ceci est un commentaire sur une ligne
cube(size=20);
/* UN  DEUXIEME COMMENTAIRE
sur deux lignes... ;)
*/
translate([20,20,20]) cube(size=20);

Commençons par un entête explicatif :

/*
File: unPetitArbre.scad

Description:
Décrivez ici votre objet.

Auteur : Prénom NOM

Licence : CC-By-SA
*/
Remarques :
  • Le texte situé entre "/*" et "*/" est considéré comme un commentaire

De quoi aurons nous besoin ?


Hypothèses de départ :

  • Le tronc de notre arbre sera conique,
  • Sa ramure sera sphérique.



Nous utiliserons :

  • la primitive "sphere", pour constituer la ramure,
  • la primitive "cylinder", pour constituer le tronc,
  • l'opération CSG "union", pour unir le tronc et la ramure.
  • la transformation "translate", pour placer correctement la ramure.

La primitive "sphere"


Elle accepte :

  • soit un paramètre "r" pour spécifier son rayon,
  • soit un paramètre "d" pour le diamètre.

sphere(r = 50);

Bravo, vous avez codé votre deuxième objet Openscad !!

La primitive "sphere" (2)


Remarquez-vous quelque-chose au sujet de cette "sphère" ?

C'est une approximation, fragmentée en facettes.
Remarque : cette "résolution" ne concerne pas uniquement l'affichage, l'objet présente réellement les facettes que vous voyez à l'écran. Nous verrons (avec le cylindre) qu'il est possible d'utiliser cette caractéristique à notre avantage.

La primitive "sphere" (3)

D'autres variables sont disponibles, mais nous utiliserons dans un premier temps la variable $fn, pour définir la résolution de la sphère :


Une sphère de très faible résolution :
sphere(r = 100, $fn=6);
Une sphère de forte résolution, quasi-sphérique :
sphere(r = 100, $fn=360);

La primitive "cylinder"


C'est un cylindre "au sens large", utilisable pour... des cônes !

Cette primitive accepte des paramètres :

  • "h" pour la hauteur du cône/cylindre,
  • "r" pour le rayon du cylindre,
  • "r1" et "r2" pour la base et le sommet, si le cylindre est un cône,
  • "d" pour le diamètre du cylindre,
  • "d1" et "d2" pour la base et le sommet, si le cylindre est un cône,
  • "center" pour centrer le cône en hauteur,
  • "$fn" pour adapter la résolution du cône.
cylinder(h = 10, r1 = 10, r2 = 20, center = false);
cylinder(h = 10, r1 = 20, r2 = 5, $fn=360, center = false);
cylinder(h = 10, r1 = 20, r2 = 5, $fn=5, center = true);

La primitive "cylinder" (2)


Quelques exemples :

cylinder(h = 10, r1 = 10, r2 = 15, center = false);
cylinder(h = 10, r1 = 20, r2 = 5, $fn=360, center = false);
cylinder(h = 10, r1 = 20, r2 = 5, $fn=5, center = true);

Maîtriser la résolution d'une forme circulaire

Nous avons vu que les formes circulaires et sphériques d'OpenSCAD sont imparfaites, en réalité composées de facettes.

Ceci ouvre certaines possibilités intéressantes. Par exemple, comment générer la forme d'un écrou hexagonal M6 ?

Après une brève recherche sur internet nous trouvons les dimensions standards des écrous hexagonaux. Définissons un cylindre selon ces caractéristiques, avec une résolution circulaire de 6 (6 facettes pour un tour complet) :

// un écrou hexagonal M6 défini en une seule ligne de code
cylinder(d=20.78,h=4.8,$fn=6);

La transformation "translate"


  • Accepte un seul paramètre "v", pour "vecteur"
  • Dans OpenSCAD, un vecteur est une série de valeurs : "[x,y,z]"
  • Les objets concernés sont placés entre accolades.


Ici la translation d'un cube de 25mm sur l'axe des X :

translate([25,0,0]) {
  cube(25);
}

L'opération "union"


  • Aucun paramètre demandé (ni accepté) pour l'opération : parenthèses vides
  • Les objets concernés par l'union sont énumérés entre les accolades.
union() {
  cube(size=50);
  sphere(r=50);
}

1ère étape : la conception


Commençons par définir les proportions de notre arbre :
  • Hauteur du tronc : 30mm
  • Rayon du tronc: 7mm à la base, 4mm au sommet
  • Diamètre de la ramure : 40mm

2ème étape : le tronc

Créons le tronc avec la primitive cylinder :

cylinder(h = 30, r1 = 7, r2=4);

3ème étape : la ramure


cylinder(h=30, r1=7, r2=4);
sphere(d = 40, $fn=60);

Question : de combien faudra-t-il déplacer la sphère pour la positionner au bon endroit ?

Réponse : la moitié du diamètre de la ramure (20mm) + la hauteur du tronc (30mm) (?)

4ème étape : plaçons la ramure

Déplaçons la ramure de 20mm+30mm=50mm sur l'axe des Z :

translate([0,0,50]) {
  sphere(d = 40, $fn=120);
}
cylinder(h = 30, r1 = 7, r2 = 4, $fn=5, center = false);

Examinons la jonction du tronc et de la ramure...

La sphère tangente le cylindre, les deux objets ne sont pas jointifs.

5ème étape : finalisons l'arbre

Allongeons légèrement le tronc puis opérons l'union des deux objets :

union(){
  translate([0,0,50]) {
    sphere(d = 40, $fn=60);
  }
  cylinder(h = 31, r1 = 7, r2 = 4, $fn=12, center = false);
}

Bravo, vous avez terminé votre premier objet !

L'export STL

  • Sauvegardez le fichier source au format OpenSCAD,
  • Appuyez sur F6 pour régénérer le fichier,
  • Sélectionnez File > Export > Export as STL...

Le fichier STL ainsi généré est utilisable dans les logiciels d'impression 3D.


Excessivement simple, non ? :)

Objet #2 : un dé

Cet objet est sensiblement plus sophistiqué que le précédent, mais ça en vaut la peine : à l'issue, vous maîtriserez toutes les fonctionnalités principales du langage OpenSCAD !

Entête

Un entête explicatif, pour expliquer le contenu du fichier :
/*
File: leNomDeMonObjet.scad

Description:
Décrivez ici votre objet.

Auteur : Prénom NOM

Licence : CC-By-SA
*/

Conception du dé


  • Forme globale : un cube de taille variable,
  • Marquage : entre 1 et 6 hémisphéres de taille variable en creux, sur chaque face du dé.

Nous commencerons par créer le cube, puis nous effectuerons les marquages en lui soustrayant des sphères placées de façon appropriée à l'aide des opérations translate() et difference().

Nous biseauterons ensuite les arêtes en ne conservant que l'intersection() du cube avec trois cylindres croisés.

Nous avons déjà vu la primitive sphere et l'opération translate lors de la réalisation de notre premier objet, l' arbre . Nous manquent seulement quelques détails sur la primitive cube et sur les opérations difference() et intersection().

La primitive "cube"

Elle accepte les paramètres suivants :

  • size : pour définir les dimensions du cube
  • center : pour centrer le cube sur les origines de l'espace de travail.

La primitive cube est utilisable pour réaliser n'importe quel parallélépipède en lui passant un vecteur de 3 valeurs [x,y,z] pour spécifier une dimension différente sur chaque axe :

cube(size=[15,40,1]);

L'opération "difference"


  • Aucun paramètre demandé (ni accepté) pour l'opération : parenthèses vides
  • Les objets concernés sont énumérés entre les accolades, le premier objet étant l'objet de base, et les suivants étant soustraits.
difference() {
  cube(size=50);
  sphere(r=50);
}

L'opération "intersection"

Syntaxiquement, l'opération intersection() s'utilise exactement comme l'opération union(). L'intersection crée un nouvel objet en se basant sur les parties communes à tous les objets listés.

intersection() {
  cube(size=50);
  sphere(d=70);
}

Créons la forme de base


Nous allons spécifier la taille, en passant le paramètre "size" :

cube(size=40);

Remarquez que le cube n'est pas centré dans l'espace, c'est un de ses coins qui se trouve à l'origine.

Positionnons le dé au centre


Il est parfois plus confortable de travailler avec des objets centrés. La primitive cube accepte justement un paramètre additionnel : "center". Comme nous l'avons vu, par défaut, ce paramètre prend la valeur "false". Donnons-lui la valeur "true" :
cube(size=40,center=true);

Le cube est maintenant centré, ce sera plus pratique pour placer les sphères que nous soustrairons pour faire les marquages.

Les variables


L'utilisation de variables peut paraître lourde aux débutants, mais un programme y gagne en lisibilité. Prenons de bonnes habitudes...

Définissons la dimension du dé dans une variable en début de fichier, et faisons-y appel pour le paramètre size. Ainsi, nous n'aurons besoin de spécifier la dimension du dé qu'à un seul endroit, facile à trouver, au tout début du fichier :

$dimensionDe = 40;
cube(size=$dimensionDe, center=true);

Premier marquage (1)

Soustrayons une grosse sphère au cube, sans égard pour sa position :

$dimensionDe = 40;
$diametreMarquage = 40.3;

difference() {
  cube(size=$dimensionDe, center=true);
  sphere(d=$diametreMarquage);
}

Jetez un petit coup d'oeil aux trous... Amusant, non ?

Premier marquage (2)

Pourquoi les trous ne sont-ils pas ronds ?

Parce que la sphère n'est pas ronde.

Premier marquage (3)

Ce ne sera jamais une sphère, mais augmentons sa résolution :

$dimensionDe = 40;
$diametreMarquage = 40.3;

difference() {
  cube(size=$dimensionDe, center=true);
  sphere(d=$diametreMarquage,$fn = 360);
}

Beaucoup plus précis... Mais plus long à calculer !

Premier marquage (4)

$fn est une variable globale. Ainsi placée, la valeur de $fn s'applique implicitement à tout le fichier (et donc en l'occurence, à tous les marquages) :

$dimensionDe = 40;
$diametreMarquage = $dimensionDe + 0.3;
$fn = 120;

difference() {
  cube(size=$dimensionDe, center=true);
  sphere(d=$diametreMarquage);
}

Premier marquage (5)

Réduisons la sphère et plaçons-la pour effectuer le premier marquage. Étant centrée comme le cube, il nous faudra la translater de la moitié de la taille du cube.

$dimensionDe = 40;
$diametreMarquage = $dimensionDe/5;
$positionPremierMarquage = [$dimensionDe/2,0,0];
$fn = 40;

difference() {
  cube(size=$dimensionDe, center=true);
  translate($positionPremierMarquage) {
    sphere(d=$diametreMarquage);
  }
}

Premier marquage (6)

Nous savons maintenant placer le marquage du "1" à la surface du dé, centré sur la face. Pour faire les marquages suivants, de combien aurons-nous besoin de décaler le marquage sur les autres axes ?

Suite des marquages (6)

Nous connaissons maintenant la valeur de la translation à appliquer pour les différents marquages. Exercice : copiez-coller ceci dans OpenSCAD et complétez-le :

Ça prend forme !

Tous les marquages sont positionnés. Le programme étant paramétrique, il est déjà possible de modifier $dimensionDe et $diametreMarquage, et les proportions du dé sont respectées. Imaginez, s'il avait fallu modifier ces valeurs un peu partout...

Quelques jolies rondeurs...

Notre dé en manque un peu, non ? Arrangeons ça avec une opération intersection() sur le cube et sur trois cylindres croisés.

Imaginez-vous que nous ne conserverons que la partie commune aux 3 cylindres ci-dessous, et au dé placé en leur centre :

Quelques jolies rondeurs... (2)

Mettons en place les variables dont nous aurons besoin pour les cylindres, dans nos "variables à usage interne" situées sous les variables paramétriques :

Quelques jolies rondeurs... (3)

Rajoutons maintenant un premier cylindre utilisant ces variables, et centré. Notez le caractère additionnel placé juste devant la primitive cylinder :

Le code complet :

Oh, un modificateur !

Au lieu de générer en appuyant sur F6 comme d'habitude, essayez de générer en appuyant sur F5.

Le cylindre apparaît en transparent, au lieu d'apparaître comme un objet à part entière, car nous l'avons fait précéder du signe modificateur #. Très utile pour se représenter la position d'objets utilisés dans les opérations telles que difference et intersection, et donc normalement invisibles en tant que tel...

Les modificateurs

Il en existe plusieurs, qui concernent tous le mode Prévisualisation (F5), mais peuvent avoir un impact sur le mode Rendu (F6).

  • Le signe # : met en évidence en rouge l'objet devant lequel il est placé. L'objet est actif dans le mode Rendu.
  • Le signe % : passe l'objet en transparence. L'objet n'est plus actif dans le mode Rendu.
  • Le signe ! : seul cet objet est actif, que ce soit dans le mode Prévisualisation ou Rendu.
  • Le signe * : désactive l'objet, dans tous les modes.

La transformation "rotate"

Pour définir une rotation, nous avons besoin de définir un ou plusieurs axes autour desquels la rotation va être effectuée, ainsi que les angles correspondants.

Elle peut être appelée de plusieurs façons :

  • soit en définissant uniquement une série de valeurs, correspondant aux angles de rotation appliqués autour des axes X, Y et Z :
    // fait tourner le paraléllépipède de 45° autour de l'axe X
    rotate(a = [45,0,0]) cube(size=[1,1,50]);
  • soit en définissant le paramètre "a" (pour "angle") et le paramètre "v" (pour "vecteur"), auquel cas le vecteur court de l'origine de l'espace de travail au point défini par le vecteur :
    // fait tourner le paraléllépipède de 45°
    // autour d'un axe d'un axe passant par l'origine ([0,0,0])
    // et par le point situé en [1,1,0], courant donc à 45° entre les axes X et Y. 
    rotate(a = 45, v = [1,1,0]) cube(size=[1,1,50]);

L'opération "rotate" (2)

À moins d'être exercé en matière de perception dans l'espace, il est parfois difficile de se représenter le résultat d'une rotation effectuée autour de plusieurs axes. Les deux façons d'utiliser la rotation pourraient laisser croire qu'on peut les utiliser de façon interchangeable, et que ce n'est qu'une question de syntaxe. Effectivement, les deux codes suivants sont équivalents :

rotate(a = 45, v = [1,0,0]) cube(size=[1,1,50]);
rotate(a = [45,0,0]) cube(size=[1,1,50]);
  • Or, les deux codes suivants, faisant intervenir plusieurs axes, ne le sont pas :
  • rotate(a = 90, v = [1,1,0]) cube(size=[1,1,50]);
    rotate(a = [90,90,0]) cube(size=[1,1,50]);

    Note : il est assez rare d'avoir besoin de rotations complexes, et ce niveau de détail peut vous paraître superflu.

    Exercice : manipulez les valeurs d'angle pour comprendre la différence entre les deux syntaxes.

    L'opération "rotate" (3)

    Avez-vous compris la différence de résultat entre les deux syntaxes ? Un angle de 90° permet de se rendre compte de cette différence.

    La première syntaxe fait faire une rotation simple autour d'un axe qui n'est pas orthogonal à X, Y, et Z, la difficulté étant de se représenter cet axe.

    La seconde syntaxe fait en réalité faire 2 rotations séparées à l'objet : la première autour de l'axe X, et la seconde autour de l'axe Y.

    À vous de jouer pour les autres cylindres

    Placez deux autres cylindres, tournés de 90° (un quart de tour, donc) autour des axes X et Y. Pour mémoire, la rotation s'effectue avec rotate([x,y,z]), x, y et z étant exprimés en degrés.

    Le code complet :

    Ajustons le diamètre des cylindres

    L'objectif est que les cylindres ne laissent dépasser que les arêtes du cube. Choisissez la valeur souhaitée, et modifiez la valeur de la variable en conséquence.

    NB : afin que le biseau soit proportionnel à la taille du dé (et donc compatible avec le fonctionnement paramétrique), optez pour un multiplicateur sur la taille du dé comme par exemple :

    $diametreCylindreIntersection = $dimensionDe * 1.35;

    Et hop ! Une intersection !

    Rajoutons simplement une opération intersection avec le cube et les trois cylindres entre ses accolades :

    Le code complet :

    Et voilà !

    Générons un rendu avec F6...

    Nous obtenons un dé qui correspond parfaitement à notre cahier des charges ! Et -plus important !- vous maîtrisez déjà une grande partie des outils essentiels à la conception d'objets sous Openscad !

    Il ne vous reste plus qu'à ajuster la taille du dé, à augmenter la précision, et à exporter le fichier STL !

    Objet #3 : un coeur

    Parallélépipèdes, sphères... très utiles, mais comment procéder pour des objets moins "géométriques" ?

    Installons Inkscape

    Nous allons utiliser Inkscape, un logiciel de dessin vectoriel, pour dessiner une forme qu'il serait difficile de décrire sous forme de programmation OpenSCAD.

    1) Télécharger Inkscape

    2) Installer le logiciel

    3) Lancer Inkscape

    Utilisation d'Inkscape

    Dessines-moi un coeur...

    (Cliquez ici pour visionner la vidéo)

    Votre exercice : dessiner vous-même un (joli) coeur.

    NB : pour plus de confort pour visionner la vidéo, passez en plein écran : touche F11, ou clic droit sur la vidéo puis "Plein écran".

    L'extrusion linéaire

    Nous disposons d'un profil en 2 dimensions, que nous allons importer dans OpenSCAD. Nous effectuerons ensuite une opération d'extrusion, pour l'étendre sur la troisième dimension et créer un objet en volume.

    Passons un paramètre "height" pour spécifier l'épaisseur de l'objet :

    linear_extrude(height = 15) {
      import(file = "chemin/vers/mon/fichier/dessinCoeur.dxf");
    }

    Autres paramètres d'extrusion

    L'opération d'extrusion linéaire accepte d'autres paramètres :

    • twist (0-360) : crée une rotation de la forme située au sommet, par rapport à l'originale.
    • scale (valeur scalaire ou vecteur) : modifie les dimensions de la forme située au sommet.
    • center (true/false) : centre verticalement la forme 3D extrudée.
    linear_extrude(height = 20, center = false, convexity = 10, twist = 180,
        scale=[1,2], slices=2000, $fn=100) {
      translate([0, 0, 0]) {
      square(size = 10,center=false);
      }
    }

    Objet #4 : une bague

    Nous allons voir comment réaliser un objet circulaire à partir d'un profil en 2D.

    L'extrusion circulaire

    Nous allons simplement importer un profil en 2D (le coeur précédemment réalisé, ou n'importe quelle autre forme) réalisé sous Inkscape, et lui appliquer l'opération d'extrusion circulaire.

    Tout se passe comme si on positionnait le profil à la verticale, avec un de ses côtés contre l'axe Z, et si on le faisait tourner autour de ce dernier. Si nous appliquons une translation sur l'objet import pour l'éloigner de l'axe Z, une cavité circulaire dont le rayon est égal à la translation est laissée au centre de l'objet extrudé.

    rotate_extrude($fn=120) {
      translate([50, 0, 0]) {
        import(file = "contourRotateExtrude.dxf");
      }
    }

    Woaw !!!

    Ça y est, vous savez faire une bague ! Incroyable, non ?

    Objet #5 : un objet déformé

    Nous avons vu comment définir la taille des différentes primitives que nous utilisons. Mais comment faire pour redimensionner voire déformer un objet plus complexe, ayant déjà fait l'objet de transformations ?

    Quatre nouvelles transformations

    OpenSCAD nous propose plusieurs transformations très utiles :

    • scale : pour modifier la proportion d'un objet,
    • resize : pour attribuer une taille précise,
    • mirror : pour renverser la forme en miroir,
    • multmatrix : pour appliquer une matrice de transformation géométrique de 4x4.

    La transformation "scale"

    Elle permet de modifier les dimensions d'un objet en leur appliquant un multiplicateur qui suivra les axes x,y et z de l'espace de travail

    • accepte un seul paramètre "v" prenant pour valeur un vecteur sous la forme "[x, y, z]" ou un seul nombre N qui sera interprété comme un vecteur "[N,N,N]",
    • s'applique sur les objets "enfants", entre accolades :
    cube(20);
      translate([30, 0, 0]) {
        scale([0.5, 1, 2]) {
          cube(20);
        }
      }
    }

    La transformation "resize"

    La transformation "resize" permet de donner à un objet des dimensions précises.

    • accepte un seul paramètre "newsize" prenant pour valeur un vecteur sous la forme "[x, y, z]" ou un seul nombre N qui sera interprété comme un vecteur "[N, N, N]",
    • s'applique sur les objets "enfants", entre accolades :
    cube(20);
      translate([30, 0, 0]) {
        resize(newsize = [10, 15, 20]) {
          cube(20);
        }
      }
    }

    La transformation "mirror"

    La transformation "mirror" permet de "retourner" l'objet selon un effet de miroir.

    • accepte un seul paramètre "v" prenant pour valeur un vecteur sous la forme "[x, y, z]", qui sera exploité comme un vecteur "normal" (~ perpendiculaire) du plan sur lequel le miroir sera placé,
    • s'applique sur les objets "enfants", entre accolades :
    cube(20);
      mirror(v = [1, 1, 1]) {
        cube(20);
      }
    }

    La transformation "multmatrix"

    Elle applique une matrice de transformations affines.

    • accepte un seul paramètre "m" prenant un vecteur de vecteurs,

    Exemple : rotation de 45° sur Z et translation de [10,20,30] :

    angle=45;
    matriceRotation = [ [cos(angle), -sin(angle), 0, 0],
                        [sin(angle), cos(angle), 0, 0],
                        [0, 0, 1, 0],
                        [0, 0, 0,  1]     ];
    matriceTranslation = [ [1, 0, 0, 10],
                           [0, 1, 0, 20],
                           [0, 0, 1, 30],
                           [0, 0, 0,  1]  ];
    
    multmatrix(m = matriceRotation * matriceTranslation) union() {
       cylinder(r=10.0,h=10,center=false);
       cube(size=[10,10,10],center=false);
    }

    Précisions (importante) sur les transformations

    • Les valeurs données pour les translations et redimensionnements s'entendent par rapport au référentiel de l'espace de travail, et non par rapport à un référentiel "objet". C'est notamment important lorsqu'on combine des transformations impliquant des rotations.
    • L'ordre dans lequel on effectue les transformations a son importance.

    Dans l'exemple suivant, la première section redimensionne un cube, PUIS le tourne. La seconde le tourne PUIS le redimensionne (il est transformé en losange) :

    rotate(a = [0,45,45]){
      resize(newsize=[50,0,0]) {
        cube(size=10);
      }
    }
    
    resize(newsize=[50,0,0]) {
      rotate(a = [0,45,45]) {
        cube(size=10);
      }
    }

    Objet #6 : un beau dé

    Une refonte de notre dé, pour le simplifier et en faire un module réutilisable dans d'autres objets.

    Mais en quoi sera-t-il plus beau, me direz-vous ? Croyez-moi, notre dé sera magnifique du point de vue du programmeur.

    Quelles améliorations allons-nous apporter ?

    Cet exercice vise tout d'abord à corriger un défaut dans la version initiale de notre dé : il comprend de nombreuses portions de code quasiment identiques, seulement différentes par la valeur des paramètres utilisés :

    (...)
    translate($positionMarquage1) {
      sphere(d=$diametreMarquage);
    }
    translate($positionMarquage2) {
      sphere(d=$diametreMarquage);
    }
    translate($positionMarquage3) {
      sphere(d=$diametreMarquage);
    }
    (...)

    En quoi est-ce un défaut ? Plusieurs raisons : une éventuelle modification devra être reportée à plusieurs endroits, la compilation du programme est susceptible d'être plus longue, et ça rallonge le programme, ce qui nuit à sa maintenabilité.

    Pour corriger ce défaut, nous ferons appel à la "boucle for".

    La boucle "for"

    C'est un outil classique en programmation : plutôt que donner l'ordre d'exécuter X actions, on donne l'ordre de réaliser une même action sur une série d'objets.

    Ainsi ces programmes parviennent à un résultat identique :

    translate([1, 0, 0]) {
      cube(size = 1, center = false);
    }
    translate([3, 0, 0]) {
      cube(size = 1, center = false);
    }
    translate([5, 0, 0]) {
      cube(size = 1, center = false);
    }
    
    for ($repetitions = [1, 3, 5])
    {
        translate([$repetitions, 0, 0])
        cube(size = 1, center = false);
    }

    OpenSCAD exécute une première fois le code situé entre accolades en donnant la valeur 1 à la variable $repetitions, après quoi il change sa valeur à 3, exécute à nouveau le code, etc.

    Deuxième amélioration

    Si vous avez suivi tous les exercices, vous avez déjà vu comment réaliser de nombreux objets.

    Imaginons que vous souhaitiez modéliser un objet plus complexe, qui incorpore un ou plusieurs de ces objets. Croyez-vous qu'il soit souhaitable de devoir copier-coller les différents objets dans un unique fichier ?

    Si vous modifiez l'objet original, vous devrez répercuter les changements dans plusieurs fichiers...

    Nous allons voir comment éviter ce genre de problèmes, en transformant notre objet en module, c'est-à-dire en composant réutilisable dans d'autres fichiers. Notre dé étant déjà paramétrique, vous allez voir que c'est aussi simple que pratique.

    Les modules d'objet

    Ci-dessous un exemple de module. Nous définissons son nom ("rondelle"), des paramètres, et leurs valeurs par défaut (diamètres intérieur et extérieur, épaisseur).

    Le code décrivant l'objet est entre accolades, et utilise les paramètres :

    module rondelle($diametreExterieur = 20, $diametreInterieur = 3, $epaisseur = 2) {
    
      difference(){
          cylinder(d = $diametreExterieur, h = $epaisseur, center=true);
          cylinder(d = $diametreInterieur, h = $epaisseur*1.1, center=true);
       }
    }

    Les modules (2)

    Les modules s'utilisent exactement de la même façon que les primitives. Et pour cause... Ne le dites à personne, mais en réalité, les primitives sont des modules.

    Le module peut être appelé sans passer de paramètres (valeurs par défaut), ou avec des valeurs spécifiques :

    rondelle();
    rondelle($diametreExterieur=60,$diametreInterieur=10,$epaisseur=4);

    Les modules (3)

    Si nous sauvegardons le code décrivant notre module dans un fichier "moduleRondelle.scad", nous pourrons très simplement y faire appel dans un autre fichier :

    use </chemin/vers/le/fichier/moduleRondelle.scad>
    
    rondelle();
    translate([50,0,0]) rondelle($diametreExterieur=60,$diametreInterieur=10,$epaisseur=4);

    Un projet complexe y gagne ainsi énormément en lisibilité, car le code des différents composants peut être isolé dans un fichier séparé.

    De plus, si une amélioration est apportée à un composant, la modification est appliquée directement à tous les endroits où le module est appelé.

    Intégrons la boucle "for" à notre dé

    Nous allons commencer par reprendre notre dé, et remplacer la longue série de translations de sphères par une boucle for :

    translate($positionMarquage1) {
      sphere(d=$diametreMarquage);
    }
    (...)
    translate($positionMarquage21) {
      sphere(d=$diametreMarquage);
    }

    ...devient ainsi :

    for($marquages =[]) {
        translate($marquages) {
          sphere(d=$diametreMarquage);
      }
    }

    Spécifions une liste de valeurs

    Nous pouvons maintenant ajouter toutes les valeurs préalablement définies, dans la liste des valeurs de la variable $marquages, en les séparant par des virgules :

    for($marquages =[
                     [$grandDecalage,0,0],
                     [$petitDecalage,-$grandDecalage,$petitDecalage],
                     // (A VOUS DE JOUER !!)
                    ]) {
        translate($marquages) {
          sphere(d=$diametreMarquage);
      }
    }

    À la fin de cet exercice, votre programme devrait afficher le dé complet exactement comme auparavant, si ce n'est que la compilation devrait être plus rapide.

    Transformons l'objet en module

    Nous allons commencer par intégrer la définition du module. Nous l'appellerons "parametricDice", et lui permettrons d'accueillir des paramètres diceSize, markSizeRatio, et resolution.

    /*
    File: parametricDice.scad
    
    Description:
    A six-face parametric dice module.
    
    Auteur : Yann Lossouarn
    
    Licence : CC-By-SA
    */
    
    module parametricDice(diceSize=20,markSizeRatio=0.2,resolution=30) {
    // C'est ici que nous allons intégrer le code de notre dé !
    }

    La langue anglaise n'est pas obligatoire, mais elle permettra à toute la communauté de "makers" de bénéficier de votre création une fois que vous l'aurez publiée sur internet.

    Intégrons le code dans le module

    Vous pouvez copier-coller l'intégralité du code du dé à l'intérieur des accolades.

    La seule modification à apporter consiste à alimenter les variables grâce aux paramètres passés au module :

    $dimensionDe = 30;
    $diametreMarquage = $dimensionDe/5;
    $fn = 30;

    ... devient donc :

    $dimensionDe = diceSize;
    $diametreMarquage = $dimensionDe * markSizeRatio;
    $fn = resolution;

    le module est prêt

    Notre module est enfin prêt :

    Il ne reste plus qu'à sauvegarder ce fichier, et en créer un autre dans le même répertoire, pour faire appel à notre module :

    use <parametricDiceModule.scad>
    parametricDice(diceSize=40,markSizeRatio=0.3,resolution=60);

    Objet #7 : un petit présentoir

    Ce petit objet va nous servir de prétexte pour apprendre à utiliser 2 dernières transformations, assez spectaculaires :

    • hull : pour créer une coque englobant une série d'objets,
    • minkowski : pour créer la "somme de Minkowski" d'une série d'objets.

    La transformation "hull"

    Cette transformation permet de créer une coque, dont la forme englobe la série d'objets sur laquelle elle est appliquée.

    Elle ne demande aucun paramètre, mais uniquement la liste d'objets sur laquelle elle doit s'appliquer, entre accolades :

    sphere(r=30);
    translate([50,0,0]) sphere(r=10);
    translate([-50,0,0]) sphere(r=10);
    
    translate([0,50,0]) hull() {
      sphere(r=30);
      translate([50,0,0]) sphere(r=10);
      translate([-50,0,0]) sphere(r=10);
    }

    La transformation "hull" (2)

    Remarque sur l'utilisation de hull() : l'opération de jonction des objets est globale, et ne restreint pas le volume de la coque pour rejoindre les contours d'un objet plus petit situé au milieu. Exemple :

    sphere(r=10);
    translate([50,0,0]) sphere(r=30);
    translate([-50,0,0]) sphere(r=30);
    
    translate([0,50,0]) hull() {
      sphere(r=10);
      translate([50,0,0]) sphere(r=30);
      translate([-50,0,0]) sphere(r=30);
    }

    La transformation "minkowski"

    La somme de minkowski est utilisée en robotique pour planifier les déplacements, ou en CAO. Plutôt difficile à décrire verbalement, ses effets concrets demandent une certaine pratique pour être anticipés. Elle ne prend aucun paramètre :

    $fn = 30;
    
    minkoswki() {
      difference() { cube([10,10,1]);
        translate([1,1,-1]) cube([8,8,10]);
        translate([-1,-1,-1]) cube([5,5,10]);
      }
      cylinder(r=1, h=1);
    }

    Conception du présentoir

    Notre présentoir aura la forme globale d'un cadre photo avec :

    • des contours arrondis,
    • un aspect massif pour survivre à la proximité de vos enfants,
    • un trépied large qui évitera qu'il tombe (pour les mêmes raisons).

    Réalisation du plateau

    Afin de créer un plateau avec des arêtes et des coins arrondis, nous ferons la somme de minkowski d'un parallélipède et d'une sphère. Préparons ces derniers.

    $fn = 30;
    cube([60,30,2]);
    sphere(r=2);

    Réalisation du plateau (2)

    Opérons la somme de Minkowski, et positionnons notre plateau

    $fn = 30;
    translate([0,0,2])
      rotate([45,0,0]) {
        minkowski() {
          cube([60,30,2]);
          sphere(r=2);
        }
      }

    Réalisation du trépied

    Nous utiliserons ici la transformation hull, sur une série de sphères réparties d'une part dans le volume du plateau, et d'autre part au niveau du plan de travail. Préparons-les :

    translate([20,30,2]) sphere(r=2);
    translate([40,30,2]) sphere(r=2);
    translate([30,0,2]) sphere(r=2);
    rotate([45,0,0]) translate([50,30,2]) sphere(r=2);
    rotate([45,0,0]) translate([10,30,2]) sphere(r=2);

    Réalisation du trépied (2)

    Réalisons la coque de ces sphères :

    hull() {
      translate([20,30,2]) sphere(r=2);
      translate([40,30,2]) sphere(r=2);
      translate([30,0,2]) sphere(r=2);
      rotate([45,0,0]) translate([50,30,2]) sphere(r=2);
      rotate([45,0,0]) translate([10,30,2]) sphere(r=2);
    }

    Finalisation du présentoir

    Pour finir, l'union du plateau et du trépied : 17 lignes de code !

    $fn = 60 ;
    
    union() {
    translate([0,0,2])
      rotate([45,0,0]) {
        minkowski() {
          cube([60,30,2]);
          sphere(r=2);
        }
      }
      hull() {
        translate([20,30,2]) sphere(r=2);
        translate([40,30,2]) sphere(r=2);
        translate([30,0,2]) sphere(r=2);
        rotate([45,0,0]) translate([50,30,2]) sphere(r=2);
        rotate([45,0,0]) translate([10,30,2]) sphere(r=2);
      }
    }

    Objet #8 : un manche

    Nous allons maintenant exploiter une des possibilités de programmation les moins connues d'OpenSCAD, pour réaliser très simplement un manche d'outil aux formes arrondies.

    Éléments de base

    On perçoit que ce que nous cherchons ressemble à une opération hull sur une série d'objets arrondis (ellipsoïde, sphère, cylindre), donc commençons par prendre nos mesures et créer ces objets :

    resize(newsize=[50,25,25]) sphere();
    translate([40,0,0]) sphere(d = 16);
    translate([55,0,0]) rotate([0,90,0]) sphere(d = 20);
    translate([65,0,0]) rotate([0,90,0]) sphere(d = 22);  
    translate([55,0,0]) rotate([0,90,0]) cylinder(d = 19,h=25);

    Hull, or not hull? That is the question!

    Comme nous l'avons vu précédemment , une hull ne suit pas les contours des petits objets situés au milieu :

    hull() {
      resize(newsize=[50,25,25]) sphere();
      translate([40,0,0]) sphere(d = 16);
      translate([55,0,0]) rotate([0,90,0]) sphere(d = 20);
      translate([65,0,0]) rotate([0,90,0]) sphere(d = 22);  
      translate([55,0,0]) rotate([0,90,0]) cylinder(d = 19,h=25);
    }

    Les modules d'opération

    Depuis le début de ce cours, nous avons abordé différents concepts : primitives, transformations, opérations...

    Avez-vous remarqué que syntaxiquement, ces concepts sont matérialisés de façon proches ? Un nom, des parenthèses contenant éventuellement des paramètres nommés, puis des accolades définissant sur quels objets porte l'opération ou la transformation.

    module monPetitCube(taille=10) {
      cube(size=taille);
    }
    module monGrandCube(taille=30) {
      cube(size=taille);
    }
    monPetitCube();
    translate(v=[40,10,10]) {
      monGrandCube();
    }

    La raison est simple : dans OpenSCAD, la distinction entre une opération, une transformation ou un objet est totalement artificielle, c'est une convention.

    Créer un module ?

    Nous avons déjà vu qu'il est possible de modulariser des objets, mais si les objets, les opérations et les transformations ne font qu'un, est-il possible de créer une opération ou une transformation spécifique, et d'en faire un module ? Oui, voyons comment...

    Concrètement, les opérations/transformations se différencient des objets, par le fait qu'elles sont appliquées sur des objets définis entre leurs accolades. Dans OpenSCAD, ces objets sont désignés comme ses "enfants" :

    • la variable $children contient le nombre d'enfants, indexé à partir de zéro : un module comportant 5 enfants verra son premier enfant indexé à 0, et le dernier à 4.
    • la fonction children(index) permet d'accéder à l'enfant concerné par l'index.

    Jouer avec les enfants, c'est important

    Voyons maintenant comment interagir avec les enfants au sein d'un module. La syntaxe vous rappellera fortement celle utilisée pour modulariser le dé, précédemment. Commençons par un module de base, strictement inutile, qui se contente d'afficher ses enfants sans interagir avec eux :

    module paresseux() {
      for(i=[0:$children]) {
        children(i);
      }
    }
    
    paresseux() {
      cube(size = 10);
      translate([30,0,0]) {
        cube(size = 10);
      }
    }

    Un module plus actif

    Voyons maintenant comment interagir avec eux :

    module alternateur() {
      for(i=[0:$children]) {
        if(i/2 == ceil(i/2)) {
          echo("iteration paire");
          scale(v=[1,2,1]) {
            children(i);
          }
        }
        else {
          echo("iteration impaire");
          children(i);
        }
      }
    }
    alternateur() {
      cube(size = 10);
      translate([20,0,0]) { cube(size = 10); }
      translate([40,0,0]) { cube(size = 10); }
      translate([60,0,0]) { cube(size = 10); }
    }
    Remarquez la fonction echo() qui permet de générer un affichage dans la console, très utile pour debugger.

    Au secours, un serial-huller !

    Nous avons maintenant tous les éléments pour créer une opération, qui prendra en entrée des objets et les "hullera" un par un :

    $fn = 60;
      module serial_huller() {
        union() {
          for(i=[0:$children-2]) // Nous ne bouclerons pas sur le dernier enfant
            hull() { // nous créons une coque...
              children(i); // ... avec l'objet courant...
              children(i+1); // ... et l'objet suivant !
            }
        }
      }
    serial_huller() {
      resize(newsize=[50,25,25]) sphere();
      translate([40,0,0]) sphere(d = 16);
      translate([55,0,0]) rotate([0,90,0]) sphere(d = 20);
      translate([65,0,0]) rotate([0,90,0]) sphere(d = 22);  
      translate([55,0,0]) rotate([0,90,0]) cylinder(d = 19,h=25); 
    }

    Pour aller plus loin

    Si vous avez suivi l'intégralité de ce module, vous connaissez maintenant la majeure partie des fonctionnalités d'OpenSCAD, et même certaines fonctions avancées.

    J'espère que vous avez pris autant de plaisir à ce voyage, que moi à jouer les guides !

    Pour aller plus loin, de nombreuses ressources s'offrent à vous :