Arquitetura do Pipeline
Este documento descreve o pipeline que transforma um modelo Doc em
G-code executável por máquina. Desde a reescrita 1.9.0, o pipeline é
construído sobre intenções raygeo: uma descrição declarativa do
trabalho que o lado Rust deve executar, acoplada a uma fina camada de
orquestração Python e um armazenamento de artefatos em processo com
contagem de referências.
O anterior DAG multiprocesso (DagScheduler, PipelineGraph,
ArtifactManager, GenerationContext, WorkPiecePipelineStage) foi
removido. Este documento descreve apenas a arquitetura ativa.
Conceitos Principais
Pipeline (Fachada Pública)
rayforge/pipeline/pipeline.py:40 — a classe com a qual o resto da
aplicação se comunica. DocEditor, ViewManager, widgets de UI e
código de teste devem depender apenas de Pipeline.
IntentController e IntentBuilder são detalhes de implementação da
fachada e podem mudar sem aviso prévio.
Pipeline possui a integração com ArtifactStore: traduz as saídas
cruas do raygeo emitidas por seu IntentController interno em handles
de artefatos com contagem de referências que a UI e os caminhos de
exportação consomem, e expõe a superfície de sinais/propriedades que
o resto da aplicação espera (estado ocupado, pausa/retomar,
recálculo, mudanças de máquina).
Sinais chave retransmitidos pela fachada:
| Sinal | Significado |
|---|---|
processing_state_changed | Transições ocupado/inativo |
workpiece_artifact_ready | Um handle de WorkPieceArtifact foi publicado |
job_generation_finished | Um handle de JobArtifact (G-code + ops + estimativas) pronto |
job_time_updated | Estimativa de tempo agregada alterada durante um rebuild |
data_stale | Reconstrução solicitada mas pausada ou modo manual |
visual_chunk_available | Fragmento de raster progressivo para atualizações incrementais de UI |
IntentController
rayforge/pipeline/intent_controller.py:108 — possui uma Intent do
raygeo e o ciclo de vida de reconstrução ao redor. Ele escuta os
mesmos sinais de Doc que o pipeline legado usava (descendant_updated,
descendant_transform_changed, descendant_added,
descendant_removed, job_assembly_invalidated) e reconstrói uma
Intent raygeo sempre que o documento muda.
Em cada reconstrução com debounce (200 ms REBUILD_DEBOUNCE_MS):
IntentBuilderé chamado para produzir uma lista fresca de objetosNodeRequesta partir doDocatual.- A nova lista é encapsulada em uma
Intentraygeo viacreate_intent_from_nodes. Intent.updatecompara a intenção anterior com a nova usando oversion_tokenpor nó e remove entradas de cache obsoletas naPipelineraygeo compartilhada.- Quando
dispatch=True, a nova intenção também é executada viarun_intent; o callbackon_completedrealiza o filtro de época (descarta resultados cujogeneration_idseja anterior à geração atual do controlador) e então marshalla um reattachment do DOM de volta à thread principal da aplicação através do gerenciador de tarefas compartilhado. - O callback
on_batch_progressretransmite o progresso agregado aos ouvintes viaprogress_changed(marshalled para a thread principal para que os manipuladores de sinais nunca executem em um trabalhador rayon).
O mapa _key_to_item do controlador (reconstruído a cada chamada bem-
sucedida a IntentBuilder.build) permite que o callback on_completed
com filtro de época reassocie as saídas ao WorkPiece ou Step
original sem percorrer novamente o Doc. As chaves de nó são
despachadas por forma:
| Chave de nó | Reassociado a | Sinal emitido |
|---|---|---|
workpiece:{wp_uid}:{step_uid} | O WorkPiece proprietário | workpiece_artifact_ready |
step:{step_uid} | O Step proprietário | step_artifact_ready |
job | O Doc | job_aggregate_ready |
job:encode | O Doc | job_generation_finished |
IntentBuilder
rayforge/pipeline/intent_builder.py:133 — percorre um Doc e
produz uma lista plana de objetos NodeRequest com chaves
estáveis e tokens de versão determinísticos. O builder não tem
estado: cada chamada a build produz uma lista fresca e
autocontida adequada para encapsular em uma Intent raygeo.
Chaves Estáveis
workpiece:{wp_uid}:{step_uid}— um nó de computação por par workpiece/step.step:{step_uid}— um nó agregado por step que concatena as saídas de computação dos workpieces e aplica transformers por step.job— um nó agregado final ligando todas as saídas dos steps com marcadores de nível de job e parâmetros de máquina.job:machinexform— nó de computação de transformação de máquina que consome as ops em espaço mundial do agregado de job e produz ops em espaço de máquina (linearização de curvas, mapeamento de eixo rotatório, mundo→máquina, offsets WCS, Z-flip, AXIS_REPLACEMENT).job:encode— nó de computação de codificador que consome as ops do nó de transformação de máquina e produz o código de máquina (G-code / vértice / textura).
Os formatos de chave estão centralizados em intent_builder.py para
que o produtor e o mapa de reattachment do IntentController sempre
concordem.
Tokens de Versão
O cache do raygeo é indexado apenas por chave de nó; o version_token
é o único sinal de invalidação. Tokens são resumos SHA-1 de uma
representação canônica das entradas que afetam a saída de um nó (ver
_hash_int, intent_builder.py:1066):
- Tokens de computação hasheiam
(geometry_revision, wp_size, step_params, assembler_params, per_workpiece_transformers). Para escopos de step que declaram um transformer sensível à posição (verStep.is_position_sensitive),transform_revisiondo workpiece e a revisão de stock são incluídas no token; caso contrário, são omitidas para que movimentos puros não invalidem resultados de computação do workpiece. - Tokens de agregado de step hasheiam
(upstream compute tokens, placements, step_params, per_step/per_workpiece transformers, position_sensitive()), maisstock_revquando o step é sensível à posição. - Token de job incorpora todos os tokens de agregado por step para que qualquer mudança upstream (movimento de workpiece, edição de transformer, alteração de parâmetro de step) se propague até o cache de job/encode.
- Token de transformação de máquina incorpora o token de job mais
a identidade da máquina (
supports_curves,reverse_z_axis, configuração WCS, configuração de módulo rotatório por camada). - Token de encode incorpora o token de transformação de máquina
mais a identidade do codificador (
driver_name,gcode_precision, extensões de eixos, ...).
Construção de Estágios
Cada NodeRequest carrega uma StageSpec descrevendo o trabalho que
o raygeo deve realizar para aquele nó. O builder produz:
StageSpec.Computepara cada par workpiece/step viaStep.build_compute_payload(machine_defaults, workpiece), que retorna umPart(geometria vetorial ou fonte de imagem) mais umComputePayload(especificação de montador). Os transformers por workpiece (OverscanTransformer,BidirScanOffsetTransformer, ...) são resolvidos viatransformer_registryem pyclasses Rust tipadas*Spece anexados ao payload para que o estágio de computação Rust os aplique após a montagem.StageSpec.Aggregatepara cada step: umAggregateGrouppor nó de computação de workpiece upstream, envolto por marcadoresWorkpieceStart/WorkpieceEnd, com cada entrada carregando a matriz de colocação mundial e o tamanho físico do workpiece comotarget_dimensions. Transformers por step (MultiPassTransformer,Optimize, ...) são anexados aAggregateSpec.transformerspara que o estágio de agregação Rust os aplique após a concatenação.MachineParamsé populado a partir da máquina resolvida para que a estimativa de tempo do agregado seja correta.StageSpec.Aggregatepara o nójob: umAggregateGrouppor camada envolto por marcadoresLayerStart/LayerEnd, cada um contendo umAggregateInputpor step visível; todo o agregado é envolto porJobStart/JobEnd.MachineTransformSpecparajob:machinexform: a matriz 4×4 mundo→máquina, offsets WCS padrão e por camada, entradasRotaryMappingSpecpor camada, sinalizador de linearização de curvas e sinalizador Z-reverse, empacotados em uma especificação serializável que o estágio RustMachineTransformComputeconsome.EncodeSpecparajob:encode: roteia máquinas Grbl para oGcodeSpecRust nativo (compilado diretamente em uma thread rayon sem cruzar o GIL) e qualquer outra máquina para umPythonEncoderencapsulando o callable de codificador específico do driver. O codificador lê ops em espaço de máquina do nó upstreamjob:machinexform.
Resolução de Stock
_resolve_stock_geometries (chamada uma vez por build e cacheada
no builder) retorna as geometrias de limite de stock em espaço mundial
que transformers como CropTransformer usam para recortar ops por
workpiece na área de trabalho da máquina ou em StockItems explícitos.
Entradas StockItem pertencentes ao Doc têm prioridade; o retângulo
da área de trabalho da máquina é usado como fallback apenas quando
não existe stock no Doc.
Pipeline raygeo & run_intent
A Pipeline do raygeo (raygeo.pipeline.execute.Pipeline) possui o
cache que Intent.update invalida. run_intent agenda os nós da
intenção em threads trabalhadores rayon sob o GIL e invoca o callback
on_completed por nó e on_batch_progress para progresso agregado.
Trabalho pesado (computação, raster, agregação, transformações de
máquina, codificação) executa em threads raygeo em vez de subprocessos,
que é a mudança principal destacada no CHANGELOG 1.9.0.
ArtifactStore & Handles de Artefatos
O antigo ArtifactStore de memória compartilhada foi substituído por
um armazenamento em processo com contagem de referências
(rayforge/pipeline/artifact/store.py:29). Todos os artefatos vivem
como objetos Python simples em um dicionário indexado por UUID;
handles carregam o UUID em seu campo key mais quaisquer metadados
que o tipo de artefato necessite. O ciclo de vida é gerenciado por
contagem de referências via ArtifactStore.retain/release.
A fachada Pipeline traduz as saídas raygeo em handles de artefatos
na thread principal:
| Saída (raygeo) | Artefato | Armazenado sob tag |
|---|---|---|
| Ops por workpiece-step | WorkPieceArtifact | wp |
| Ops agregadas por step | StepOpsArtifact | step |
| Agregado de job + encode | JobArtifact | job |
JobArtifact carrega as Ops em espaço mundial, distância total,
estimativa de tempo, o EncodedOutput (texto mais mapa
op→código de máquina) e — quando módulos rotatórios estão
configurados — ops mapeadas cinematicamente para a pré-visualização 3D.
IDs de Geração & Filtro de Época
Cada reconstrução incrementa IntentController.generation_id. Cada
nó completado carrega a geração da qual foi criado. O callback
on_completed compara o generation_id do nó com a geração atual do
controlador e descarta silenciosamente resultados obsoletos, para que
saídas antigas de uma reconstrução anterior nunca sejam reassociadas
ao DOM.
Pausa, Retomar & Modo Manual
Pipeline.pause()/resume()incrementa/decrementa um contador de pausa no controlador. Enquanto pausado, mudanças no Doc definem uma bandeiradata_stale(e emitemdata_stale) em vez de agendar uma reconstrução; ao retomar, a bandeira é limpa e uma reconstrução é agendada seauto_rebuildestiver habilitado.Pipeline.auto_pipeline=False(modo manual): o recálculo é acionado explicitamente viaPipeline.recalculate()em vez de automaticamente a cada mudança no Doc.
Estratégia de Invalidação
A invalidação é implícita e orientada a tokens: qualquer mudança que
afete as entradas de um nó faz o builder produzir um version_token
diferente para a chave daquele nó. Intent.update remove a entrada
de cache obsoleta e o raygeo reexecuta apenas aquele nó (e seus
consumidores downstream).
| Tipo de Mudança | Efeito nos Tokens |
|---|---|
| Geometria / parâmetros | Novos tokens de computação de workpiece cascateiam para step, job, machinexform, encode |
| Posição / rotação | Tokens de computação de workpiece inalterados a menos que o step seja sensível à posição; tokens de agregado de step sempre mudam devido a posicionamentos incorporados, o que cascateia para job/encode |
| Mudança de tamanho | Igual à geometria: tokens cascateiam dos pares workpiece-step para cima |
| Stock items visíveis/movidos/adicionados | Afeta stock_rev (incorporado nos tokens de computação e agregado de steps sensíveis à posição) |
| Configuração de máquina | Todos os tokens job:machinexform e job:encode mudam; tokens de computação/agregado de step mudam se kerf_mm/cut_speed/cabeçalho laser/tolerância de arco/supports_curves/supports_arcs mudarem |
Detalhamento
Entrada
O processo começa com o Modelo Doc, que contém:
- WorkPieces: Elementos de design individuais (SVGs, imagens) colocados no canvas
- Steps: Instruções de processamento (Contorno, Raster, etc.) com
configurações, organizadas em um
Workflowpor camada - Layers: Agrupamento de workpieces, cada um com seu próprio workflow, WCS e configuração rotatória
- StockItems: Limites de stock explícitos opcionais usados por transformers sensíveis à posição (ex. CropTransformer)
Orquestração Python
Pipeline (Fachada)
A classe Pipeline:
- Escuta mudanças no modelo Doc através de sinais (retransmitidos
através do
IntentController) - Debounceia mudanças (atraso de reconciliação de 200 ms)
- Coordena com o
IntentControllerpara acionar a regeneração - Gerencia o estado geral de processamento e a detecção de ocupado
- Suporta pausa/retomar para operações em lote
- Suporta modo manual (
auto_pipeline=False) onde o recálculo é acionado explicitamente - Conecta sinais entre componentes e os retransmite aos consumidores
- Publica handles de artefatos com contagem de referências no
ArtifactStore
IntentController
O IntentController:
- Possui uma
Intentraygeo e o ciclo de vida de reconstrução - Reconstrói uma intenção fresca a cada mudança de Doc com debounce
- Executa a intenção via
run_intentquandodispatch=True - Filtra resultados obsoletos por
generation_id(filtro de época) - Marshalla reattachments do DOM para a thread principal através do gerenciador de tarefas compartilhado
IntentBuilder
O IntentBuilder não tem estado; cada chamada a build percorre o
Doc e produz um NodeRequest por par workpiece/step, um agregado
por step, e os nós job, job:machinexform e job:encode. Veja
Chaves Estáveis, Tokens de Versão
e Construção de Estágios acima.
Pipeline raygeo
run_intent agenda a execução de nós em threads trabalhadores rayon
sob o GIL. A instância compartilhada RaygeoPipeline mantém o cache
de nós indexado por chave de nó; Intent.update é o único ponto de
entrada de invalidação. Computação, raster, shrinkwrap, wavefront,
contorno, renderização de visualização e transformação/codificação de
máquina executam todos em threads raygeo.
Geração de Artefatos
WorkPieceArtifacts
Gerados para cada combinação (WorkPiece, Step). Contém:
- Toolpaths (
Ops) no sistema de coordenadas local do workpiece - Bandeira de escalabilidade e dimensões de origem para ops independentes de resolução
- ID de geração
Workpieces de raster grandes são processados incrementalmente em
fragmentos (retransmitidos via visual_chunk_available), permitindo
feedback visual progressivo durante a geração.
StepOpsArtifacts
Gerados para cada Step, consumindo todos os WorkPieceArtifacts relacionados:
Opscombinados para todos os workpieces em coordenadas de espaço mundial- Transformers por step aplicados (
Optimize,MultiPass, ...)
JobArtifact
Gerado quando G-code é necessário, consumindo o agregado job e o nó
job:encode:
- Código de máquina final (G-code ou formato específico do driver)
via
EncodedOutput(texto + mapa op→código de máquina) Opsem espaço mundial para simulação e reprodução- Estimativa de tempo de alta fidelidade e distância total
- Ops mapeadas rotatoriamente para pré-visualização 3D quando módulos rotatórios estão configurados
Camada de Visualização 2D (Desacoplada)
O ViewManager está desacoplado do pipeline de dados. Ele gerencia a
renderização para o canvas 2D baseado no estado da UI.
RenderContext
Contém os parâmetros de visualização atuais (pixels por milímetro, offset do viewport, opções de exibição).
WorkPieceViewArtifacts
O ViewManager cria WorkPieceViewArtifacts que rasterizam
WorkPieceArtifacts para o espaço de tela, aplicam o RenderContext
atual e são armazenados em cache e atualizados quando o contexto ou a
fonte mudam. A re-renderização é limitada (intervalo de 33 ms) e com
limite de concorrência; a junção progressiva de fragmentos fornece
atualizações visuais incrementais. O ViewManager indexa vistas por
(workpiece_uid, step_uid) para suportar a visualização de estados
intermediários de um workpiece através de múltiplos steps.
Camada 3D / Simulador (Desacoplada)
O sistema de visualização e simulação 3D está desacoplado do pipeline
de dados, seguindo um padrão similar ao ViewManager. Consiste em:
- Um Scene Compiler que executa em um subprocesso para converter
ops
JobArtifactem dados de vértice prontos para GPU - Um OpPlayer que reproduz as ops do job para simulação de máquina em tempo real com controles de reprodução
Ambos consomem o JobArtifact produzido pelo pipeline.
CompiledSceneArtifact
O Scene Compiler produz um CompiledSceneArtifact contendo:
- Camadas de vértice: Buffers de vértice powered/travel/zero-power com offsets por comando para revelação progressiva
- Camadas de textura: Mapas de potência de linhas de varredura rasterizados para pré-visualização de gravação
- Camadas de sobreposição: Segmentos de potência de linhas de varredura para destaque em tempo real
- Suporte para geometria rotatória (envolta em cilindro)
Pipeline de Compilação
- Canvas3D escuta sinais
job_generation_finished - Quando um novo job está pronto, ele agenda a compilação de cena em um subprocesso
- O subprocesso lê o
JobArtifactdo armazenamento e compila as ops em dados de vértice para GPU - A cena compilada é adotada de volta e enviada para os renderizadores GPU
OpPlayer (Backend do Simulador)
O OpPlayer percorre as ops do job comando por comando, mantendo um
MachineState que rastreia posição, estado do laser e eixos
auxiliares. Isso impulsiona a reprodução do canvas 3D (revelação
progressiva do toolpath), a visualização da posição da cabeça da
máquina e do feixe de laser, e o avanço por comando para o controle
deslizante de reprodução.
Consumidores
| Consumidor | Usa | Propósito |
|---|---|---|
| Canvas 2D | WorkPieceViewArtifacts | Renderiza workpieces no espaço da tela |
| Canvas 3D | CompiledSceneArtifact | Renderiza o job completo em 3D com reprodução |
| Máquina | JobArtifact (código máquina) | Saída de fabricação |
Decisões Arquiteturais Chave
-
Agendamento Baseado em Intenções: Em vez de um DAG Python explícito com agendadores residentes em Python, o pipeline declara o que computar (uma
IntentdeNodeRequests com chaves estáveis e tokens de versão) e deixarun_intentdo raygeo agendar o trabalho em threads rayon. A invalidação de cache é puramente orientada a tokens viaIntent.update. -
Fachada + Controlador Interno:
Pipelineé a única superfície pública;IntentControllereIntentBuildersão detalhes de implementação. Isso mantém o contrato público de sinais/propriedades estável enquanto permite que os detalhes internos de orquestração evoluam. -
Armazenamento de Artefatos em Processo: Substituir o armazenamento de memória compartilhada multiprocesso por um dicionário em processo com contagem de referências remove a complexidade de IPC e transferência de propriedade, mantendo o contrato de handle/ciclo de vida do qual a UI e os caminhos de exportação dependem.
-
IDs de Geração: Cada reconstrução incrementa um ID de geração; cada nó completado carrega sua geração de origem. O filtro de época de
on_completeddescarta silenciosamente resultados obsoletos, para que saídas antigas nunca sejam reassociadas ao DOM. -
Reattachment na Thread Principal: Os callbacks raygeo (
on_completed,on_batch_progress) disparam em threads trabalhadores rayon sob o GIL; o controlador marshalla cada callback que toca o DOM para a thread principal da aplicação através do gerenciador de tarefas compartilhado, para que os manipuladores de sinais nunca executem em um trabalhador. -
Separação das Camadas de Visualização: Tanto o canvas 2D (
ViewManager) quanto o canvas 3D (Scene Compiler / OpPlayer) estão desacoplados do pipeline de dados. Cada um é orientado por sinais do pipeline em vez de fazer parte da intenção. -
Invalidação Orientada a Tokens: Não há uma tabela de invalidação explícita. O builder produz tokens de versão SHA-1 canônicos; qualquer mudança de entrada produz um token diferente, que
Intent.updateusa para remover exatamente as entradas de cache afetadas. -
Reconciliação com Debounce: Mudanças no Doc são agrupadas com um debounce de 200 ms (
REBUILD_DEBOUNCE_MS) para evitar ciclos excessivos de pipeline durante edições rápidas.