freecad-scripts

Par github · awesome-copilot

Compétence experte pour l'écriture de scripts Python FreeCAD, de macros et d'automatisation. À utiliser lorsqu'on vous demande de créer des modèles FreeCAD, des objets paramétriques, des scripts Part/Mesh/Sketcher, des outils d'atelier, des boîtes de dialogue GUI avec PySide, la manipulation du scenegraph Coin3D, ou toute tâche liée à l'API Python FreeCAD. Couvre les bases du scripting FreeCAD, la création de géométries, les objets FeaturePython, les outils d'interface et le développement de macros.

npx skills add https://github.com/github/awesome-copilot --skill freecad-scripts

Scripts FreeCAD

Skill expert pour générer des scripts Python de qualité production pour l'application CAO FreeCAD. Interprète les raccourcis, pseudo-codes et descriptions en langage naturel de tâches de modélisation 3D et les traduit en appels corrects de l'API Python FreeCAD.

Quand utiliser ce skill

  • Écrire des scripts Python pour la console intégrée ou le système de macros de FreeCAD
  • Créer ou manipuler de la géométrie 3D (Part, Mesh, Sketcher, Path, FEM)
  • Construire des objets FeaturePython paramétriques avec des propriétés personnalisées
  • Développer des outils GUI en utilisant PySide/Qt au sein de FreeCAD
  • Manipuler le scénario Coin3D via Pivy
  • Créer des workbenches ou des Gui Commands personnalisés
  • Automatiser les opérations CAO répétitives avec des macros
  • Convertir entre les représentations maille et solide
  • Scripter les analyses FEM, le raytracing ou les exports de dessins

Prérequis

  • FreeCAD installé (0.19+ recommandé; 0.21+/1.0+ pour l'API la plus récente)
  • Python 3.x (intégré avec FreeCAD)
  • Pour le travail GUI : PySide2 (intégré avec FreeCAD)
  • Pour le scénario : Pivy (intégré avec FreeCAD)

Environnement Python FreeCAD

FreeCAD intègre un interpréteur Python. Les scripts s'exécutent dans un environnement où ces modules clés sont disponibles :

import FreeCAD          # Module core (aussi aliasé en 'App')
import FreeCADGui       # Module GUI (aussi aliasé en 'Gui') — uniquement en mode GUI
import Part             # Workbench Part — formes BRep/OpenCASCADE
import Mesh             # Workbench Mesh — mailles triangulées
import Sketcher         # Workbench Sketcher — esquisses 2D contraintes
import Draft            # Workbench Draft — outils de dessin 2D
import Arch             # Workbench Arch/BIM
import Path             # Workbench Path/CAM
import FEM              # Workbench FEM
import TechDraw         # Workbench TechDraw (remplace Drawing)
import BOPTools         # Opérations booléennes
import CompoundTools    # Utilitaires de formes composées

Le modèle de document FreeCAD

# Créer ou accéder à un document
doc = FreeCAD.newDocument("MyDoc")
doc = FreeCAD.ActiveDocument

# Ajouter des objets
box = doc.addObject("Part::Box", "MyBox")
box.Length = 10.0
box.Width = 10.0
box.Height = 10.0

# Recalculer
doc.recompute()

# Accéder aux objets
obj = doc.getObject("MyBox")
obj = doc.MyBox  # L'accès par attribut fonctionne aussi

# Supprimer des objets
doc.removeObject("MyBox")

Concepts fondamentaux

Vecteurs et placements

import FreeCAD

# Vecteurs
v1 = FreeCAD.Vector(1, 0, 0)
v2 = FreeCAD.Vector(0, 1, 0)
v3 = v1.cross(v2)          # Produit vectoriel
d = v1.dot(v2)              # Produit scalaire
v4 = v1 + v2                # Addition
length = v1.Length           # Magnitude
v_norm = FreeCAD.Vector(v1)
v_norm.normalize()           # Normalisation sur place

# Rotations
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 45)  # axe, angle(deg)
rot = FreeCAD.Rotation(0, 0, 45)                       # Angles d'Euler (yaw, pitch, roll)

# Placements (position + orientation)
placement = FreeCAD.Placement(
    FreeCAD.Vector(10, 20, 0),    # translation
    FreeCAD.Rotation(0, 0, 45),   # rotation
    FreeCAD.Vector(0, 0, 0)       # centre de rotation
)
obj.Placement = placement

# Matrice (transformation 4x4)
import math
mat = FreeCAD.Matrix()
mat.move(FreeCAD.Vector(10, 0, 0))
mat.rotateZ(math.radians(45))

Créer et manipuler de la géométrie (module Part)

Le module Part encapsule OpenCASCADE et fournit la modélisation solide BRep :

import FreeCAD
import Part

# --- Formes primitives ---
box = Part.makeBox(10, 10, 10)               # longueur, largeur, hauteur
cyl = Part.makeCylinder(5, 20)               # rayon, hauteur
sphere = Part.makeSphere(10)                  # rayon
cone = Part.makeCone(5, 2, 10)               # r1, r2, hauteur
torus = Part.makeTorus(10, 2)                 # rayon_majeur, rayon_mineur

# --- Wires et arêtes ---
edge1 = Part.makeLine((0, 0, 0), (10, 0, 0))
edge2 = Part.makeLine((10, 0, 0), (10, 10, 0))
edge3 = Part.makeLine((10, 10, 0), (0, 0, 0))
wire = Part.Wire([edge1, edge2, edge3])

# Cercles et arcs
circle = Part.makeCircle(5)                   # rayon
arc = Part.makeCircle(5, FreeCAD.Vector(0, 0, 0),
                       FreeCAD.Vector(0, 0, 1), 0, 180)  # angle_début/fin

# --- Faces ---
face = Part.Face(wire)                        # À partir d'un wire fermé

# --- Solides à partir de faces/wires ---
extrusion = face.extrude(FreeCAD.Vector(0, 0, 10))       # Extrusion
revolved = face.revolve(FreeCAD.Vector(0, 0, 0),
                         FreeCAD.Vector(0, 0, 1), 360)    # Révolution

# --- Opérations booléennes ---
fused = box.fuse(cyl)           # Union
cut = box.cut(cyl)              # Soustraction
common = box.common(cyl)        # Intersection
fused_clean = fused.removeSplitter()  # Nettoyer les coutures

# --- Congés et chanfreins ---
filleted = box.makeFillet(1.0, box.Edges)          # rayon, arêtes
chamfered = box.makeChamfer(1.0, box.Edges)        # distance, arêtes

# --- Loft et sweep ---
loft = Part.makeLoft([wire1, wire2], True)          # wires, solide
swept = Part.Wire([path_edge]).makePipeShell([profile_wire],
                                              True, False)  # solide, frenet

# --- Courbes BSpline ---
from FreeCAD import Vector
points = [Vector(0,0,0), Vector(1,2,0), Vector(3,1,0), Vector(4,3,0)]
bspline = Part.BSplineCurve()
bspline.interpolate(points)
edge = bspline.toShape()

# --- Afficher dans le document ---
Part.show(box, "MyBox")    # Affichage rapide (ajoute au doc actif)
# Ou explicitement :
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()
obj = doc.addObject("Part::Feature", "MyShape")
obj.Shape = box
doc.recompute()

Exploration topologique

shape = obj.Shape

# Accéder aux sous-éléments
shape.Vertexes    # Liste des objets Vertex
shape.Edges       # Liste des objets Edge
shape.Wires       # Liste des objets Wire
shape.Faces       # Liste des objets Face
shape.Shells      # Liste des objets Shell
shape.Solids      # Liste des objets Solid

# Boîte englobante
bb = shape.BoundBox
print(bb.XMin, bb.XMax, bb.YMin, bb.YMax, bb.ZMin, bb.ZMax)
print(bb.Center)

# Propriétés
shape.Volume
shape.Area
shape.Length       # Pour arêtes/wires
face.Surface       # Surface géométrique sous-jacente
edge.Curve         # Courbe géométrique sous-jacente

# Type de forme
shape.ShapeType    # "Solid", "Shell", "Face", "Wire", "Edge", "Vertex", "Compound"

Module Mesh

import Mesh

# Créer une maille à partir de sommets et facettes
mesh = Mesh.Mesh()
mesh.addFacet(
    0.0, 0.0, 0.0,   # sommet 1
    1.0, 0.0, 0.0,   # sommet 2
    0.0, 1.0, 0.0    # sommet 3
)

# Importer/Exporter
mesh = Mesh.Mesh("/path/to/file.stl")
mesh.write("/path/to/output.stl")

# Convertir une forme Part en Mesh
import Part
import MeshPart
shape = Part.makeBox(1, 1, 1)
mesh = MeshPart.meshFromShape(Shape=shape, LinearDeflection=0.1,
                                AngularDeflection=0.5)

# Convertir une Mesh en forme Part
shape = Part.Shape()
shape.makeShapeFromMesh(mesh.Topology, 0.05)  # tolérance
solid = Part.makeSolid(shape)

Module Sketcher

# Créer une esquisse sur le plan XY
sketch = doc.addObject("Sketcher::SketchObject", "MySketch")
sketch.Placement = FreeCAD.Placement(
    FreeCAD.Vector(0, 0, 0),
    FreeCAD.Rotation(0, 0, 0, 1)
)

# Ajouter de la géométrie (retourne l'indice de géométrie)
idx_line = sketch.addGeometry(Part.LineSegment(
    FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(10, 0, 0)))
idx_circle = sketch.addGeometry(Part.Circle(
    FreeCAD.Vector(5, 5, 0), FreeCAD.Vector(0, 0, 1), 3))

# Ajouter des contraintes
sketch.addConstraint(Sketcher.Constraint("Coincident", 0, 2, 1, 1))
sketch.addConstraint(Sketcher.Constraint("Horizontal", 0))
sketch.addConstraint(Sketcher.Constraint("DistanceX", 0, 1, 0, 2, 10.0))
sketch.addConstraint(Sketcher.Constraint("Radius", 1, 3.0))
sketch.addConstraint(Sketcher.Constraint("Fixed", 0, 1))
# Types de contrainte : Coincident, Horizontal, Vertical, Parallel, Perpendicular,
#   Tangent, Equal, Symmetric, Distance, DistanceX, DistanceY, Radius, Angle,
#   Fixed (Block), InternalAlignment

doc.recompute()

Module Draft

import Draft
import FreeCAD

# Formes 2D
line = Draft.makeLine(FreeCAD.Vector(0,0,0), FreeCAD.Vector(10,0,0))
circle = Draft.makeCircle(5)
rect = Draft.makeRectangle(10, 5)
poly = Draft.makePolygon(6, radius=5)   # hexagone

# Opérations
moved = Draft.move(obj, FreeCAD.Vector(10, 0, 0), copy=True)
rotated = Draft.rotate(obj, 45, FreeCAD.Vector(0,0,0),
                        axis=FreeCAD.Vector(0,0,1), copy=True)
scaled = Draft.scale(obj, FreeCAD.Vector(2,2,2), center=FreeCAD.Vector(0,0,0),
                      copy=True)
offset = Draft.offset(obj, FreeCAD.Vector(1,0,0))
array = Draft.makeArray(obj, FreeCAD.Vector(15,0,0),
                         FreeCAD.Vector(0,15,0), 3, 3)

Créer des objets paramétriques (FeaturePython)

Les objets FeaturePython sont des objets paramétriques personnalisés avec des propriétés qui déclenchent le recalcul :

import FreeCAD
import Part

class MyBox:
    """Une boîte paramétrique personnalisée."""

    def __init__(self, obj):
        obj.Proxy = self
        obj.addProperty("App::PropertyLength", "Length", "Dimensions",
                         "Longueur de la boîte").Length = 10.0
        obj.addProperty("App::PropertyLength", "Width", "Dimensions",
                         "Largeur de la boîte").Width = 10.0
        obj.addProperty("App::PropertyLength", "Height", "Dimensions",
                         "Hauteur de la boîte").Height = 10.0

    def execute(self, obj):
        """Appelé lors du recalcul du document."""
        obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)

    def onChanged(self, obj, prop):
        """Appelé quand une propriété change."""
        pass

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        return None


class ViewProviderMyBox:
    """Fournisseur d'affichage pour icône personnalisée et paramètres d'affichage."""

    def __init__(self, vobj):
        vobj.Proxy = self

    def getIcon(self):
        return ":/icons/Part_Box.svg"

    def attach(self, vobj):
        self.Object = vobj.Object

    def updateData(self, obj, prop):
        pass

    def onChanged(self, vobj, prop):
        pass

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        return None


# --- Utilisation ---
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("Test")
obj = doc.addObject("Part::FeaturePython", "CustomBox")
MyBox(obj)
ViewProviderMyBox(obj.ViewObject)
doc.recompute()

Types de propriétés courants

Type de propriété Type Python Description
App::PropertyBool bool Booléen
App::PropertyInteger int Entier
App::PropertyFloat float Flottant
App::PropertyString str Chaîne
App::PropertyLength float (unités) Longueur avec unités
App::PropertyAngle float (deg) Angle en degrés
App::PropertyVector FreeCAD.Vector Vecteur 3D
App::PropertyPlacement FreeCAD.Placement Position + rotation
App::PropertyLink Ref d'objet Lien vers un autre objet
App::PropertyLinkList Liste de refs Liens vers plusieurs objets
App::PropertyEnumeration list/str Sélection déroulante
App::PropertyFile str Chemin de fichier
App::PropertyColor tuple Couleur RGB (0.0-1.0)
App::PropertyPythonObject Quelconque Objet Python sérialisable

Créer des outils GUI

Gui Commands

import FreeCAD
import FreeCADGui

class MyCommand:
    """Une commande personnalisée de barre d'outils/menu."""

    def GetResources(self):
        return {
            "Pixmap": ":/icons/Part_Box.svg",
            "MenuText": "Ma commande personnalisée",
            "ToolTip": "Crée une boîte personnalisée",
            "Accel": "Ctrl+Shift+B"
        }

    def IsActive(self):
        return FreeCAD.ActiveDocument is not None

    def Activated(self):
        # Logique de la commande ici
        FreeCAD.Console.PrintMessage("Commande activée\n")

FreeCADGui.addCommand("My_CustomCommand", MyCommand())

Dialogues PySide

from PySide2 import QtWidgets, QtCore, QtGui

class MyDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent or FreeCADGui.getMainWindow())
        self.setWindowTitle("Mon outil")
        self.setMinimumWidth(300)

        layout = QtWidgets.QVBoxLayout(self)

        # Champs d'entrée
        self.label = QtWidgets.QLabel("Longueur :")
        self.spinbox = QtWidgets.QDoubleSpinBox()
        self.spinbox.setRange(0.1, 1000.0)
        self.spinbox.setValue(10.0)
        self.spinbox.setSuffix(" mm")

        form = QtWidgets.QFormLayout()
        form.addRow(self.label, self.spinbox)
        layout.addLayout(form)

        # Boutons
        btn_layout = QtWidgets.QHBoxLayout()
        self.btn_ok = QtWidgets.QPushButton("OK")
        self.btn_cancel = QtWidgets.QPushButton("Annuler")
        btn_layout.addWidget(self.btn_ok)
        btn_layout.addWidget(self.btn_cancel)
        layout.addLayout(btn_layout)

        self.btn_ok.clicked.connect(self.accept)
        self.btn_cancel.clicked.connect(self.reject)

# Utilisation
dialog = MyDialog()
if dialog.exec_() == QtWidgets.QDialog.Accepted:
    length = dialog.spinbox.value()
    FreeCAD.Console.PrintMessage(f"Longueur : {length}\n")

Panneau de tâches (Recommandé pour l'intégration FreeCAD)

class MyTaskPanel:
    """Panneau de tâches affiché dans la barre latérale gauche."""

    def __init__(self):
        self.form = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(self.form)
        self.spinbox = QtWidgets.QDoubleSpinBox()
        self.spinbox.setValue(10.0)
        layout.addWidget(QtWidgets.QLabel("Longueur :"))
        layout.addWidget(self.spinbox)

    def accept(self):
        # Appelé quand l'utilisateur clique OK
        length = self.spinbox.value()
        FreeCAD.Console.PrintMessage(f"Accepté : {length}\n")
        FreeCADGui.Control.closeDialog()
        return True

    def reject(self):
        FreeCADGui.Control.closeDialog()
        return True

    def getStandardButtons(self):
        return int(QtWidgets.QDialogButtonBox.Ok |
                   QtWidgets.QDialogButtonBox.Cancel)

# Afficher le panneau
panel = MyTaskPanel()
FreeCADGui.Control.showDialog(panel)

Scénario Coin3D (Pivy)

from pivy import coin
import FreeCADGui

# Accéder à la racine du scénario
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()

# Ajouter un séparateur personnalisé avec une sphère
sep = coin.SoSeparator()
mat = coin.SoMaterial()
mat.diffuseColor.setValue(1.0, 0.0, 0.0)  # Rouge
trans = coin.SoTranslation()
trans.translation.setValue(10, 10, 10)
sphere = coin.SoSphere()
sphere.radius.setValue(2.0)
sep.addChild(mat)
sep.addChild(trans)
sep.addChild(sphere)
sg.addChild(sep)

# Supprimer plus tard
sg.removeChild(sep)

Création de workbench personnalisé

import FreeCADGui

class MyWorkbench(FreeCADGui.Workbench):
    MenuText = "Mon workbench"
    ToolTip = "Un workbench personnalisé"
    Icon = ":/icons/freecad.svg"

    def Initialize(self):
        """Appelé à l'activation du workbench."""
        import MyCommands  # Importer votre module de commandes
        self.appendToolbar("Mes outils", ["My_CustomCommand"])
        self.appendMenu("Mon menu", ["My_CustomCommand"])

    def Activated(self):
        pass

    def Deactivated(self):
        pass

    def GetClassName(self):
        return "Gui::PythonWorkbench"

FreeCADGui.addWorkbench(MyWorkbench)

Bonnes pratiques des macros

# En-tête de macro standard
# -*- coding: utf-8 -*-
# FreeCAD Macro: MyMacro
# Description: Brève description de ce que fait la macro
# Author: VotreNom
# Version: 1.0
# Date: 2026-04-07

import FreeCAD
import Part
from FreeCAD import Base

# Garde pour la disponibilité de l'interface
if FreeCAD.GuiUp:
    import FreeCADGui
    from PySide2 import QtWidgets, QtCore

def main():
    doc = FreeCAD.ActiveDocument
    if doc is None:
        FreeCAD.Console.PrintError("Aucun document actif\n")
        return

    if FreeCAD.GuiUp:
        sel = FreeCADGui.Selection.getSelection()
        if not sel:
            FreeCAD.Console.PrintWarning("Aucun objet sélectionné\n")

    # ... logique de la macro ...

    doc.recompute()
    FreeCAD.Console.PrintMessage("Macro terminée\n")

if __name__ == "__main__":
    main()

Gestion de la sélection

# Obtenir les objets sélectionnés
sel = FreeCADGui.Selection.getSelection()           # Liste d'objets
sel_ex = FreeCADGui.Selection.getSelectionEx()       # Étendu (sous-éléments)

for selobj in sel_ex:
    obj = selobj.Object
    for sub in selobj.SubElementNames:
        print(f"{obj.Name}.{sub}")
        shape = obj.getSubObject(sub)  # Obtenir la sous-forme

# Sélectionner par programmation
FreeCADGui.Selection.addSelection(doc.MyBox)
FreeCADGui.Selection.addSelection(doc.MyBox, "Face1")
FreeCADGui.Selection.clearSelection()

Sortie console

FreeCAD.Console.PrintMessage("Message d'info\n")
FreeCAD.Console.PrintWarning("Message d'avertissement\n")
FreeCAD.Console.PrintError("Message d'erreur\n")
FreeCAD.Console.PrintLog("Message de débogage/journal\n")

Motifs courants

Pad paramétrique à partir d'une esquisse

doc = FreeCAD.ActiveDocument

# Créer une esquisse
sketch = doc.addObject("Sketcher::SketchObject", "Sketch")
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(0,0,0), FreeCAD.Vector(10,0,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(10,0,0), FreeCAD.Vector(10,10,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(10,10,0), FreeCAD.Vector(0,10,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(0,10,0), FreeCAD.Vector(0,0,0)))
# Fermer avec des contraintes de coïncidence
for i in range(3):
    sketch.addConstraint(Sketcher.Constraint("Coincident", i, 2, i+1, 1))
sketch.addConstraint(Sketcher.Constraint("Coincident", 3, 2, 0, 1))

# Pad (PartDesign)
pad = doc.addObject("PartDesign::Pad", "Pad")
pad.Profile = sketch
pad.Length = 5.0
sketch.Visibility = False
doc.recompute()

Exporter les formes

# Export STEP
Part.export([doc.MyBox], "/path/to/output.step")

# Export STL (maille)
import Mesh
Mesh.export([doc.MyBox], "/path/to/output.stl")

# Export IGES
Part.export([doc.MyBox], "/path/to/output.iges")

# Formats multiples via importlib
import importlib
importlib.import_module("importOBJ").export([doc.MyBox], "/path/to/output.obj")

Unités et quantités

# FreeCAD utilise les mm en interne
q = FreeCAD.Units.Quantity("10 mm")
q_inch = FreeCAD.Units.Quantity("1 in")
print(q_inch.getValueAs("mm"))  # 25.4

# Parser l'entrée utilisateur avec unités
q = FreeCAD.Units.parseQuantity("2.5 in")
value_mm = float(q)  # Valeur en mm (unité interne)

Règles de compensation (intégration quasi-coder)

Quand on interprète des raccourcis ou pseudo-codes pour les scripts FreeCAD :

  1. Mappage de terminologie : "boîte" → Part.makeBox(), "cylindre" → Part.makeCylinder(), "sphère" → Part.makeSphere(), "fusionner/combiner/joindre" → .fuse(), "soustraire/couper/retirer" → .cut(), "intersect" → .common(), "arrondir les arêtes/congé" → .makeFillet(), "biseauter/chanfrein" → .makeChamfer()
  2. Document implicite : Si aucune gestion de document n'est mentionnée, encapsuler dans le standard doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()
  3. Hypothèse d'unités : Par défaut en millimètres sauf indication contraire
  4. Recalcul : Toujours appeler doc.recompute() après les modifications
  5. Garde GUI : Enrober le code dépendant de GUI dans if FreeCAD.GuiUp: quand le script peut s'exécuter sans interface
  6. Part.show() : Utiliser Part.show(shape, "Name") pour un affichage rapide, ou doc.addObject("Part::Feature", "Name") pour les objets persistants nommés

Références

Liens principaux

Documents de référence groupés

Voir le répertoire references/ pour des guides organisés par sujet :

  1. scripting-fundamentals.md — Scripting core, modèle de document, console
  2. geometry-and-shapes.md — Part, Mesh, Sketcher, topologie
  3. parametric-objects.md — FeaturePython, propriétés, objets scriptés
  4. gui-and-interface.md — PySide, dialogues, panneaux de tâches, Coin3D
  5. workbenches-and-advanced.md — Workbenches, macros, FEM, Path, recettes

Skills similaires