Un décor d’après le plan d’un bâtiment

Un bâtiment réel se dessine une fois, en 2D, dans LibreCAD. Ce plan donne ensuite deux choses :

  • un décor Gazebo (.sdf), pour essayer la navigation dans le bâtiment avant d’y faire rouler le robot ;

  • la carte de navigation (.pgm + .yaml), dessinée d’après ce décor par la même méthode que celle de tous les autres décors.

plan.dxf  ──dxf_world──▶  decor.sdf  ──world_map──▶  carte.pgm + carte.yaml
(LibreCAD)                 (Gazebo)                   (Nav2)

Le décor lisv, le laboratoire en entier (34 × 38 m, 50 portes), est fait ainsi depuis le 2026-10-25 : plan ratbot_gazebo/worlds/lisv.dxf, décor lisv.sdf, carte ratbot_maps/maps/lisv.yaml.

Pourquoi un plan plutôt que le SLAM : une carte faite au SLAM hérite des erreurs de celui qui l’a faite (odométrie, réglages), alors que le plan est exact. Les cartes des décors simulés sont toutes dessinées d’après leur plan pour cette raison (voir Simulation).

Dessiner le plan dans LibreCAD

Le plan du LISV dans LibreCAD : murs en magenta, fenêtres en cyan, portes en jaune, charnières en vert ; à droite la liste des calques.

Le plan du LISV dans LibreCAD, et ses quatre calques (liste à droite) : murs en magenta, fenêtres en cyan, portes en jaune, charnières en vert. Les couleurs sont libres ; seuls les noms des calques comptent.

L’unité est le mètre. Dans LibreCAD, régler l’unité du dessin sur le mètre (Options › Préférences du dessin, onglet Unités) avant de commencer : une porte de 80 cm mesure 0,8 sur le plan. Une grille de 10 cm et l’accrochage aux extrémités et à la grille (barre du bas) évitent les petits écarts entre deux segments qui devraient se toucher.

Quatre calques, aux noms exacts (en majuscules), créés depuis la Liste des calques (bouton +) :

Calque

Entité

Ce qu’on y dessine

WALLS

lignes

Les murs, un segment par pan, le long de l’axe du mur. À chaque porte, laisser dans le mur un trou de la largeur de la porte : c’est ce trou que la carte verra, pas le battant.

WINDOWS

lignes

Les fenêtres, dans les trous des murs extérieurs. Un mur extérieur est une suite continue de murs et de fenêtres, sans aucun vide.

DOORS

lignes

Un segment par battant. Une porte simple a un battant, une double porte deux. Le battant se dessine ouvert (perpendiculaire au mur, dans la pièce où il s’ouvre) ou fermé (dans le trou du mur, dans le prolongement du mur). Sa longueur est la largeur qu’il ferme : 0,8 pour une porte de 80 cm, 2 × 0,4 pour une double porte de 80 cm.

HINGES

points

La charnière de chaque battant : un point (outil Point), posé exactement sur l’extrémité du battant côté gonds. Une charnière par battant, donc deux pour une double porte.

Trois règles évitent presque toutes les erreurs :

  • Seules les lignes et les points sont lus. Une polyligne, un arc ou un rectangle est ignoré : les éclater en lignes avant d’enregistrer (Modifier › Éclater).

  • Une charnière est toujours au bout d’un battant, à moins d’un millimètre. L’accrochage aux extrémités la place exactement ; l’outil refuse un battant sans charnière, ou une charnière seule.

  • Le bâtiment doit être fermé. Le moindre vide dans un mur extérieur fait communiquer l’intérieur avec l’extérieur, et la carte est refusée (voir Vérifier ci-dessous).

Enregistrer au format DXF (Fichier › Enregistrer sous, type Drawing Exchange DXF 2007, le format par défaut de LibreCAD), dans ros2_ws/src/ratbot_gazebo/worlds/NOM.dxf.

Générer le décor et la carte

Une seule fois, la bibliothèque qui lit le DXF (elle est déjà dans l’image Docker et dans ./scripts/ratbot deps) :

sudo apt install python3-ezdxf

Les places de départ des robots s’écrivent à la main, à côté du plan, dans worlds/NOM.spawn.yaml : quatre places (le maximum de la simulation), le robot 1 en tête, dans un endroit libre, à 60 cm l’une de l’autre et à plus de 30 cm des murs. Les coordonnées sont celles du plan. Celles du LISV, dans le grand couloir :

spawn:
  - {x: 19.20, y: 10.00, yaw: 1.5708}
  - {x: 19.20, y: 10.60, yaw: 1.5708}
  - {x: 19.20, y: 11.20, yaw: 1.5708}
  - {x: 19.20, y: 11.80, yaw: 1.5708}

Puis, depuis ros2_ws/src/ratbot_gazebo :

# le plan -> le décor Gazebo
python3 -m ratbot_gazebo.dxf_world worlds/NOM.dxf worlds/NOM.sdf

# le décor -> la carte (coupe à la hauteur du lidar, 22 cm)
python3 -m ratbot_gazebo.world_map worlds/NOM.sdf ../ratbot_maps/maps/NOM

# installer, puis s'en servir
cd ../.. && colcon build --symlink-install --packages-select ratbot_gazebo ratbot_maps
./scripts/ratbot up NOM            # le bâtiment en simulation
./scripts/ratbot nav 1 NOM         # naviguer sur sa carte

Sur la carte du décor en cours, ratbot nav N démarre AMCL à la place de départ du robot N (lue dans NOM.spawn.yaml), et non à l’origine : dans un bâtiment en L comme le LISV, l’origine du plan est hors des murs. Une carte faite au SLAM garde la pose de départ (0, 0), qui est la sienne. Pour une autre pose : ratbot nav N NOM initial_x:=… initial_y:=… initial_yaw:=….

dxf_world affiche ce qu’il a lu (176 walls, 53 windows, 50 door leaves) ; world_map la taille de la carte et son origine. La carte remplit l’espace libre à partir de la place du robot 1 : ce qui est accessible depuis là est libre (blanc), les murs sont noirs, le reste est inconnu (gris).

L’état des portes se choisit à la génération du décor :

--doors

Portes

as-drawn (défaut)

Chaque battant tel que dessiné, ouvert ou fermé.

open

Les battants fermés sont retirés, les autres gardés : toutes les portes laissent passer.

closed

Chaque battant ouvert est rabattu dans son trou, autour de sa charnière, et prolongé jusqu’au mur d’en face : aucune porte ne laisse passer. Utile pour tester une navigation bloquée.

Ce que devient le plan

Calque

Dans le décor

WALLS

Boîtes de 10 cm d’épaisseur et 2,50 m de haut, prolongées de 5 cm à chaque bout pour que les angles se ferment.

WINDOWS

Le mur sous l’appui, de 0,90 m de haut : à 22 cm du sol, c’est tout ce que voit le lidar d’une fenêtre. Elle ferme donc la carte comme un mur.

DOORS + HINGES

Des battants de 4 cm d’épaisseur et 2,00 m de haut, immobiles.

Ces valeurs sont en tête de ratbot_gazebo/dxf_world.py. Le sol est uni, gris beige, comme les dalles plastiques du LISV.

La carte du LISV : murs noirs, espace libre blanc, pièces inaccessibles grises.

La carte du LISV produite par world_map (5 cm par pixel). Les zones grises sont des pièces sans porte, ou derrière une porte fermée : depuis le couloir, le robot ne peut pas y entrer.

Comment se fabrique la carte

La carte de navigation est une image (NOM.pgm) et sa description (NOM.yaml), le format que lit le serveur de carte de Nav2. world_map la tire du décor en quatre étapes :

  1. La coupe à la hauteur du lidar. Seules les formes de collision du décor (boîtes et cylindres) qui traversent le plan horizontal à 22 cm sont gardées : ce que le lidar verrait. Un mur, un battant, le mur sous une fenêtre traversent ce plan ; le plateau d’une table ou le pied bas d’une chaise, non. Ceux-là, seul le capteur de distance avant doit les trouver.

  2. La grille. Le plan coupé est découpé en cases de 5 cm, avec 25 cm de marge autour du bâtiment. Une case est un mur dès qu’un de ses points d’échantillonnage, espacés de 1 cm, tombe dans une forme : une planche de 2 cm ou une jonction entre deux murs ne laisse ainsi aucun trou.

  3. Le remplissage depuis le robot. Partant de la place du robot 1, toutes les cases libres qu’il peut atteindre de proche en proche deviennent libres ; les cases murs restent occupées ; tout le reste, pièces fermées et extérieur, devient inconnu. Si ce remplissage atteint le bord de l’image, le bâtiment n’est pas fermé et la carte est refusée ; si la place de départ tombe dans un mur, aussi.

  4. L’écriture. L’image est en niveaux de gris, une case par pixel : 0 (noir) pour un mur, 254 (blanc) pour le libre, 205 (gris) pour l’inconnu. Le .yaml dit comment la lire :

Champ

LISV

Sens

image

lisv.pgm

L’image, à côté du .yaml.

mode

trinary

Trois valeurs seulement : occupé, libre, inconnu.

resolution

0.05

Mètres par pixel.

origin

[-0.35, -0.35, 0]

Position, dans le repère du décor, du coin en bas à gauche de l’image (x, y, rotation). Ici, 35 cm hors du coin du plan : la marge de 25 cm, le demi-mur et l’arrondi à la case.

negate

0

Noir = occupé (et non l’inverse).

occupied_thresh, free_thresh

0.65, 0.25

Seuils de lecture des gris ; en trinary, ils séparent simplement les trois valeurs ci-dessus.

Les deux réglages de world_map : --height (hauteur de la coupe, 0,22 m par défaut, celle du lidar) et --resolution (0,05 m par défaut ; une carte à 2 cm, essayée au banc, améliorait la localisation mais rapprochait le robot des pieds de table : non retenue). La carte s’ouvre avec n’importe quel visualiseur d’images (eog lisv.pgm), ou dans RViz une fois la navigation lancée.

Note

Plan ou exploration ? Une carte se fait de deux façons. En roulant (ratbot explore ou ratbot slam) : le robot part sans rien connaître et la construit au fur et à mesure ; c’est la méthode d’un lieu inconnu, et la seule qui voie les meubles. D’après un plan, comme ici : exacte pour les murs, sans rouler, mais aveugle à ce que le plan ne montre pas. Les cartes des décors de simulation sont toutes dessinées d’après leur plan, parce qu’une carte faite au SLAM en simulation hérite des erreurs de la simulation. Pour le vrai laboratoire, les deux se complètent : la carte du plan sert à préparer et à tester en simulation, une exploration du labo meublé donne la carte réelle, et la première sert de référence pour juger la seconde.

Vérifier

  • « no hinge at either end » ou « hinges without a door » : un battant sans charnière, ou une charnière seule. Le message donne ses coordonnées ; la reprendre avec l’accrochage aux extrémités.

  • « the free space reaches the map border: decor not closed » : un vide dans un mur extérieur. Sur le plan du LISV, il manquait 40 cm de mur entre deux fenêtres (x = 34, y de 21,7 à 22,1). Pour le trouver, parcourir le mur extérieur de segment en segment : chaque fin de segment doit être le début du suivant.

  • Une zone grise inattendue : une pièce qui devrait être accessible a sa porte fermée, ou son trou de porte n’a pas été laissé dans le mur.

  • Une porte trop étroite sur la carte : le trou du mur est plus petit que la porte réelle. C’est le trou qui compte : un battant un peu court ne gêne pas, un trou trop petit, si.

Les règles de l’outil sont au banc de tests du paquet (test/test_dxf_world.py : appariement des charnières, modes des portes, porte rabattue jusqu’au mur, fenêtre qui ferme la carte) :

cd ros2_ws/src/ratbot_gazebo && python3 -m pytest -q test/

Limites

  • Le plan ne montre que les murs. Les meubles, les armoires, les cartons n’y sont pas : sur le vrai robot, le lidar les voit, et la navigation les traite comme des obstacles nouveaux. Pour naviguer au quotidien, une carte faite au SLAM dans le bâtiment meublé reste utile ; le plan sert à préparer et à tester.

  • Le lidar voit souvent à travers une vitre basse : là où le plan met le mur sous une fenêtre, une carte réelle peut avoir un trou.

  • Les portes sont figées dans l’état choisi à la génération ; elles ne s’ouvrent pas pendant une simulation.