Skip to content

Streaming data capture, RTC6 large-layer exposure, resumable downloads and Svelte client fixes (Data capture rebased 2026 11 04) - #98

Open
yrodzikau wants to merge 17 commits into
alexanderoster:developfrom
yrodzikau:data_capture_rebased_2026-11-04
Open

yrodzikau wants to merge 17 commits into
alexanderoster:developfrom
yrodzikau:data_capture_rebased_2026-11-04

Conversation

@yrodzikau

Copy link
Copy Markdown

Summary

This PR brings the data_capture work onto develop. The branch was rebased onto develop at 8873718 ("Added Svelte package") on 4 November 2026. Two commits that develop already covers were dropped (shared triggerButtonEvent in BuildList/ExecutionList, page-level scrolling; PRs #81, #82, #90), and ModuleLayerView.svelte was re-merged on top of the upstream "Layer Viewer updates" and "Updated Layer View" commits.

17 commits, 14 self-contained changes. All ACT interface changes are additive; existing machine configurations and plugins keep working unchanged. Each commit message carries the full rationale, design and verification of its change.

Refs #

What is included

Server and framework

  1. Streaming DataCapture, chunked columnar DataTable v2, api/datatable, datacapture UI module, incremental RTC recording readout (99ec7da). Continuous high-rate sensor data (an RTC recording at 100 kHz over a whole build) is written from a background thread into segmented storage streams, stored in a chunked columnar format with per-page statistics and DELTA/BITPACK/LZ4/Deflate encodings, read selectively by a DataTableReader, exposed through REST endpoints and shown in a new datacapture content item. Retention by storage limit, oldest segments first. 72 unit tests.
  2. Downloads of any size with pause and resume, downloads handed over by state machines, button tooltips (f7cd39f). The download endpoint streams storage streams in 4 MiB chunks with Content-Length, ETag, Last-Modified, HEAD and single byte ranges; tickets expire after 24 h and can be revoked; every download is logged. A state machine can queue a streamdownload client action for one session, follow the delivery and cancel it. <button hint|sync:hint> shows a tooltip.
  3. Machine-driven modal dialogs that the user cannot close (c7d0c43). <dialog> accepts active / sync:active and closable; the server re-asserts the state on every frontend poll. Used for the "Pausing Build…" and "Canceling Build…" wait screens. Svelte and Vue 2 clients.
  4. V2 frontend state fixes (557964c, a609dba). /api/frontend includes the page show event and reports values set by UI event handlers through SetUIProperty; configuration list fallback buttons send a valid UUID; invisible tabs are hidden; the LayerView base image name is resolved to its UUID. New UIFrontend unit test group.
  5. Build scripts (93afc60). createPackageXML writes the resolved client variant as client="…" into the generated amc_server.xml; build_clean_win64.bat pins -A x64.

ScanLab RTC6 driver

  1. Alternating List1/List2 for layers larger than one list (b4fd487). Exact list capacity count instead of the two-positions-per-vector estimate; layers above 4 194 304 positions are marked in one continuous exposure by alternating the two lists with automatic list changes. Opt-in per machine, off by default. Verified on an RTC6 Ethernet card with a 60 155 411 vector layer (15 segments, 0 fallbacks).
  2. Stepwise microvector download with recording readout in parallel (b5f931c). BeginMicrovectorStreaming / ContinueMicrovectorStreaming / AbortMicrovectorStreaming let the plugin read out the RTC recording between download steps of a long layer; the readout at the wrap-around of the measurement memory no longer skips one sample per channel. UI event requests in the client core get a 120 s timeout.

Other drivers

  1. Reliable library loading on Windows for gRPC, ScanLab OIE and ScanLab SMC (7e61911). LoadLibraryExW with LOAD_WITH_ALTERED_SEARCH_PATH and a preloaded gRPC wrapper remove the race on the process-wide DLL directory that failed about every second server start. Linux unchanged.
  2. SMC: temporary files removed after use, simulation output switch (cc83641). Module recording, RTC log record file and the complete simulation output set are deleted once read; SMCConfiguration::SetSimulationFileOutputEnabled(false) sets DisableFileOutput.
  3. OIE: packet and error diagnostics in the server log (52e2783). Counters in the SDK callback thread, reports through the driver environment; ACT interface unchanged.
  4. Pylon: ConnectToUniqueEmulatedDevice (bb86f77). Connects to the pylon camera emulator without exposing PYLON_CAMEMU to later enumerations of physical cameras.

Web clients

  1. LayerView parity with the Vue 2 client, plate framing, point rendering in groups (206c025, c29782f, d3521f1, ). Layer slider logic, loading indicator, hover tooltip, canvas re-attachment and stale-response guards on top of the upstream module; centerOnPlatform() frames the plate in plate coordinates again; WebGLScreenPointsElement renders points in groups of 524 288 because some GPU drivers silently truncate a single POINTS draw call above about 2 million points (observed with the Parallels display adapter through ANGLE/Direct3D 11).
  2. Failed UI events reported in the snackbar (b522401). Svelte and Vue 2 clients.
  3. Overflow-safe dialog layout in the Svelte client (a5b065e). Long 3MF names and footer buttons stay inside the dialog; applied to the shared shadcn-svelte dialog components.

Interface and behaviour changes to review

  • ACT interfaces (additive): LibMCEnv (DataCaptureOptions, DataCaptureChunk, DataCapture, DataTableReader, BuildExecution.*DataCapture*, new DataTable column types, download and client action methods), LibMCData (DataCapture, download tickets and log), LibMC (download and client action handling), LibMCDriver_ScanLab (recording readout, alternating lists, stepwise download), LibMCDriver_ScanLabSMC (SetSimulationFileOutputEnabled), LibMCDriver_Pylon (ConnectToUniqueEmulatedDevice). Bindings and headers regenerated with ACT.
  • Error codes are placed after the ones develop uses: LibMCData 454–468, LibMC 721–729, LibMCEnv 10270–10306 (upstream owns 10266–10269 for the Formula API), LibMCDriver_ScanLab 1168. The commit bodies written before the rebase still quote the old numbers.
  • Database: migrations for the tables datacaptures, datacapture_columns, datacapture_segments and the download log, ordered after the named-configuration-versions step of develop. MIME type application/amcf-datatable.
  • REST: GET api/datatable/execution/{uuid}, api/datatable/{uuid}/info, api/datatable/{uuid}?format=json|csv|bin|bin32&…, api/datatable/{uuid}/segment/{index}; download endpoint with HEAD, Range/If-Range, 206/416/410/503; POST api/frontend/actions/{uuid}/claim; pendingactions in the polling response and actions in the live update stream.
  • config.xml: <dialog active|sync:active closable>, <button hint|sync:hint>, content item <datacapture title captureuuid defaultcolumns>.
  • Frontend state: session client variables set by UI event handlers take precedence over definition values; sync: attributes are still evaluated from the state machine.
  • SMC driver: LoadSimulationData / LoadRawSimulationData read their data once; the temporary files are deleted afterwards (documented in the IDL).
  • Client core: requests to the event endpoint use a 120 s timeout; all other requests keep 10 s.

Known limits

  • DataCapture: readers see only closed segments while a capture is recording; after a hard stop of the server the rows of the open segment are lost and the capture stays in status recording; the Vue 2 client has no component for the datacapture item.
  • Alternating RTC6 lists are opt-in; without the opt-in only the exact capacity count changes behaviour.
  • The A3200, TML, OPC UA, OpenCV and LibOAPC drivers set the DLL directory in the same way as the fixed drivers and are unchanged.
  • <buttongroup> rows in config.xml dialogs still do not wrap; the shadcn-svelte dialog components are generated files and shadcn-svelte add dialog would overwrite the layout fix.

Verification

  • Windows x64, after the rebase onto 8873718: incremental build_win64 build, amc_unittest 599 of 599 tests passed (the timing-sensitive ExecComputeElapsedTime failed once while two builds ran in parallel and passed on the repeat run), Svelte client package rebuilt (Artifacts/clientdist/clientpackage_svelte.zip is part of the PR), amc_server smoke start with REST login, /api/frontend and /api/executions, svelte-check without new errors in Svelte or TypeScript sources (the pre-existing timelineDuration error in ModuleLayerView.svelte remains; the untyped core JavaScript gains 18 implicit-any diagnostics of the kind develop already has 980 of).
  • Linux build not run on our side.
  • Hardware, before the rebase: RTC6 Ethernet card (60 M vector layer as 15 segments; recording streamed at 100 kHz over four layers, 8.0 M rows, values matched the per-layer tables), server start 5 of 5 with the driver loading fix, SMC planning with the simulation output switched off, pylon emulator with pylon 25.09. These checks have not been repeated on the rebased build yet.
  • The point rendering fix was verified in Edge on the affected GPU (all 18 parts of a 4.4 M point layer drawn, 9 draw calls per frame) and in headless Chromium.

…Footer and DialogTitle to keep long 3MF names and buttons inside dialogs

On the Build Library page, selecting a build opened the details dialog
with the 3MF file name and the "Close"/"Select" buttons running past
the dialog border. DialogContent is a single-column CSS grid whose
implicit auto column grows to the min-content width of its widest
child. A file name without spaces is one unbreakable word, so the
column grew beyond the 500px dialog box and the right-aligned footer
moved with it. The fix is applied to the shared shadcn-svelte dialog
components, so every dialog in the Svelte client gets it.

feat(dialog-content): cap the content column and wrap long words
- Add grid-cols-[minmax(0,1fr)]: the column can no longer grow wider
  than the dialog, so header, body and footer stay inside the border.
- Add wrap-anywhere (overflow-wrap: anywhere). The property is
  inherited, so long names, paths and module content wrap in every
  dialog. Unlike wrap-break-word it also lowers the min-content width,
  which the grid column needs in order to shrink. Button captions keep
  whitespace-nowrap and stay on one line.

feat(dialog-footer): wrap footer buttons
- Add sm:flex-wrap. Buttons are shrink-0 and whitespace-nowrap, so
  several or long buttons (e.g. multiple <button> entries in a
  <buildlist>) now move to the next row instead of overflowing.

feat(dialog-title): reserve space for the close button
- Add pe-6 so a title that wraps does not run under the absolutely
  positioned close (X) button.

Affected dialogs:
- Build Library details dialog (ModuleBuildList): fixes the reported
  overflow.
- Build History details dialog (ModuleExecutionList): same markup,
  same fix.
- config.xml dialogs rendered in routes/+page.svelte (e.g.
  pausedialog): long titles now wrap instead of being clipped.

Notes:
- components/ui/dialog/* are shadcn-svelte generated files. Running
  "shadcn-svelte add dialog" again will overwrite these changes.
- <buttongroup> rows (ModuleButtonGroup) still do not wrap. In
  config.xml dialogs, long captions produce a horizontal scrollbar.
  This commit does not change that.
- The prebuilt package Artifacts/clientdist/clientpackage_svelte.zip
  is not part of this commit. Rebuild it with
  Artifacts/build_client_svelte_clean.bat and commit it separately.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…oad/Set Active and tab visibility

The Svelte client showed the config.xml defaults on the ScanlabOCM
System Settings page instead of the active machine configuration. The
page fills its fields in the "onshowsystemsettings" show event with
UIEnvironment::SetUIProperty, but the V2 frontend (/api/frontend) did
not include the show event and ignored values set by SetUIProperty.

feat(ui): send the page show event in the V2 frontend
- CUIPage::frontendWritePageStatusToJSON() writes "showevent", so
  AMCPage.setActive() triggers it again.

feat(ui): report values and visibility set by UI event handlers
- CUIFrontendState::writeModuleAttributesToJSON() writes the session
  client variable with the attribute's name, if one exists, instead of
  the definition value. Attributes bound with sync: are still
  evaluated from the state machine, as in the legacy
  syncClientVariables().
- Add CUIFrontendDefinitionAttribute::isSynchronized(),
  writeClientValueToFrontendJSON() and
  CUIFrontendDefinitionModuleStore::getPath().

feat(svelte-client): fix the configuration list fallback buttons
- Load and Set Active no longer send their placeholder id as the
  button UUID, which the server rejected with "invalid uuid string".
- Remove the Delete fallback: no plugin handles "deleteconfiguration".

feat(svelte-client): hide invisible tabs in ModuleTabs

feat(unittest): add the UIFrontend test group

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
Client variables take precedence over module attributes in the new
frontend, so the resource name written by populateClientVariables()
overrode the resolved UUID and the Svelte client could not load the
platform image. Resolve the name in all three places through a shared
helper; an empty name yields the null UUID (empty string for the dark
image, meaning "fall back to the light one").

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
… in the snackbar

- triggerUIEvent(): an errorcode in the /event response or a request
  failure is shown via the new showEventError(); a failed event no
  longer runs the success path
- add showSnackBar() that sets every SnackBar field and bumps a new
  Sequence counter, so repeated identical messages restart auto-hide
- Snackbar.svelte depends on Sequence
- App.vue: snackBarVisible gets a setter, otherwise Vuetify cannot
  reset Visible and the next error is never shown
- triggerWidgetRequest(): optional failureCallback(err) for callers
  that need to roll back optimistic UI state

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…ture set

- load scatter plot points and the laser channel (laseron, power),
  color modes, LaserOff point toggle, toolpath visibility toggle
- hover info for the point or toolpath segment under the cursor,
  throttled to one raycast per animation frame
- layer slider: correct range (layercount - 1), send changelayer only
  on release, -/+ step buttons, sliderfixed support, follow the server
  only on changed values, optimistic change with rollback on failure
- platform label and loading indicator
- re-attach the canvas and resize observer when the module is hidden
  and shown again
- ignore stale toolpath/scatter plot responses, treat the null UUID as
  "no build"/"no image"
- remove the mouse-move debug logging from WebGLLocalizedPointsElement

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…n modal dialogs that the user cannot close, for the "Pausing Build…" / "Canceling Build…" wait screens

State machines cannot open dialogs and UI event handlers cannot set machine
parameters, so a dialog could only be opened by a click and closed by the
user. The pause/resume/cancel build controls need dialogs that main opens
while it finishes the current layer, that cannot be dismissed, and that close
by themselves once the machine is ready. This adds that generically; the
ScanlabOCM machine uses it for "Pausing Build…" and "Canceling Build…".

feat(ui-core): dialog attributes sync:active and closable

- <dialog> accepts `active` / `sync:active` and `closable` (default "1"),
  stored as CUIExpression in CUIDialog, validated at startup, passed through
  addDialog_Unsafe (signature extended).
- /api/frontend emits "closable" for every dialog and "active" only for
  dialogs with an active expression, so event-driven dialogs stay untouched.
  Legacy /api/ui/state emits "closable" too. New keys
  AMC_API_KEY_UI_DIALOGACTIVE / AMC_API_KEY_UI_DIALOGCLOSABLE.
- The server is authoritative: a dialog is open while the bound parameter
  is true and re-asserted on every frontend poll.

feat(client-core): follow server-driven dialog state

- AMCApplicationDialog initialises `closable` and `serverDriven` in the
  constructor (Vue 2 reactivity).
- retrieveFrontendState() indexes dialog modules into frontendLookup and
  calls new _syncServerDrivenDialogs(), which opens a dialog via showDialog()
  when "active" turns true and clears dialogIsActive when it turns false.
  Dialogs without "active" keep the client-action driven behaviour.

feat(svelte-client): non-closable dialogs

- Dialog.Content gets showCloseButton, escapeKeydownBehavior and
  interactOutsideBehavior from dialog.closable; setDialogOpen() ignores
  close requests for closable="0"; dialogIsClosable() reads through the
  poll tick so the property mutation re-renders.

feat(vue2-client): non-closable dialogs

- v-dialog gets :persistent and :no-click-animation for closable === false.

Backward compatible: dialogs without the new attributes behave as before.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…e platform

feat(create_package_xml): write the resolved client variant into amc_server.xml

The package builder already resolved which web client to ship (from the
`--client` flag, then the `client` attribute on `<machinedefinition>` in
config.xml, then the default `vue2`) and wrote it to the package XML as
`<coreclients default="...">`. The generated amc_server.xml, however, was
written without a `client` attribute on the `<amc>` root element.

The server reads that attribute at startup to pick the client package,
so every clean build silently reset the served UI to the Vue 2 client
until `client="svelte"` was re-added to amc_server.xml by hand.

The `<amc>` element now carries `client="<variant>"` with the same
resolved value that goes into the package XML, e.g.:

    <amc xmlns="http://schemas.autodesk.com/amc/2020/06" client="svelte">

Both files are generated in one run, so the package SHA-256 recorded in
amc_server.xml always matches the package XML written alongside it.

feat(build_clean_win64): pass -A x64 to the Visual Studio generator

The Windows clean build configures CMake with an explicit x64 platform
instead of relying on the generator default. This makes the architecture
deterministic across machines with different default toolchains.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…erView

CenterOnRectangle() works in plate coordinates: the plate image is drawn
from (0, 0) to (sizex, sizey) and the toolpath is shifted by the origin.
The framing introduced with "Layer Viewer updates" subtracted the origin
from the frame, which put the plate corner into the view center for
origin = (sizex/2, sizey/2) on the ScanlabOCM machine.

centerOnPlatform() frames 0 .. size again, keeping the paddingx/paddingy
attributes and the auto-framing behaviour. The Vue 2 client is unchanged.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…ble v2 format, selective DataTableReader, api/datatable REST endpoints, datacapture web module and incremental RTC recording readout

Continuous high-rate sensor data (an RTC recording at 100 kHz over a whole
build, tens of gigabytes) could only be stored as in-memory DataTables, one
per layer. This change adds a streaming capture path that writes chunks to
segmented storage streams from a background thread, a chunked columnar
on-disk format with per-page statistics, a reader that touches only the
pages a request needs, REST endpoints and a web module on top of it, and an
incremental readout in the ScanLab driver so that the RTC recording can be
streamed while the layer is still being exposed. Implements the design
"Streaming Data Capture and Chunked Columnar Storage" (draft v2 with the v3
corrections): one producer per capture, captures live only inside a build
execution, retention by storage limit with oldest-first deletion, new v2
signature.

feat(libmcenv): DataCapture write API and DataTableReader

- New classes DataCaptureOptions (chunk row count, segment size, queue
  depth, backpressure mode Block/DropOldest/Fail, storage limit,
  compression, per-column encoding and double quantisation),
  DataCaptureChunk (column-wise and row-wise setters) and DataCapture
  (AddColumn, BeginChunk, CommitChunk, Flush, counters, Finish, Abort,
  AddColumnsFromDataTable, AppendDataTable).
- New class DataTableReader: row count, first available row, schema,
  segment and chunk information with min/max statistics, typed range
  reads, ReadColumnDownsampled (Stride, MinMax, Average),
  FindChunksInValueRange and ExtractDataTable.
- BuildExecution gains CreateDataCaptureOptions, CreateDataCapture,
  HasDataCapture, GetDataCaptureRowCount, GetDataCaptureSegmentCount,
  LoadDataCaptureSegment and OpenDataCaptureReader. A capture can only be
  created while the execution is in process.
- The capture engine (libmcenv_datacaptureinstance) uses a pool of
  MaxQueuedChunks + 1 buffers, a bounded queue, a writer thread that
  encodes and writes pages, and a finalizer thread that registers the
  segment, finishes the storage stream (SHA-256) and enforces the storage
  limit, so that the writer never waits for the database. A chunk is never
  split across segments; a segment rolls over before a chunk that would
  exceed the configured size.
- DataTableColumnType gains FloatColumn, BoolColumn and TimestampColumn;
  DataTable gains GetRowCount64 and typed accessors for the new types;
  LoadFromStream reads both the v1 and the v2 format and reports
  truncated or corrupt segments with dedicated error codes.
- New enums DataCaptureBackpressureMode, DataCaptureStatus,
  DataCaptureCompression, DataCaptureEncoding and
  DataTableDownsampleMode; error codes 10266 to 10299. All interface
  changes are additive.

feat(core): DataTable v2 format, encodings and selective reader

- amc_datatableformat: append-only segment layout with signature
  0x6A9B23E2: 64-byte header, one page per column and chunk with a 32-byte
  page header, a footer with the schema and a page directory (file offset,
  sizes, row count, codec, encoding, typed min and max), and a 16-byte
  trailer that locates the footer. Segment builder, chunk encoder with
  per-page fallback to PLAIN or uncompressed when a step does not shrink
  the page, and a validating segment reader.
- amc_datatableencoding: DELTA (Parquet DELTA_BINARY_PACKED, blocks of
  128 values in 4 miniblocks, zigzag ULEB128), BITPACK for bool columns,
  LZ4 and Deflate codecs, opt-in double quantisation stored as integer
  multiples of a step.
- amc_datacapturereader: lazy multi-segment reader with an LRU of open
  segments and a page cache, binary search of segments and chunks,
  downsampling that takes min and max from the page directory for chunks
  that lie inside one bucket.
- amc_datacapturequery: JSON, CSV and binary (float64 and float32)
  responses, bucketed downsampling in the shape of the journal envelope,
  response cap of 1,000,000 values.

feat(libmcdata): data capture manifest and retention

- Database schema version 24 with the tables datacaptures,
  datacapture_columns and datacapture_segments.
- New class DataCapture (columns, segments, row count, stored size,
  Finish, Abort); BuildJobExecution gains CreateDataCapture,
  HasDataCapture, RetrieveDataCapture, GetDataCaptureCount and
  RetrieveDataCaptureByIndex; BuildJobHandler gains RetrieveDataCapture.
- Storage gains DiscardRandomWriteStream, GetDataCaptureStorageSize and
  EnforceDataCaptureStorageLimit: the oldest stored segments of all
  captures are deleted until the total size is below the limit; purged
  segments keep their manifest row with status purged.
- MIME type application/amcf-datatable is registered; error codes 453 to
  464.

feat(api): api/datatable REST endpoints

- GET api/datatable/execution/{uuid} lists the captures of a build
  execution.
- GET api/datatable/{uuid}/info returns schema, row counts, segments and
  the value range of every column.
- GET api/datatable/{uuid}?format=json|csv|bin|bin32&columns=&fromrow=
  &rowcount=&downsample=none|stride|minmax|average&targetpoints= returns
  rows or downsampled data; requests above 1,000,000 values are rejected
  with a hint.
- GET api/datatable/{uuid}/segment/{index} issues a download ticket for
  one native segment stream.
- All endpoints require a session; row parameters are parsed strictly.

feat(ui-core, client-core, svelte-client): datacapture content item and module

- New content item type "datacapture" with the attributes title,
  captureuuid (usually bound to a state machine parameter) and
  defaultcolumns, registered as frontend attributes of the differential
  frontend state.
- Client core module AMCModule_DataCapture and schema entry.
- Svelte module ModuleDataCapture: min/max envelope chart at the pixel
  width of the chart with zoom to a row window, a paged table of exact
  rows, CSV export of the current window within the value cap, segment
  download through a ticket, refresh while the capture is recording.
- dataCaptureApi.ts wraps the REST endpoints; UPlotChart gains an
  optional formatX prop so that the x axis can show row indices.

feat(driver-scanlab): incremental readout of an RTC recording

- RTCRecording gains StartListWithRecording, ReadRecordedData and
  DiscardRecords, so that a plugin can read the recorded data from the
  card piece by piece while the list executes and hand every piece on at
  once. ExecuteListWithRecording is built on the same code and keeps its
  polling sequence. New error code 1163 RECORDINGREADOUTNOTSTARTED.

feat(build): lz4 sources are compiled into libmc and amc_unittest.

test: 72 unit tests cover the v1 compatibility changes, the v2 format
(round trips of all column types, rollover, truncation, known-answer
bytes of the delta encoding), the capture engine (chunk validation, all
backpressure modes, retention, abort, abandoned captures,
AppendDataTable), the reader (page skipping, downsampling, purged
segments) and the query layer.

Verification: amc_unittest 541 tests pass on upstream/develop 6ee32c6
(this change is rebased onto it; the DataCapture migration is schema step
23 to 24 because upstream took 23 for named configuration versions, and
the error codes start at 10266 because upstream took 10265). On the bench
the recording of an RTC6 card streamed at 100 kHz over four layers (8.0
million rows, 17 bytes per row instead of 52 in the per-layer tables) and
an RTC plus OIE recording over three layers (8.9 million rows, 15 columns,
23 bytes per row instead of 92); every capture matched the per-layer
tables value for value, the exposure time per layer was unchanged against
the blocking readout, and the capture of a cancelled build was closed by
the end-of-build signal within one second.

Known limits: readers see only closed segments while a capture is
recording; after a hard stop of the server the rows of the open segment
are lost and the capture stays in status recording; a min/max envelope
over a window smaller than about 100 million rows decodes every page of
the column; only std::exception is caught in the engine threads; the
Vue2 client has no component for the datacapture item.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…sure so microvector layers larger than one list mark continuously

Until now a microvector layer had to fit into one RTC6 list (4 194 304
positions), and the driver estimated the list usage with the worst case of two
positions per vector. On hardware a 3.15 M vector layer was rejected with "RTC
list capacity exceeded" although it would have fit, and any layer above the list
size could not be marked at all. The ScanLab driver now measures the list usage
exactly and, when a layer is larger than a list, marks it in one continuous
exposure by alternating the two RTC6 lists while the laser keeps running. The
RTC6 has no circular queue mode; alternating lists with automatic list changes
is the replacement its manual describes (Doc.Rev.1.1.5, ch. 6.4.6, p. 118-119,
383, 599, 758).

feat(capacity): exact list capacity instead of a worst-case estimate

- The driver pre-scans the layer and counts the power commands the
  deduplicating download will really emit, using the same power quantization as
  the download itself (one command per change, two for the dual analog port).
- A layer that fits by exact count is marked from one list exactly as before.
- The capacity error reports the exact numbers (vectors, power commands,
  margin, positions left in the open list) and names the configuration switch
  that enables alternating lists.

feat(exposure): layers larger than one list are marked without interruption

- The layer is written as segments: every full list is closed and the card is
  told to continue in the other list the moment it reaches the end, so the
  laser signals are untouched at the boundary and marking does not stop.
- The first segment is streamed as before (prebuffer, then execution starts
  while the rest downloads); each further segment is written into the list the
  card has just left. A list is only reused after the card has confirmed that
  it finished it, so unprocessed commands are never overwritten.
- The overrun protection of the streamed download is kept and extended: a
  download into the list the card executes is still checked for the output
  pointer catching up, a download into the other list cannot collide.
- Opt-in per machine; off by default. Without the opt-in the driver behaves as
  before, apart from the exact capacity count.

feat(exposure): safe behaviour when the card gets ahead of the download

- If the card has already finished its segment before the next list change
  could be armed, the next segment is started explicitly once enough of it is
  written. The marking then has a gap at that point; the event is counted and
  logged as a warning so that it is never silent.
- Any error during the download stops the list execution, switches the laser
  off, neutralises a list change that is still armed (otherwise it would fire
  in the next layer and run stale list memory) and resets the layer state.
- Waiting for a list to become free has a timeout derived from the segment
  size; on timeout the card's list status is logged and the layer fails.

feat(diagnostics): per-layer visibility of the segmented exposure

- Log lines for the capacity decision, every segment boundary (list, vector
  count, wait time, whether the automatic change was armed) and a summary with
  segment count, fallbacks, minimum download margin and maximum list wait.
- New driver API for the machine plugin: enable alternating lists with an
  optional test-aid capacity limit (forces many segments on an ordinary layer)
  and a tail reserve per segment, and read the statistics of the last layer.
- RTC6 list handling calls needed for this (protected list opening, list status
  bits, list-relative pointers) are added to the SDK wrapper and recorded in
  the SDK call journal like every other call.
- Bindings and interfaces regenerated with ACT from LibMCDriver_ScanLab.xml.

Verified on the RTC6 Ethernet card #20421070 on 2026-10-07: a 3-layer job
(1.98 M / 3.15 M / 3.13 M vectors) marked from one list each, and a 60 155 411
vector layer marked as 15 segments with 0 fallbacks, a minimum margin of
2 072 268 positions and a maximum list wait of 31.8 s. Design document:
Plan/RTC6_DoubleBuffering_Implementation_Plan_2026-10-07.html.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…the exposure of large microvector layers and let long-running UI events finish without a timeout

The download of a microvector layer was one blocking driver call. For a layer
that is marked from alternating RTC6 lists it returns only when the last
segment is written, i.e. after most of the layer has been marked. During that
time the caller cannot read out the RTC recording, although the measurement
memory is a ring buffer of 4 194 304 entries per channel and has to be read
while a long layer is being marked. The ScanLab driver now also offers the
download as a session of short steps that never wait for the card, so that the
machine plugin can read out the recording between two steps. The readout is
prepared for running in parallel to the download and reads the measurement
memory completely at its wrap-around. In the web client, UI events get their
own request timeout, because loading a large recording takes longer than the
10 s of an ordinary request.

feat(exposure): stepwise download of a microvector layer

- New driver API on the RTC context: BeginMicrovectorStreaming writes the
  first part of the layer and, on the streamed path, starts list execution;
  ContinueMicrovectorStreaming writes the next part within a time budget in
  milliseconds and reports when the layer is complete.
- A step returns when its time budget is used up, when the next list is not
  free yet or when the layer is complete. It never waits for the card: the
  wait for a list to become free is one status poll per step, with the same
  timeout as before. A budget of 0 writes a single block of 20 000 vectors.
- Layers that are not streamed (small layers, sequential download) are
  written completely by BeginMicrovectorStreaming. It then reports that
  execution has not been started and the caller executes the list as usual.
- AddMicrovectorMovement keeps its behaviour; it is now the blocking wrapper
  around the same steps.
- The microvector buffer is not copied. It must stay valid and unchanged
  until the layer is complete or the session has been aborted.

feat(exposure): safe handling of an interrupted download

- An error in any step stops the list execution, switches the laser off,
  neutralises a list change that is still armed and resets the layer state,
  exactly as the blocking call did.
- New driver API AbortMicrovectorStreaming does the same on request, for a
  caller that fails between two steps, and logs a warning with the number of
  vectors written so far. Without an active session it does nothing.
- A second download while a session is active and a step without a session
  are rejected with an error. A step after completion reports completion and
  does nothing.
- A session that is still open when the next list is set up is dropped with
  a warning; that list is not running any more, so only the bookkeeping is
  reset.

feat(recording): readout in parallel to the download

- The incremental readout no longer reports the end of the recording while a
  stepwise download is in progress. The card can be idle between two segments
  of a layer (explicit start of a segment after the card got ahead of the
  download), and this must not be taken for the end of the layer.

fix(recording): complete readout at the wrap-around of the measurement memory

- At the wrap-around the last block ended one entry before the end of the
  measurement memory, so one sample per channel was skipped at every wrap.
  The block now ends exactly at the end of the memory.
- The card rejects a block that crosses the end of the memory with RTC error
  16 (confirmed on hardware on 2026-10-07), so the size must not be raised.
- A readout position beyond the measurement memory at a wrap-around is
  reported as a data recording overflow.

feat(client-core): own request timeout for UI events

- Requests to the event endpoint use a timeout of 120 s instead of the
  general 10 s. A UI event runs plugin code on the server: loading a
  recording of 60 million rows for the SMC Viewer takes 10 to 50 s, longer
  than the client waited.
- All other requests keep their timeouts.

feat(driver-scanlab): interface definition and bindings

- The three new methods are added to LibMCDriver_ScanLab.xml, the C++
  dynamic bindings and the interface wrapper.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…ver reliably on Windows when the gRPC, ScanLab OIE and ScanLab SMC drivers initialize at the same time

On Windows about every second start of amc_server ended with one state machine
in fatalerror: plc_grpc, less often scanner or pathplanning, failed with
"COULDNOTLOADLIBRARY: the library could not be loaded". Which of them failed
changed from start to start, and the message named neither the library nor the
Windows error. The gRPC, ScanLab OIE and ScanLab SMC drivers now load their
libraries independent of process-wide state, so that their state machines can
initialize at the same time.

Cause: each driver unpacks its DLLs into its own temporary directory and loads
the main DLL by absolute path. The Windows loader searches the dependencies of
that DLL by name (abseil, protobuf, re2 and libcrypto for gRPC; Qt5Core,
Qt5Network and libssl for OIE; RtcService and xerces for SMC), so the drivers
set the DLL directory of the process to their own directory right before
LoadLibraryW, OIE and SMC the current directory as well. Both are one value
for the whole process, and the three state machines run their init step in
their own threads within a few tens of milliseconds. A second thread could
replace the directory between the two calls of the first, whose dependencies
were then searched in the wrong place.

fix(driver-scanlaboie, driver-scanlabsmc): SDK load independent of the DLL directory

- The OIE and the SMC library are loaded with LoadLibraryExW and
  LOAD_WITH_ALTERED_SEARCH_PATH. With the absolute file name the loader
  resolves the dependencies from the directory of the library itself,
  whatever another thread has set in the meantime.
- The calls that set the DLL directory and the current directory stay in
  place; the SMC library may read files relative to the current directory.

fix(driver-grpc): wrapper library loaded ahead of the generated binding

- The LoadLibraryW call for the gRPC wrapper is part of its ACT-generated
  binding and is not changed. The driver loads the wrapper DLL first, in the
  same way as above; the binding then finds the module already in the process
  and no dependency search takes place.
- The handle of the preloaded module is kept for the lifetime of the process,
  like the handle the wrapper holds itself.

fix(diagnostics): retry and an error message that names the cause

- A load is attempted up to five times with a pause of 200 ms; before a
  repeated attempt the driver sets its directory again. This covers a
  dependency the loader still picks up by name.
- The exception names the library and carries the Windows error code, e.g.
  126 when the DLL or one of its dependencies is not found.
- Windows only; the Linux load path is unchanged.

Not chosen: a shared mutex around the two calls would have to be adopted by
every driver that touches this state and be held at each later directory
switch; initializing the state machines one after another would not cover the
directory switches the OIE and SMC drivers make at run time.

Known limit: the A3200, TML, OPC UA, OpenCV and LibOAPC drivers set the DLL
directory in the same way and are unchanged; they were not part of the
affected configuration.

Verified on the bench on 2026-10-07 (Windows, amc_server built from 101ce4a):
with all three drivers changed 5 of 5 starts came up, every library loading at
the first attempt.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…temporary SMC files after use and letting plugins switch off the simulation output

Planning a large layer with SCANmotionControl left several gigabytes of
temporary files per layer in the working directory until the SMC context
was released, so multi-layer jobs filled the system drive. The driver now
removes each temporary file as soon as its content has been handed over,
and plugins that never read the SMC simulation output can stop SMC from
writing it in the first place.

feat: delete the module recording (.slm) right after it has been read

LoadRawSimulationData deletes the temporary module file as soon as the
raw content has been copied to the caller. The job destructor removes a
file that was never read (exception in the plugin, context re-creation
after an error). A failed deletion is logged as a warning and the file
stays managed, so the working directory cleanup removes it at the
latest. A second read of the same job reports
SIMULATIONWORKINGFILEISNOTINITIALIZED. The call also validates the size
of the caller's buffer and reports BUFFERTOOSMALL instead of writing
past it.

feat: delete the RTC log record file after it has been parsed

ReadLogRecordFile (hardware mode, SMC 1.x) removes its temporary CSV
when the data table has been filled, on the normal path and when the
parser or the table throws.

feat: allow switching off the SMC simulation file output

SMCConfiguration gets SetSimulationFileOutputEnabled and
GetSimulationFileOutputEnabled. The default is true and keeps the
previous behaviour. With false the generated SMC configuration sets
DisableFileOutput, so SMC writes no simulation output at all; this
saves several gigabytes of disk space and minutes of planning time per
large layer. LoadSimulationData then fails with the new error
SIMULATIONFILEOUTPUTDISABLED instead of failing on a missing file.

feat: delete the complete SMC simulation output set after it has been read

When the simulation output is enabled, LoadSimulationData removes the
output files of the job after the data table has been filled. SMC
rotates its output into <name>.csv, <name>.1.csv, <name>.2.csv and
writes <name>.xml, so the whole set is removed by prefix instead of the
single file name SMC reports. The job remembers the name at the end of
Execute, and the destructor deletes the set for jobs whose data was
never read.

docs: document the single-read semantics in the interface description

The IDL descriptions of LoadSimulationData and LoadRawSimulationData
state that the data can be read once and that the temporary files are
deleted afterwards. The generated headers and interface wrappers are
updated accordingly.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…sume, let background operations hand a download to the user's browser and cancel it, and show button tooltips

Large downloads such as the ZIP of all build recordings (9 GB on the
rig) broke on Windows at 2 GiB and needed four copies of the file in
memory, and a plugin had to build such an archive inside the UI event
that the button triggered, so the request timed out, a page reload lost
the result and nothing could be cancelled. The framework now streams
downloads from disk and gives state machines the means to prepare an
archive on their own thread, hand the download to the browser of the
session that asked for it, follow the delivery and cancel it in a way
the browser shows as a failed download.

feat: stream downloads of any size with pause and resume

The download endpoint sends storage streams in 4 MiB chunks through the
HTTP content provider instead of loading them into memory, so the file
size no longer matters and the first byte leaves at once. Responses
carry Content-Length, Accept-Ranges, a strong ETag and Last-Modified,
answer HEAD, and serve single byte ranges with 206, so browser download
managers can pause, resume and continue after a connection drop. The
server validates Range and If-Range itself, because the HTTP library
does not check ranges of streamed bodies: unsatisfiable or multiple
ranges get 416 with Content-Range: bytes */N, a stale If-Range gets the
full body, and ranges are ignored on every non-streamed response. The
socket write timeout is 30 s and at most four downloads run at the same
time; further ones get 503 with Retry-After.

feat: expire and revoke download tickets and log every download

Download tickets are valid for 24 hours and can be revoked; an expired
or revoked ticket answers 410 Gone, also for a resume. Every download
is logged with the client address, the number of bytes sent and
whether it completed (database schema 25).

feat: hand a download from a state machine to the browser of one session

A state machine can start a download in the browser of a given client
session: the server creates the ticket for the user of that session and
queues a "streamdownload" client action for it. Pending actions reach
the browser as the "actions" event of the live update stream or as the
"pendingactions" field of the polling response. Of several tabs of one
session only the first one to claim an action (POST
api/frontend/actions/<uuid>/claim, 409 for the others) executes it;
unclaimed actions expire after 30 s. State machines can read the
delivery progress of a stream (active connections, bytes delivered
without a gap, completion), cancel its downloads so that running
connections stop, the tickets are revoked and queued actions are
dropped, delete a finished stream and keep only the newest archives of
a name.

feat: keep the disk clean of unfinished and outdated archives

A ZIP stream that is discarded or released without Finish deletes its
file and its database entry, finished writers release their handles,
and ZIP streams left behind by a crash or a forced stop are removed at
server start. Errors of the storage stream read callbacks now reach
the caller instead of being reported as success.

feat: show tooltips on buttons

Buttons accept a hint attribute, also bound with sync:hint to a state
machine parameter; the client shows it as the tooltip and accessible
label of the button.

feat: download via the browser download manager, ignore double clicks

The client downloads through a programmatic click on a link with the
download attribute instead of window.open, so popup blocking and page
memory no longer limit the size; the file name sent by the server is
used. A button whose previous event is still pending does not send a
second one, so a double click cannot start and cancel an operation.
Client actions from event responses, widget responses and the session
queue run through one function; the temporary one-hour timeout of the
recordings download event is removed.

feat: unit tests for the download registry, ranges and storage

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…e server log instead of the console

The OIE driver printed its packet diagnostics (xy fallbacks, signal-count
mismatches, callback exceptions) to std::cout, which nobody keeps on the
rigs, and swallowed the error events of the OIE SDK entirely. When the
measurement tag was lost on 2026-10-13, no trace of the OIE side survived.

The device now counts in the SDK callback thread only (atomics, the first
occurrence of each category under a diagnostics mutex) and writes through
the driver environment from the plugin thread:

feat: every RetrieveCurrentRecording reports the categories whose counter
grew since the last report, with the first packet, tag and error text.

feat: ClearCurrentRecording logs a summary of the previous layer: packets
not retrieved since the last retrieval, packets since the previous clear
(recorded and skipped), all counters and the last measurement tag.

feat: onErrorEvent counts the events and keeps the first and the last
error code and value.

feat: the app start and the recording frequency are logged as info lines.

AddDevice hands the driver environment to the device instance; a null
environment switches the output off. The ACT interface is unchanged.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>
…ToUniqueEmulatedDevice

The new driver method enables the pylon camera emulation (PYLON_CAMEMU) only for the time of
the connection, enumerates the devices, picks the single device of the class BaslerCamEmu and
opens it under the given connection identifier. Physical cameras are ignored, so the emulator
can be selected while a real camera is connected, and the environment variable never reaches a
later enumeration of physical cameras. The existing methods are unchanged.

Generated interfaces and bindings were regenerated with ACT 1.8.0-develop; the diff contains
only the new method.

Signed-off-by: Yury Rodzikau <y.rodzikau@c1-technologies.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant