Архітектура конвеєра
Цей документ описує конвеєр, який перетворює модель Doc на
виконуваний машиною G-code. Починаючи з переписування 1.9.0, конвеєр
побудовано на інтентах raygeo: декларативному описі роботи, яку
має виконати сторона Rust, у поєднанні з тонким шаром оркестрації
Python та сховищем артефактів у процесі з підрахунком посилань.
Попередній багатопроцесорний DAG (DagScheduler, PipelineGraph,
ArtifactManager, GenerationContext, WorkPiecePipelineStage)
було видалено. Цей документ описує лише активну архітектуру.
Основні концепції
Pipeline (публічний фасад)
rayforge/pipeline/pipeline.py:40 — клас, з яким спілкується решта
застосунку. DocEditor, ViewManager, віджети UI та тестовий код
мають залежати лише від Pipeline. IntentController та
IntentBuilder є деталями реалізації фасаду і можуть змінюватися без
попередження.
Pipeline володіє інтеграцією ArtifactStore: він перекладає
необроблені вихідні дані raygeo, що надходять від його внутрішнього
IntentController, у дескриптори артефактів з підрахунком посилань,
які споживають UI та шляхи експорту, і надає поверхню
сигналів/властивостей, яку очікує решта застосунку (стан зайнятості,
пауза/відновлення, перерахунок, зміни машини).
Ключові сигнали, що ретранслюються фасадом:
| Сигнал | Значення |
|---|---|
processing_state_changed | Переходи зайнятий/вільний |
workpiece_artifact_ready | Опубліковано дескриптор WorkPieceArtifact |
job_generation_finished | Дескриптор JobArtifact (G-code + ops + оцінки) готовий |
job_time_updated | Агрегована оцінка часу змінена під час перебудови |
data_stale | Перебудову запитано, але вона призупинена або ручний режим |
visual_chunk_available | Прогресивний фрагмент растру для інкрементальних оновлень UI |
IntentController
rayforge/pipeline/intent_controller.py:108 — володіє Intent
raygeo та життєвим циклом перебудови. Він слухає ті самі спливливі
сигнали Doc, що й legacy-конвеєр (descendant_updated,
descendant_transform_changed, descendant_added,
descendant_removed, job_assembly_invalidated) і перебудовує
Intent raygeo щоразу, коли документ змінюється.
При кожній перебудові з дебаунсом (200 мс REBUILD_DEBOUNCE_MS):
IntentBuilderвикликається для створення свіжого списку об'єктівNodeRequestз поточногоDoc.- Новий список загортається в
Intentraygeo черезcreate_intent_from_nodes. Intent.updateпорівнює попередній intent з новим, використовуючиversion_tokenна вузол, та видаляє застарілі записи кешу в спільномуPipelineraygeo.- Коли
dispatch=True, новий intent також виконується черезrun_intent; зворотний викликon_completedвиконує фільтр епохи (відкидає результати, чийgeneration_idстарший за поточне покоління контролера), а потім маршалює повторне прикріплення DOM назад до головного потоку застосунку через спільний менеджер завдань. - Зворотний виклик
on_batch_progressпередає агрегований прогрес слухачам черезprogress_changed(маршалізований до головного потоку, щоб обробники сигналів ніколи не виконувалися на працівнику rayon).
Мапа _key_to_item контролера (перебудовується при кожному
успішному виклику IntentBuilder.build) дозволяє зворотному
виклику on_completed з фільтром епохи прикріплювати вихідні дані
до вихідного WorkPiece або Step без повторного обходу Doc.
Ключі вузлів розподіляються за формою:
| Ключ вузла | Прикріплюється до | Випромінюваний сигнал |
|---|---|---|
workpiece:{wp_uid}:{step_uid} | Власний WorkPiece | workpiece_artifact_ready |
step:{step_uid} | Власний Step | step_artifact_ready |
job | Doc | job_aggregate_ready |
job:encode | Doc | job_generation_finished |
IntentBuilder
rayforge/pipeline/intent_builder.py:133 — обходить Doc і створює
плоский список об'єктів NodeRequest зі стабільними ключами та
детермінованими токенами версій. Будівельник не має стану:
кожен виклик build створює свіжий, самодостатній список,
придатний для загортання в Intent raygeo.
Стабільні ключі
workpiece:{wp_uid}:{step_uid}— один обчислювальний вузол на пару workpiece/step.step:{step_uid}— один агрегатний вузол на step, який конкатенує вихідні дані обчислень workpiece і застосовує трансформери на step.job— один фінальний агрегатний вузол, що пов'язує всі вихідні дані step з маркерами рівня job та параметрами машини.job:machinexform— обчислювальний вузол трансформації машини, який споживає ops у світовому просторі з агрегату job і створює ops у просторі машини (лінеаризація кривих, мапування осі обертання, світ→машина, зміщення WCS, Z-flip, AXIS_REPLACEMENT).job:encode— обчислювальний вузол кодувальника, який споживає ops з вузла трансформації машини та створює код машини (G-code / вершина / текстура).
Формати ключів централізовані в intent_builder.py, щоб виробник і
мапа повторного прикріплення IntentController завжди збігалися.
Токени версій
Кеш raygeo індексується лише за ключем вузла; version_token є
єдиним сигналом інвалідації. Токени — це дайджести SHA-1 канонічного
представлення вхідних даних, що впливають на вихід вузла (див.
_hash_int, intent_builder.py:1066):
- Обчислювальні токени хешують
(geometry_revision, wp_size, step_params, assembler_params, per_workpiece_transformers). Для областей step, що оголошують чутливий до позиції трансформер (див.Step.is_position_sensitive),transform_revisionworkpiece та ревізія stock включаються в токен; інакше вони опускаються, щоб прості переміщення не інвалідували результати обчислень workpiece. - Токени агрегату step хешують
(upstream compute tokens, placements, step_params, per_step/per_workpiece transformers, position_sensitive()), плюсstock_rev, коли step чутливий до позиції. - Токен job вкладає всі токени агрегатів step, щоб будь-яка зміна вище за течією (переміщення workpiece, редагування трансформера, зміна параметра step) поширювалася до кешу job/encode.
- Токен трансформації машини вкладає токен job плюс
ідентичність машини (
supports_curves,reverse_z_axis, конфігурацію WCS, конфігурацію модуля обертання на шар). - Токен encode вкладає токен трансформації машини плюс
ідентичність кодувальника (
driver_name,gcode_precision, протяжності осей, ...).
Побудова етапів
Кожен NodeRequest несе StageSpec, що описує роботу, яку raygeo
має виконати для цього вузла. Будівельник створює:
StageSpec.Computeдля кожної пари workpiece/step черезStep.build_compute_payload(machine_defaults, workpiece), який повертаєPart(векторну геометрію або джерело зображення) плюсComputePayload(специфікацію збирача). Трансформери на workpiece (OverscanTransformer,BidirScanOffsetTransformer, ...) розв'язуються черезtransformer_registryу типізовані Rust*Specпікласи та прикріплюються до payload, щоб Rust-етап обчислення застосував їх після збирання.StageSpec.Aggregateдля кожного step: одинAggregateGroupна вузол обчислення workpiece вище за течією, обгорнутий маркерамиWorkpieceStart/WorkpieceEnd, кожен вхід несе матрицю розміщення у світі та фізичний розмір workpiece якtarget_dimensions. Трансформери на step (MultiPassTransformer,Optimize, ...) прикріплюються доAggregateSpec.transformers, щоб Rust-етап агрегації застосував їх після конкатенації.MachineParamsзаповнюється з розв'язаної машини, щоб оцінка часу агрегату була правильною.StageSpec.Aggregateдля вузлаjob: одинAggregateGroupна шар, обгорнутий маркерамиLayerStart/LayerEnd, кожен містить одинAggregateInputна видимий step; весь агрегат обгорнутий маркерамиJobStart/JobEnd.MachineTransformSpecдляjob:machinexform: матриця 4×4 світ→машина, стандартні та пошарові зміщення WCS, пошарові записиRotaryMappingSpec, прапорець лінеаризації кривих та прапорець Z-reverse, упаковані в серіалізовану специфікацію, яку споживає Rust-етапMachineTransformCompute.EncodeSpecдляjob:encode: спрямовує машини Grbl до рідного RustGcodeSpec(компілюється безпосередньо на потоці rayon без перетинання GIL) та всі інші машини доPythonEncoder, що обгортає виклик кодувальника, специфічного для драйвера. Кодувальник читає ops у просторі машини з вузла вище за течієюjob:machinexform.
Розв'язання Stock
_resolve_stock_geometries (викликається один раз на build і
кешується на будівельнику) повертає геометрії меж stock у світовому
просторі, які трансформери на кшталт CropTransformer використовують
для обрізання ops на workpiece до робочої області машини або
явних StockItem. Записи StockItem, що належать Doc, мають
пріоритет; прямокутник робочої області машини використовується як
резервний варіант, лише коли немає stock у Doc.
Конвеєр raygeo та run_intent
Pipeline raygeo (raygeo.pipeline.execute.Pipeline) володіє кешем,
який Intent.update інвалідує. run_intent планує вузли intent на
потоках працівників rayon під GIL та викликає зворотний виклик
on_completed на вузол і on_batch_progress для агрегованого
прогресу. Важка робота (обчислення, растер, агрегація, трансформації
машини, кодування) виконується в потоках raygeo замість підпроцесів,
що є головною зміною, зазначеною в CHANGELOG 1.9.0.
ArtifactStore та дескриптори артефактів
Попереднє ArtifactStore зі спільною пам'яттю замінено на сховище
в процесі з підрахунком посилань
(rayforge/pipeline/artifact/store.py:29). Усі артефакти живуть як
прості об'єкти Python у словнику, індексованому за UUID; дескриптори
несуть UUID у своєму полі key плюс будь-які метадані, необхідні
типу артефакту. Життєвий цикл керується підрахунком посилань через
ArtifactStore.retain/release.
Фасад Pipeline перекладає вихідні дані raygeo в дескриптори
артефактів у головному потоці:
| Вихідні дані (raygeo) | Артефакт | Зберігається під тегом |
|---|---|---|
| Ops на workpiece-step | WorkPieceArtifact | wp |
| Агреговані ops на step | StepOpsArtifact | step |
| Агрегат job + encode | JobArtifact | job |
JobArtifact несе Ops у світовому просторі, загальну відстань,
оцінку часу, EncodedOutput (текст плюс мапа op→код машини)
та — коли налаштовано модулі обертання — кінематично зіставлені ops
для 3D-попереднього перегляду.
Ідентифікатори поколінь та фільтр епох
Кожна перебудова збільшує IntentController.generation_id. Кожен
завершений вузол несе покоління, з якого він був створений.
Зворотний виклик on_completed порівнює generation_id вузла з
поточним поколінням контролера та мовчки відкидає застарілі
результати, тому вихідні дані з попередньої перебудови ніколи не
прикріплюються до DOM.
Пауза, відновлення та ручний режим
Pipeline.pause()/resume()збільшує/зменшує лічильник паузи на контролері. Під час паузи зміни Doc встановлюють прапорецьdata_stale(та випромінюютьdata_stale) замість планування перебудови; при відновленні прапорець очищається і планується перебудова, якщоauto_rebuildувімкнено.Pipeline.auto_pipeline=False(ручний режим): перерахунок запускається явно черезPipeline.recalculate()замість автоматично при кожній зміні Doc.
Стратегія інвалідації
Інвалідація є неявною та керованою токенами: будь-яка зміна, що
впливає на вхідні дані вузла, змушує будівельника створити інший
version_token для ключа цього вузла. Intent.update видаляє
застарілий запис кешу, і raygeo повторно виконує лише цей вузол
(та його споживачів нижче за течією).
| Тип зміни | Вплив на токени |
|---|---|
| Геометрія / параметри | Нові обчислювальні токени workpiece каскадують до step, job, machinexform, encode |
| Позиція / обертання | Обчислювальні токени workpiece незмінні, якщо step не чутливий до позиції; токени агрегату step завжди змінюються через вкладені розміщення, що каскадує до job/encode |
| Зміна розміру | Як геометрія: токени каскадують від пар workpiece-step вгору |
| Stock items видимі/переміщені/додані | Впливає на stock_rev (вкладений в обчислювальні та агрегатні токени чутливих до позиції step) |
| Конфігурація машини | Всі токени job:machinexform та job:encode змінюються; обчислювальні/агрегатні токени step змінюються, якщо kerf_mm/cut_speed/лазерна головка/допуск дуги/supports_curves/supports_arcs змінюються |
Детальний опис
Вхід
Процес починається з моделі Doc, яка містить:
- WorkPieces: Окремі елементи дизайну (SVG, зображення), розміщені на полотні
- Steps: Інструкції обробки (Контур, Растер тощо) з
налаштуваннями, організовані в
Workflowна шар - Layers: Групування workpiece, кожен зі своїм workflow, WCS та конфігурацією обертання
- StockItems: Опціональні явні межі stock, що використовуються чутливими до позиції трансформерами (напр. CropTransformer)
Оркестрація Python
Pipeline (фасад)
Клас Pipeline:
- Слухає зміни моделі Doc через сигнали (ретрансльовані через
IntentController) - Дебаунсить зміни (затримка узгодження 200 мс)
- Координує з
IntentControllerзапуск регенерації - Керує загальним станом обробки та виявленням зайнятості
- Підтримує паузу/відновлення для пакетних операцій
- Підтримує ручний режим (
auto_pipeline=False), де перерахунок запускається явно - З'єднує сигнали між компонентами та ретранслює їх споживачам
- Публікує дескриптори артефактів з підрахунком посилань у
ArtifactStore
IntentController
IntentController:
- Володіє
Intentraygeo та життєвим циклом перебудови - Перебудовує свіжий intent при кожній зміні Doc з дебаунсом
- Виконує intent через
run_intent, колиdispatch=True - Фільтрує застарілі результати за
generation_id(фільтр епохи) - Маршалює повторні прикріплення DOM до головного потоку через спільний менеджер завдань
IntentBuilder
IntentBuilder не має стану; кожен виклик build обходить Doc і
створює один NodeRequest на пару workpiece/step, один агрегат на
step та вузли job, job:machinexform і job:encode. Див.
Стабільні ключі,
Токени версій та
Побудова етапів вище.
Конвеєр raygeo
run_intent планує виконання вузлів на потоках працівників rayon
під GIL. Спільний екземпляр RaygeoPipeline містить кеш вузлів,
індексований за ключем вузла; Intent.update є єдиною точкою входу
інвалідації. Обчислення, растер, shrinkwrap, wavefront, контур,
рендеринг перегляду та трансформація/кодування машини виконуються
в потоках raygeo.
Генерація артефактів
WorkPieceArtifacts
Генеруються для кожної комбінації (WorkPiece, Step). Містять:
- Toolpaths (
Ops) у локальній системі координат workpiece - Прапорець масштабованості та вихідні розміри для роздільно-незалежних ops
- Ідентифікатор покоління
Великі растрові workpiece обробляються інкрементально фрагментами
(ретранслюються через visual_chunk_available), що забезпечує
прогресивний візуальний зворотний зв'язок під час генерації.
StepOpsArtifacts
Генеруються для кожного Step, споживаючи всі пов'язані WorkPieceArtifacts:
- Комбіновані
Opsдля всіх workpiece у світових координатах - Застосовані трансформери на step (
Optimize,MultiPass, ...)
JobArtifact
Генерується, коли потрібен G-code, споживаючи агрегат job та вузол
job:encode:
- Фінальний код машини (G-code або формат, специфічний для драйвера)
через
EncodedOutput(текст + мапа op→код машини) Opsу світовому просторі для симуляції та відтворення- Високоточна оцінка часу та загальна відстань
- Зіставлені для обертання ops для 3D-попереднього перегляду, коли налаштовано модулі обертання
Шар 2D перегляду (розділений)
ViewManager відокремлено від конвеєра даних. Він керує рендерингом
для 2D полотна на основі стану UI.
RenderContext
Містить поточні параметри перегляду (пікселі на міліметр, зміщення viewport, опції відображення).
WorkPieceViewArtifacts
ViewManager створює WorkPieceViewArtifacts, які растерізують
WorkPieceArtifacts у простір екрану, застосовують поточний
RenderContext та кешуються й оновлюються при зміні контексту або
джерела. Повторний рендеринг обмежується (інтервал 33 мс) та має
обмеження паралельності; прогресивне зшивання фрагментів забезпечує
інкрементальні візуальні оновлення. ViewManager індексує
перегляди за (workpiece_uid, step_uid) для підтримки візуалізації
проміжних станів workpiece через кілька step.
Шар 3D / Симулятор (розділений)
Система 3D візуалізації та симуляції відокремлена від конвеєра
даних, слідуючи шаблону, подібному до ViewManager. Вона
складається з:
- Scene Compiler, що виконується в підпроцесі для перетворення
ops
JobArtifactу готові для GPU дані вершин - OpPlayer, що відтворює ops job для симуляції машини в реальному часі з елементами керування відтворенням
Обидва споживають JobArtifact, створений конвеєром.
CompiledSceneArtifact
Scene Compiler створює CompiledSceneArtifact, що містить:
- Шари вершин: Буфери вершин powered/travel/zero-power зі зміщеннями на команду для прогресивного розкриття
- Текстурні шари: Растеризовані мапи потужності ліній сканування для попереднього перегляду гравіювання
- Накладні шари: Сегменти потужності ліній сканування для підсвічування в реальному часі
- Підтримку обертової (циліндрично обгорнутої) геометрії
Конвеєр компіляції
- Canvas3D слухає сигнали
job_generation_finished - Коли новий job готовий, він планує компіляцію сцени в підпроцесі
- Підпроцес читає
JobArtifactзі сховища та компілює ops у дані вершин GPU - Скомпільована сцена приймається назад і завантажується в рендерери GPU
OpPlayer (бекенд симулятора)
OpPlayer проходить через ops job команда за командою, підтримуючи
MachineState, який відстежує позицію, стан лазера та допоміжні осі.
Це керує відтворенням 3D полотна (прогресивне розкриття траєкторії
інструмента), візуалізацією позиції головки машини та лазерного
променя, а також покроковим проходженням команд для повзунка
відтворення.
Споживачі
| Споживач | Використовує | Призначення |
|---|---|---|
| 2D полотно | WorkPieceViewArtifacts | Рендерить workpiece у просторі екрану |
| 3D полотно | CompiledSceneArtifact | Рендерить весь job у 3D з відтворенням |
| Машина | JobArtifact (код машини) | Вихідні дані для виробництва |
Ключові архітектурні рішення
-
Планування на основі інтентів: Замість явного DAG Python з планувальниками, що проживають у Python, конвеєр оголошує що обчислювати (
IntentзNodeRequestзі стабільними ключами та токенами версій) і дозволяєrun_intentraygeo планувати роботу на потоках rayon. Інвалідація кешу є чисто керованою токенами черезIntent.update. -
Фасад + внутрішній контролер:
Pipelineє єдиною публічною поверхнею;IntentControllerтаIntentBuilderє деталями реалізації. Це зберігає стабільним публічний контракт сигналів/властивостей, дозволяючи внутрішнім деталям оркестрації розвиватися. -
Сховище артефактів у процесі: Заміна багатопроцесорного сховища зі спільною пам'яттю на словник у процесі з підрахунком посилань усуває складність IPC та передачі власності, зберігаючи контракт дескриптора/життєвого циклу, на який покладаються UI та шляхи експорту.
-
Ідентифікатори поколінь: Кожна перебудова збільшує ідентифікатор покоління; кожен завершений вузол несе своє покоління створення. Фільтр епохи
on_completedмовчки відкидає застарілі результати, тому вихідні дані з попередньої перебудови ніколи не прикріплюються до DOM. -
Повторне прикріплення в головному потоці: Зворотні виклики raygeo (
on_completed,on_batch_progress) спрацьовують на потоках працівників rayon під GIL; контролер маршалює кожен зворотний виклик, що торкається DOM, до головного потоку застосунку через спільний менеджер завдань, тому обробники сигналів ніколи не виконуються на працівнику. -
Розділення шарів перегляду: Як 2D полотно (
ViewManager), так і 3D полотно (Scene Compiler / OpPlayer) відокремлені від конвеєра даних. Кожен керується сигналами конвеєра, а не є частиною intent. -
Інвалідація, керована токенами: Немає явної таблиці інвалідації. Будівельник створює канонічні токени версій SHA-1; будь-яка зміна вхідних даних створює інший токен, який
Intent.updateвикористовує для видалення саме тих записів кешу, на які вплинула зміна. -
Узгодження з дебаунсом: Зміни Doc групуються з дебаунсом 200 мс (
REBUILD_DEBOUNCE_MS), щоб уникнути надмірних циклів конвеєра під час швидкого редагування.