Architecture du Pipeline
Ce document décrit le pipeline qui transforme un modèle Doc en
G-code exécutable par une machine. Depuis la réécriture 1.9.0, le
pipeline est construit sur les intentions raygeo : une description
déclarative du travail que le côté Rust doit effectuer, couplée à une
fine couche d'orchestration Python et un magasin d'artefacts en
processus avec comptage de références.
L'ancien DAG multiprocessus (DagScheduler, PipelineGraph,
ArtifactManager, GenerationContext, WorkPiecePipelineStage) a
été supprimé. Ce document décrit uniquement l'architecture active.
Concepts Clés
Pipeline (Façade Publique)
rayforge/pipeline/pipeline.py:40 — la classe avec laquelle le reste
de l'application communique. DocEditor, ViewManager, les widgets
UI et le code de test ne doivent dépendre que de Pipeline.
IntentController et IntentBuilder sont des détails
d'implémentation de la façade et peuvent changer sans préavis.
Pipeline possède l'intégration ArtifactStore : elle traduit les
sorties brutes de raygeo émises par son IntentController interne en
handles d'artefacts refcountés que l'UI et les chemins d'exportation
consomment, et expose la surface de signaux/propriétés que le reste
de l'application attend (état occupé, pause/reprise, recalcul,
changements de machine).
Signaux clés relayés par la façade :
| Signal | Signification |
|---|---|
processing_state_changed | Transitions occupé/inactif |
workpiece_artifact_ready | Un handle WorkPieceArtifact a été publié |
job_generation_finished | Un handle JobArtifact (G-code + ops + estimations) prêt |
job_time_updated | Estimation de temps agrégée modifiée pendant un rebuild |
data_stale | Reconstruction demandée mais en pause ou mode manuel |
visual_chunk_available | Fragment raster progressif pour mises à jour UI incrémentales |
IntentController
rayforge/pipeline/intent_controller.py:108 — possède une Intent
raygeo et le cycle de vie de reconstruction associé. Il écoute les
mêmes signaux Doc que l'ancien pipeline (descendant_updated,
descendant_transform_changed, descendant_added,
descendant_removed, job_assembly_invalidated) et reconstruit une
Intent raygeo à chaque modification du document.
À chaque reconstruction avec debounce (200 ms REBUILD_DEBOUNCE_MS) :
IntentBuilderest appelé pour produire une liste fraîche d'objetsNodeRequestà partir duDoccourant.- La nouvelle liste est encapsulée dans une
Intentraygeo viacreate_intent_from_nodes. Intent.updatecompare l'intention précédente avec la nouvelle en utilisant leversion_tokenpar nœud et supprime les entrées de cache obsolètes sur laPipelineraygeo partagée.- Quand
dispatch=True, la nouvelle intention est également exécutée viarun_intent; le callbackon_completedeffectue le filtre d'époque (élimine les résultats dont legeneration_idest antérieur à la génération actuelle du contrôleur) puis marshalle un rattachement DOM vers le thread principal de l'application via le gestionnaire de tâches partagé. - Le callback
on_batch_progressrelaye la progression agrégée aux auditeurs viaprogress_changed(marshalé vers le thread principal pour que les gestionnaires de signaux ne s'exécutent jamais sur un travailleur rayon).
La carte _key_to_item du contrôleur (reconstruite à chaque appel
réussi à IntentBuilder.build) permet au callback on_completed
avec filtre d'époque de rattacher les sorties au WorkPiece ou
Step d'origine sans reparcourir le Doc. Les clés de nœud sont
distribuées par forme :
| Clé de nœud | Rattaché à | Signal émis |
|---|---|---|
workpiece:{wp_uid}:{step_uid} | Le WorkPiece propriétaire | workpiece_artifact_ready |
step:{step_uid} | Le Step propriétaire | step_artifact_ready |
job | Le Doc | job_aggregate_ready |
job:encode | Le Doc | job_generation_finished |
IntentBuilder
rayforge/pipeline/intent_builder.py:133 — parcourt un Doc et
produit une liste plate d'objets NodeRequest avec clés stables
et jetons de version déterministes. Le builder est sans état :
chaque appel à build produit une liste fraîche et autonome adaptée
à l'encapsulation dans une Intent raygeo.
Clés Stables
workpiece:{wp_uid}:{step_uid}— un nœud de calcul par paire workpiece/step.step:{step_uid}— un nœud d'agrégat par step qui concatène les sorties de calcul des workpieces et applique les transformers par step.job— un nœud d'agrégat final liant toutes les sorties des steps avec des marqueurs de niveau job et des paramètres machine.job:machinexform— nœud de calcul de transformation machine qui consomme les ops en espace monde de l'agrégat job et produit des ops en espace machine (linéarisation de courbes, mappage d'axe rotatif, monde→machine, offsets WCS, Z-flip, AXIS_REPLACEMENT).job:encode— nœud de calcul d'encodeur qui consomme les ops du nœud de transformation machine et produit le code machine (G-code / sommet / texture).
Les formats de clé sont centralisés dans intent_builder.py pour que
le producteur et la carte de rattachement d'IntentController
soient toujours en accord.
Jetons de Version
Le cache raygeo est indexé uniquement par clé de nœud ; le
version_token est le seul signal d'invalidation. Les jetons sont
des condensés SHA-1 d'une représentation canonique des entrées qui
affectent la sortie d'un nœud (voir _hash_int,
intent_builder.py:1066) :
- Jetons de calcul hachent
(geometry_revision, wp_size, step_params, assembler_params, per_workpiece_transformers). Pour les scopes de step déclarant un transformer sensible à la position (voirStep.is_position_sensitive),transform_revisiondu workpiece et la révision de stock sont incluses dans le jeton ; sinon elles sont omises pour que les simples mouvements n'invalident pas les résultats de calcul du workpiece. - Jetons d'agrégat de step hachent
(upstream compute tokens, placements, step_params, per_step/per_workpiece transformers, position_sensitive()), plusstock_revquand le step est sensible à la position. - Jeton job intègre tous les jetons d'agrégat par step pour que tout changement amont (déplacement de workpiece, édition de transformer, modification de paramètre de step) se propage jusqu'au cache job/encode.
- Jeton de transformation machine intègre le jeton job plus
l'identité de la machine (
supports_curves,reverse_z_axis, configuration WCS, configuration du module rotatif par couche). - Jeton d'encode intègre le jeton de transformation machine plus
l'identité de l'encodeur (
driver_name,gcode_precision, étendues d'axes, ...).
Construction des Étapes
Chaque NodeRequest porte une StageSpec décrivant le travail que
raygeo doit effectuer pour ce nœud. Le builder produit :
StageSpec.Computepour chaque paire workpiece/step viaStep.build_compute_payload(machine_defaults, workpiece), qui retourne unPart(géométrie vectorielle ou source d'image) plus unComputePayload(spécification d'assembleur). Les transformers par workpiece (OverscanTransformer,BidirScanOffsetTransformer, ...) sont résolus viatransformer_registryen pyclasses Rust typées*Specet attachés au payload pour que l'étape de calcul Rust les applique après l'assemblage.StageSpec.Aggregatepour chaque step : unAggregateGrouppar nœud de calcul workpiece amont, entouré de marqueursWorkpieceStart/WorkpieceEnd, chaque entrée portant la matrice de placement monde et la taille physique du workpiece commetarget_dimensions. Les transformers par step (MultiPassTransformer,Optimize, ...) sont attachés àAggregateSpec.transformerspour que l'étape d'agrégat Rust les applique après concaténation.MachineParamsest peuplé depuis la machine résolue pour que l'estimation de temps de l'agrégat soit correcte.StageSpec.Aggregatepour le nœudjob: unAggregateGrouppar couche entouré de marqueursLayerStart/LayerEnd, chacun contenant unAggregateInputpar step visible ; l'agrégat entier est entouré deJobStart/JobEnd.MachineTransformSpecpourjob:machinexform: la matrice 4×4 monde→machine, les offsets WCS par défaut et par couche, les entréesRotaryMappingSpecpar couche, le drapeau de linéarisation de courbes et le drapeau Z-reverse, empaquetés dans une spécification sérialisable que l'étape RustMachineTransformComputeconsomme.EncodeSpecpourjob:encode: achemine les machines Grbl vers leGcodeSpecRust natif (compilé directement sur un thread rayon sans traverser le GIL) et toute autre machine vers unPythonEncoderencapsulant le callable d'encodeur spécifique au driver. L'encodeur lit les ops en espace machine depuis le nœud amontjob:machinexform.
Résolution de Stock
_resolve_stock_geometries (appelée une fois par build et mise en
cache sur le builder) retourne les géométries de limite de stock en
espace monde que les transformers comme CropTransformer utilisent
pour découper les ops par workpiece dans la zone de travail de la
machine ou dans des StockItems explicites. Les entrées StockItem
appartenant au Doc ont priorité ; le rectangle de la zone de travail
de la machine est utilisé comme solution de repli uniquement quand
aucun stock de Doc n'existe.
Pipeline raygeo & run_intent
La Pipeline de raygeo (raygeo.pipeline.execute.Pipeline) possède
le cache que Intent.update invalide. run_intent planifie les
nœuds de l'intention sur des threads travailleurs rayon sous le GIL
et invoque le callback on_completed par nœud et on_batch_progress
pour la progression agrégée. Les travaux lourds (calcul, raster,
agrégat, transformations machine, encodage) s'exécutent dans des
threads raygeo au lieu de sous-processus, ce qui est le changement
principal mentionné dans CHANGELOG 1.9.0.
ArtifactStore & Handles d'Artefacts
L'ancien ArtifactStore en mémoire partagée a été remplacé par un
magasin en processus avec comptage de références
(rayforge/pipeline/artifact/store.py:29). Tous les artefacts vivent
comme des objets Python simples dans un dict indexé par UUID ; les
handles portent l'UUID dans leur champ key plus les métadonnées
nécessaires au type d'artefact. Le cycle de vie est géré par comptage
de références via ArtifactStore.retain/release.
La façade Pipeline traduit les sorties raygeo en handles d'artefacts
sur le thread principal :
| Sortie (raygeo) | Artefact | Stocké sous tag |
|---|---|---|
| Ops par workpiece-step | WorkPieceArtifact | wp |
| Ops agrégées par step | StepOpsArtifact | step |
| Agrégat job + encode | JobArtifact | job |
JobArtifact porte les Ops en espace monde, la distance totale,
l'estimation de temps, l'EncodedOutput (texte plus carte
op→code machine) et — quand des modules rotatifs sont
configurés — des ops mappées cinématiquement pour l'aperçu 3D.
IDs de Génération & Filtre d'Époque
Chaque reconstruction incrémente IntentController.generation_id.
Chaque nœud terminé porte la génération dont il est issu. Le callback
on_completed compare le generation_id du nœud avec la génération
actuelle du contrôleur et supprime silencieusement les résultats
obsolètes, de sorte que les sorties périmées d'une reconstruction
antérieure ne soient jamais rattachées au DOM.
Pause, Reprise & Mode Manuel
Pipeline.pause()/resume()incrémente/décrémente un compteur de pause sur le contrôleur. Pendant la pause, les modifications du Doc positionnent un drapeaudata_stale(et émettentdata_stale) au lieu de planifier une reconstruction ; à la reprise, le drapeau est effacé et une reconstruction est planifiée siauto_rebuildest activé.Pipeline.auto_pipeline=False(mode manuel) : le recalcul est déclenché explicitement viaPipeline.recalculate()plutôt qu'automatiquement à chaque modification du Doc.
Stratégie d'Invalidation
L'invalidation est implicite et pilotée par les jetons : tout
changement qui affecte les entrées d'un nœud amène le builder à
produire un version_token différent pour la clé de ce nœud.
Intent.update supprime l'entrée de cache obsolète et raygeo
réexécute uniquement ce nœud (et ses consommateurs aval).
| Type de Changement | Effet sur les Jetons |
|---|---|
| Géométrie / paramètres | Nouveaux jetons de calcul workpiece cascade vers step, job, machinexform, encode |
| Position / rotation | Jetons de calcul workpiece inchangés sauf si le step est sensible à la position ; les jetons d'agrégat step changent toujours à cause des placements intégrés, ce qui cascade vers job/encode |
| Changement de taille | Comme géométrie : les jetons cascade depuis les paires workpiece-step vers le haut |
| Stock items visibles/déplacés/ajoutés | Affecte stock_rev (intégré dans les jetons de calcul et d'agrégat des steps sensibles à la position) |
| Configuration machine | Tous les jetons job:machinexform et job:encode changent ; les jetons de calcul/agrégat step changent si kerf_mm/cut_speed/tête laser/tolérance d'arc/supports_curves/supports_arcs changent |
Ventilation Détaillée
Entrée
Le processus commence avec le Modèle Doc, qui contient :
- WorkPieces : Éléments de conception individuels (SVG, images) placés sur le canvas
- Steps : Instructions de traitement (Contour, Raster, etc.) avec
paramètres, organisées dans un
Workflowpar couche - Layers : Regroupement de workpieces, chacun avec son propre workflow, WCS et configuration rotative
- StockItems : Limites de stock explicites optionnelles utilisées par les transformers sensibles à la position (ex. CropTransformer)
Orchestration Python
Pipeline (Façade)
La classe Pipeline :
- Écoute les modifications du modèle Doc via des signaux (relayés à
travers l'
IntentController) - Debounce les changements (délai de réconciliation de 200 ms)
- Coordonne avec l'
IntentControllerpour déclencher la régénération - Gère l'état global de traitement et la détection d'occupation
- Supporte la pause/reprise pour les opérations par lots
- Supporte le mode manuel (
auto_pipeline=False) où le recalcul est déclenché explicitement - Connecte les signaux entre les composants et les relaie aux consommateurs
- Publie les handles d'artefacts refcountés dans l'
ArtifactStore
IntentController
L'IntentController :
- Possède une
Intentraygeo et le cycle de vie de reconstruction - Reconstruit une intention fraîche à chaque changement de Doc avec debounce
- Exécute l'intention via
run_intentquanddispatch=True - Filtre les résultats obsolètes par
generation_id(filtre d'époque) - Marshalle les rattachements DOM vers le thread principal via le gestionnaire de tâches partagé
IntentBuilder
L'IntentBuilder est sans état ; chaque appel à build parcourt le
Doc et produit un NodeRequest par paire workpiece/step, un
agrégat par step, et les nœuds job, job:machinexform et
job:encode. Voir Clés Stables,
Jetons de Version et
Construction des Étapes ci-dessus.
Pipeline raygeo
run_intent planifie l'exécution des nœuds sur des threads
travailleurs rayon sous le GIL. L'instance partagée RaygeoPipeline
contient le cache de nœuds indexé par clé de nœud ; Intent.update
est le seul point d'entrée d'invalidation. Calcul, raster, shrinkwrap,
wavefront, contour, rendu de vue et transformation/encodage machine
s'exécutent tous dans des threads raygeo.
Génération d'Artefacts
WorkPieceArtifacts
Générés pour chaque combinaison (WorkPiece, Step). Contient :
- Toolpaths (
Ops) dans le système de coordonnées local du workpiece - Drapeau d'évolutivité et dimensions source pour les ops indépendantes de la résolution
- ID de génération
Les grands workpieces raster sont traités par incréments en fragments
(relayés via visual_chunk_available), permettant un retour visuel
progressif pendant la génération.
StepOpsArtifacts
Générés pour chaque Step, consommant tous les WorkPieceArtifacts associés :
Opscombinés pour tous les workpieces en coordonnées monde- Transformers par step appliqués (
Optimize,MultiPass, ...)
JobArtifact
Généré quand du G-code est nécessaire, consommant l'agrégat job et
le nœud job:encode :
- Code machine final (G-code ou format spécifique au driver) via
EncodedOutput(texte + carte op→code machine) Opsen espace monde pour simulation et lecture- Estimation de temps haute fidélité et distance totale
- Ops mappées rotativement pour l'aperçu 3D quand des modules rotatifs sont configurés
Couche de Vue 2D (Découplée)
Le ViewManager est découplé du pipeline de données. Il gère le
rendu pour le canvas 2D basé sur l'état de l'UI.
RenderContext
Contient les paramètres de vue actuels (pixels par millimètre, décalage du viewport, options d'affichage).
WorkPieceViewArtifacts
Le ViewManager crée des WorkPieceViewArtifacts qui rastérisent
les WorkPieceArtifacts dans l'espace écran, appliquent le
RenderContext actuel, et sont mis en cache et mis à jour quand le
contexte ou la source change. Le re-rendu est limité (intervalle de
33 ms) et à concurrence limitée ; l'assemblage progressif de
fragments fournit des mises à jour visuelles incrémentales. Le
ViewManager indexe les vues par (workpiece_uid, step_uid) pour
permettre la visualisation des états intermédiaires d'un workpiece à
travers plusieurs steps.
Couche 3D / Simulateur (Découplée)
Le système de visualisation et de simulation 3D est découplé du
pipeline de données, suivant un modèle similaire au ViewManager. Il
se compose :
- D'un Scene Compiler qui s'exécute dans un sous-processus pour
convertir les ops
JobArtifacten données de sommet prêtes pour le GPU - D'un OpPlayer qui rejoue les ops du job pour une simulation machine en temps réel avec des contrôles de lecture
Les deux consomment le JobArtifact produit par le pipeline.
CompiledSceneArtifact
Le Scene Compiler produit un CompiledSceneArtifact contenant :
- Couches de sommets : Buffers de sommets powered/travel/zero-power avec décalages par commande pour révélation progressive
- Couches de texture : Cartes de puissance de lignes de balayage rastérisées pour l'aperçu de gravure
- Couches de superposition : Segments de puissance de lignes de balayage pour surbrillance en temps réel
- Support pour la géométrie rotative (enveloppée cylindriquement)
Pipeline de Compilation
- Canvas3D écoute les signaux
job_generation_finished - Quand un nouveau job est prêt, il planifie la compilation de scène dans un sous-processus
- Le sous-processus lit le
JobArtifactdepuis le magasin et compile les ops en données de sommet GPU - La scène compilée est reprise et téléchargée vers les moteurs de rendu GPU
OpPlayer (Backend du Simulateur)
L'OpPlayer parcourt les ops du job commande par commande, en
maintenant un MachineState qui suit la position, l'état du laser et
les axes auxiliaires. Cela pilote la lecture du canvas 3D (révélation
progressive du trajet d'outil), la visualisation de la position de la
tête de machine et du faisceau laser, et le pas à pas par commande
pour le curseur de lecture.
Consommateurs
| Consommateur | Utilise | But |
|---|---|---|
| Canvas 2D | WorkPieceViewArtifacts | Affiche les workpieces en espace écran |
| Canvas 3D | CompiledSceneArtifact | Affiche le job complet en 3D avec lecture |
| Machine | JobArtifact (code machine) | Sortie de fabrication |
Décisions Architecturales Clés
-
Ordonnancement basé sur les Intentions : Au lieu d'un DAG Python explicite avec des ordonnanceurs résidents Python, le pipeline déclare quoi calculer (une
IntentdeNodeRequests avec des clés stables et des jetons de version) et laisserun_intentde raygeo ordonnancer le travail sur des threads rayon. L'invalidation du cache est purement pilotée par les jetons viaIntent.update. -
Façade + Contrôleur Interne :
Pipelineest la seule surface publique ;IntentControlleretIntentBuildersont des détails d'implémentation. Cela maintient le contrat public de signaux/propriétés stable tout en permettant aux internes d'orchestration d'évoluer. -
Magasin d'Artefacts en Processus : Le remplacement du magasin multiprocessus en mémoire partagée par un dictionnaire en processus avec comptage de références supprime la complexité IPC et de transfert de propriété tout en conservant le contrat de handle/cycle de vie sur lequel reposent l'UI et les chemins d'exportation.
-
IDs de Génération : Chaque reconstruction incrémente un ID de génération ; chaque nœud terminé porte sa génération d'origine. Le filtre d'époque de
on_completedsupprime silencieusement les résultats obsolètes, de sorte que les sorties périmées ne soient jamais rattachées au DOM. -
Rattachement sur le Thread Principal : Les callbacks raygeo (
on_completed,on_batch_progress) se déclenchent sur des threads travailleurs rayon sous le GIL ; le contrôleur marshalle chaque callback touchant le DOM vers le thread principal de l'application via le gestionnaire de tâches partagé, de sorte que les gestionnaires de signaux ne s'exécutent jamais sur un travailleur. -
Séparation des Couches de Vue : Le canvas 2D (
ViewManager) et le canvas 3D (Scene Compiler / OpPlayer) sont tous deux découplés du pipeline de données. Chacun est piloté par des signaux du pipeline plutôt que de faire partie de l'intention. -
Invalidation Pilotée par les Jetons : Il n'y a pas de table d'invalidation explicite. Le builder produit des jetons de version SHA-1 canoniques ; tout changement d'entrée produit un jeton différent, qu'
Intent.updateutilise pour supprimer exactement les entrées de cache affectées. -
Réconciliation avec Debounce : Les modifications du Doc sont regroupées avec un debounce de 200 ms (
REBUILD_DEBOUNCE_MS) pour éviter les cycles excessifs du pipeline pendant les modifications rapides.