跳转到主要内容

管道架构

本文档描述了将 Doc 模型转换为机器可执行 G-code 的管道。自 1.9.0 重写以来,该管道基于 raygeo 意图 构建:这是一种声明式描述,说明 Rust 端应执行的工作,配合一个轻量级的 Python 编排层和一个进程内引用计数的工件存储。

之前的多进程 DAG(DagSchedulerPipelineGraphArtifactManagerGenerationContextWorkPiecePipelineStage)已被移除。本文档仅描述当前的活跃架构。

核心概念

Pipeline(公共外观)

rayforge/pipeline/pipeline.py:40 — 应用程序其他部分与之通信的类。DocEditorViewManager、UI 小部件和测试代码应仅依赖于 PipelineIntentControllerIntentBuilder 是外观的实现细节,可能随时更改,恕不另行通知。

Pipeline 拥有 ArtifactStore 集成:它将内部 IntentController 发出的原始 raygeo 输出转换为 UI 和导出路径所消费的引用计数工件句柄,并公开应用程序其余部分所期望的信号/属性接口(忙状态、暂停/恢复、重新计算、机器更改)。

外观转发的关键信号:

信号含义
processing_state_changed忙/空闲状态转换
workpiece_artifact_ready已发布 WorkPieceArtifact 句柄
job_generation_finishedJobArtifact 句柄(G-code + ops + 估计)已就绪
job_time_updated重建期间聚合时间估计已更改
data_stale请求重建但当前已暂停或处于手动模式
visual_chunk_available渐进式光栅块,用于增量 UI 更新

IntentController

rayforge/pipeline/intent_controller.py:108 — 拥有一个 raygeo Intent 及周围的重建生命周期。它监听旧管道使用的相同冒泡 Doc 信号(descendant_updateddescendant_transform_changeddescendant_addeddescendant_removedjob_assembly_invalidated),并在文档更改时重建 raygeo Intent

在每次防抖重建(200 毫秒 REBUILD_DEBOUNCE_MS)时:

  1. 调用 IntentBuilder 从当前 Doc 生成一个全新的 NodeRequest 对象列表。
  2. 新列表通过 create_intent_from_nodes 包装成 raygeo Intent
  3. Intent.update 使用每个节点的 version_token 比较前后意图,并在共享的 raygeo Pipeline 上驱逐所有过时的缓存条目。
  4. dispatch=True 时,新意图也通过 run_intent 执行;on_completed 回调执行 epoch 过滤器(丢弃 generation_id 早于控制器当前世代的结果),然后通过共享的任务管理器将 DOM 重新附加编组回应用程序主线程。
  5. on_batch_progress 回调通过 progress_changed 将聚合进度中继给监听器(编组到主线程,以便信号处理程序永远不会在 rayon 工作线程上运行)。

控制器的 _key_to_item 映射(在每次成功的 IntentBuilder.build 调用时重建)允许带 epoch 过滤器的 on_completed 回调将输出重新附加到原始 WorkPieceStep,而无需重新遍历 Doc。节点键按形状分发:

节点键重新附加到发出的信号
workpiece:{wp_uid}:{step_uid}所属 WorkPieceworkpiece_artifact_ready
step:{step_uid}所属 Stepstep_artifact_ready
jobDocjob_aggregate_ready
job:encodeDocjob_generation_finished

IntentBuilder

rayforge/pipeline/intent_builder.py:133 — 遍历 Doc 并生成一个扁平的 NodeRequest 对象列表,带有稳定键确定性版本令牌。构建器是无状态的:每次调用 build 都会生成一个全新的、自包含的列表,适合包装到 raygeo Intent 中。

稳定键

  • workpiece:{wp_uid}:{step_uid} — 每个工件/步骤对一个计算节点。
  • step:{step_uid} — 每个步骤一个聚合节点,连接工件计算输出并应用按步骤转换器。
  • job — 一个最终聚合节点,将所有步骤输出与作业级标记和机器参数链接起来。
  • job:machinexform — 机器变换计算节点,消费作业聚合的世界空间 ops 并生成机器空间 ops(曲线线性化、旋转轴映射、世界→机器、WCS 偏移、Z 翻转、AXIS_REPLACEMENT)。
  • job:encode — 编码器计算节点,消费机器变换节点的 ops 并生成机器代码(G-code / 顶点 / 纹理)。

键格式集中在 intent_builder.py 中,以便生产者和 IntentController 重新附加映射始终保持一致。

版本令牌

raygeo 的缓存仅按节点键索引;version_token 是唯一的失效信号。令牌是影响节点输出的输入规范表示的 SHA-1 摘要(参见 _hash_intintent_builder.py:1066):

  • 计算令牌 哈希 (geometry_revision, wp_size, step_params, assembler_params, per_workpiece_transformers)。对于声明了位置敏感转换器的步骤范围(参见 Step.is_position_sensitive),工件的 transform_revision 和 stock 修订被折叠到令牌中;否则它们被省略,以便纯移动不会使工件计算结果失效。
  • 步骤聚合令牌 哈希 (upstream compute tokens, placements, step_params, per_step/per_workpiece transformers, position_sensitive()),加上步骤位置敏感时的 stock_rev
  • 作业令牌 折叠所有按步骤聚合令牌,以便任何上游更改(工件移动、转换器编辑、步骤参数更改)都会传播到作业/编码缓存。
  • 机器变换令牌 折叠作业令牌加上机器标识(supports_curvesreverse_z_axis、WCS 配置、每层旋转模块配置)。
  • 编码令牌 折叠机器变换令牌加上编码器标识(driver_namegcode_precision、轴范围等)。

阶段构建

每个 NodeRequest 携带一个 StageSpec,描述 raygeo 应为该节点执行的工作。构建器生成:

  • 每个工件/步骤对的 StageSpec.Compute,通过 Step.build_compute_payload(machine_defaults, workpiece),返回一个 Part(矢量几何或图像源)加上一个 ComputePayload(装配器规范)。按工件转换器(OverscanTransformerBidirScanOffsetTransformer 等)通过 transformer_registry 解析为类型化的 Rust *Spec pyclass 并附加到有效负载,以便 Rust 计算阶段在装配后应用它们。
  • 每个步骤的 StageSpec.Aggregate:每个上游工件计算节点一个 AggregateGroup,由 WorkpieceStart/WorkpieceEnd 标记包裹,每个输入携带工件的世界放置矩阵和物理大小作为 target_dimensions。按步骤转换器(MultiPassTransformerOptimize 等)附加到 AggregateSpec.transformers,以便 Rust 聚合阶段在连接后应用它们。MachineParams 从解析的机器填充,以便聚合的时间估计正确。
  • job 节点的 StageSpec.Aggregate:每层一个 AggregateGroup,由 LayerStart/LayerEnd 标记包裹,每个包含每个可见步骤的一个 AggregateInput;整个聚合由 JobStart/JobEnd 包裹。
  • job:machinexformMachineTransformSpec:世界→机器 4×4 矩阵、默认和每层 WCS 偏移、每层 RotaryMappingSpec 条目、曲线线性化标志和 Z 反转标志,打包成可序列化的规范,由 Rust MachineTransformCompute 阶段消费。
  • job:encodeEncodeSpec:将 Grbl 机器路由到原生 Rust GcodeSpec(直接在 rayon 线程上编译,无需跨越 GIL)以及所有其他机器到 PythonEncoder,包装驱动程序特定的编码器可调用对象。编码器从上游 job:machinexform 节点读取机器空间 ops。

Stock 解析

_resolve_stock_geometries(每次 build 调用一次,并在构建器上缓存)返回世界空间 stock 边界几何,CropTransformer 等转换器使用这些几何将按工件 ops 裁剪到机器工作区域或显式 StockItem。属于 Doc 的 StockItem 条目优先;仅当没有 Doc stock 时,机器工作区域矩形才用作后备。

raygeo 管道与 run_intent

raygeo 的 Pipelineraygeo.pipeline.execute.Pipeline)拥有 Intent.update 使其失效的缓存。run_intent 将意图的节点调度到 GIL 下的 rayon 工作线程上,并为每个节点调用 on_completed 回调,为聚合进度调用 on_batch_progress。繁重的工作(计算、光栅、聚合、机器变换、编码)在 raygeo 线程中运行,而不是在子进程中,这是 CHANGELOG 1.9.0 中提到的头版更改。

ArtifactStore 与工件句柄

旧的共享内存 ArtifactStore 已被进程内引用计数存储替换(rayforge/pipeline/artifact/store.py:29)。所有工件作为普通 Python 对象存在于由 UUID 键控的字典中;句柄在 key 字段中携带 UUID 以及工件类型所需的任何元数据。生命周期通过 ArtifactStore.retain/release 的引用计数进行管理。

Pipeline 外观在主线程上将 raygeo 输出转换为工件句柄:

输出(raygeo)工件存储标签
每工件-步骤 opsWorkPieceArtifactwp
每步骤聚合 opsStepOpsArtifactstep
作业聚合 + 编码JobArtifactjob

JobArtifact 携带世界空间 Ops、总距离、时间估计、EncodedOutput(文本加上 op→机器代码映射)以及——当配置了旋转模块时——用于 3D 预览的运动学映射 ops。

世代 ID 与 Epoch 过滤

每次重建都会递增 IntentController.generation_id。每个完成的节点都携带其产生的世代。on_completed 回调将节点的 generation_id 与控制器的当前世代进行比较,并静默丢弃过时的结果,因此来自先前重建的陈旧输出永远不会重新附加到 DOM。

暂停、恢复与手动模式

  • Pipeline.pause()/resume() 递增/递减控制器上的暂停计数器。暂停期间,文档更改设置 data_stale 标志(并发出 data_stale),而不是调度重建;恢复时,清除该标志,如果启用了 auto_rebuild,则调度重建。
  • Pipeline.auto_pipeline=False(手动模式):通过 Pipeline.recalculate() 显式触发重新计算,而不是在每次文档更改时自动触发。

失效策略

失效是隐式的且由令牌驱动:任何影响节点输入的更改都会导致构建器为该节点的键生成不同的 version_tokenIntent.update 驱逐过时的缓存条目,raygeo 仅重新执行该节点(及其下游消费者)。

更改类型对令牌的影响
几何/参数新的工件计算令牌级联到步骤、作业、机器变换、编码
位置/旋转工件计算令牌不变,除非步骤对位置敏感;步骤聚合令牌始终因折叠的放置而更改,这级联到作业/编码
大小更改与几何相同:令牌从工件-步骤对向上级联
Stock 项目可见/移动/添加影响 stock_rev(折叠到位置敏感步骤的计算和聚合令牌中)
机器配置所有 job:machinexformjob:encode 令牌更改;如果 kerf_mm/cut_speed/激光头/弧公差/supports_curves/supports_arcs 更改,则步骤计算/聚合令牌更改

详细分解

输入

该过程从 Doc 模型 开始,包含:

  • WorkPieces: 放置在画布上的单个设计元素(SVG、图像)
  • Steps: 带有设置的处理指令(轮廓、光栅等),组织成每层 Workflow
  • Layers: 工件的分组,每个都有自己的 workflow、WCS 和旋转配置
  • StockItems: 可选的显式 stock 边界,由位置敏感的转换器(如 CropTransformer)使用

Python 编排

Pipeline(外观)

Pipeline 类:

  • 通过信号监听 Doc 模型的更改(通过 IntentController 中继)
  • 对更改进行防抖(200 毫秒协调延迟)
  • IntentController 协调以触发重新生成
  • 管理整体处理状态和忙检测
  • 支持批量操作的暂停/恢复
  • 支持手动模式auto_pipeline=False),其中重新计算显式触发
  • 连接组件之间的信号并将其中继给消费者
  • 将引用计数的工件句柄发布到 ArtifactStore

IntentController

IntentController

  • 拥有一个 raygeo Intent 及周围的重建生命周期
  • 在每次防抖的文档更改时重建全新的意图
  • dispatch=True 时通过 run_intent 执行意图
  • generation_id 过滤过时的结果(epoch 过滤器)
  • 通过共享的任务管理器将 DOM 重新附加编组到主线程

IntentBuilder

IntentBuilder 是无状态的;每次调用 build 都会遍历 Doc,并为每个工件/步骤对生成一个 NodeRequest,每个步骤一个聚合,以及 jobjob:machinexformjob:encode 节点。请参见上面的稳定键版本令牌阶段构建

raygeo 管道

run_intent 在 GIL 下的 rayon 工作线程上调度节点执行。共享的 RaygeoPipeline 实例持有按节点键索引的节点缓存;Intent.update 是唯一的失效入口点。计算、光栅、收缩包装、波前、轮廓、视图渲染和机器变换/编码都在 raygeo 线程中运行。

工件生成

WorkPieceArtifacts

为每个 (WorkPiece, Step) 组合生成。包含:

  • 工件本地坐标系中的刀具路径(Ops
  • 用于分辨率无关 ops 的可伸缩性标志和源尺寸
  • 世代 ID

大型光栅工件以增量块处理(通过 visual_chunk_available 中继),从而在生成期间实现渐进式视觉反馈。

StepOpsArtifacts

为每个步骤生成,消费所有相关的 WorkPieceArtifacts:

  • 世界坐标中所有工件的组合 Ops
  • 应用的按步骤转换器(OptimizeMultiPass 等)

JobArtifact

在需要 G-code 时生成,消费 job 聚合和 job:encode 节点:

  • 通过 EncodedOutput(文本 + op→机器代码映射)的最终机器代码(G-code 或特定于驱动程序的格式)
  • 用于模拟和回放的世界空间 Ops
  • 高保真时间估计和总距离
  • 当配置了旋转模块时,用于 3D 预览的旋转映射 ops

2D 视图层(解耦)

ViewManager 与数据管道解耦。它基于 UI 状态处理 2D 画布的渲染。

RenderContext

包含当前视图参数(每毫米像素数、视口偏移、显示选项)。

WorkPieceViewArtifacts

ViewManager 创建 WorkPieceViewArtifacts,将 WorkPieceArtifacts 光栅化到屏幕空间,应用当前 RenderContext,并在上下文或源更改时缓存和更新。重新渲染受到限制(33 毫秒间隔)和并发限制;渐进式块拼接提供增量视觉更新。ViewManager(workpiece_uid, step_uid) 索引视图,以支持可视化工件在多个步骤中的中间状态。

3D / 模拟器层(解耦)

3D 可视化和模拟系统与数据管道解耦,遵循与 ViewManager 类似的模式。它包括:

  • 一个在子进程中运行的 Scene Compiler,用于将 JobArtifact ops 转换为 GPU 就绪的顶点数据
  • 一个 OpPlayer,用于重放作业的 ops,进行带有播放控制的实时机器模拟

两者都消费管道生成的 JobArtifact

CompiledSceneArtifact

Scene Compiler 生成包含以下内容的 CompiledSceneArtifact

  • 顶点层: 带有每命令偏移的 powered/travel/zero-power 顶点缓冲区,用于渐进式显示
  • 纹理层: 用于雕刻预览的光栅化扫描线功率图
  • 覆盖层: 用于实时高亮的扫描线功率段
  • 支持旋转(圆柱包裹)几何

编译管道

  1. Canvas3D 监听 job_generation_finished 信号
  2. 当新作业就绪时,它在子进程中调度场景编译
  3. 子进程从存储中读取 JobArtifact 并将 ops 编译为 GPU 顶点数据
  4. 编译后的场景被回收并上传到 GPU 渲染器

OpPlayer(模拟器后端)

OpPlayer 逐命令遍历作业的 ops,维护跟踪位置、激光状态和辅助轴的 MachineState。这驱动 3D 画布播放(刀具路径的渐进式显示)、机器头位置和激光束可视化以及播放滑块的逐命令步进。

消费者

消费者使用目的
2D 画布WorkPieceViewArtifacts在屏幕空间中渲染工件
3D 画布CompiledSceneArtifact在 3D 中渲染完整作业并播放
机器JobArtifact(机器代码)制造输出

关键架构决策

  1. 基于意图的调度: 代替显式的 Python DAG 和 Python 驻留的调度器,管道声明要计算什么(具有稳定键和版本令牌的 NodeRequestIntent),并让 raygeo 的 run_intent 在 rayon 线程上调度工作。缓存失效完全是通过 Intent.update 进行令牌驱动的。

  2. 外观 + 内部控制器: Pipeline 是唯一的公共表面;IntentControllerIntentBuilder 是实现细节。这使得公共信号/属性契约保持稳定,同时允许编排内部细节不断发展。

  3. 进程内工件存储: 用引用计数的进程内字典替换多进程共享内存存储,消除了 IPC 和所有权移交的复杂性,同时保留了 UI 和导出路径所依赖的句柄/生命周期契约。

  4. 世代 ID: 每次重建都会递增一个世代 ID;每个完成的节点都携带其产生世代。on_completed 的 epoch 过滤器会静默丢弃过时的结果,因此陈旧输出永远不会重新附加到 DOM。

  5. 主线程重新附加: raygeo 回调(on_completedon_batch_progress)在 GIL 下的 rayon 工作线程上触发;控制器通过共享的任务管理器将每个接触 DOM 的回调编组到应用程序主线程,因此信号处理程序永远不会在工作线程上运行。

  6. 视图层分离: 2D 画布(ViewManager)和 3D 画布(Scene Compiler / OpPlayer)都与数据管道解耦。每个都由管道信号驱动,而不是作为意图的一部分。

  7. 令牌驱动的失效: 没有显式的失效表。构建器生成规范的 SHA-1 版本令牌;任何输入更改都会产生不同的令牌,Intent.update 使用该令牌精确地删除受影响的缓存条目。

  8. 防抖协调: 文档更改以 200 毫秒的防抖(REBUILD_DEBOUNCE_MS)进行批处理,以避免在快速编辑期间出现过多的管道周期。