Changelog#
1.5.0 (2026-05-02)#
Breaking changes
QScanPattern.__init__:polargraphis now a required keyword argument (noNonedefault). Any code that constructs a scan pattern without supplyingpolargraph=will raiseTypeError.QScanner.setupScannerandQScanPatternWidgethave been updated accordingly;QScanPatternWidgetno longer creates a defaultPolarScan()without hardware.QScanner._SCAN_LOCKED: removed. Replaced by the instance method_lockedWidgets() -> list, which returns the widget objects directly. Subclasses that extended_SCAN_LOCKEDmust now override_lockedWidgets()instead (e.g.return super()._lockedWidgets() + [self.my_widget]).
Other changes
QScanPattern._moveTo: return type changed fromstrto the new private_MoveResultenum (COMPLETE,PAUSED,ABANDONED). All six comparison sites updated; bare string literals eliminated.Polargraph.r2f,r2i: corrected return-type annotations from the runtime callstuple(float, float)/tuple(int, int)to the correct subscript formstuple[float, float]/tuple[int, int].Polargraph.i2r: now accepts scalar or array inputs formandn; theysq < 0guard usesnp.anyandnp.maximumso the method works correctly for both cases.RasterScan.trajectorycalls it directly with arrays and the duplicatei2r_vechelper is removed.Motors.running: consolidated to delegate tobool(self.indexes[2])(thePcommand), eliminating the separateRcommand.Motors.process: added NumPy-style docstring documenting its role as a hook for unsolicited serial data and the subclass override contract.FakeMotors.__init__: added inline comment explaining why_accelerationis reset to zero (fake hardware does not simulate trapezoidal ramp dynamics).Tests: added coverage for
_onMeasuresubclass override,interruptAndClose/closeRequested(all three states), and_syncPatternThread(no-op for fake, retry when device on main thread, move when device on worker thread).
1.4.0 (2026-05-02)#
Polargraph.moveTo: replaced proportional-speed formula with an acceleration-aware calculation that ensures both motors arrive simultaneously despite AccelStepper’s trapezoidal ramp times. The faster motor runs at fullspeed; the slower motor’s speed is solved from the arrival-time equation, using the triangular profilet = 2√(n/a)for short moves that never reachspeed, and the trapezoidal profilet = n/v + v/aotherwise. The correction eliminates the arc-shaped deviation that occurred during inter-arc transitions inPolarScanwhen one motor finished its ramp before the other. New module-level helpers_motor_timeand_sync_speedimplement the calculation.Motors.__init__: initialise_accelerationto[1000., 1000.]to match the acam3 firmware default (stepper1/2.setAcceleration(1000.0)insetup()), so the timing correction activates automatically without the user having to callself.acceleration = [...].FakeMotorsresets acceleration to zero so fake hardware falls back to proportional speeds unchanged.Polargraph: addedr2f(continuous step-index conversion) andr2i(integer step-index conversion) as explicit methods, replacing the inline computation previously duplicated acrossmoveToandi2r.QScanPattern: redesigned as a four-state machine (IDLE,MOVING,SCANNING,PAUSED). Replaced themoveFinishedandscanFinishedsignals with a singlestateChanged(ScanState)signal. Addedpause(),resume(),abandon(),toggle(), andinterruptAndClose()slots; addedscanning(),moving(), andactive()predicates. The_moveToengine returns'complete','paused', or'abandoned'and handles the continuation-callable pattern soresume()re-enters the correct scan phase after a pause. Motors are not released on pause (hold position).QScanner: updated for the newQScanPatternstate machine. Button text and enabled state are driven entirely bystateChanged. Added_toggleand_interruptCloseprivate signals for safe cross-thread dispatch to the scan pattern. Added_syncPatternThreadto move the scan pattern to the polargraph device thread afterQPolargraphWidget._firstShowhas completed, eliminatingQSocketNotifiercross-thread errors and serial corruption with real hardware.plotBeltcaches the last emitted position so it never readspolargraph.positionfrom the GUI thread.FakePolargraph.position: fixed position tracking duringstop()—_store['indexes']is now updated at each consumed trajectory step so the stored position matches the actual simulated position after a mid-trajectory halt.
1.3.3 (2026-04-29)#
pyproject.toml: bumpedQInstrumentdependency floor to>=3.0. QInstrument 3.0 is a breaking release; QPolargraph is fully compatible asMotorsonly uses APIs onQSerialInstrument, which are unchanged.
1.3.2 (2026-04-20)#
QPolargraphWidget: when no acam3 device is found at startup, the widget now checks for any Arduino-like serial device and, if one is found, opensFlashDialogwith an explanatory message. On successful flash the connection is retried after a 2-second boot delay so the Arduino has time to restart. If no Arduino is present, the user closes the dialog, or the flash fails, the widget falls back toFakePolargraphas before.FlashDialog: added optionalmessageparameter; explanatory text is shown above the form when the dialog is opened automatically. On a successful flash the dialog now callsaccept()(closing itself) rather than showing a separate successQMessageBox; the output area already shows Firmware installed successfully.FlashFirmware: fixedFIRMWARE_VERSIONimport — it is a class attribute ofMotors, not a module-level name.
1.3.1 (2026-04-19)#
QScanPattern: removedQCoreApplication.processEvents()from the scan loop now thatQSerialInterface.receive()no longer blocks the event loop (requiresQInstrument>=2.4).QScanner._startMove: removed outdated docstring note aboutprocessEvents().pyproject.toml: bumpedQInstrumentdependency to>=2.4.
1.3.0 (2026-04-19)#
acam3firmware 3.5.0: raised baud rate from 9600 to 115200; removed diagnosticIcommand;Pquery now always respondsP:n1:n2:running(running state encoded as a third field rather than an ambiguous prefix);n1/n2made local to the functions that use them.Motors.comm: updated baud rate toBaud115200.Motors.indexes: updated parser for the newP:n1:n2:runningresponse format.
1.2.4 (2026-04-18)#
QScanner.plotData: fixedpg.hsvColorkeyword argument froms=tosat=(correct pyqtgraph API).
1.2.3 (2026-04-18)#
QScanner.plotData: renamedvalueparameter tosaturationand mapped it to HSV saturation (s=) instead of brightness. Loud/strong areas render as pure hue; quiet/weak areas appear white.
1.2.2 (2026-04-18)#
QScanner.plotData: added optionalvalueparameter (HSV brightness, default1.0) so subclasses can encode a second scalar quantity as point brightness without changing existing behavior.
1.2.1 (2026-04-18)#
QScanPattern.rect: converted from a plain method to a@propertyso that callers can writepattern.rectconsistently with other geometric properties (width,height,step, etc.). Updated all internal callers inPolarScan,RasterScan, andTarzanScan.
1.2.0 (2026-04-18)#
QScanPattern: replaced_moving/_scanningbool flags with aScanStateenum (IDLE,MOVING,SCANNING). Addedmoving()predicate and updatedscanning()to use the enum.QScanPattern: addedcloseRequestedsignal, emitted (via_setIdle()) when the state returns to IDLE afterinterruptAndClose()was called.QScanPattern.home/center: set state to MOVING before the move and call_setIdle()on completion.QScanPattern.scan: full state-machine transitions IDLE→MOVING→SCANNING→MOVING→IDLE; skips home only on close-interrupt.QScanner.closeEvent: usemoving()(notscanning()) andinterruptAndClose(); drop the 100 ms timer —closeRequestedtriggersclose()via aQueuedConnectionwhen motion stops.QScanner.toggleScan: usemoving()so Stop works during the initial positioning move, not only during active data collection.
1.1.7 (2026-04-18)#
QScanPattern: addedinterruptAndClose()method that sets a new_closingflag in addition to_interrupt.scan()now skipshome()only when_closingis set (window-close path), preserving the original behavior of returning home after a normal Stop.QScanner.closeEvent: callinterruptAndClose()instead ofinterrupt()so Stop still goes home but window-close does not hang.
1.1.6 (2026-04-18)#
QScanPattern.scan: set_scanning = Truebefore the initial move to the start position so thatcloseEventinterrupts rather than accepts a close request during early scan setup, preventing a hang intime.sleep.
1.1.5 (2026-04-18)#
QScanPattern.scan: skiphome()when the scan was interrupted, preventing a hang when the application is closed during an active scan.
1.1.4 (2026-04-18)#
FakePolargraph.moveTo: number of trajectory waypoints is now proportional to arc length (dist_mm / speed / step_delay), matching the real hardware which records data at equal time intervals. Longer arcs produce more data points than shorter ones. Falls back to one-step-per-motor-step whenstep_delay=0(automated tests).
1.1.3 (2026-04-18)#
FakePolargraph.moveTo: interpolates in motor step-index space rather than Cartesian space, then converts viai2r. The simulated trajectory now follows arcs matching the real belt-drive geometry instead of straight lines.
1.1.2 (2026-04-18)#
Fixed
__init__.pylazy__getattr__: resolved value is now cached intoglobals()so that Python’s submodule binding side effect cannot shadow the class with the module object on subsequent accesses.
1.1.1 (2026-04-17)#
Refactored
FlashFirmware: importsFIRMWARE_VERSIONfromMotorsinstead of duplicating_firmware_version().Fixed
PolarScan.trajectory(): now returnsnp.ndarraywith shape(2, n)consistent with all other scan patterns.Refactored
QScanPatternWidget: extracted_FIELDSclass constant; added type hints.Refactored
QScanPattern:polargraphparameter now annotatedPolargraph | NoneviaTYPE_CHECKINGguard.
1.1.0 (2026-04-17)#
Added
TarzanScan: geometry-native scan pattern where each move engages exactly one motor; the payload swings on a circular arc along the natural coordinate lines of the polargraph.Added
TarzanScan.tarzan_B,is_degenerate, andfixed_pointproperties that characterise the periodicity of the scan map.Added
-r/--raster,-p/--polar, and-t/--tarzancommand-line flags toqpolargraphfor selecting the scan pattern at launch (default: polar).Redesigned
PolargraphWidget: removed nestedConfigurationandBelt Drivegroup boxes; all controls now in a compact flat grid with right-aligned labels beside spinboxes.Redesigned
RasterScanWidget: replaced label-above-spinbox layout with the same label-beside-spinbox grid used byPolargraphWidget.Improved startup layout: plot panel maximised by default; scan setup panel takes all spare vertical space in the controls column.
1.0.2 (2026-04-17)#
Fixed
QScanner: serial I/O stays on the main thread;processEvents()in the scan loop keeps the UI responsive without crossing thread boundaries.Added
Motors.scan_i2c()diagnostic to report I2C devices found on the Arduino’s bus — useful for diagnosing motor shield detection failures.Added
Motors.scan_i2c()error hints when motor shield is not detected.Firmware 3.3.2: explicit
Wire.begin(); retryAFMS.begin()up to 5 times on startup;Icommand scans and reports I2C bus addresses.
1.0.1 (2026-04-17)#
Fixed
FlashFirmware: usesystemLocation()instead ofportName()soavrdudereceives the full device path (e.g./dev/tty.usbmodem2101).Fixed
FlashFirmware: usearduino-cli lib upgradeto update already-installed Arduino libraries before flashing.
1.0.0 (2026-04-15)#
Initial release as a polished, cross-platform package.
Migrated from PyQt5 to qtpy for Qt-binding independence.
Replaced MIT license with GPL v3 for consistency with QInstrument.
Added
pyproject.toml, GitHub Actions CI/CD, and git hooks.Added
__main__.pyentry point andqpolargraphGUI script.