Pourquoi la stratégie de modélisation est essentielle en CAO procédurale

2025-12-14 Catégorie: Modélisation 3D Sylvain

Pourquoi la stratégie de modélisation est-elle si importante en CAO procédurale ? Deux scripts peuvent produire exactement la même géométrie, tout en affichant des temps de génération très différents.

À partir d’un cas concret sous build123d, cet article explique pourquoi certaines opérations, comme les fillets, deviennent coûteuses lorsqu’elles sont répétées à grande échelle, et comment un changement d’approche permet de réduire fortement les temps de calcul.

Un résumé clair des enjeux de la modélisation procédurale, illustrant l’impact direct des choix de conception sur les performances du moteur CAO.

De quoi parle-t-on ?

Toute personne qui a déjà écrit du code, par loisir ou par métier, sait qu'il existe souvent, pour ne pas dire toujours, plusieurs façons d'obtenir un même résultat.

Pour prendre un exemple volontairement simple, le nombre 2 peut être obtenu avec 1 + 1, mais aussi avec 12 / (4 + 2).

Il ne fait aucun doute que 1 + 1 est plus efficace : plus simple à lire, plus rapide à comprendre, et cela demande moins d'opérations, aussi bien pour nous, humains, que pour la machine qui exécute le code. Si vous tapez ces deux calculs sur votre calculatrice ou sur votre ordinateur, vous obtiendrez le même résultat, instantanément à vos yeux. Mais sous le capot, vous l'aurez compris, ce n'est pas la même chose : la deuxième méthode demande plus de calculs, "inutiles", pour la machine.

La modélisation procédurale 3D est évidemment d'un tout autre ordre, bien plus complexe que ces simples calculs. Aussi, la stratégie de modélisation, c’est-à-dire la manière dont vous aurez programmé la génération du modèle, aura un impact direct sur les performances, et donc sur le temps de génération. C’est ce que nous allons voir précisément ici.

Des opérations plus coûteuses que d'autres

Pour traiter cette problématique nous allons prendre appui sur le template de génération de casier de rangement avec tiroirs. En plus de tiroirs et d'éventuels séparateurs, ce template génère en pièce maîtresse un bloc composé de cavités dont le nombre est paramétrable à travers le nombre de colonnes et de lignes. Afin de faciliter l'insertion et le retrait des tiroirs les cavités présentent des arrondis (des fillets). Le script de base peut prendre cette forme :


from build123d import *
from ocp_vscode import show
import time

ROWS = 20
COLS = 20

CAVITY_W = 60
CAVITY_H = 40
CAVITY_D = 100

WALL = 2
FILLET = 0.8
RADIUS = 1.8
TOL = 1e-3

# --------------------------
# Derived dimensions
# --------------------------
BLOCK_W = COLS * CAVITY_W + (COLS + 1) * WALL
BLOCK_H = ROWS * CAVITY_H + (ROWS + 1) * WALL
BLOCK_D = WALL + CAVITY_D

PITCH_X = CAVITY_W + WALL
PITCH_Y = CAVITY_H + WALL
CUT_D   = BLOCK_D - WALL

t0 = time.perf_counter()

# --------------------------
# Geometry
# --------------------------
with BuildPart() as bp:
    # main block
    with BuildSketch(Plane.XY):
        RectangleRounded(BLOCK_W, BLOCK_H, radius=RADIUS)
    extrude(amount=BLOCK_D)

    # subtract grid of cavities (20x20 = 400)
    with BuildSketch(Plane.XY.offset(WALL)):
        with GridLocations(
            x_spacing=PITCH_X,
            y_spacing=PITCH_Y,
            x_count=COLS,
            y_count=ROWS
        ):
            Rectangle(CAVITY_W, CAVITY_H)
    extrude(amount=CUT_D, mode=Mode.SUBTRACT)

    # fillet only top opening edges
    top_edges = [e for e in bp.edges() if abs(e.center().Z - BLOCK_D) <= TOL]

    def is_x_edge(e: Edge) -> bool:
        v1, v2 = [v.position for v in e.vertices()]
        return abs(v1.Y - v2.Y) <= TOL

    def is_y_edge(e: Edge) -> bool:
        v1, v2 = [v.position for v in e.vertices()]
        return abs(v1.X - v2.X) <= TOL

    edges = (
        [e for e in top_edges if is_x_edge(e) and abs(e.length - CAVITY_W) <= TOL] +
        [e for e in top_edges if is_y_edge(e) and abs(e.length - CAVITY_H) <= TOL]
    )

    if edges:
        fillet(edges, radius=FILLET)

block = bp.part

t1 = time.perf_counter()

print(f"Build time: {t1 - t0:.2f} s")
print(f"Cavities: {ROWS * COLS}")

show(block)
        
vue dans OCP CAD Viewer des fillets générés sur chaque arête des cavités
Vue dans OCP CAD Viewer des fillets générés sur chaque arête des cavités

Cette approche est assez "linéaire". On crée un bloc, on soustrait des cavités puis on sélectionne la totalité des arêtes pour leur appliquer un fillet. L'éxéxution de ce script nécessite quelques secondes pour une dizaine de cavités. Mais lorsqu'il s'agit de générer 400 compartiments (20 lignes × 20 colonnes), ce script nécessitera plus d'une heure !

Pourquoi ? La génération de fillets est une opération lourde, coûteuse, car elle repose sur des calculs géométriques complexes effectués sur des surfaces déjà construites. Contrairement à une extrusion ou à une découpe simple, un fillet oblige le moteur CAD à analyser les arêtes existantes, à reconstruire localement les surfaces adjacentes, puis à recalculer les intersections et la continuité géométrique entre toutes les faces concernées.

Changer de stratégie de modélisation

Le script présenté ci-dessus fonctionne dans le sens où la géométrie 3D demandée est bien obtenue. Mais son exécution est extrêmement longue du fait de l'application de nombreuses opérations coûteuses en calcul et donc en temps.

Nous allons voir qu'il est possible d'obtenir la même géométrie de manière beaucoup plus efficace en changeant de stratégie.

Jern sur le Discord build123d propose une autre approche bien plus efficace. L'idée est de créer une tool présentant l'empreinte des fillets et d'utiliser cet outil pour créer les cavités. Le gain est énorme, la géométrie est générée en quelques secondes :


from build123d import *
from ocp_vscode import show
import time

# --------------------------
# Minimal fixed geometry
# --------------------------
ROWS = 20
COLS = 20

CAVITY_W = 60
CAVITY_H = 40
CAVITY_D = 100

WALL = 2
RADIUS = 1.8
OPENING_FILLET = 0.8

# --------------------------
# Derived dimensions
# --------------------------
BLOCK_W = COLS * CAVITY_W + (COLS + 1) * WALL
BLOCK_H = ROWS * CAVITY_H + (ROWS + 1) * WALL
BLOCK_D = WALL + CAVITY_D

PITCH_X = CAVITY_W + WALL
PITCH_Y = CAVITY_H + WALL
CUT_D   = BLOCK_D - WALL

t0 = time.perf_counter()

# --------------------------
# Main block
# --------------------------
with BuildPart() as bp:
    # EN: Main solid block
    with BuildSketch(Plane.XY):
        RectangleRounded(BLOCK_W, BLOCK_H, radius=RADIUS)
    extrude(amount=BLOCK_D)

# --------------------------
# Single cavity "tool" with built-in top fillet/flare
# (this tool will later be patterned with GridLocations)
# --------------------------
with BuildPart() as p2:
    # EN: Base cavity prism (starts at Z=WALL)
    with BuildSketch(Plane.XY.offset(WALL)):
        Rectangle(CAVITY_W, CAVITY_H)
    extrude(amount=CUT_D)

    # EN: Grab current top face of the cavity prism
    top_face = p2.faces().sort_by(Axis.Z)[-1]

    # EN: Create a small "cap" by offsetting the top face (to create space for fillet)
    with BuildSketch(top_face):
        add(top_face)
        offset(amount=2)
    extrude(amount=1)

    # EN: Apply fillet on the original top face edges (fast on a single cavity tool)
    fillet(top_face.edges(), OPENING_FILLET)

    # EN: Remove the non-fillet extra cap to avoid overlaps when patterning
    new_top_face = p2.faces().sort_by(Axis.Z)[-1]
    with BuildSketch(new_top_face):
        add(new_top_face)
    extrude(amount=-1, mode=Mode.SUBTRACT)

# --------------------------
# Pattern the cavity tool and subtract in algebra mode
# --------------------------
tool = GridLocations(
    x_spacing=PITCH_X,
    y_spacing=PITCH_Y,
    x_count=COLS,
    y_count=ROWS
) * p2.part

block = bp.part - tool

t1 = time.perf_counter()

print(f"Build time: {t1 - t0:.2f} s")
print(f"Cavities: {ROWS * COLS}")

# --------------------------
# Show
# --------------------------
show(block)

        
vue de l'empreinte des fillets sur la tool qui sera soustraire au bloc principal
Vue de l'empreinte des fillets sur la tool qui sera soustraite au bloc principal

Pourquoi cette version est bien plus efficace ? La seconde stratégie n'est pas plus rapide parce qu'elle fait moins de cavités, mais parce qu'elle évite d'appliquer un fillet à 1600 arêtes (4 × 400) sur un solide devenant de plus en plus complexe. À la place, la seconde approche calcule les 4 fillets sur une forme simple, une seule fois, puis exécute 400 opérations booléennes de découpe d'une forme déjà arrondie.

La présente démonstration met en exergue le fait que la modélisation programmatique ne consiste pas seulement en une succession d"opérations. Le code de la seconde stratégie n'est pas plus complexe, il est plus intelligent car il tient compte de la mécanique interne du moteur CAO.