From a25824e478d520f12ad9ef279d2df43404f29f31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 05:21:48 +0000 Subject: [PATCH 1/5] Initial plan From 9f26f25ac8e626a0cfd4f14ca14158362d48b895 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 05:40:09 +0000 Subject: [PATCH 2/5] Add validation signature locking plan Co-authored-by: jordanrobot <119146+jordanrobot@users.noreply.github.com> --- .../20260105-validation-signatures-changes.md | 52 +++++++++++++++++++ .../20260105-validation-signatures-details.md | 21 ++++++++ .../20260105-validation-signatures-plan.md | 15 ++++++ 3 files changed, 88 insertions(+) create mode 100644 .copilot-tracking/changes/20260105-validation-signatures-changes.md create mode 100644 .copilot-tracking/details/20260105-validation-signatures-details.md create mode 100644 .copilot-tracking/plans/20260105-validation-signatures-plan.md diff --git a/.copilot-tracking/changes/20260105-validation-signatures-changes.md b/.copilot-tracking/changes/20260105-validation-signatures-changes.md new file mode 100644 index 0000000..380c0ca --- /dev/null +++ b/.copilot-tracking/changes/20260105-validation-signatures-changes.md @@ -0,0 +1,52 @@ + +# Release Changes: Validation Signature Editing Locks + +**Related Plan**: .copilot-tracking/plans/20260105-validation-signatures-plan.md +**Implementation Date**: 2026-01-05 + +## Summary + +Tracking implementation of validation signature checks and edit locking in MotorEditor. + +## Changes + +### Added + +- .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan covering validation signature lock scope and tasks. +- .copilot-tracking/details/20260105-validation-signatures-details.md - Task-level requirements for signature verification and edit locking. + +### Modified + +- None. + +### Removed + +- None. + +## Release Summary + +**Total Files Affected**: 2 + +### Files Created (2) + +- .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan for validation signature locking. +- .copilot-tracking/details/20260105-validation-signatures-details.md - Detailed steps for tasks in the plan. + +### Files Modified (0) + +- None. + +### Files Removed (0) + +- None. + +### Dependencies & Infrastructure + +- **New Dependencies**: None +- **Updated Dependencies**: None +- **Infrastructure Changes**: None +- **Configuration Updates**: None + +### Deployment Notes + +- None. diff --git a/.copilot-tracking/details/20260105-validation-signatures-details.md b/.copilot-tracking/details/20260105-validation-signatures-details.md new file mode 100644 index 0000000..0f115d5 --- /dev/null +++ b/.copilot-tracking/details/20260105-validation-signatures-details.md @@ -0,0 +1,21 @@ + +# Validation Signature Editing Locks Details + +## T1: Evaluate signatures on load + +- After a motor file is loaded or a new motor is created, verify motor, drive, and curve validation signatures using the existing `DataIntegrityService`. +- Store lock state per motor, drive, and curve without mutating the underlying model so that future signature add/remove UI can toggle state cleanly. +- Surface computed lock state through view model properties that can be reused by future UI. + +## T2: Enforce locks in UI and viewmodels + +- Prevent edits to motor properties (including units) when a valid motor signature is present. +- Prevent edits to selected drive and its voltages when that drive has a valid signature; allow adding other drives that are unsigned. +- Prevent edits (rename, delete, data changes) to curve series with valid signatures; ensure data grids and commands respect the lock. +- Keep user feedback minimal but clear (status messaging) while avoiding destructive changes to signatures. + +## T3: Tests + +- Add targeted view model tests that cover signature verification and editing lock behavior for motor, drive/voltage, and curve series paths. +- Prefer unit-level tests that reuse existing factories/mocks and real `DataIntegrityService` for realistic signatures. +- Ensure tests assert that data does not change when a locked section is edited and that unlocked data remains editable. diff --git a/.copilot-tracking/plans/20260105-validation-signatures-plan.md b/.copilot-tracking/plans/20260105-validation-signatures-plan.md new file mode 100644 index 0000000..462983d --- /dev/null +++ b/.copilot-tracking/plans/20260105-validation-signatures-plan.md @@ -0,0 +1,15 @@ + +# Validation Signature Editing Locks Plan + +## Scope + +- Enforce validation signature checks whenever motor files are opened. +- Lock editing of signed motor, drive/voltage, and curve data in MotorEditor while leaving unsigned data editable. +- Prepare code paths so future UI for adding/removing validation signatures can plug into the same lock state. + +## Tasks + +- [ ] T1: Evaluate validation signatures on motor load and capture lock state for motor, drives, and curves. +- [ ] T2: Enforce editing locks in UI/viewmodels based on signature validity for motor properties, drives/voltages, and curve series. +- [ ] T3: Add or update tests covering validation signature detection and lock behavior. +- [ ] T4: Update tracking changes file after each task and mark tasks complete. From c0f9b5a763c4470a16ac760ff4ec7d789222a735 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 05:45:30 +0000 Subject: [PATCH 3/5] Track validation signature lock state Co-authored-by: jordanrobot <119146+jordanrobot@users.noreply.github.com> --- .../20260105-validation-signatures-changes.md | 10 +- .../20260105-validation-signatures-plan.md | 2 +- .../ViewModels/CurveDataTableViewModel.cs | 17 +- .../ViewModels/MainWindowViewModel.cs | 290 ++++++++++++------ 4 files changed, 225 insertions(+), 94 deletions(-) diff --git a/.copilot-tracking/changes/20260105-validation-signatures-changes.md b/.copilot-tracking/changes/20260105-validation-signatures-changes.md index 380c0ca..4e67b54 100644 --- a/.copilot-tracking/changes/20260105-validation-signatures-changes.md +++ b/.copilot-tracking/changes/20260105-validation-signatures-changes.md @@ -17,7 +17,8 @@ Tracking implementation of validation signature checks and edit locking in Motor ### Modified -- None. +- src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs - Captures validation signature lock state for motors, drives, and curves during load and tab changes. +- src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs - Tracks signature-locked series for downstream read-only handling. ### Removed @@ -25,16 +26,17 @@ Tracking implementation of validation signature checks and edit locking in Motor ## Release Summary -**Total Files Affected**: 2 +**Total Files Affected**: 4 ### Files Created (2) - .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan for validation signature locking. - .copilot-tracking/details/20260105-validation-signatures-details.md - Detailed steps for tasks in the plan. -### Files Modified (0) +### Files Modified (2) -- None. +- src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs - Computes validation signature lock state and exposes editability flags. +- src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs - Adds signature lock tracking for curve series. ### Files Removed (0) diff --git a/.copilot-tracking/plans/20260105-validation-signatures-plan.md b/.copilot-tracking/plans/20260105-validation-signatures-plan.md index 462983d..f04beeb 100644 --- a/.copilot-tracking/plans/20260105-validation-signatures-plan.md +++ b/.copilot-tracking/plans/20260105-validation-signatures-plan.md @@ -9,7 +9,7 @@ ## Tasks -- [ ] T1: Evaluate validation signatures on motor load and capture lock state for motor, drives, and curves. +- [x] T1: Evaluate validation signatures on motor load and capture lock state for motor, drives, and curves. - [ ] T2: Enforce editing locks in UI/viewmodels based on signature validity for motor properties, drives/voltages, and curve series. - [ ] T3: Add or update tests covering validation signature detection and lock behavior. - [ ] T4: Update tracking changes file after each task and mark tasks complete. diff --git a/src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs b/src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs index 241f20b..576188e 100644 --- a/src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs +++ b/src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs @@ -125,6 +125,7 @@ public partial class CurveDataTableViewModel : ViewModelBase { private Voltage? _currentVoltage; private CellPosition? _anchorCell; + private ISet _signatureLockedSeries = new HashSet(); [ObservableProperty] private ObservableCollection _rows = []; @@ -227,6 +228,19 @@ public Voltage? CurrentVoltage } } + /// + /// Gets or sets the set of series that are locked due to validation signatures. + /// + public IEnumerable SignatureLockedSeries + { + get => _signatureLockedSeries; + set + { + _signatureLockedSeries = new HashSet(value ?? Enumerable.Empty()); + RefreshData(); + } + } + /// /// Applies a scalar torque value to all currently selected cells. /// @@ -418,7 +432,8 @@ public double GetTorque(int rowIndex, string seriesName) public bool IsSeriesLocked(string seriesName) { var series = _currentVoltage?.Curves.FirstOrDefault(s => s.Name == seriesName); - return series?.Locked ?? false; + var isSignatureLocked = series is not null && _signatureLockedSeries.Contains(series); + return (series?.Locked ?? false) || isSignatureLocked; } /// diff --git a/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs b/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs index 0a2fcfa..aa41021 100644 --- a/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.Input; using CurveEditor.Services; using JordanRobot.MotorDefinition.Model; +using JordanRobot.MotorDefinition.Services; using MotorEditor.Avalonia.Models; using Serilog; using System; @@ -34,6 +35,7 @@ public enum UnsavedChangesChoice private readonly IFileService _fileService; private readonly ICurveGeneratorService _curveGeneratorService; private readonly IValidationService _validationService; + private readonly IDataIntegrityService _dataIntegrityService; private readonly IDriveVoltageSeriesService _driveVoltageSeriesService; private readonly IMotorConfigurationWorkflow _motorConfigurationWorkflow; private readonly IUserSettingsStore _settingsStore; @@ -41,6 +43,9 @@ public enum UnsavedChangesChoice private readonly UnitPreferencesService _unitPreferencesService; private readonly IRecentFilesService _recentFilesService; private readonly IUserPreferencesService _userPreferencesService; + private bool _motorSignatureValid; + private readonly Dictionary _driveSignatureValidity = new(); + private readonly Dictionary _curveSignatureValidity = new(); // Track previous units for conversion private string? _previousTorqueUnit; @@ -126,18 +131,58 @@ public ServoMotor? CurrentMotor if (ActiveTab != null && ActiveTab.Motor != value) { ActiveTab.Motor = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(WindowTitle)); - OnCurrentMotorChanged(value); - } - } - } - - /// - /// Whether the current document has unsaved changes (delegates to active tab). - /// - public bool IsDirty - { + OnPropertyChanged(); + OnPropertyChanged(nameof(WindowTitle)); + OnCurrentMotorChanged(value); + } + } + } + + /// + /// Indicates whether the current motor has a valid validation signature. + /// + public bool HasValidMotorSignature => _motorSignatureValid; + + /// + /// Whether motor properties are editable (blocked when a valid motor signature exists). + /// + public bool CanEditMotorProperties => !_motorSignatureValid; + + /// + /// Indicates whether the currently selected drive has a valid validation signature. + /// + public bool SelectedDriveHasValidSignature => + SelectedDrive is not null && + _driveSignatureValidity.TryGetValue(SelectedDrive, out var valid) && + valid; + + /// + /// Whether drive and voltage properties are editable for the selected drive. + /// + public bool CanEditSelectedDriveAndVoltages => + SelectedDrive is not null && + (!_driveSignatureValidity.TryGetValue(SelectedDrive, out var valid) || !valid); + + /// + /// Indicates whether the currently selected series has a valid validation signature. + /// + public bool SelectedSeriesHasValidSignature => + SelectedSeries is not null && + _curveSignatureValidity.TryGetValue(SelectedSeries, out var valid) && + valid; + + /// + /// Whether the selected curve series is editable. + /// + public bool CanEditSelectedSeries => + SelectedSeries is not null && + (!_curveSignatureValidity.TryGetValue(SelectedSeries, out var valid) || !valid); + + /// + /// Whether the current document has unsaved changes (delegates to active tab). + /// + public bool IsDirty + { get => ActiveTab?.IsDirty ?? false; set { @@ -287,11 +332,11 @@ public Voltage? SelectedVoltage } } - private void NotifySelectionRelatedPropertiesChanged(bool selectionChanged, bool voltageChanged, bool seriesChanged) - { - // Note: AvailableDrives/AvailableVoltages/AvailableSeries are ObservableCollections. - // Their *contents* update via collection change notifications. - // We still raise property-changed for the Selected* properties so other bindings + private void NotifySelectionRelatedPropertiesChanged(bool selectionChanged, bool voltageChanged, bool seriesChanged) + { + // Note: AvailableDrives/AvailableVoltages/AvailableSeries are ObservableCollections. + // Their *contents* update via collection change notifications. + // We still raise property-changed for the Selected* properties so other bindings // (e.g., IsVisible, SelectedItem display) update immediately. if (selectionChanged) @@ -305,10 +350,68 @@ private void NotifySelectionRelatedPropertiesChanged(bool selectionChanged, bool } if (seriesChanged) - { - OnPropertyChanged(nameof(SelectedSeries)); - } - } + { + OnPropertyChanged(nameof(SelectedSeries)); + } + + RaiseValidationLockPropertiesChanged(); + } + + private void RefreshValidationLockState() + { + _motorSignatureValid = false; + _driveSignatureValidity.Clear(); + _curveSignatureValidity.Clear(); + + var motor = CurrentMotor; + if (motor is not null) + { + if (motor.MotorSignature is not null && motor.MotorSignature.IsValid()) + { + _motorSignatureValid = _dataIntegrityService.VerifyMotorProperties(motor); + } + + foreach (var drive in motor.Drives) + { + var driveValid = drive.DriveSignature is not null && + drive.DriveSignature.IsValid() && + _dataIntegrityService.VerifyDrive(drive); + _driveSignatureValidity[drive] = driveValid; + + foreach (var voltage in drive.Voltages) + { + foreach (var curve in voltage.Curves) + { + var curveValid = curve.CurveSignature is not null && + curve.CurveSignature.IsValid() && + _dataIntegrityService.VerifyCurve(curve); + _curveSignatureValidity[curve] = curveValid; + } + } + } + } + + if (CurveDataTableViewModel is not null) + { + var locked = _curveSignatureValidity + .Where(kvp => kvp.Value) + .Select(kvp => kvp.Key) + .ToArray(); + CurveDataTableViewModel.SignatureLockedSeries = locked; + } + + RaiseValidationLockPropertiesChanged(); + } + + private void RaiseValidationLockPropertiesChanged() + { + OnPropertyChanged(nameof(HasValidMotorSignature)); + OnPropertyChanged(nameof(CanEditMotorProperties)); + OnPropertyChanged(nameof(SelectedDriveHasValidSignature)); + OnPropertyChanged(nameof(CanEditSelectedDriveAndVoltages)); + OnPropertyChanged(nameof(SelectedSeriesHasValidSignature)); + OnPropertyChanged(nameof(CanEditSelectedSeries)); + } /// /// Selected series/curve (delegates to active tab). @@ -849,14 +952,15 @@ private void OpenLogsFolder() /// public MainWindowViewModel() { - _curveGeneratorService = new CurveGeneratorService(); - _fileService = new FileService(_curveGeneratorService); - _validationService = new ValidationService(); - _driveVoltageSeriesService = new DriveVoltageSeriesService(); - var chartViewModel = new ChartViewModel(); - var curveDataTableViewModel = new CurveDataTableViewModel(); - var editingCoordinator = new EditingCoordinator(); - _motorConfigurationWorkflow = new MotorConfigurationWorkflow(_driveVoltageSeriesService); + _curveGeneratorService = new CurveGeneratorService(); + _fileService = new FileService(_curveGeneratorService); + _validationService = new ValidationService(); + _dataIntegrityService = new DataIntegrityService(); + _driveVoltageSeriesService = new DriveVoltageSeriesService(); + var chartViewModel = new ChartViewModel(); + var curveDataTableViewModel = new CurveDataTableViewModel(); + var editingCoordinator = new EditingCoordinator(); + _motorConfigurationWorkflow = new MotorConfigurationWorkflow(_driveVoltageSeriesService); _settingsStore = new PanelLayoutUserSettingsStore(); _recentFilesService = new RecentFilesService(_settingsStore); _userPreferencesService = new UserPreferencesService(); @@ -901,15 +1005,16 @@ public MainWindowViewModel() /// public MainWindowViewModel(IUserPreferencesService userPreferencesService) { - _userPreferencesService = userPreferencesService ?? throw new ArgumentNullException(nameof(userPreferencesService)); - _curveGeneratorService = new CurveGeneratorService(); - _fileService = new FileService(_curveGeneratorService); - _validationService = new ValidationService(); - _driveVoltageSeriesService = new DriveVoltageSeriesService(); - var chartViewModel = new ChartViewModel(); - var curveDataTableViewModel = new CurveDataTableViewModel(); - var editingCoordinator = new EditingCoordinator(); - _motorConfigurationWorkflow = new MotorConfigurationWorkflow(_driveVoltageSeriesService); + _userPreferencesService = userPreferencesService ?? throw new ArgumentNullException(nameof(userPreferencesService)); + _curveGeneratorService = new CurveGeneratorService(); + _fileService = new FileService(_curveGeneratorService); + _validationService = new ValidationService(); + _dataIntegrityService = new DataIntegrityService(); + _driveVoltageSeriesService = new DriveVoltageSeriesService(); + var chartViewModel = new ChartViewModel(); + var curveDataTableViewModel = new CurveDataTableViewModel(); + var editingCoordinator = new EditingCoordinator(); + _motorConfigurationWorkflow = new MotorConfigurationWorkflow(_driveVoltageSeriesService); _settingsStore = new PanelLayoutUserSettingsStore(); _recentFilesService = new RecentFilesService(_settingsStore); UnsavedChangesPromptAsync = ShowUnsavedChangesPromptAsync; @@ -943,14 +1048,15 @@ public MainWindowViewModel(IUserPreferencesService userPreferencesService) public MainWindowViewModel(IFileService fileService, ICurveGeneratorService curveGeneratorService) { - _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); - _curveGeneratorService = curveGeneratorService ?? throw new ArgumentNullException(nameof(curveGeneratorService)); - _validationService = new ValidationService(); - _driveVoltageSeriesService = new DriveVoltageSeriesService(); - var chartViewModel = new ChartViewModel(); - var curveDataTableViewModel = new CurveDataTableViewModel(); - var editingCoordinator = new EditingCoordinator(); - _motorConfigurationWorkflow = new MotorConfigurationWorkflow(_driveVoltageSeriesService); + _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); + _curveGeneratorService = curveGeneratorService ?? throw new ArgumentNullException(nameof(curveGeneratorService)); + _validationService = new ValidationService(); + _dataIntegrityService = new DataIntegrityService(); + _driveVoltageSeriesService = new DriveVoltageSeriesService(); + var chartViewModel = new ChartViewModel(); + var curveDataTableViewModel = new CurveDataTableViewModel(); + var editingCoordinator = new EditingCoordinator(); + _motorConfigurationWorkflow = new MotorConfigurationWorkflow(_driveVoltageSeriesService); _settingsStore = new PanelLayoutUserSettingsStore(); _recentFilesService = new RecentFilesService(_settingsStore); _userPreferencesService = new UserPreferencesService(); @@ -990,21 +1096,23 @@ public MainWindowViewModel(IFileService fileService, ICurveGeneratorService curv /// public MainWindowViewModel( IFileService fileService, - ICurveGeneratorService curveGeneratorService, - IValidationService validationService, - IDriveVoltageSeriesService driveVoltageSeriesService, - IMotorConfigurationWorkflow motorConfigurationWorkflow, - ChartViewModel chartViewModel, - CurveDataTableViewModel curveDataTableViewModel, - IUserSettingsStore? settingsStore = null, - Func>? unsavedChangesPromptAsync = null) - { - _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); - _curveGeneratorService = curveGeneratorService ?? throw new ArgumentNullException(nameof(curveGeneratorService)); - _validationService = validationService ?? throw new ArgumentNullException(nameof(validationService)); - _driveVoltageSeriesService = driveVoltageSeriesService ?? throw new ArgumentNullException(nameof(driveVoltageSeriesService)); - _motorConfigurationWorkflow = motorConfigurationWorkflow ?? throw new ArgumentNullException(nameof(motorConfigurationWorkflow)); - _settingsStore = settingsStore ?? new PanelLayoutUserSettingsStore(); + ICurveGeneratorService curveGeneratorService, + IValidationService validationService, + IDriveVoltageSeriesService driveVoltageSeriesService, + IMotorConfigurationWorkflow motorConfigurationWorkflow, + ChartViewModel chartViewModel, + CurveDataTableViewModel curveDataTableViewModel, + IUserSettingsStore? settingsStore = null, + Func>? unsavedChangesPromptAsync = null, + IDataIntegrityService? dataIntegrityService = null) + { + _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); + _curveGeneratorService = curveGeneratorService ?? throw new ArgumentNullException(nameof(curveGeneratorService)); + _validationService = validationService ?? throw new ArgumentNullException(nameof(validationService)); + _dataIntegrityService = dataIntegrityService ?? new DataIntegrityService(); + _driveVoltageSeriesService = driveVoltageSeriesService ?? throw new ArgumentNullException(nameof(driveVoltageSeriesService)); + _motorConfigurationWorkflow = motorConfigurationWorkflow ?? throw new ArgumentNullException(nameof(motorConfigurationWorkflow)); + _settingsStore = settingsStore ?? new PanelLayoutUserSettingsStore(); _recentFilesService = new RecentFilesService(_settingsStore); _userPreferencesService = new UserPreferencesService(); UnsavedChangesPromptAsync = unsavedChangesPromptAsync ?? ShowUnsavedChangesPromptAsync; @@ -1203,14 +1311,16 @@ private void InitializeActiveTabWithMotor() // - Auto-select preferred voltage // - Update chart and editor fields if (AvailableDrives.Count > 0) - { - SelectedDrive = AvailableDrives.FirstOrDefault(); - Log.Information("[INIT] Auto-selected drive: {SelectedDrive}", SelectedDrive?.Name); - } - - Log.Information("[INIT] Tab.SelectedDrive AFTER auto-select: {SelectedDrive}", ActiveTab?.SelectedDrive?.Name); - Log.Information("[INIT] InitializeActiveTabWithMotor() - END"); - } + { + SelectedDrive = AvailableDrives.FirstOrDefault(); + Log.Information("[INIT] Auto-selected drive: {SelectedDrive}", SelectedDrive?.Name); + } + + RefreshValidationLockState(); + + Log.Information("[INIT] Tab.SelectedDrive AFTER auto-select: {SelectedDrive}", ActiveTab?.SelectedDrive?.Name); + Log.Information("[INIT] InitializeActiveTabWithMotor() - END"); + } /// /// Handles active tab changes by notifying all dependent properties. @@ -1259,16 +1369,18 @@ private void OnActiveTabChanged() Log.Information("[TAB_CHANGE] Notifying combo-box bindings to refresh display"); OnPropertyChanged(nameof(AvailableDrives)); OnPropertyChanged(nameof(AvailableVoltages)); - OnPropertyChanged(nameof(AvailableSeries)); - OnPropertyChanged(nameof(SelectedDrive)); - OnPropertyChanged(nameof(SelectedVoltage)); - OnPropertyChanged(nameof(SelectedSeries)); - - Log.Information( - "[TAB_CHANGE] Post-notify snapshot: AvailableDrives={AvailableDrives} AvailableVoltages={AvailableVoltages} AvailableSeries={AvailableSeries} SelectedDrive={SelectedDrive} SelectedVoltage={SelectedVoltage}", - ActiveTab?.AvailableDrives.Count ?? 0, - ActiveTab?.AvailableVoltages.Count ?? 0, - ActiveTab?.AvailableSeries.Count ?? 0, + OnPropertyChanged(nameof(AvailableSeries)); + OnPropertyChanged(nameof(SelectedDrive)); + OnPropertyChanged(nameof(SelectedVoltage)); + OnPropertyChanged(nameof(SelectedSeries)); + + RefreshValidationLockState(); + + Log.Information( + "[TAB_CHANGE] Post-notify snapshot: AvailableDrives={AvailableDrives} AvailableVoltages={AvailableVoltages} AvailableSeries={AvailableSeries} SelectedDrive={SelectedDrive} SelectedVoltage={SelectedVoltage}", + ActiveTab?.AvailableDrives.Count ?? 0, + ActiveTab?.AvailableVoltages.Count ?? 0, + ActiveTab?.AvailableSeries.Count ?? 0, ActiveTab?.SelectedDrive?.Name, ActiveTab?.SelectedVoltage?.Value); @@ -2632,15 +2744,17 @@ private void OnCurrentMotorChanged(ServoMotor? value) VoltagePowerEditor = SelectedVoltage?.Power.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; VoltageMaxSpeedEditor = SelectedVoltage?.MaxSpeed.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; VoltagePeakTorqueEditor = SelectedVoltage?.RatedPeakTorque.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; - VoltageContinuousTorqueEditor = SelectedVoltage?.RatedContinuousTorque.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; - VoltageContinuousAmpsEditor = SelectedVoltage?.ContinuousAmperage.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; - VoltagePeakAmpsEditor = SelectedVoltage?.PeakAmperage.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; - } - - // Populate ValidationErrors/ValidationErrorsList immediately when switching or loading motors. - // Without this, errors only show up after an edit (MarkDirty) or manual refresh. - ValidateMotor(); - } + VoltageContinuousTorqueEditor = SelectedVoltage?.RatedContinuousTorque.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; + VoltageContinuousAmpsEditor = SelectedVoltage?.ContinuousAmperage.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; + VoltagePeakAmpsEditor = SelectedVoltage?.PeakAmperage.ToString(System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty; + } + + RefreshValidationLockState(); + + // Populate ValidationErrors/ValidationErrorsList immediately when switching or loading motors. + // Without this, errors only show up after an edit (MarkDirty) or manual refresh. + ValidateMotor(); + } private void SyncUnitSubscriptionsForMotor(ServoMotor? motor) { From 18daac8d08dc65aaa1128add1d0273f8d4a73ed7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 06:13:03 +0000 Subject: [PATCH 4/5] Enforce validation signature edit locks Co-authored-by: jordanrobot <119146+jordanrobot@users.noreply.github.com> --- .../20260105-validation-signatures-changes.md | 10 +- .../20260105-validation-signatures-plan.md | 2 +- .../ViewModels/MainWindowViewModel.cs | 966 +++++++++++------- .../Views/CurveDataPanel.axaml.cs | 42 +- .../Views/MotorPropertiesPanel.axaml | 71 +- 5 files changed, 681 insertions(+), 410 deletions(-) diff --git a/.copilot-tracking/changes/20260105-validation-signatures-changes.md b/.copilot-tracking/changes/20260105-validation-signatures-changes.md index 4e67b54..8ace306 100644 --- a/.copilot-tracking/changes/20260105-validation-signatures-changes.md +++ b/.copilot-tracking/changes/20260105-validation-signatures-changes.md @@ -19,6 +19,8 @@ Tracking implementation of validation signature checks and edit locking in Motor - src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs - Captures validation signature lock state for motors, drives, and curves during load and tab changes. - src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs - Tracks signature-locked series for downstream read-only handling. +- src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml - Disables motor/drive/voltage editors when corresponding signatures are valid. +- src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs - Blocks rename/lock/delete and column lock toggles for signature-locked curve series. ### Removed @@ -26,17 +28,19 @@ Tracking implementation of validation signature checks and edit locking in Motor ## Release Summary -**Total Files Affected**: 4 +**Total Files Affected**: 6 ### Files Created (2) - .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan for validation signature locking. - .copilot-tracking/details/20260105-validation-signatures-details.md - Detailed steps for tasks in the plan. -### Files Modified (2) +### Files Modified (4) -- src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs - Computes validation signature lock state and exposes editability flags. +- src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs - Computes validation signature lock state, exposes editability flags, and blocks edits when signed. - src/MotorEditor.Avalonia/ViewModels/CurveDataTableViewModel.cs - Adds signature lock tracking for curve series. +- src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml - Disables motor/drive/voltage editors based on signature lock state. +- src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs - Prevents edits and lock toggles for signature-locked curve series. ### Files Removed (0) diff --git a/.copilot-tracking/plans/20260105-validation-signatures-plan.md b/.copilot-tracking/plans/20260105-validation-signatures-plan.md index f04beeb..9ded3ec 100644 --- a/.copilot-tracking/plans/20260105-validation-signatures-plan.md +++ b/.copilot-tracking/plans/20260105-validation-signatures-plan.md @@ -10,6 +10,6 @@ ## Tasks - [x] T1: Evaluate validation signatures on motor load and capture lock state for motor, drives, and curves. -- [ ] T2: Enforce editing locks in UI/viewmodels based on signature validity for motor properties, drives/voltages, and curve series. +- [x] T2: Enforce editing locks in UI/viewmodels based on signature validity for motor properties, drives/voltages, and curve series. - [ ] T3: Add or update tests covering validation signature detection and lock behavior. - [ ] T4: Update tracking changes file after each task and mark tasks complete. diff --git a/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs b/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs index aa41021..e5a9c22 100644 --- a/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/src/MotorEditor.Avalonia/ViewModels/MainWindowViewModel.cs @@ -412,6 +412,50 @@ private void RaiseValidationLockPropertiesChanged() OnPropertyChanged(nameof(SelectedSeriesHasValidSignature)); OnPropertyChanged(nameof(CanEditSelectedSeries)); } + + private bool EnsureMotorEditingAllowed() + { + if (_motorSignatureValid) + { + StatusMessage = "Motor properties are locked by a validation signature."; + return false; + } + + return true; + } + + private bool EnsureDriveEditingAllowed() + { + if (!CanEditSelectedDriveAndVoltages) + { + StatusMessage = "Drive properties are locked by a validation signature."; + return false; + } + + return true; + } + + private bool IsDriveLockedBySignature(Drive drive) + => _driveSignatureValidity.TryGetValue(drive, out var valid) && valid; + + internal bool IsSeriesLockedBySignature(Curve series) + => _curveSignatureValidity.TryGetValue(series, out var valid) && valid; + + internal bool EnsureSeriesEditingAllowed(Curve? series) + { + if (series is null) + { + return false; + } + + if (IsSeriesLockedBySignature(series)) + { + StatusMessage = $"Series '{series.Name}' is locked by a validation signature."; + return false; + } + + return true; + } /// /// Selected series/curve (delegates to active tab). @@ -1774,15 +1818,20 @@ private void RefreshMotorEditorsFromCurrentMotor() /// Edits the motor name via an undoable command. /// /// The new motor name. - public void EditMotorName(string newName) - { - if (CurrentMotor is null) - { - return; - } - - var oldName = CurrentMotor.MotorName ?? string.Empty; - var newNameValue = newName ?? string.Empty; + public void EditMotorName(string newName) + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldName = CurrentMotor.MotorName ?? string.Empty; + var newNameValue = newName ?? string.Empty; if (string.Equals(oldName, newNameValue, StringComparison.Ordinal)) { @@ -1800,15 +1849,20 @@ public void EditMotorName(string newName) /// Edits the motor manufacturer via an undoable command. /// /// The new manufacturer. - public void EditMotorManufacturer(string newManufacturer) - { - if (CurrentMotor is null) - { - return; - } - - var oldManufacturer = CurrentMotor.Manufacturer ?? string.Empty; - var newManufacturerValue = newManufacturer ?? string.Empty; + public void EditMotorManufacturer(string newManufacturer) + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldManufacturer = CurrentMotor.Manufacturer ?? string.Empty; + var newManufacturerValue = newManufacturer ?? string.Empty; if (string.Equals(oldManufacturer, newManufacturerValue, StringComparison.Ordinal)) { @@ -1824,18 +1878,23 @@ public void EditMotorManufacturer(string newManufacturer) /// /// Edits the motor max speed via an undoable command. /// - public void EditMotorMaxSpeed() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.MaxSpeed; - if (!TryParseDouble(MaxSpeedEditor, oldValue, out var newValue)) - { - MaxSpeedEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorMaxSpeed() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.MaxSpeed; + if (!TryParseDouble(MaxSpeedEditor, oldValue, out var newValue)) + { + MaxSpeedEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -1854,18 +1913,23 @@ public void EditMotorMaxSpeed() /// Edits the motor part number via an undoable command. /// /// The new part number. - public void EditMotorPartNumber(string newPartNumber) - { - if (CurrentMotor is null) - { - return; - } - - var oldPartNumber = CurrentMotor.PartNumber ?? string.Empty; - var newPartNumberValue = newPartNumber ?? string.Empty; - if (string.Equals(oldPartNumber, newPartNumberValue, StringComparison.Ordinal)) - { - return; + public void EditMotorPartNumber(string newPartNumber) + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldPartNumber = CurrentMotor.PartNumber ?? string.Empty; + var newPartNumberValue = newPartNumber ?? string.Empty; + if (string.Equals(oldPartNumber, newPartNumberValue, StringComparison.Ordinal)) + { + return; } var command = new EditMotorPropertyCommand(CurrentMotor, nameof(ServoMotor.PartNumber), oldPartNumber, newPartNumberValue); @@ -1946,15 +2010,20 @@ private void RefreshAvailableVoltages() ActiveTab.AvailableVoltages.Count); } - public void EditDriveName() - { - if (SelectedDrive is null) - { - return; - } - - var oldValue = SelectedDrive.Name ?? string.Empty; - var newValue = DriveNameEditor ?? string.Empty; + public void EditDriveName() + { + if (SelectedDrive is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedDrive.Name ?? string.Empty; + var newValue = DriveNameEditor ?? string.Empty; if (string.Equals(oldValue, newValue, StringComparison.Ordinal)) { @@ -1967,15 +2036,20 @@ public void EditDriveName() IsDirty = true; } - public void EditDrivePartNumber() - { - if (SelectedDrive is null) - { - return; - } - - var oldValue = SelectedDrive.PartNumber ?? string.Empty; - var newValue = DrivePartNumberEditor ?? string.Empty; + public void EditDrivePartNumber() + { + if (SelectedDrive is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedDrive.PartNumber ?? string.Empty; + var newValue = DrivePartNumberEditor ?? string.Empty; if (string.Equals(oldValue, newValue, StringComparison.Ordinal)) { @@ -1988,15 +2062,20 @@ public void EditDrivePartNumber() IsDirty = true; } - public void EditDriveManufacturer() - { - if (SelectedDrive is null) - { - return; - } - - var oldValue = SelectedDrive.Manufacturer ?? string.Empty; - var newValue = DriveManufacturerEditor ?? string.Empty; + public void EditDriveManufacturer() + { + if (SelectedDrive is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedDrive.Manufacturer ?? string.Empty; + var newValue = DriveManufacturerEditor ?? string.Empty; if (string.Equals(oldValue, newValue, StringComparison.Ordinal)) { @@ -2009,18 +2088,23 @@ public void EditDriveManufacturer() IsDirty = true; } - public void EditSelectedVoltageValue() - { - if (SelectedVoltage is null) - { - return; - } - - var oldValue = SelectedVoltage.Value; - if (!TryParseDouble(VoltageValueEditor, oldValue, out var newValue)) - { - VoltageValueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditSelectedVoltageValue() + { + if (SelectedVoltage is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedVoltage.Value; + if (!TryParseDouble(VoltageValueEditor, oldValue, out var newValue)) + { + VoltageValueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2037,18 +2121,23 @@ public void EditSelectedVoltageValue() IsDirty = true; } - public void EditSelectedVoltagePower() - { - if (SelectedVoltage is null) - { - return; - } - - var oldValue = SelectedVoltage.Power; - if (!TryParseDouble(VoltagePowerEditor, oldValue, out var newValue)) - { - VoltagePowerEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditSelectedVoltagePower() + { + if (SelectedVoltage is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedVoltage.Power; + if (!TryParseDouble(VoltagePowerEditor, oldValue, out var newValue)) + { + VoltagePowerEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2065,18 +2154,23 @@ public void EditSelectedVoltagePower() IsDirty = true; } - public void EditSelectedVoltageMaxSpeed() - { - if (SelectedVoltage is null) - { - return; - } - - var oldValue = SelectedVoltage.MaxSpeed; - if (!TryParseDouble(VoltageMaxSpeedEditor, oldValue, out var newValue)) - { - VoltageMaxSpeedEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditSelectedVoltageMaxSpeed() + { + if (SelectedVoltage is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedVoltage.MaxSpeed; + if (!TryParseDouble(VoltageMaxSpeedEditor, oldValue, out var newValue)) + { + VoltageMaxSpeedEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2093,18 +2187,23 @@ public void EditSelectedVoltageMaxSpeed() IsDirty = true; } - public void EditSelectedVoltagePeakTorque() - { - if (SelectedVoltage is null) - { - return; - } - - var oldValue = SelectedVoltage.RatedPeakTorque; - if (!TryParseDouble(VoltagePeakTorqueEditor, oldValue, out var newValue)) - { - VoltagePeakTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditSelectedVoltagePeakTorque() + { + if (SelectedVoltage is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedVoltage.RatedPeakTorque; + if (!TryParseDouble(VoltagePeakTorqueEditor, oldValue, out var newValue)) + { + VoltagePeakTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2121,18 +2220,23 @@ public void EditSelectedVoltagePeakTorque() IsDirty = true; } - public void EditSelectedVoltageContinuousTorque() - { - if (SelectedVoltage is null) - { - return; - } - - var oldValue = SelectedVoltage.RatedContinuousTorque; - if (!TryParseDouble(VoltageContinuousTorqueEditor, oldValue, out var newValue)) - { - VoltageContinuousTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditSelectedVoltageContinuousTorque() + { + if (SelectedVoltage is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedVoltage.RatedContinuousTorque; + if (!TryParseDouble(VoltageContinuousTorqueEditor, oldValue, out var newValue)) + { + VoltageContinuousTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2149,18 +2253,23 @@ public void EditSelectedVoltageContinuousTorque() IsDirty = true; } - public void EditSelectedVoltageContinuousAmps() - { - if (SelectedVoltage is null) - { - return; - } - - var oldValue = SelectedVoltage.ContinuousAmperage; - if (!TryParseDouble(VoltageContinuousAmpsEditor, oldValue, out var newValue)) - { - VoltageContinuousAmpsEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditSelectedVoltageContinuousAmps() + { + if (SelectedVoltage is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedVoltage.ContinuousAmperage; + if (!TryParseDouble(VoltageContinuousAmpsEditor, oldValue, out var newValue)) + { + VoltageContinuousAmpsEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2177,18 +2286,23 @@ public void EditSelectedVoltageContinuousAmps() IsDirty = true; } - public void EditSelectedVoltagePeakAmps() - { - if (SelectedVoltage is null) - { - return; - } - - var oldValue = SelectedVoltage.PeakAmperage; - if (!TryParseDouble(VoltagePeakAmpsEditor, oldValue, out var newValue)) - { - VoltagePeakAmpsEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditSelectedVoltagePeakAmps() + { + if (SelectedVoltage is null) + { + return; + } + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + var oldValue = SelectedVoltage.PeakAmperage; + if (!TryParseDouble(VoltagePeakAmpsEditor, oldValue, out var newValue)) + { + VoltagePeakAmpsEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2205,18 +2319,23 @@ public void EditSelectedVoltagePeakAmps() IsDirty = true; } - public void EditMotorRatedSpeed() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.RatedSpeed; - if (!TryParseDouble(RatedSpeedEditor, oldValue, out var newValue)) - { - RatedSpeedEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorRatedSpeed() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.RatedSpeed; + if (!TryParseDouble(RatedSpeedEditor, oldValue, out var newValue)) + { + RatedSpeedEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2231,21 +2350,26 @@ public void EditMotorRatedSpeed() IsDirty = true; } - public void EditMotorRatedPeakTorque() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.RatedPeakTorque; - if (!TryParseDouble(RatedPeakTorqueEditor, oldValue, out var newValue)) - { - RatedPeakTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; - } - - if (Math.Abs(oldValue - newValue) < 0.000001) + public void EditMotorRatedPeakTorque() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.RatedPeakTorque; + if (!TryParseDouble(RatedPeakTorqueEditor, oldValue, out var newValue)) + { + RatedPeakTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; + } + + if (Math.Abs(oldValue - newValue) < 0.000001) { return; } @@ -2256,18 +2380,23 @@ public void EditMotorRatedPeakTorque() IsDirty = true; } - public void EditMotorRatedContinuousTorque() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.RatedContinuousTorque; - if (!TryParseDouble(RatedContinuousTorqueEditor, oldValue, out var newValue)) - { - RatedContinuousTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorRatedContinuousTorque() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.RatedContinuousTorque; + if (!TryParseDouble(RatedContinuousTorqueEditor, oldValue, out var newValue)) + { + RatedContinuousTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2281,18 +2410,23 @@ public void EditMotorRatedContinuousTorque() IsDirty = true; } - public void EditMotorPower() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.Power; - if (!TryParseDouble(PowerEditor, oldValue, out var newValue)) - { - PowerEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorPower() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.Power; + if (!TryParseDouble(PowerEditor, oldValue, out var newValue)) + { + PowerEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2306,18 +2440,23 @@ public void EditMotorPower() IsDirty = true; } - public void EditMotorWeight() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.Weight; - if (!TryParseDouble(WeightEditor, oldValue, out var newValue)) - { - WeightEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorWeight() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.Weight; + if (!TryParseDouble(WeightEditor, oldValue, out var newValue)) + { + WeightEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2331,18 +2470,23 @@ public void EditMotorWeight() IsDirty = true; } - public void EditMotorRotorInertia() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.RotorInertia; - if (!TryParseDouble(RotorInertiaEditor, oldValue, out var newValue)) - { - RotorInertiaEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorRotorInertia() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.RotorInertia; + if (!TryParseDouble(RotorInertiaEditor, oldValue, out var newValue)) + { + RotorInertiaEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2356,18 +2500,23 @@ public void EditMotorRotorInertia() IsDirty = true; } - public void EditMotorFeedbackPpr() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.FeedbackPpr; - if (!int.TryParse(FeedbackPprEditor, out var newValue)) - { - FeedbackPprEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorFeedbackPpr() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.FeedbackPpr; + if (!int.TryParse(FeedbackPprEditor, out var newValue)) + { + FeedbackPprEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (oldValue == newValue) @@ -2381,15 +2530,20 @@ public void EditMotorFeedbackPpr() IsDirty = true; } - public void EditMotorHasBrake() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.HasBrake; - var newValue = HasBrakeEditor; + public void EditMotorHasBrake() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.HasBrake; + var newValue = HasBrakeEditor; if (oldValue == newValue) { @@ -2403,18 +2557,23 @@ public void EditMotorHasBrake() IsDirty = true; } - public void EditMotorBrakeTorque() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.BrakeTorque; - if (!TryParseDouble(BrakeTorqueEditor, oldValue, out var newValue)) - { - BrakeTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorBrakeTorque() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.BrakeTorque; + if (!TryParseDouble(BrakeTorqueEditor, oldValue, out var newValue)) + { + BrakeTorqueEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2429,18 +2588,23 @@ public void EditMotorBrakeTorque() IsDirty = true; } - public void EditMotorBrakeAmperage() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.BrakeAmperage; - if (!TryParseDouble(BrakeAmperageEditor, oldValue, out var newValue)) - { - BrakeAmperageEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorBrakeAmperage() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.BrakeAmperage; + if (!TryParseDouble(BrakeAmperageEditor, oldValue, out var newValue)) + { + BrakeAmperageEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2454,18 +2618,23 @@ public void EditMotorBrakeAmperage() IsDirty = true; } - public void EditMotorBrakeVoltage() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.BrakeVoltage; - if (!TryParseDouble(BrakeVoltageEditor, oldValue, out var newValue)) - { - BrakeVoltageEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorBrakeVoltage() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.BrakeVoltage; + if (!TryParseDouble(BrakeVoltageEditor, oldValue, out var newValue)) + { + BrakeVoltageEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2479,18 +2648,23 @@ public void EditMotorBrakeVoltage() IsDirty = true; } - public void EditMotorBrakeReleaseTime() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.BrakeReleaseTime; - if (!TryParseDouble(BrakeReleaseTimeEditor, oldValue, out var newValue)) - { - BrakeReleaseTimeEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorBrakeReleaseTime() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.BrakeReleaseTime; + if (!TryParseDouble(BrakeReleaseTimeEditor, oldValue, out var newValue)) + { + BrakeReleaseTimeEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2504,18 +2678,23 @@ public void EditMotorBrakeReleaseTime() IsDirty = true; } - public void EditMotorBrakeEngageTimeMov() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.BrakeEngageTimeMov; - if (!TryParseDouble(BrakeEngageTimeMovEditor, oldValue, out var newValue)) - { - BrakeEngageTimeMovEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorBrakeEngageTimeMov() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.BrakeEngageTimeMov; + if (!TryParseDouble(BrakeEngageTimeMovEditor, oldValue, out var newValue)) + { + BrakeEngageTimeMovEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2529,18 +2708,23 @@ public void EditMotorBrakeEngageTimeMov() IsDirty = true; } - public void EditMotorBrakeEngageTimeDiode() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.BrakeEngageTimeDiode; - if (!TryParseDouble(BrakeEngageTimeDiodeEditor, oldValue, out var newValue)) - { - BrakeEngageTimeDiodeEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorBrakeEngageTimeDiode() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.BrakeEngageTimeDiode; + if (!TryParseDouble(BrakeEngageTimeDiodeEditor, oldValue, out var newValue)) + { + BrakeEngageTimeDiodeEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2554,18 +2738,23 @@ public void EditMotorBrakeEngageTimeDiode() IsDirty = true; } - public void EditMotorBrakeBacklash() - { - if (CurrentMotor is null) - { - return; - } - - var oldValue = CurrentMotor.BrakeBacklash; - if (!TryParseDouble(BrakeBacklashEditor, oldValue, out var newValue)) - { - BrakeBacklashEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); - return; + public void EditMotorBrakeBacklash() + { + if (CurrentMotor is null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + var oldValue = CurrentMotor.BrakeBacklash; + if (!TryParseDouble(BrakeBacklashEditor, oldValue, out var newValue)) + { + BrakeBacklashEditor = oldValue.ToString(System.Globalization.CultureInfo.InvariantCulture); + return; } if (Math.Abs(oldValue - newValue) < 0.000001) @@ -2871,16 +3060,21 @@ private void OnDataTableDataChanged(object? sender, EventArgs e) /// /// Handles changes to unit settings and converts stored motor data. /// - private void OnUnitsPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (CurrentMotor == null || CurrentMotor.Units == null) - { - return; - } - - // Determine old and new units - string? oldValue = null; - string? newValue = null; + private void OnUnitsPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (CurrentMotor == null || CurrentMotor.Units == null) + { + return; + } + + if (!EnsureMotorEditingAllowed()) + { + return; + } + + // Determine old and new units + string? oldValue = null; + string? newValue = null; switch (e.PropertyName) { @@ -3458,16 +3652,21 @@ private async Task AddDriveAsync() await AddDriveInternalAsync(); } - [RelayCommand] - private async Task RemoveDriveAsync() - { - if (CurrentMotor is null || SelectedDrive is null) return; - - // Show confirmation dialog - var dialog = new Views.MessageDialog - { - Title = "Confirm Delete", - Message = $"Are you sure you want to delete the selected drive '{SelectedDrive.Name}'?" + [RelayCommand] + private async Task RemoveDriveAsync() + { + if (CurrentMotor is null || SelectedDrive is null) return; + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + // Show confirmation dialog + var dialog = new Views.MessageDialog + { + Title = "Confirm Delete", + Message = $"Are you sure you want to delete the selected drive '{SelectedDrive.Name}'?" }; if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) @@ -3511,16 +3710,21 @@ private async Task AddVoltageAsync() await AddVoltageInternalAsync(); } - [RelayCommand] - private async Task RemoveVoltageAsync() - { - if (SelectedDrive is null || SelectedVoltage is null) return; - - // Show confirmation dialog - var dialog = new Views.MessageDialog - { - Title = "Confirm Delete", - Message = $"Are you sure you want to delete the selected voltage '{SelectedVoltage.Value}V'?" + [RelayCommand] + private async Task RemoveVoltageAsync() + { + if (SelectedDrive is null || SelectedVoltage is null) return; + + if (!EnsureDriveEditingAllowed()) + { + return; + } + + // Show confirmation dialog + var dialog = new Views.MessageDialog + { + Title = "Confirm Delete", + Message = $"Are you sure you want to delete the selected voltage '{SelectedVoltage.Value}V'?" }; if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) @@ -3646,16 +3850,22 @@ private async Task AddVoltageInternalAsync() return; } - var result = dialog.Result; - - // The target drive is selected in the dialog - var targetDrive = result.TargetDrive; - - // Delegate duplicate check and creation to the workflow - var voltageResult = _motorConfigurationWorkflow.CreateVoltageWithOptionalSeries(targetDrive, result); - if (voltageResult.IsDuplicate) - { - StatusMessage = $"Voltage {result.Voltage}V already exists for drive '{targetDrive.Name}'."; + var result = dialog.Result; + + // The target drive is selected in the dialog + var targetDrive = result.TargetDrive; + + if (IsDriveLockedBySignature(targetDrive)) + { + StatusMessage = $"Drive '{targetDrive.Name}' is locked by a validation signature."; + return; + } + + // Delegate duplicate check and creation to the workflow + var voltageResult = _motorConfigurationWorkflow.CreateVoltageWithOptionalSeries(targetDrive, result); + if (voltageResult.IsDuplicate) + { + StatusMessage = $"Voltage {result.Voltage}V already exists for drive '{targetDrive.Name}'."; return; } @@ -3751,6 +3961,11 @@ private async Task RemoveSeriesAsync() { if (SelectedVoltage is null || SelectedSeries is null) return; + if (!EnsureSeriesEditingAllowed(SelectedSeries)) + { + return; + } + // Show confirmation dialog var dialog = new Views.MessageDialog { @@ -3795,6 +4010,11 @@ private void ToggleSeriesLock(Curve? series) return; } + if (!EnsureSeriesEditingAllowed(series)) + { + return; + } + var newLocked = !series.Locked; var command = new EditSeriesCommand(series, newLocked: newLocked); diff --git a/src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs b/src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs index 9f79291..4cec54a 100644 --- a/src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs +++ b/src/MotorEditor.Avalonia/Views/CurveDataPanel.axaml.cs @@ -891,6 +891,16 @@ private async void OnSeriesNameDoubleTapped(object? sender, TappedEventArgs e) { if (sender is TextBlock textBlock && textBlock.DataContext is Curve series) { + if (DataContext is not MainWindowViewModel viewModel) + { + return; + } + + if (!viewModel.EnsureSeriesEditingAllowed(series)) + { + return; + } + // Show a simple input dialog for renaming var dialog = new Window { @@ -951,11 +961,8 @@ private async void OnSeriesNameDoubleTapped(object? sender, TappedEventArgs e) if (result && !string.IsNullOrWhiteSpace(textBox.Text)) { series.Name = textBox.Text; - if (DataContext is MainWindowViewModel viewModel) - { - viewModel.MarkDirty(); - viewModel.ChartViewModel.RefreshChart(); - } + viewModel.MarkDirty(); + viewModel.ChartViewModel.RefreshChart(); } } } @@ -993,6 +1000,12 @@ private void OnSeriesLockToggleClick(object? sender, RoutedEventArgs e) return; } + if (!viewModel.EnsureSeriesEditingAllowed(series)) + { + toggleButton.IsChecked = series.Locked; + return; + } + if (viewModel.ToggleSeriesLockCommand.CanExecute(series)) { viewModel.ToggleSeriesLockCommand.Execute(series); @@ -1008,6 +1021,11 @@ private async void OnDeleteSeriesClick(object? sender, RoutedEventArgs e) { if (DataContext is MainWindowViewModel viewModel && viewModel.SelectedVoltage is not null) { + if (!viewModel.EnsureSeriesEditingAllowed(series)) + { + return; + } + // Check if series is locked - prevent deletion of locked series if (series.Locked) { @@ -2152,6 +2170,13 @@ private void OnPercentLockToggleClick(object? sender, RoutedEventArgs e) return; } + if (viewModel.SelectedVoltage.Curves.Any(viewModel.IsSeriesLockedBySignature)) + { + viewModel.StatusMessage = "Cannot change percent lock: a series is locked by validation signature."; + PercentLockToggle.IsChecked = viewModel.SelectedVoltage.Curves.All(s => s.LockedPercent); + return; + } + // Determine current lock state (if any series allows percent editing, we consider it unlocked) var isCurrentlyLocked = viewModel.SelectedVoltage.Curves.All(s => s.LockedPercent); var newLockedState = !isCurrentlyLocked; @@ -2182,6 +2207,13 @@ private void OnRpmLockToggleClick(object? sender, RoutedEventArgs e) return; } + if (viewModel.SelectedVoltage.Curves.Any(viewModel.IsSeriesLockedBySignature)) + { + viewModel.StatusMessage = "Cannot change RPM lock: a series is locked by validation signature."; + RpmLockToggle.IsChecked = viewModel.SelectedVoltage.Curves.All(s => s.LockedRpm); + return; + } + // Determine current lock state (if any series allows RPM editing, we consider it unlocked) var isCurrentlyLocked = viewModel.SelectedVoltage.Curves.All(s => s.LockedRpm); var newLockedState = !isCurrentlyLocked; diff --git a/src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml b/src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml index 16a8b13..9aab39d 100644 --- a/src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml +++ b/src/MotorEditor.Avalonia/Views/MotorPropertiesPanel.axaml @@ -26,6 +26,7 @@ Grid.IsSharedSizeScope="True" IsVisible="{Binding CurrentMotor, Converter={x:Static ObjectConverters.IsNotNull}}"> + @@ -396,6 +397,7 @@ Classes="PropertyComboBox"/> + @@ -445,6 +448,7 @@ @@ -496,6 +503,7 @@ Margin="4,0,0,0" Command="{Binding RemoveVoltageCommand}" ToolTip.Tip="Delete selected voltage" + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"> @@ -512,10 +520,11 @@ + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" + Text="{Binding VoltageValueEditor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" + Classes="PropertyInput" + IsUndoEnabled="False" + LostFocus="OnVoltageValueLostFocus"/> @@ -527,10 +536,11 @@ + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" + Text="{Binding VoltagePowerEditor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" + Classes="PropertyInput" + IsUndoEnabled="False" + LostFocus="OnVoltagePowerLostFocus"/> @@ -542,10 +552,11 @@ + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" + Text="{Binding VoltageMaxSpeedEditor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" + Classes="PropertyInput" + IsUndoEnabled="False" + LostFocus="OnMaxSpeedLostFocus"/> @@ -557,10 +568,11 @@ + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" + Text="{Binding VoltagePeakTorqueEditor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" + Classes="PropertyInput" + IsUndoEnabled="False" + LostFocus="OnVoltagePeakTorqueLostFocus"/> @@ -572,10 +584,11 @@ + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" + Text="{Binding VoltageContinuousTorqueEditor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" + Classes="PropertyInput" + IsUndoEnabled="False" + LostFocus="OnVoltageContinuousTorqueLostFocus"/> @@ -587,10 +600,11 @@ + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" + Text="{Binding VoltageContinuousAmpsEditor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" + Classes="PropertyInput" + IsUndoEnabled="False" + LostFocus="OnVoltageContinuousAmpsLostFocus"/> @@ -602,10 +616,11 @@ + IsEnabled="{Binding CanEditSelectedDriveAndVoltages}" + Text="{Binding VoltagePeakAmpsEditor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" + Classes="PropertyInput" + IsUndoEnabled="False" + LostFocus="OnVoltagePeakAmpsLostFocus"/> From 4b9c940f3d11f29e925dd6d6e78074dda3f26137 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:48:00 +0000 Subject: [PATCH 5/5] Add signed and unsigned sample motor files Co-authored-by: jordanrobot <119146+jordanrobot@users.noreply.github.com> --- .../20260105-validation-signatures-changes.md | 8 +- samples/motor-with-signatures.json | 112 ++++++++++++++++++ samples/motor-without-signatures.json | 94 +++++++++++++++ 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 samples/motor-with-signatures.json create mode 100644 samples/motor-without-signatures.json diff --git a/.copilot-tracking/changes/20260105-validation-signatures-changes.md b/.copilot-tracking/changes/20260105-validation-signatures-changes.md index 8ace306..c2f4b7e 100644 --- a/.copilot-tracking/changes/20260105-validation-signatures-changes.md +++ b/.copilot-tracking/changes/20260105-validation-signatures-changes.md @@ -14,6 +14,8 @@ Tracking implementation of validation signature checks and edit locking in Motor - .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan covering validation signature lock scope and tasks. - .copilot-tracking/details/20260105-validation-signatures-details.md - Task-level requirements for signature verification and edit locking. +- samples/motor-with-signatures.json - Sample motor file with valid motor/drive/curve signatures for UI validation. +- samples/motor-without-signatures.json - Sample motor file without signatures for editing scenarios. ### Modified @@ -28,12 +30,14 @@ Tracking implementation of validation signature checks and edit locking in Motor ## Release Summary -**Total Files Affected**: 6 +**Total Files Affected**: 8 -### Files Created (2) +### Files Created (4) - .copilot-tracking/plans/20260105-validation-signatures-plan.md - Plan for validation signature locking. - .copilot-tracking/details/20260105-validation-signatures-details.md - Detailed steps for tasks in the plan. +- samples/motor-with-signatures.json - Signed sample motor file. +- samples/motor-without-signatures.json - Unsigned sample motor file. ### Files Modified (4) diff --git a/samples/motor-with-signatures.json b/samples/motor-with-signatures.json new file mode 100644 index 0000000..25ed049 --- /dev/null +++ b/samples/motor-with-signatures.json @@ -0,0 +1,112 @@ +{ + "schemaVersion": "1.0.0", + "motorName": "Validation Sample Motor", + "manufacturer": "Contoso Motors", + "partNumber": "VAL-1000", + "power": 1500, + "maxSpeed": 4800, + "ratedSpeed": 3000, + "ratedContinuousTorque": 42, + "ratedPeakTorque": 52, + "weight": 8.5, + "rotorInertia": 0.0025, + "feedbackPpr": 131072, + "hasBrake": false, + "brakeTorque": 0, + "brakeAmperage": 0, + "brakeVoltage": 0, + "brakeReleaseTime": 0, + "brakeEngageTimeDiode": 0, + "brakeEngageTimeMOV": 0, + "brakeBacklash": 0, + "units": { + "torque": "Nm", + "speed": "rpm", + "power": "W", + "weight": "kg", + "voltage": "V", + "current": "A", + "inertia": "kg-m^2", + "torqueConstant": "Nm/A", + "backlash": "arcmin", + "responseTime": "ms", + "percentage": "%", + "temperature": "C" + }, + "drives": [ + { + "name": "Sample Drive", + "manufacturer": "Contoso Drives", + "partNumber": "DRV-208", + "voltages": [ + { + "voltage": 208, + "power": 1400, + "maxSpeed": 4700, + "ratedSpeed": 2950, + "ratedContinuousTorque": 41.5, + "ratedPeakTorque": 51.5, + "continuousAmperage": 9.5, + "peakAmperage": 21.8, + "percent": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100 + ], + "rpm": [0, 47, 94, 141, 188, 235, 282, 329.00000000000006, 376, 423, 470, 517, + 564, 611, 658.00000000000011, 705, 752, 799.00000000000011, 846, 893, 940, 987, 1034, 1081, + 1128, 1175, 1222, 1269, 1316.0000000000002, 1363, 1410, 1457, 1504, 1551, 1598.0000000000002, 1645, + 1692, 1739, 1786, 1833, 1880, 1926.9999999999998, 1974, 2021, 2068, 2115, 2162, 2209, + 2256, 2303, 2350, 2397, 2444, 2491, 2538, 2585, 2632.0000000000005, 2678.9999999999995, 2726, 2773, + 2820, 2867, 2914, 2961, 3008, 3055, 3102, 3149, 3196.0000000000005, 3242.9999999999995, 3290, 3337, + 3384, 3431, 3478, 3525, 3572, 3619, 3666, 3713, 3760, 3807.0000000000005, 3853.9999999999995, 3901, + 3948, 3995, 4042, 4089, 4136, 4183, 4230, 4277, 4324, 4371, 4418, 4465, + 4512, 4559, 4606, 4653, 4700 + ], + "series": { + "Peak": { + "locked": false, + "torque": [51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5 + ], + "curveSignature": { + "checksum": "b60cb2611316701d7ed93e5cd5a0d5af6c8b487427120d2df4ad3fe81a3cfff0", + "timestamp": "2026-01-05T12:47:15.1860204Z", + "verifiedBy": "qa@example.com", + "algorithm": "SHA256" + } + } + } + } + ], + "driveSignature": { + "checksum": "f8586fbcca6ddd1e92cf206ff06e126de7a0666f59517c407d300f2acc917af3", + "timestamp": "2026-01-05T12:47:15.1824272Z", + "verifiedBy": "qa@example.com", + "algorithm": "SHA256" + } + } + ], + "metadata": { + "created": "2026-01-05T12:47:14.4786849Z", + "modified": "2026-01-05T12:47:14.478685Z" + }, + "motorSignature": { + "checksum": "8e44f0e5efa24cabaa742ff6fe261dd842ce8c9ad3b9dea26df3a4fcbdca62af", + "timestamp": "2026-01-05T12:47:15.1779223Z", + "verifiedBy": "qa@example.com", + "algorithm": "SHA256" + } +} \ No newline at end of file diff --git a/samples/motor-without-signatures.json b/samples/motor-without-signatures.json new file mode 100644 index 0000000..2597811 --- /dev/null +++ b/samples/motor-without-signatures.json @@ -0,0 +1,94 @@ +{ + "schemaVersion": "1.0.0", + "motorName": "Validation Sample Motor", + "manufacturer": "Contoso Motors", + "partNumber": "VAL-1000", + "power": 1500, + "maxSpeed": 4800, + "ratedSpeed": 3000, + "ratedContinuousTorque": 42, + "ratedPeakTorque": 52, + "weight": 8.5, + "rotorInertia": 0.0025, + "feedbackPpr": 131072, + "hasBrake": false, + "brakeTorque": 0, + "brakeAmperage": 0, + "brakeVoltage": 0, + "brakeReleaseTime": 0, + "brakeEngageTimeDiode": 0, + "brakeEngageTimeMOV": 0, + "brakeBacklash": 0, + "units": { + "torque": "Nm", + "speed": "rpm", + "power": "W", + "weight": "kg", + "voltage": "V", + "current": "A", + "inertia": "kg-m^2", + "torqueConstant": "Nm/A", + "backlash": "arcmin", + "responseTime": "ms", + "percentage": "%", + "temperature": "C" + }, + "drives": [ + { + "name": "Sample Drive", + "manufacturer": "Contoso Drives", + "partNumber": "DRV-208", + "voltages": [ + { + "voltage": 208, + "power": 1400, + "maxSpeed": 4700, + "ratedSpeed": 2950, + "ratedContinuousTorque": 41.5, + "ratedPeakTorque": 51.5, + "continuousAmperage": 9.5, + "peakAmperage": 21.8, + "percent": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100 + ], + "rpm": [0, 47, 94, 141, 188, 235, 282, 329.00000000000006, 376, 423, 470, 517, + 564, 611, 658.00000000000011, 705, 752, 799.00000000000011, 846, 893, 940, 987, 1034, 1081, + 1128, 1175, 1222, 1269, 1316.0000000000002, 1363, 1410, 1457, 1504, 1551, 1598.0000000000002, 1645, + 1692, 1739, 1786, 1833, 1880, 1926.9999999999998, 1974, 2021, 2068, 2115, 2162, 2209, + 2256, 2303, 2350, 2397, 2444, 2491, 2538, 2585, 2632.0000000000005, 2678.9999999999995, 2726, 2773, + 2820, 2867, 2914, 2961, 3008, 3055, 3102, 3149, 3196.0000000000005, 3242.9999999999995, 3290, 3337, + 3384, 3431, 3478, 3525, 3572, 3619, 3666, 3713, 3760, 3807.0000000000005, 3853.9999999999995, 3901, + 3948, 3995, 4042, 4089, 4136, 4183, 4230, 4277, 4324, 4371, 4418, 4465, + 4512, 4559, 4606, 4653, 4700 + ], + "series": { + "Peak": { + "locked": false, + "torque": [51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, 51.5, + 51.5, 51.5, 51.5, 51.5, 51.5 + ] + } + } + } + ] + } + ], + "metadata": { + "created": "2026-01-05T12:47:14.4786849Z", + "modified": "2026-01-05T12:47:14.478685Z" + } +} \ No newline at end of file