Skip to content

Development Workflows

Bartłomiej Mucha edited this page Oct 11, 2026 · 3 revisions

Workflows

What happens, step by step, when a workspace is opened or created, a project is opened, a render runs or is cancelled, templates are generated, and canvas profiles are managed.

Open a workspace

File → Open workspace…  (or Open recent ▸)
  → QFileDialog (*.platemaker.json)
  → loadWorkspace(path):
      take the folder's WorkspaceLock          // in use elsewhere? → OK / Take over / Cancel
      more than one *.platemaker.json there?   → resolve wizard (keep one, the rest to the
                                                 Recycle Bin), or cancel and keep what is open
      WorkspaceSerializer::load(path)
      register the workspace's own fonts/
      read the authoring records back from the overlay SVGs (Svg::readOverlays())
      populate the workspace panel (project list)
  → next event-loop pass: repair notice, stale-canvas warning,
                          then, with the folder held: healFontFallbacks(), sweepWorkspaceFolder()

See Workspace folder ownership for the lock, the fonts, the heal and the sweep.

Create a workspace

File → New workspace…               (Save as… goes through the same folder check)
  → QFileDialog (choose save location)
  → folder already holds another workspace?  → refused: create a subfolder named after the file,
                                               choose another location, or cancel
  → an empty Workspace — no output profiles of its own; the code-defined presets are always
    available, and the first one is the active profile
  → WorkspaceSerializer::save(path)
  → open workspace panel

Open or create a project

Workspace panel → double-click project  OR  right-click the list → New
  → Project dock opens (tabbed with the workspace panel), or is raised if already open
  → ProjectEditor::mergeFileScan() populates the tile grid from the input directory
  → thumbnails load asynchronously in background

Run the pipeline

The pipeline runs on a RenderWorker moved to its own QThread; it holds copies of the inputs, profiles and output dir, so it never touches the live workspace. The lib reports progress through a Core::ProcessingCallbacks struct (plain std::functions called synchronously on the worker thread); each callback lambda does one cheap thing — emit a Qt signal — which is delivered to the main thread via a queued connection, so the reaction (repainting tiles) happens on the main thread while the render is never blocked on the UI.

Render → Render current project (F5)  (or the project's Render button)
  → startRender(projectIndex):
      askRenderGate()     // an Error advisory? → its action / its resolution / Render anyway / Cancel
      project.sanitize(workspace.canvasProfiles())   // refresh statuses (disk + config)
      if up-to-date and no config change → inform user, skip
      confirm if outputs are stale (format/size/canvas changed since last render)
      worker = new RenderWorker(copies…);  setColourCorrection(), setStripOverlays()
      worker.moveToThread(thread)
      connect worker → MainWindow:  progress, log,
                                    sliceSaved(index,…)  → setOutputTile(index,…)   // live, positional
                                    inputStatus(path,…)  → setInputTileStatus(path,…) // live, phase 1
      ProcessingPipeline::run(inputs, outProfile, canvasProfiles, canvasProfileIds,
                              outDir, cancel, callbacks, onlySlices?, cacheDir,
                              colourCorrection, stripOverlays)  // static; worker thread
      show progress bar + Stop button
  → onRenderFinished():
      project.applyProcessingResults(records, appliedProfiles, outcome.skippedPages,
                                     workspace.canvasProfiles(), outDir, timestamp)
        // skipped pages are recorded as FileStatus::Skipped, not Processed
      deleteOrphanedOutputs()   // files the new config no longer produces
      populate()  + WorkspaceSerializer::save()

During phase 1 (strip building) each input's tile turns green as it is appended, cyan Processed (no canvas profile) when it is rendered without a matching profile, or violet Skipped when it is left out (missing / load error); then output tiles stream in per slice. See The image tile in Main window. The render gate is described in Inputs and anchoring.

The two optional steps ride along. startRender copies the project's colourCorrection and getStripOverlays() into the worker; both default to empty, so a project using neither renders byte-identically to one built before they existed. They also need a staleness axis of their own: a grade tweak or a moved bubble changes no input and no output file, so Models::processingConfigSignature() is compared against the stored ProjectItem::processingSignature and a mismatch folds into the "needs a full re-render" decision, exactly as outputProfileSignature() does for format/size changes. The signature is stamped back after a completed render.

Render output contract (consumer side — the library's Render page). startRender passes the workspace's .platemaker-cache dir to run(), so the pipeline warms each slice's thumbnail from its in-RAM pixels before sliceSaved fires. The output tile's getOrGenerate() is then a cache hit that never re-reads a slice the render is still writing — on Windows, reading a slice while it is being written aborts the render with unable to open for write. Rules the GUI must keep: treat sliceSaved(path) as the only "ready" signal (never read an output before it or during a run that rewrites it); a locked output surfaces as ProcessingErrorCode::OutputLocked and is shown as a short Render failed — see the action log status (the lib does not retry — that policy is ours).

Cancel the pipeline

Stop (Esc), or the project's Render button while a render runs
  → cancelRender(): m_cancelToken.cancel(), "Cancelling…", Stop disabled
  → the pipeline checks the token between slices and exits early
  → onRenderFinished(): the slices already written are applied as partial results,
    so their hashes and statuses are current; "Render cancelled (partial output kept)."

Generate a template

Canvas → Templates…  (TemplatesDialog)   OR   ManageCanvasProfilesDialog → generate for the selected profile
  → a profile not saved yet (no id)?  → "save the new profile first"
  → TemplatesDialog::confirmOverwrite()       // an existing template is replaced only on a yes
  → TemplatesDialog::generateTemplate():
        TemplateGenerator::generate(profile, outputProfile, <workspace>/templates/<profile>.psd|.png,
                                    TemplateGenerator::optionsFor(profile))   // dpi, format, empty layer
        // the output profile is required by the signature and unused
  → WorkspaceEditor::setCanvasProfileTemplateInfo(id, templateInfo)   // path + fingerprint

The format combo and empty-layer box under the table write the selected profile's templateSettings through WorkspaceEditor::replaceCanvasProfiles(); the whole dialog is one undo step (Edit templates).

Canvas → Open templates folder opens templates/.

Manage canvas profiles

Canvas → Manage profiles…
  → ManageCanvasProfilesDialog opens on a COPY of workspace.canvasProfiles()
  → Add / Edit / Delete happen on that copy inside the dialog
  → OK  → WorkspaceEditor(m_workspace).replaceCanvasProfiles(copy)
            // mints ids for new profiles, dedups, carries templateInfo by id
          setDirty(true);  emit workspaceProfilesChanged()   // open projects refresh live

The output-profile equivalent is the same through replaceOutputProfiles() (which additionally strips any preset — presets are code-defined and never persisted). Single-profile edits (edit-active, double-click) use a copy → mutate → replace of the whole palette, since the palette is private and cannot be mutated in place.

Conflict guard (the library's Project page):
Linking a canvas profile to a project goes through WorkspaceEditor::addCanvasProfileToProject(), which returns false when the profile's canvas W×H collides with one already linked; the GUI shows an error and does not link it. The project's canvasProfileIds are private in the model, so a raw bypass of this guard is not possible.


See also: Main window · Rendering · Canvas profiles

Clone this wiki locally