From 6463348a560aa24dc9722ccfb8b783e0d4a736e6 Mon Sep 17 00:00:00 2001 From: Tom Teasdale Date: Sat, 7 Oct 2023 14:35:58 +0200 Subject: [PATCH] Reworked measurement import; fixed common plotting issue; cosmetics; upgrade to 2023a --- +tests/+ui/FrictionEllipseAxes.m | 3 +- +tests/+ui/TyreAnalysisPanel.m | 3 +- MagicFormulaTyreTool.prj | 23 +- README.md | 6 +- ToolboxPackager.prj | 6 +- doc/GettingStarted.mlx | Bin 10199882 -> 10199881 bytes .../cornering.mat} | 0 .../drivebrake.mat} | 0 .../fsaettc_obfuscated.tir | 0 ...-data.type.File.xml => fsae.type.File.xml} | 0 .../1.type.DIR_SIGNIFIER.xml | 0 .../cornering.mat.type.File.xml} | 0 .../drivebrake.mat.type.File.xml} | 0 .../fsaettc_obfuscated.tir.type.File.xml | 0 ...MeasurementImportRequested.m.type.File.xml | 6 + .../getParsers.m.type.File.xml | 6 + .../LayoutSettings.m.type.File.xml | 6 + .../TextSettings.m.type.File.xml | 6 + ...ml => TyreDataImportPanel.m.type.File.xml} | 0 ...File.xml => TyreDataPanel.m.type.File.xml} | 0 ...File.xml => TyreDataTable.m.type.File.xml} | 0 ...-4474-9639-07cd1240a040.type.Reference.xml | 2 - ...-4e40-a691-4f3aea49308d.type.Reference.xml | 2 + sandbox/+tydex/+parsers/ExampleParser.m | 2 +- sandbox/test_data.mlx | Bin 4025 -> 3909 bytes sandbox/test_mfeval.mlx | Bin 52248 -> 52513 bytes src/+events/MeasurementImportRequested.m | 12 + src/+helpers/getParsers.m | 11 + src/+settings/AppSettings.m | 14 +- src/+settings/FitterSettings.m | 2 +- src/+settings/LayoutSettings.m | 13 + src/+settings/TextSettings.m | 7 + src/+ui/HelpHint.m | 9 +- src/+ui/MeasurementImporter.m | 254 ------------------ src/+ui/TyreAnalysisPanel.m | 22 +- src/+ui/TyreDataImportPanel.m | 211 +++++++++++++++ ...yreMeasurementsPanel.m => TyreDataPanel.m} | 103 ++++--- ...yreMeasurementsTable.m => TyreDataTable.m} | 2 +- src/+ui/TyreFitterFittingModesPanel.m | 3 + src/+ui/TyreFitterPanel.m | 28 +- src/+ui/TyreFitterSolverSettingsPanel.m | 15 +- src/+ui/TyreModelPanel.m | 14 +- src/+ui/TyrePlotCurvesPanel.m | 101 +++---- src/+ui/TyrePlotFrictionEllipsePanel.m | 76 +++--- src/MagicFormulaTyreTool.m | 72 +++-- src/about.json | 7 +- 46 files changed, 577 insertions(+), 470 deletions(-) rename doc/examples/{fsae-ttc-data/fsaettc_obscured_testbench_cornering.mat => fsae/cornering.mat} (100%) rename doc/examples/{fsae-ttc-data/fsaettc_obscured_testbench_drivebrake.mat => fsae/drivebrake.mat} (100%) rename doc/examples/{fsae-ttc-data => fsae}/fsaettc_obfuscated.tir (100%) rename resources/project/Root.type.Files/doc.type.File/examples.type.File/{fsae-ttc-data.type.File.xml => fsae.type.File.xml} (100%) rename resources/project/Root.type.Files/doc.type.File/examples.type.File/{fsae-ttc-data.type.File => fsae.type.File}/1.type.DIR_SIGNIFIER.xml (100%) rename resources/project/Root.type.Files/doc.type.File/examples.type.File/{fsae-ttc-data.type.File/fsaettc_obscured_testbench_cornering.mat.type.File.xml => fsae.type.File/cornering.mat.type.File.xml} (100%) rename resources/project/Root.type.Files/doc.type.File/examples.type.File/{fsae-ttc-data.type.File/fsaettc_obscured_testbench_drivebrake.mat.type.File.xml => fsae.type.File/drivebrake.mat.type.File.xml} (100%) rename resources/project/Root.type.Files/doc.type.File/examples.type.File/{fsae-ttc-data.type.File => fsae.type.File}/fsaettc_obfuscated.tir.type.File.xml (100%) create mode 100644 resources/project/Root.type.Files/src.type.File/+events.type.File/MeasurementImportRequested.m.type.File.xml create mode 100644 resources/project/Root.type.Files/src.type.File/+helpers.type.File/getParsers.m.type.File.xml create mode 100644 resources/project/Root.type.Files/src.type.File/+settings.type.File/LayoutSettings.m.type.File.xml create mode 100644 resources/project/Root.type.Files/src.type.File/+settings.type.File/TextSettings.m.type.File.xml rename resources/project/Root.type.Files/src.type.File/+ui.type.File/{MeasurementImporter.m.type.File.xml => TyreDataImportPanel.m.type.File.xml} (100%) rename resources/project/Root.type.Files/src.type.File/+ui.type.File/{TyreMeasurementsPanel.m.type.File.xml => TyreDataPanel.m.type.File.xml} (100%) rename resources/project/Root.type.Files/src.type.File/+ui.type.File/{TyreMeasurementsTable.m.type.File.xml => TyreDataTable.m.type.File.xml} (100%) delete mode 100644 resources/project/Root.type.ProjectPath/213618a8-a7aa-4474-9639-07cd1240a040.type.Reference.xml create mode 100644 resources/project/Root.type.ProjectPath/e93ef97a-84ce-4e40-a691-4f3aea49308d.type.Reference.xml create mode 100644 src/+events/MeasurementImportRequested.m create mode 100644 src/+helpers/getParsers.m create mode 100644 src/+settings/LayoutSettings.m create mode 100644 src/+settings/TextSettings.m delete mode 100644 src/+ui/MeasurementImporter.m create mode 100644 src/+ui/TyreDataImportPanel.m rename src/+ui/{TyreMeasurementsPanel.m => TyreDataPanel.m} (58%) rename src/+ui/{TyreMeasurementsTable.m => TyreDataTable.m} (98%) diff --git a/+tests/+ui/FrictionEllipseAxes.m b/+tests/+ui/FrictionEllipseAxes.m index 9e1ddd2..8967f13 100644 --- a/+tests/+ui/FrictionEllipseAxes.m +++ b/+tests/+ui/FrictionEllipseAxes.m @@ -24,8 +24,7 @@ function deleteTestObject(testCase) methods (Test) function testChangeTyreModel(testCase) - file = fullfile('doc','examples','fsae-ttc-data',... - 'fsaettc_obfuscated.tir'); + file = 'doc/examples/fsae/fsaettc_obfuscated.tir'; model = MagicFormulaTyre(file); ax = testCase.TestObject; ax.Model = model; diff --git a/+tests/+ui/TyreAnalysisPanel.m b/+tests/+ui/TyreAnalysisPanel.m index cf07d1c..1d309c8 100644 --- a/+tests/+ui/TyreAnalysisPanel.m +++ b/+tests/+ui/TyreAnalysisPanel.m @@ -24,8 +24,7 @@ function deleteTestObject(testCase) methods (Test) function testChangeTyreModel(testCase) - file = fullfile('doc','examples','fsae-ttc-data',... - 'fsaettc_obfuscated.tir'); + file = 'doc/examples/fsae/fsaettc_obfuscated.tir'; model = MagicFormulaTyre(file); p = testCase.TestObject; e = events.ModelChangedEventData(model); diff --git a/MagicFormulaTyreTool.prj b/MagicFormulaTyreTool.prj index cd9436e..6c5f740 100644 --- a/MagicFormulaTyreTool.prj +++ b/MagicFormulaTyreTool.prj @@ -1,5 +1,5 @@ - + MagicFormulaTyreTool Tom Teasdale teasdale@lightsaber.red @@ -13,7 +13,7 @@ MATLAB GUI for Magic Formula Tyre Modeling https://github.com/teasit/magic-formula-tyre-tool ${PROJECT_ROOT}\assets\img\App_Screenshot_Main.jpg - 1.4.0 + 1.5.0 MATLAB Optimization Toolbox @@ -25,9 +25,9 @@ 8 - 9.13 - 9.4 - 9.1 + 23.2 + 23.2 + 23.2 ${PROJECT_ROOT} @@ -46,6 +46,7 @@ ${PROJECT_ROOT}\src\+events\FitterMeasurementsLoadedEventData.m ${PROJECT_ROOT}\src\+events\FitterSettingsChangedEventData.m ${PROJECT_ROOT}\src\+events\FittingModesChangedEventData.m + ${PROJECT_ROOT}\src\+events\MeasurementImportRequested.m ${PROJECT_ROOT}\src\+events\MeasurementSelectionChanged.m ${PROJECT_ROOT}\src\+events\ModelChangedEventData.m ${PROJECT_ROOT}\src\+events\PlotTyreMeasurementsRequested.m @@ -64,10 +65,13 @@ ${PROJECT_ROOT}\src\+exceptions\InvalidSolverOptions.m ${PROJECT_ROOT}\src\+helpers\checkUpdateAvailable.m ${PROJECT_ROOT}\src\+helpers\fitterOutputFcn.m + ${PROJECT_ROOT}\src\+helpers\getParsers.m ${PROJECT_ROOT}\src\+settings\AbstractSettings.m ${PROJECT_ROOT}\src\+settings\AppSettings.m ${PROJECT_ROOT}\src\+settings\FitterSettings.m ${PROJECT_ROOT}\src\+settings\LastSessionSettings.m + ${PROJECT_ROOT}\src\+settings\LayoutSettings.m + ${PROJECT_ROOT}\src\+settings\TextSettings.m ${PROJECT_ROOT}\src\+settings\TyreAnalysisPanelViewSettings.m ${PROJECT_ROOT}\src\+settings\TyreModelPanelViewSettings.m ${PROJECT_ROOT}\src\+settings\TyreParametersTableViewSettings.m @@ -76,16 +80,15 @@ ${PROJECT_ROOT}\src\+settings\ViewLayoutSettings.m ${PROJECT_ROOT}\src\+settings\ViewSettings.m ${PROJECT_ROOT}\src\+ui\FrictionEllipseAxes.m - ${PROJECT_ROOT}\src\+ui\HelpHint.m - ${PROJECT_ROOT}\src\+ui\MeasurementImporter.m ${PROJECT_ROOT}\src\+ui\NumericRangeSelector.m ${PROJECT_ROOT}\src\+ui\SearchBar.m ${PROJECT_ROOT}\src\+ui\TyreAnalysisPanel.m + ${PROJECT_ROOT}\src\+ui\TyreDataImportPanel.m + ${PROJECT_ROOT}\src\+ui\TyreDataPanel.m + ${PROJECT_ROOT}\src\+ui\TyreDataTable.m ${PROJECT_ROOT}\src\+ui\TyreFitterFittingModesPanel.m ${PROJECT_ROOT}\src\+ui\TyreFitterPanel.m ${PROJECT_ROOT}\src\+ui\TyreFitterSolverSettingsPanel.m - ${PROJECT_ROOT}\src\+ui\TyreMeasurementsPanel.m - ${PROJECT_ROOT}\src\+ui\TyreMeasurementsTable.m ${PROJECT_ROOT}\src\+ui\TyreModelPanel.m ${PROJECT_ROOT}\src\+ui\TyreParametersTable.m ${PROJECT_ROOT}\src\+ui\TyrePlotCurvesPanel.m @@ -144,7 +147,7 @@ - C:\Program Files\MATLAB\R2022b + C:\Program Files\MATLAB\R2023b diff --git a/README.md b/README.md index 998e615..39195b8 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,10 @@ de-identified and obscured data for demonstration purposes. ## Requirements -- MATLAB Base (tested with R2021a) +- MATLAB Base - Optimization Toolbox (for fitting) - Signal Processing Toolbox (for raw measurement import) +- Parallel Computing Toolbox (if `parfor` is to be used while fitting) ## Installation @@ -49,7 +50,7 @@ There are several ways: - Download latest Release from [MATLAB File Exchange](https://de.mathworks.com/matlabcentral/fileexchange/111375) - Download latest Release from [GitHub](https://github.com/teasit/magic-formula-tyre-tool/releases) -- Clone using Git and integrate into your projects using a [Project Reference](https://de.mathworks.com/help/simulink/ug/add-or-remove-a-reference-to-another-project.html) +- Clone using Git and integrate into your projects using a [Project Reference](https://mathworks.com/help/simulink/ug/add-or-remove-a-reference-to-another-project.html) ## Usage @@ -133,6 +134,5 @@ comparison. ## Known Issues -- Currently only Fitting of Fx0, Fy0, Fx, Fy is supported - Only Magic Formula version 6.1.2 (62) is supported. - The FSAE TTC parsers might not always work. You might have to create your own parser. diff --git a/ToolboxPackager.prj b/ToolboxPackager.prj index c99eb38..5344b2e 100644 --- a/ToolboxPackager.prj +++ b/ToolboxPackager.prj @@ -7,7 +7,7 @@ MATLAB GUI for Magic Formula Tyre Modeling https://github.com/teasit/magic-formula-tyre-tool ${PROJECT_ROOT}\assets\img\App_Screenshot_Main.jpg - 1.4.0 + 1.5.0 ${PROJECT_ROOT}\MagicFormulaTyreTool.mltbx @@ -63,7 +63,7 @@ MagicFormulaTyreTool_resources R2021a - R2022b + latest false true true @@ -125,7 +125,7 @@ MagicFormulaTyreTool_resources - C:\Program Files\MATLAB\R2022b + C:\Program Files\MATLAB\R2023b diff --git a/doc/GettingStarted.mlx b/doc/GettingStarted.mlx index 1c1521320a91df71bcedff8a8c855332f84c6ad7..1a9aec616911b83090f390ebd8abba0b916ab6c2 100644 GIT binary patch delta 7490 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 delta 7524 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 RW^(YX2a9|mc}Vc>e*isoPBj1k diff --git a/doc/examples/fsae-ttc-data/fsaettc_obscured_testbench_cornering.mat b/doc/examples/fsae/cornering.mat similarity index 100% rename from doc/examples/fsae-ttc-data/fsaettc_obscured_testbench_cornering.mat rename to doc/examples/fsae/cornering.mat diff --git a/doc/examples/fsae-ttc-data/fsaettc_obscured_testbench_drivebrake.mat b/doc/examples/fsae/drivebrake.mat similarity index 100% rename from doc/examples/fsae-ttc-data/fsaettc_obscured_testbench_drivebrake.mat rename to doc/examples/fsae/drivebrake.mat diff --git a/doc/examples/fsae-ttc-data/fsaettc_obfuscated.tir b/doc/examples/fsae/fsaettc_obfuscated.tir similarity index 100% rename from doc/examples/fsae-ttc-data/fsaettc_obfuscated.tir rename to doc/examples/fsae/fsaettc_obfuscated.tir diff --git a/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File.xml b/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File.xml similarity index 100% rename from resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File.xml rename to resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File.xml diff --git a/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/1.type.DIR_SIGNIFIER.xml b/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/1.type.DIR_SIGNIFIER.xml similarity index 100% rename from resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/1.type.DIR_SIGNIFIER.xml rename to resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/1.type.DIR_SIGNIFIER.xml diff --git a/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/fsaettc_obscured_testbench_cornering.mat.type.File.xml b/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/cornering.mat.type.File.xml similarity index 100% rename from resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/fsaettc_obscured_testbench_cornering.mat.type.File.xml rename to resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/cornering.mat.type.File.xml diff --git a/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/fsaettc_obscured_testbench_drivebrake.mat.type.File.xml b/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/drivebrake.mat.type.File.xml similarity index 100% rename from resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/fsaettc_obscured_testbench_drivebrake.mat.type.File.xml rename to resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/drivebrake.mat.type.File.xml diff --git a/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/fsaettc_obfuscated.tir.type.File.xml b/resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/fsaettc_obfuscated.tir.type.File.xml similarity index 100% rename from resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae-ttc-data.type.File/fsaettc_obfuscated.tir.type.File.xml rename to resources/project/Root.type.Files/doc.type.File/examples.type.File/fsae.type.File/fsaettc_obfuscated.tir.type.File.xml diff --git a/resources/project/Root.type.Files/src.type.File/+events.type.File/MeasurementImportRequested.m.type.File.xml b/resources/project/Root.type.Files/src.type.File/+events.type.File/MeasurementImportRequested.m.type.File.xml new file mode 100644 index 0000000..7a6326b --- /dev/null +++ b/resources/project/Root.type.Files/src.type.File/+events.type.File/MeasurementImportRequested.m.type.File.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/Root.type.Files/src.type.File/+helpers.type.File/getParsers.m.type.File.xml b/resources/project/Root.type.Files/src.type.File/+helpers.type.File/getParsers.m.type.File.xml new file mode 100644 index 0000000..7a6326b --- /dev/null +++ b/resources/project/Root.type.Files/src.type.File/+helpers.type.File/getParsers.m.type.File.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/Root.type.Files/src.type.File/+settings.type.File/LayoutSettings.m.type.File.xml b/resources/project/Root.type.Files/src.type.File/+settings.type.File/LayoutSettings.m.type.File.xml new file mode 100644 index 0000000..7a6326b --- /dev/null +++ b/resources/project/Root.type.Files/src.type.File/+settings.type.File/LayoutSettings.m.type.File.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/Root.type.Files/src.type.File/+settings.type.File/TextSettings.m.type.File.xml b/resources/project/Root.type.Files/src.type.File/+settings.type.File/TextSettings.m.type.File.xml new file mode 100644 index 0000000..7a6326b --- /dev/null +++ b/resources/project/Root.type.Files/src.type.File/+settings.type.File/TextSettings.m.type.File.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/Root.type.Files/src.type.File/+ui.type.File/MeasurementImporter.m.type.File.xml b/resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreDataImportPanel.m.type.File.xml similarity index 100% rename from resources/project/Root.type.Files/src.type.File/+ui.type.File/MeasurementImporter.m.type.File.xml rename to resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreDataImportPanel.m.type.File.xml diff --git a/resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreMeasurementsPanel.m.type.File.xml b/resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreDataPanel.m.type.File.xml similarity index 100% rename from resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreMeasurementsPanel.m.type.File.xml rename to resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreDataPanel.m.type.File.xml diff --git a/resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreMeasurementsTable.m.type.File.xml b/resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreDataTable.m.type.File.xml similarity index 100% rename from resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreMeasurementsTable.m.type.File.xml rename to resources/project/Root.type.Files/src.type.File/+ui.type.File/TyreDataTable.m.type.File.xml diff --git a/resources/project/Root.type.ProjectPath/213618a8-a7aa-4474-9639-07cd1240a040.type.Reference.xml b/resources/project/Root.type.ProjectPath/213618a8-a7aa-4474-9639-07cd1240a040.type.Reference.xml deleted file mode 100644 index 2bdee40..0000000 --- a/resources/project/Root.type.ProjectPath/213618a8-a7aa-4474-9639-07cd1240a040.type.Reference.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/Root.type.ProjectPath/e93ef97a-84ce-4e40-a691-4f3aea49308d.type.Reference.xml b/resources/project/Root.type.ProjectPath/e93ef97a-84ce-4e40-a691-4f3aea49308d.type.Reference.xml new file mode 100644 index 0000000..7f077ad --- /dev/null +++ b/resources/project/Root.type.ProjectPath/e93ef97a-84ce-4e40-a691-4f3aea49308d.type.Reference.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/sandbox/+tydex/+parsers/ExampleParser.m b/sandbox/+tydex/+parsers/ExampleParser.m index 7512262..05b8430 100644 --- a/sandbox/+tydex/+parsers/ExampleParser.m +++ b/sandbox/+tydex/+parsers/ExampleParser.m @@ -1,4 +1,4 @@ -classdef ExampleParser < tydex.Parser +classdef (Hidden) ExampleParser < tydex.Parser methods (Access = protected) [measurements, bins, binvalues] = parse(obj,file) end diff --git a/sandbox/test_data.mlx b/sandbox/test_data.mlx index c06408538b9dfc74fbc23a43afd3fe1bb9bc8dbc..e42f1e77a943c8277ad07d569557b9b372e124f5 100644 GIT binary patch delta 2031 zcmY*adpy$(7hjvzjOG5wohFyLG_;0jCYpO8mn>QyL~glnF+cUtC?0I(T57H#rD&PQ z+@cn__ZC}PtS5AFX+>`J>Ulr!=Y2cpbN=|A%jbN~`J4+|qgTCg0t{c>jjVCm1_Dih z8A7lz;L&xa+eVr*V=&6OH5~GQ<`F0>Q~w5!(zedxmZ)L!CJ_hfO#p%GXkBx_x%a^r zr#Mv6Ytyb-!`KxuWwSkpOc&RBpv9=Nr&Nc7^ocP8C42l_+*Ng`gV3+tu0!%^S5bG3 z{1^mwu?%J77g5tpdi>|PBhFP6rWZWZ+wCpzg)IP>7lP@}>BQdpRz=t2@2h*O`?`!a z<@yf=QbA02o4sUD7kR7OI?=+QHePUnTB};F-yiaWF7g(dlUk4xlo6gkP?qpq$?}>~ z2%8<)M(p^2c=_Qlf1lUGh2p5_+1p}ht24JcGzu|SajkrpK+jK)k#Bfs9-zt&_T429Dty0t6Z@j39R067 zpQqvG*vkE7!JC7x)V|C=xhbfzyL(!h3O?!#5itaR# z@dzI+pE~1XM;YgrR4VckqI6hY-nG~f(_L~>QNq#CL3t=Ti zHea3w+fb5_Bw~wneACvBk?^lTG(fzXixOr|CtQI&0PPSvP7$-O4P!gB^uv~4a8abp zC(jixH0IboD>n}uJsM7`)7KeZ?yuE$^=$+5*DZc?f+ z|8kVc4gE~=A%V&_t(d~1SS~xNUK7yPUY2otQ{$%FfpFBf2av=AyYq7C!ZOnwG;P*cokA&ASH3Z+z2B%G#*m3qzEV_W$?ZGtZS?TV-cIG>H*xyH;d>QJjks1Cq^=1WIlEE4vt3Q_eh8@NF#qd#lqTyn=fU;%q$ zc-?JNeBdMyB;sTEiM+LKpuH#fFNQ!w9Qf@4Kb+X>1SeK(TxTtIthkqHtVan&eWddS zGu1rUS@-%2cE{hNT`AGI>Jh8c>_o{gR5tc{{};j45m?9VvifAbJ@@K*NQN{QlinnP zc&7$DS}{G&?Nm_E1lvhS?`C2H+!U@TFDF^CC{1FR*&AQ9DVK) z*53O0jElEvSXgoMa^QW0pdE+Y58vy1zQO5!q|wAO@~Pzymt`flp;xc`;I z^<(>-?{Nl3H=w_6_3R%SS*naFm?e;r?;rk1Q~u(@x|LP8ld6sJnuwK z23$jv)LeJED%SR^`aE@^lYrQ^hk>wV!xsYMFXY`!Bn5HhPI

aq>;9fz1N*+JL`* z<6awunn(v5RA>`Uz0*M+)F)!}CrcL&;stf-r~USQPa(Psd-)%+O-=nx z1b(M~nCDmj_hV__Zdy(hdL=n-ZboIu;^fqCXo=DEpLynfgBkMAF!&*%013d%P2pi*gZ4I zdu@H4%HHI%U^75*_b2EkXl=Bx!JzH$oBcap&fZVA$^D1JI7wF}yW~qx582VTTSh7| z`W+G}(jqx=lm-Xo&PwkGc6KDLS)u=t&~Tbt|6KSc-6_MMsd|hQqu)`90J%djQF2FI z#`i-DmMF1UiRj41%y!5REMG86G{@=U$Op8vo}ZuA4F33rpaZa!JV-pFTxZ3)?OPS1R9GYHjr4_v$ z4ekzQSC%;aU`lGRL04}HPJBPToT!9oS)_BV%Q6&CW)^k^nkQwjh-_-|?3+PlyAY$e z0)InQSp|BUWvB7)UY^(dATPqsren@iR_Sx&BRgV|3elQoQ5?;_5b%;v}Bt2?@dggn-g0{qj&%__s28jTOYcvG-ofVavQ@#QJ!uD+Tpg;od6JH4z@ zsbeXtZR&FSqF)qzXjUEzau!_mFq@o<-08h6#C6c*gFh`**ZlFiz5*TZYRg9e!LLw? zx1>aLvn%d6oBHF;!{qklpy>mabwBZe8aHS}ugdhOOp`PYri_a`;eEMgQ-`!&r6T}d zI1tw=cn{jB88cMow`W4Gmw^X0JoF|R=l)7%PqzAME^VmXS2}6{U60}5qRl5!b}>sl-PL@K%+RT znRVBTIJVRY2LgvL5L42#R^uQa!Da% zT!%^H!{idCptN*D%Np+_V9|_c)~{LHzo3fKbb8hAv1mxv%G;2wxVN`D0@{Wrh7XU# z{KQp>GeyO@kzoW!Uzo6Tw(#^cQR{lZ1-oP=c+m&+41j1Zh&M1fBPul(_S&e3 zC3u3aUunijKRY(1zeioz0VJ#0X!Zc>re}Ce$a^upgfM@^rfF{xGun!l#CRlmcNLw` z*S|n1PPS7Me*nWgaxC_2C8U2gaWyq4S9CMz@bnnm;A62@K2_VW=R1SdgFG4L#e|;m z+))T}xs2h6?<#7f!M&aq@lxDKKS>7Zivwi@UfMxYBRM1STy8Nu^gN4k$=iv&g|3;k z2mV&USa_DUAOLl$s$D+j*=@HjdiF=&vhIIA#ji&(STee#z`OuN((a|Auv~S;n@d}J z5~46lor5WC8)7)Gt!>{uOLWuaHne2^_;y2ORoEAZYUd^uYu{MsqZWG0zWMqAlU=c9qj;kcYdYKjqx_&Hv zul?PG+mq~TxP*0DsEr?;3bj$cYkN)}jH=ZHQshg*Ev8sC=`3F3%V1b7u zY%K}opp9{^L?Ph)?x&GwaK?Y;S_49b5CZaNG1V3V)FI)ATCiPUuiw=}tB1{B4mcHX z9!qQjZ6E=rQo!;Z%!Dy__^eCp{%Va5-`nXC+7X3naIUYszk8F#7X{?|owc7J=9=`K z|0VTZu%+^UApro$?OwxQwaf9{qBF%9nixADc diff --git a/sandbox/test_mfeval.mlx b/sandbox/test_mfeval.mlx index e6758fed57051f3c6afbb7111071ffe1bbab4635..9ca6cdaeb68512e32f92b3810ede09617356dc82 100644 GIT binary patch literal 52513 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 Ezp(3D?*IS* diff --git a/src/+events/MeasurementImportRequested.m b/src/+events/MeasurementImportRequested.m new file mode 100644 index 0000000..aa5064e --- /dev/null +++ b/src/+events/MeasurementImportRequested.m @@ -0,0 +1,12 @@ +classdef (ConstructOnLoad) MeasurementImportRequested < event.EventData + properties + Files cell + Parser function_handle; + end + methods + function eventData = MeasurementImportRequested(files, parser) + eventData.Files = cellstr(files); + eventData.Parser = parser; + end + end +end \ No newline at end of file diff --git a/src/+helpers/getParsers.m b/src/+helpers/getParsers.m new file mode 100644 index 0000000..6bd9726 --- /dev/null +++ b/src/+helpers/getParsers.m @@ -0,0 +1,11 @@ +function [names, handles] = getParsers() +metaPackageObj = meta.package.fromName('tydex.parsers'); +metaClassObj = metaPackageObj.ClassList; +isAbstract = [metaClassObj.Abstract]; +isHidden = [metaClassObj.Hidden]; +metaClassObj(isAbstract|isHidden) = []; +namesFull = {metaClassObj.Name}; +namesSplit = cellfun(@(x)strsplit(x,'.'), namesFull, 'UniformOutput', 0); +names = cellfun(@(x)x{end}, namesSplit, 'UniformOutput', 0); +handles = cellfun(@str2func, namesFull, 'UniformOutput', 0); +end \ No newline at end of file diff --git a/src/+settings/AppSettings.m b/src/+settings/AppSettings.m index 57cc539..451980a 100644 --- a/src/+settings/AppSettings.m +++ b/src/+settings/AppSettings.m @@ -1,7 +1,9 @@ classdef AppSettings < settings.AbstractSettings %APPSETTINGS Loads app settings from and saves them to persistent storage. properties (SetObservable, AbortSet) + Layout settings.LayoutSettings Fitter settings.FitterSettings + Text settings.TextSettings View settings.ViewSettings LastSession settings.LastSessionSettings Version char @@ -32,9 +34,15 @@ function save(obj) end end function init(obj) - obj.Fitter = settings.FitterSettings(); - obj.View = settings.ViewSettings(); - obj.LastSession = settings.LastSessionSettings(); + props = metaclass(obj).PropertyList; + isSetObservable = [props.SetObservable]; + props(~isSetObservable) = []; + for i = 1:numel(props) + p = props(i); + name = p.Name; + class = str2func(p.Validation.Class.Name); + obj.(name) = class(); + end try s = settings(); if hasGroup(s, obj.SettingsGroupTopLevel) diff --git a/src/+settings/FitterSettings.m b/src/+settings/FitterSettings.m index 0efa6c5..2aa2e30 100644 --- a/src/+settings/FitterSettings.m +++ b/src/+settings/FitterSettings.m @@ -2,7 +2,7 @@ %FITTERSETTINGS Contains app settings for fitter properties (SetObservable, AbortSet) FitModes magicformula.FitMode = magicformula.FitMode.empty - DownsampleFactor double = double.empty + DownsampleFactor double = 1 OptimizerSettings struct = struct.empty end methods diff --git a/src/+settings/LayoutSettings.m b/src/+settings/LayoutSettings.m new file mode 100644 index 0000000..a206637 --- /dev/null +++ b/src/+settings/LayoutSettings.m @@ -0,0 +1,13 @@ +classdef LayoutSettings < settings.AbstractSettings + %LAYOUTSETTINGS Contains app settings for layout (e.g. default padding) + properties (Constant, AbortSet) + DefaultPadding double = 5*ones(1,4); + DefaultButtonHeight double = 22 + DefaultButtonWidthTextIcon = 110 + DefaultButtonWidthOnlyText = 75 + DefaultButtonWidthOnlyIcon = 25 + DefaultColumnSpacing double = 10 + DefaultRowSpacing double = 5 + DefaultSidebarWidth double = 250 + end +end \ No newline at end of file diff --git a/src/+settings/TextSettings.m b/src/+settings/TextSettings.m new file mode 100644 index 0000000..4df75cb --- /dev/null +++ b/src/+settings/TextSettings.m @@ -0,0 +1,7 @@ +classdef TextSettings < settings.AbstractSettings + %LAYOUTSETTINGS Contains app settings for layout (e.g. default padding) + properties (Constant, AbortSet) + FontWeightPanel = 'bold' + FontNamePanel = 'Helvetica' + end +end \ No newline at end of file diff --git a/src/+ui/HelpHint.m b/src/+ui/HelpHint.m index 3aa831d..35ff675 100644 --- a/src/+ui/HelpHint.m +++ b/src/+ui/HelpHint.m @@ -3,10 +3,9 @@ %callback. Can be used to open HTML file in browser for example. properties - Icon char = 'circle-question-regular.svg' + Icon char = 'circle-info-solid.svg' Tooltip char = 'Add Tooltip here' ClickedFcn function_handle = function_handle.empty - Height cell = {22} end properties (Access = private) @@ -17,9 +16,11 @@ end methods (Access = protected) function setup(obj) + s = settings.LayoutSettings(); + h = s.DefaultButtonHeight; grid = uigridlayout(obj, ... - 'RowHeight', obj.Height, ... - 'ColumnWidth', obj.Height, ... + 'RowHeight', h, ... + 'ColumnWidth', h, ... 'ColumnSpacing', 0, ... 'Padding', zeros(1,4)); diff --git a/src/+ui/MeasurementImporter.m b/src/+ui/MeasurementImporter.m deleted file mode 100644 index 2b7734c..0000000 --- a/src/+ui/MeasurementImporter.m +++ /dev/null @@ -1,254 +0,0 @@ -classdef MeasurementImporter < matlab.apps.AppBase - - properties (Access = public) - MeasurementImported tydex.Measurement = tydex.Measurement.empty - MeasurementFileFullPath char - OutputDirectoryFullPath char - end - - properties (Access = private) - CallingApp matlab.ui.Figure - end - - properties (Access = public) - UIFigure matlab.ui.Figure - GridMain matlab.ui.container.GridLayout - GridConfig matlab.ui.container.GridLayout - GridWindowButtons matlab.ui.container.GridLayout - MeasurementFileLabel matlab.ui.control.Label - MeasurementFileEditField matlab.ui.control.EditField - MeasurementFileDialogButton matlab.ui.control.Button - ParserLabel matlab.ui.control.Label - ParserDropdown matlab.ui.control.DropDown - ParserFileDialogButton matlab.ui.control.Button - StartImportButton matlab.ui.control.Button - CancelImportButton matlab.ui.control.Button - end - - methods (Access = private) - function onCloseRequested(app, ~, ~) - callingApp = app.CallingApp; - if isempty(callingApp) - delete(app) - else - callingApp = app.CallingApp; - uiresume(callingApp) - end - end - function onTestTypeChanged(app, source, ~) - testType = source.Value; - app.TestType = testType; - end - function chooseFileDialog(app, ~, ~) - [file,path] = uigetfile('.mat', 'Select measurement file'); - if ~file; return; end - fileFullPath = [path file]; - app.MeasurementFileEditField.Value = file; - app.MeasurementFileFullPath = fileFullPath; - end - function chooseParserDialog(app, ~, ~) - [fileName, path] = uigetfile('.m', 'Select parser class file'); - if path == 0 - return - end - [~, fileBaseName] = fileparts(fileName); - pathParts = strsplit(path,filesep()); - packageFlag = '+'; - I = cellfun(@(x) startsWith(x, packageFlag), pathParts); - pathToPackage = fullfile(pathParts{~I}); - addpath(pathToPackage) - packageParts = pathParts(I); - packageParts = erase(packageParts, packageFlag); - parserClassName = strjoin([packageParts fileBaseName], '.'); - - superclassNames = superclasses(parserClassName); - isParser = any(contains(superclassNames, 'tydex.Parser')); - - if ~isParser - title = 'Choose Parser'; - message = ['Invalid parser selected. Must inherit from ' ... - '"tydex.Parser".']; - uialert(app.UIFigure, message, title, 'icon', 'error') - return - end - - item = [fileBaseName ' (custom)']; - itemData = parserClassName; - - dd = app.ParserDropdown; - dd.Items = [{item} dd.Items]; - dd.ItemsData = [{itemData} dd.ItemsData]; - dd.Value = itemData; - end - function runImport(app, ~, ~) - file = app.MeasurementFileFullPath; - if isempty(file) - uialert(app.UIFigure, ... - 'No measurement file selected', ... - 'Import failed'); - return - end - fig = app.UIFigure; - dlg = uiprogressdlg(fig, ... - 'Title', 'Importing measurement file',... - 'Message', 'Please wait...', ... - 'Indeterminate','on'); - - try - parserClassName = app.ParserDropdown.Value; - parser = eval(parserClassName); - measurement = parser.run(file); - app.MeasurementImported = measurement; - catch ME - close(dlg) - uialert(app.UIFigure, ME.message, 'Import failed'); - return - end - close(dlg) - uialert(app.UIFigure, ... - 'Measurement has been imported.', ... - 'Import successful', ... - 'Icon', 'success', ... - 'CloseFcn', @app.onCloseRequested); - end - end - - methods (Access = private) - function startupFcn(app) - end - function createComponents(app) - if ~isempty(app.CallingApp) - name = app.CallingApp.Name; - name = [name ' - Measurement Importer']; - else - name = 'Measurement Importer'; - end - app.UIFigure = uifigure(... - 'Name', name, ... - 'Visible', 'off',... - 'HandleVisibility', 'off', ... - 'Resize', 'off', ... - 'Color',[1 1 1], ... - 'Position', [500 500 500 200], ... - 'Icon', 'tyre_icon.png', ... - 'CloseRequestFcn', @app.onCloseRequested, ... - 'WindowStyle', 'modal'); - - app.GridMain = uigridlayout(app.UIFigure, ... - 'Padding', 10*ones(1,4), ... - 'RowSpacing', 20, ... - 'RowHeight', {'fit', '1x', 'fit'}, ... - 'ColumnWidth', {'1x'}); - - app.GridConfig = uigridlayout(app.GridMain, ... - 'Padding', 0*ones(1,4), ... - 'RowHeight', {'fit','fit'}, ... - 'ColumnWidth', {'fit','fit','1x','fit'}); - - app.MeasurementFileLabel = uilabel(app.GridConfig, ... - 'Text', 'Measurement File'); - app.MeasurementFileLabel.Layout.Row = 1; - - helpHint = ui.HelpHint(app.GridConfig); - helpHint.Tooltip = ['Measurement file must be a single file. ' ... - 'Depending on the Parser used, it can be MAT or any other ' ... - 'file format.']; - - app.MeasurementFileEditField = uieditfield(app.GridConfig, ... - 'Editable', 'off'); - app.MeasurementFileEditField.Layout.Row = 1; - - app.MeasurementFileDialogButton = uibutton(app.GridConfig, ... - 'Text', 'Choose...', ... - 'ButtonPushedFcn', @app.chooseFileDialog); - app.MeasurementFileDialogButton.Layout.Row = 1; - - app.ParserLabel = uilabel(app.GridConfig, ... - 'Text', 'Parser'); - app.ParserLabel.Layout.Row = 2; - - helpHint = ui.HelpHint(app.GridConfig); - helpHint.Tooltip = ['Parser must inherit from base class ' ... - '"tydex.Parser". Some parsers come with the tool, but ' ... - 'you might need to implement your own.' ... - newline() newline() ... - 'A parser must create "tydex.Measurement" objects from ' ... - 'a given measurement file.']; - - [parserNames, parserClassNames] = app.getParserNames(); - app.ParserDropdown = uidropdown(app.GridConfig, ... - 'Items', parserNames, ... - 'ItemsData', parserClassNames, ... - 'Tooltip', ['Currently only FSAE TTC data '... - 'in SI units supplied as .mat file is suppored.']); - app.ParserDropdown.Layout.Row = 2; - - app.ParserFileDialogButton = uibutton(app.GridConfig, ... - 'Text', 'Choose...', ... - 'ButtonPushedFcn', @app.chooseParserDialog); - - app.GridWindowButtons = uigridlayout(app.GridMain, ... - 'Padding', zeros(1,4), ... - 'RowHeight', {22}, ... - 'ColumnWidth', {'1x', 100, 100}); - app.GridWindowButtons.Layout.Row = 3; - - app.StartImportButton = uibutton(app.GridWindowButtons, ... - 'Text', 'Import', ... - 'BackgroundColor', [144, 202, 249]/255, ... - 'ButtonPushedFcn', @app.runImport); - app.StartImportButton.Layout.Column = 2; - - app.CancelImportButton = uibutton(app.GridWindowButtons, ... - 'Text', 'Cancel', ... - 'ButtonPushedFcn', @(o,e) close(app.UIFigure)); - app.CancelImportButton.Layout.Column = 3; - - app.UIFigure.Visible = 'on'; - end - end - - methods (Static, Access = private) - function [parserNames, parserClassNames] = getParserNames() - try - parsersMetaObj = meta.package.fromName('tydex.parsers'); - parserMetaClassObjs = parsersMetaObj.ClassList; - isAbstract = [parserMetaClassObjs.Abstract]; - parserMetaClassObjs(isAbstract) = []; - parserClassNames = {parserMetaClassObjs.Name}; - parserNamesSplit = cellfun(... - @(x) strsplit(x, '.'), parserClassNames, ... - 'UniformOutput', false); - parserNames = cellfun(@(x) x{end}, parserNamesSplit, ... - 'UniformOutput', false); - catch - warning('Could not find any pre-installed parsers!') - [parserNames, parserClassNames] = deal({}); - end - end - end - - methods (Access = public) - function app = MeasurementImporter(varargin) - if nargin == 1 - callingApp = varargin{1}; - app.CallingApp = callingApp; - end - runningApp = getRunningApp(app); - if isempty(runningApp) - createComponents(app) - registerApp(app, app.UIFigure) - runStartupFcn(app, @startupFcn) - else - figure(runningApp.UIFigure) - app = runningApp; - end - if nargout == 0 - clear app - end - end - function delete(app) - delete(app.UIFigure) - end - end -end \ No newline at end of file diff --git a/src/+ui/TyreAnalysisPanel.m b/src/+ui/TyreAnalysisPanel.m index 15d4a3b..b332ca4 100644 --- a/src/+ui/TyreAnalysisPanel.m +++ b/src/+ui/TyreAnalysisPanel.m @@ -16,8 +16,12 @@ end properties (Access = protected) Settings settings.AppSettings - ButtonsGridColumnWidthWithText = [repmat({110}, 1, 2) {'1x' 110}]; - ButtonsGridColumnWidthOnlyIcon = [repmat({25}, 1, 2) {'1x' 25}]; + ButtonsGridColumnWidthWithText = [repmat({... + settings.LayoutSettings().DefaultButtonWidthTextIcon... + }, 1, 2) {'1x' 110}]; + ButtonsGridColumnWidthOnlyIcon = [repmat({... + settings.LayoutSettings().DefaultButtonWidthOnlyIcon... + }, 1, 2) {'1x' 25}]; ButtonsTexts cell end events (NotifyAccess = public) @@ -126,10 +130,11 @@ function setupPlotCurves(obj) end end function setupGrid(obj) + s = settings.LayoutSettings(); obj.Grid = uigridlayout(obj, ... - 'RowHeight', {22,'1x'}, ... + 'RowHeight', {s.DefaultButtonHeight, '1x'}, ... 'ColumnWidth', {'1x'}, ... - 'ColumnSpacing', 10, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... 'Padding', 0*ones(1,4), ... 'Scrollable', false); end @@ -141,10 +146,13 @@ function setupGridPlot(obj) 'Scrollable', false); end function setupButtons(obj) + s = settings.LayoutSettings(); + w1 = s.DefaultButtonWidthTextIcon; + w2 = s.DefaultButtonWidthOnlyIcon; obj.ButtonsGrid = uigridlayout(obj.Grid, ... - 'RowHeight', {22}, ... - 'ColumnWidth', {110, 110, '1x', 25}, ... - 'ColumnSpacing', 10, ... + 'RowHeight', {s.DefaultButtonHeight}, ... + 'ColumnWidth', {w1, w1, '1x', w2}, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... 'Padding', zeros(1,4)); btn1 = uibutton(obj.ButtonsGrid, 'state', ... diff --git a/src/+ui/TyreDataImportPanel.m b/src/+ui/TyreDataImportPanel.m new file mode 100644 index 0000000..71858d4 --- /dev/null +++ b/src/+ui/TyreDataImportPanel.m @@ -0,0 +1,211 @@ +classdef TyreDataImportPanel < matlab.ui.componentcontainer.ComponentContainer + %TYREDATAIMPORTPANEL User can select measurements to parse. + events (HasCallbackProperty, NotifyAccess = protected) + DataImportRequested + end + events (NotifyAccess = public) + DataImportFinished + end + properties (Access = private, Transient, NonCopyable) + Grid matlab.ui.container.GridLayout + Table matlab.ui.control.Table + TableRemoveButton matlab.ui.control.Button + ParserDropDown matlab.ui.control.DropDown + end + properties (Access = public) + Parser function_handle + Parsers + MeasurementFiles cell + MeasurementFilesSelected cell + end + properties (Access = private) + Settings settings.AppSettings + end + methods (Access = private) + function onDataImportFinished(obj, ~, ~) + obj.MeasurementFiles = {}; + end + function onRunDataImportRequested(obj, ~, ~) + files = obj.MeasurementFiles; + if isempty(files) + return + end + parser = obj.Parser; + e = events.MeasurementImportRequested(files, parser); + notify(obj, 'DataImportRequested', e); + end + function onSelectDataFilesRequested(obj, ~, ~) + filter = '.mat'; + prompt = 'Select measurement files'; + [filenames,folder] = uigetfile(filter, prompt, ... + 'MultiSelect', 'on'); + if folder == 0 + return + end + files = fullfile(folder, filenames); + files = cellstr(files)'; + obj.MeasurementFiles = [obj.MeasurementFiles; files]; + end + function onSelectParserDialog(obj, ~, ~) + filter = '.m'; + prompt = 'Select parser class file'; + [fileName, folder] = uigetfile(filter, prompt); + if folder == 0 + return + end + [~, fileBaseName] = fileparts(fileName); + pathParts = strsplit(folder, filesep()); + I = cellfun(@(x) startsWith(x, '+'), pathParts); + pathToPackage = fullfile(pathParts{~I}); + addpath(pathToPackage) + packageParts = pathParts(I); + packageParts = erase(packageParts, '+'); + parserClassName = strjoin([packageParts fileBaseName], '.'); + + superclassNames = superclasses(parserClassName); + isParser = any(contains(superclassNames, 'tydex.Parser')); + + if ~isParser + fig = ancestor(obj, 'figure'); + title = 'Select Parser'; + msg = 'Must be subclass of ''tydex.Parser''.'; + uialert(fig, msg, title, 'icon', 'error') + return + end + + parserName = [fileBaseName ' (custom)']; + parserHandle = str2func(parserClassName); + + dd = obj.ParserDropDown; + dd.Items = [{parserName} dd.Items]; + dd.ItemsData = [{parserHandle} dd.ItemsData]; + obj.Parser = parserHandle; + end + function onParserDropdownChangedFcn(obj, ~, event) + obj.Parser = event.Value; + end + function onMeasurementFilesSelected(obj, table, event) + if isempty(event.Indices) + obj.MeasurementFilesSelected = []; + return + end + rows = event.Indices(:,1); + filesSelected = table.Data(rows); + obj.MeasurementFilesSelected = filesSelected; + end + function onRemoveDataFiles(obj, ~, ~) + files = obj.MeasurementFiles; + filesToRemove = obj.MeasurementFilesSelected; + I = strcmp(files, filesToRemove); + files(I) = []; + obj.MeasurementFiles = files; + obj.MeasurementFilesSelected = []; + end + function onOpenParserScript(obj, ~, ~) + open(func2str(obj.Parser)); + end + end + + methods (Access = protected) + function setupSidebarPanel(obj) + s = settings.LayoutSettings(); + g = uigridlayout(obj, ... + 'RowHeight', {'1x'}, ... + 'ColumnWidth', {'1x'}, ... + 'Padding', zeros(1,4)); + p = uipanel(g, 'Title', char.empty); + obj.Grid = uigridlayout(p, ... + 'RowHeight', {'fit', 'fit', 'fit'}, ... + 'ColumnWidth', {'1x'}, ... + 'Padding', zeros(1,4), ... + 'ColumnSpacing', s.DefaultColumnSpacing); + end + function setupParserPanel(obj) + s = obj.Settings; + p = uipanel(obj.Grid, ... + 'Title', 'Parser', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'BorderType', 'none'); + g = uigridlayout(p, ... + 'RowHeight', {s.Layout.DefaultButtonHeight, 'fit'}, ... + 'RowSpacing', s.Layout.DefaultRowSpacing, ... + 'ColumnWidth', {'1x'}, ... + 'Padding', s.Layout.DefaultPadding); + [parserNames, parserHandles] = helpers.getParsers(); + obj.ParserDropDown = uidropdown(g, ... + 'Items', parserNames, ... + 'ItemsData', parserHandles, ... + 'ValueChangedFcn', @obj.onParserDropdownChangedFcn); + obj.Parser = parserHandles{1}; + w = s.Layout.DefaultButtonWidthOnlyText; + g = uigridlayout(g, ... + 'RowHeight', {s.Layout.DefaultButtonHeight}, ... + 'ColumnWidth', {'1x', w, w}, ... + 'Padding', zeros(1,4)); + uilabel(g, 'Text', char.empty); + uibutton(g, 'Text', 'Edit', ... + 'ButtonPushedFcn', @obj.onOpenParserScript); + uibutton(g, 'Text', 'Add...', ... + 'ButtonPushedFcn', @obj.onSelectParserDialog); + end + function setupFilesPanel(obj) + s = obj.Settings; + p = uipanel(obj.Grid, ... + 'Title', 'Files', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'BorderType', 'none'); + g = uigridlayout(p, ... + 'RowHeight', {200, s.Layout.DefaultButtonHeight}, ... + 'ColumnWidth', {'1x'}, ... + 'Padding', s.Layout.DefaultPadding); + obj.Table = uitable(g, 'Data', {}, ... + 'ColumnName', [], 'RowName', [], ... + 'CellSelectionCallback', @obj.onMeasurementFilesSelected); + w = s.Layout.DefaultButtonWidthOnlyText; + g = uigridlayout(g, ... + 'ColumnWidth', {'1x', w, w}, ... + 'RowHeight', {s.Layout.DefaultButtonHeight}, ... + 'Padding', zeros(1,4), ... + 'ColumnSpacing', s.Layout.DefaultColumnSpacing); + uilabel(g, 'Text', char.empty); + obj.TableRemoveButton = uibutton(g, 'Text', 'Remove', ... + 'ButtonPushedFcn', @obj.onRemoveDataFiles); + uibutton(g, 'Text', 'Add...', ... + 'ButtonPushedFcn', @obj.onSelectDataFilesRequested); + end + function setupImportButtonPanel(obj) + s = obj.Settings; + p = uipanel(obj.Grid, ... + 'Title', ' ', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'BorderType', 'none'); + g = uigridlayout(p, ... + 'RowHeight', {s.Layout.DefaultButtonHeight}, ... + 'ColumnWidth', {'1x'}, ... + 'Padding', s.Layout.DefaultPadding); + uibutton(g, 'Text', 'Run Import', 'Icon', 'play-solid.svg',... + 'ButtonPushedFcn', @obj.onRunDataImportRequested); + end + function setupListeners(obj) + addlistener(obj, 'DataImportFinished', ... + @obj.onDataImportFinished); + end + function setup(obj) + obj.Position = [0 0 300 400]; + obj.Settings = settings.AppSettings(); + setupSidebarPanel(obj) + setupFilesPanel(obj) + setupParserPanel(obj) + setupListeners(obj) + setupImportButtonPanel(obj) + end + function update(obj) + files = obj.MeasurementFiles; + filesSelected = obj.MeasurementFilesSelected; + obj.Table.Data = files; + obj.ParserDropDown.Value = obj.Parser; + enable = matlab.lang.OnOffSwitchState(~isempty(filesSelected)); + obj.TableRemoveButton.Enable = enable; + end + end +end \ No newline at end of file diff --git a/src/+ui/TyreMeasurementsPanel.m b/src/+ui/TyreDataPanel.m similarity index 58% rename from src/+ui/TyreMeasurementsPanel.m rename to src/+ui/TyreDataPanel.m index b7a0b64..8de8ce1 100644 --- a/src/+ui/TyreMeasurementsPanel.m +++ b/src/+ui/TyreDataPanel.m @@ -1,4 +1,4 @@ -classdef TyreMeasurementsPanel < matlab.ui.componentcontainer.ComponentContainer +classdef TyreDataPanel < matlab.ui.componentcontainer.ComponentContainer %TYREMEASUREMENTSPANEL events (HasCallbackProperty, NotifyAccess = protected) @@ -11,26 +11,34 @@ events (NotifyAccess = public) MeasurementDataChanged + MeasurementDataImportFinished end properties (Access = private, Transient, NonCopyable) - Grid matlab.ui.container.GridLayout - ButtonsGrid matlab.ui.container.GridLayout - Table ui.TyreMeasurementsTable + GridMain matlab.ui.container.GridLayout + GridCentral matlab.ui.container.GridLayout + GridSidebar matlab.ui.container.GridLayout + GridButtons matlab.ui.container.GridLayout + Table ui.TyreDataTable ImportButton matlab.ui.control.Button ExportButton matlab.ui.control.Button ClearButton matlab.ui.control.Button PlotButton matlab.ui.control.Button + Sidebar ui.TyreDataImportPanel end properties (Access = protected) - ButtonsGridColumnWidthWithText = [repmat({110}, 1, 4) {'1x'}]; - ButtonsGridColumnWidthOnlyIcon = [repmat({25}, 1, 4) {'1x'}]; + GridButtonsColumnWidthWithText = [repmat({... + settings.LayoutSettings().DefaultButtonWidthTextIcon... + }, 1, 3) {'1x'}]; + GridButtonsColumnWidthOnlyIcon = [repmat({... + settings.LayoutSettings().DefaultButtonWidthOnlyIcon... + }, 1, 3) {'1x'}]; ButtonsTexts cell end methods (Access = private) - function onImportMeasurementDialogRequested(obj, ~, ~) - notify(obj, 'MeasurementDataImportRequested') + function onDataImportRequested(obj, ~, event) + notify(obj, 'MeasurementDataImportRequested', event); end function onMeasurementDataClearRequested(obj, ~, ~) notify(obj, 'MeasurementDataClearRequested') @@ -55,6 +63,9 @@ function onMeasurementSelectionChanged(obj, ~, event) e = events.MeasurementSelectionChanged(measurements, I); notify(obj, 'MeasurementDataSelectionChanged', e) end + function onMeasurementDataImportFinished(obj, ~, ~) + notify(obj.Sidebar, 'DataImportFinished') + end function onUiFigureSizeChanged(obj, ~, ~) parent = obj.Parent; while isa(parent, 'matlab.ui.container.GridLayout') @@ -62,74 +73,90 @@ function onUiFigureSizeChanged(obj, ~, ~) end width = parent.Position(3); - buttonsGrid = obj.ButtonsGrid; - buttons = obj.ButtonsGrid.Children; - buttonWidths = obj.ButtonsGridColumnWidthWithText; + g = obj.GridButtons; + buttons = obj.GridButtons.Children; + buttonWidths = obj.GridButtonsColumnWidthWithText; buttonWidths = buttonWidths(cellfun(@isnumeric, buttonWidths)); minWidthButtonsWithText = sum([buttonWidths{:}]) ... - + (numel(buttons)+2)*buttonsGrid.ColumnSpacing; + + (numel(buttons)+2)*g.ColumnSpacing; removeTextFromButtons = width < minWidthButtonsWithText; if removeTextFromButtons set(buttons, 'Text', '') - set(buttonsGrid, ... - 'ColumnWidth', obj.ButtonsGridColumnWidthOnlyIcon); + set(g, ... + 'ColumnWidth', obj.GridButtonsColumnWidthOnlyIcon); else texts = obj.ButtonsTexts; [buttons(:).Text] = deal(texts{:}); - set(buttonsGrid, ... - 'ColumnWidth', obj.ButtonsGridColumnWidthWithText); + set(g, 'ColumnWidth', obj.GridButtonsColumnWidthWithText); end end end methods (Access = protected) + function setupSidebar(obj) + s = settings.LayoutSettings(); + obj.GridSidebar = uigridlayout(obj.GridCentral, ... + 'RowHeight', {'fit'}, ... + 'ColumnWidth', {s.DefaultSidebarWidth}, ... + 'ColumnSpacing', 0, ... + 'Padding', [0 0 0 0], ... + 'Scrollable', true, ... + 'Visible', true); + + obj.Sidebar = ui.TyreDataImportPanel(obj.GridSidebar, ... + 'DataImportRequestedFcn', @obj.onDataImportRequested); + end function setup(obj) + s = settings.LayoutSettings(); % Position only used for standalone-testing. obj.Position = [0 0 400 400]; - obj.Grid = uigridlayout(obj, ... - 'RowHeight', {'fit', '1x'}, ... + obj.GridMain = uigridlayout(obj, ... + 'RowHeight', {'fit','1x'}, ... 'ColumnWidth', {'1x'}, ... 'ColumnSpacing', 0, ... 'Padding', zeros(1,4)); - obj.ButtonsGrid = uigridlayout(obj.Grid, ... - 'RowHeight', {22}, ... - 'ColumnWidth', obj.ButtonsGridColumnWidthWithText, ... - 'ColumnSpacing', 10, ... + obj.GridButtons = uigridlayout(obj.GridMain, ... + 'RowHeight', {s.DefaultButtonHeight}, ... + 'ColumnWidth', obj.GridButtonsColumnWidthWithText, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... 'Padding', zeros(1,4)); - obj.ImportButton = uibutton(obj.ButtonsGrid, ... - 'Text', 'Import Data', ... - 'Icon', 'folder-open-solid.svg', ... - 'Tooltip', 'Import measurements using TYDEX parser', ... - 'ButtonPushedFcn', @obj.onImportMeasurementDialogRequested); - obj.ExportButton = uibutton(obj.ButtonsGrid, ... - 'Text', 'Export Data', ... - 'Icon', 'floppy-disk-regular.svg', ... - 'Tooltip', 'Export measurements as TYDEX files', ... - 'ButtonPushedFcn', @obj.onMeasurementDataExportRequested); - obj.ClearButton = uibutton(obj.ButtonsGrid, ... + obj.GridCentral = uigridlayout(obj.GridMain, ... + 'RowHeight', {'1x'}, ... + 'ColumnWidth', {'1x','fit'}, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... + 'Padding', zeros(1,4)); + obj.ClearButton = uibutton(obj.GridButtons, ... 'Text', 'Clear Data', ... 'Icon', 'trash-can-regular.svg', ... 'Tooltip', 'Delete loaded measurements (does not delete files on disk)', ... 'ButtonPushedFcn', @obj.onMeasurementDataClearRequested); - obj.PlotButton = uibutton(obj.ButtonsGrid, ... + obj.ExportButton = uibutton(obj.GridButtons, ... + 'Text', 'Export Data', ... + 'Icon', 'floppy-disk-regular.svg', ... + 'Tooltip', 'Export measurements as TYDEX files', ... + 'ButtonPushedFcn', @obj.onMeasurementDataExportRequested); + obj.PlotButton = uibutton(obj.GridButtons, ... 'Text', 'Stacked Plot', ... 'Icon', 'chart-line-solid.svg', ... 'Tooltip', 'Visualize imported measurements in Stacked Plot', ... 'ButtonPushedFcn', @obj.onPlotMeasurementRequested); - btns = obj.ButtonsGrid.Children; + btns = obj.GridButtons.Children; obj.ButtonsTexts = {btns.Text}; - obj.Table = ui.TyreMeasurementsTable(obj.Grid, ... + obj.Table = ui.TyreDataTable(obj.GridCentral, ... 'MeasurementSelectionChangedFcn', ... @obj.onMeasurementSelectionChanged); - + set(obj, 'SizeChangedFcn', @obj.onUiFigureSizeChanged) - + + setupSidebar(obj) setupListeners(obj) end function setupListeners(obj) addlistener(obj, 'MeasurementDataChanged', ... @obj.onMeasurementDataChanged); + addlistener(obj, 'MeasurementDataImportFinished', ... + @obj.onMeasurementDataImportFinished); end function update(obj) end diff --git a/src/+ui/TyreMeasurementsTable.m b/src/+ui/TyreDataTable.m similarity index 98% rename from src/+ui/TyreMeasurementsTable.m rename to src/+ui/TyreDataTable.m index 214ad45..014af68 100644 --- a/src/+ui/TyreMeasurementsTable.m +++ b/src/+ui/TyreDataTable.m @@ -1,4 +1,4 @@ -classdef TyreMeasurementsTable < matlab.ui.componentcontainer.ComponentContainer +classdef TyreDataTable < matlab.ui.componentcontainer.ComponentContainer %TYREMEASUREMENTSTABLE Shows measurements in tabular view. properties diff --git a/src/+ui/TyreFitterFittingModesPanel.m b/src/+ui/TyreFitterFittingModesPanel.m index e01330c..015de6a 100644 --- a/src/+ui/TyreFitterFittingModesPanel.m +++ b/src/+ui/TyreFitterFittingModesPanel.m @@ -49,12 +49,15 @@ function onCheckboxValueChanged(obj, origin, event) methods (Access = protected) function setupGrid(obj) + s = obj.Settings; g = uigridlayout(obj, ... 'RowHeight', {'1x'}, ... 'ColumnWidth', {'1x'}, ... 'Padding', zeros(1,4)); obj.Panel = uipanel(g, ... 'Title', 'Fit-Modes', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'FontName', s.Text.FontNamePanel, ... 'BorderType', 'none'); obj.Grid = uigridlayout(obj.Panel, ... 'RowHeight', repmat({'fit'}, 1, 3), ... diff --git a/src/+ui/TyreFitterPanel.m b/src/+ui/TyreFitterPanel.m index c548dec..6df9055 100644 --- a/src/+ui/TyreFitterPanel.m +++ b/src/+ui/TyreFitterPanel.m @@ -19,6 +19,10 @@ SolverSettingsPanel ui.TyreFitterSolverSettingsPanel RunStateButton matlab.ui.control.Button end + + properties (Access = private) + Settings settings.AppSettings + end methods (Access = private) function onFittingModesChanged(obj, ~, event) @@ -42,37 +46,35 @@ function onTyreFitterModesChanged(obj, ~, event) methods (Access = protected) function setup(obj) - % Position only used for standalone-testing. + obj.Settings = settings.AppSettings(); + s = obj.Settings; obj.Position = [0 0 400 400]; - obj.MainGrid = uigridlayout(obj, ... 'RowHeight', {'1x'}, ... 'ColumnWidth', {'1x'}, ... 'Padding', zeros(1,4)); - obj.Panel = uipanel(obj.MainGrid); - obj.Grid = uigridlayout(obj.Panel, ... 'RowHeight', {'fit', 'fit', 'fit'}, ... 'ColumnWidth', {'1x'}, ... 'Padding', zeros(1,4), ... - 'ColumnSpacing', 10); - + 'ColumnSpacing', s.Layout.DefaultColumnSpacing); obj.FittingModesPanel = ui.TyreFitterFittingModesPanel(obj.Grid, ... 'SelectionChangedFcn', @obj.onFittingModesChanged); - obj.SolverSettingsPanel = ui.TyreFitterSolverSettingsPanel(obj.Grid, ... 'SettingsChangedFcn', @obj.onSolverSettingsChanged); - - g = uigridlayout(obj.Grid, ... - 'RowHeight', {20}, ... + p = uipanel(obj.Grid, ... + 'Title', ' ', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'BorderType', 'none'); + g = uigridlayout(p, ... + 'RowHeight', {s.Layout.DefaultButtonHeight}, ... 'ColumnWidth', {'1x'}, ... - 'Padding', 5*ones(1,4)); + 'Padding', s.Layout.DefaultPadding); obj.RunStateButton = uibutton(g, ... - 'Text', 'Start Fitter', ... + 'Text', 'Run Fitter', ... 'Icon', 'play-solid.svg', ... 'ButtonPushedFcn', @obj.onRunStateButtonValueChanged); - addlistener(obj, 'TyreFitterModesChanged', ... @obj.onTyreFitterModesChanged); end diff --git a/src/+ui/TyreFitterSolverSettingsPanel.m b/src/+ui/TyreFitterSolverSettingsPanel.m index f51da23..d6f2819 100644 --- a/src/+ui/TyreFitterSolverSettingsPanel.m +++ b/src/+ui/TyreFitterSolverSettingsPanel.m @@ -69,10 +69,9 @@ function onSettingsChanged(obj, ~, ~) methods (Access = protected) function setup(obj) - obj.Position = [0 0 400 400]; - obj.Settings = settings.AppSettings(); - + s = obj.Settings; + obj.Position = [0 0 400 400]; if isempty(obj.OptimizerOptions) opts = magicformula.v61.Fitter.initOptimizerOptions(); obj.OptimizerOptions = opts; @@ -84,12 +83,14 @@ function setup(obj) 'Padding', zeros(1,4)); obj.Panel = uipanel(g, ... 'Title', 'Optimization Settings', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'FontName', s.Text.FontNamePanel, ... 'BorderType', 'none'); obj.Grid = uigridlayout(obj.Panel, ... - 'RowHeight', repmat({20}, 1, 5), ... - 'ColumnWidth', repmat({'fit'}, 1, 2), ... - 'Padding', 5*ones(1,4), ... - 'ColumnSpacing', 10); + 'RowHeight', repmat({s.Layout.DefaultButtonHeight}, 1, 5), ... + 'ColumnWidth', {'1x','fit'}, ... + 'Padding', s.Layout.DefaultPadding, ... + 'ColumnSpacing', s.Layout.DefaultColumnSpacing); algorithms = { 'interior-point' diff --git a/src/+ui/TyreModelPanel.m b/src/+ui/TyreModelPanel.m index cda2453..ef0cd38 100644 --- a/src/+ui/TyreModelPanel.m +++ b/src/+ui/TyreModelPanel.m @@ -149,10 +149,11 @@ function onSearchTextChanged(obj, ~, event) end methods (Access = protected) function setupButtons(obj) + s = settings.LayoutSettings(); obj.ButtonsGrid = uigridlayout(obj.MainGrid, ... - 'RowHeight', {22}, ... + 'RowHeight', {s.DefaultButtonHeight}, ... 'ColumnWidth', obj.ButtonsGridColumnWidthWithText, ... - 'ColumnSpacing', 10, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... 'Padding', zeros(1,4)); obj.ButtonsGrid.Layout.Column = [1 2]; @@ -217,17 +218,18 @@ function setupTyreParametersTable(obj) obj.TyreParametersTable.Layout.Column = [1 2]; end function setupMainGrid(obj) + s = settings.LayoutSettings(); obj.MainGrid = uigridlayout(obj, ... - 'RowHeight', {22,'1x'}, ... - 'ColumnWidth', {'1x', 'fit'}, ... - 'ColumnSpacing', 10, ... + 'RowHeight', {s.DefaultButtonHeight,'1x'}, ... + 'ColumnWidth', {'1x', s.DefaultSidebarWidth}, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... 'Padding', zeros(1,4), ... 'Scrollable', false); end function setupSidePanel(obj) obj.SidePanelGrid = uigridlayout(obj.MainGrid, ... 'RowHeight', {'fit', 'fit'}, ... - 'ColumnWidth', {'fit'}, ... + 'ColumnWidth', {'1x'}, ... 'ColumnSpacing', 0, ... 'Padding', zeros(1,4), ... 'Scrollable', true, ... diff --git a/src/+ui/TyrePlotCurvesPanel.m b/src/+ui/TyrePlotCurvesPanel.m index 65f1c80..4c3e2ce 100644 --- a/src/+ui/TyrePlotCurvesPanel.m +++ b/src/+ui/TyrePlotCurvesPanel.m @@ -6,6 +6,7 @@ Measurements tydex.Measurement = tydex.Measurement.empty end properties (Access = private) + MeasurementsPlottable tydex.Measurement = tydex.Measurement.empty SteadyStateNamesAll = { 'LONGSLIP' 'SLIPANGL' @@ -33,19 +34,12 @@ SteadyStateSettingsPanel matlab.ui.container.Panel PlotSettingsPanelGrid matlab.ui.container.GridLayout - AutoRefreshStateButtonLabel matlab.ui.control.Label AutoRefreshStateButton matlab.ui.control.StateButton - HoldOnSettingLabel matlab.ui.control.Label HoldOnSettingStateButton matlab.ui.control.StateButton - DataShowSettingLabel matlab.ui.control.Label DataShowSettingStateButton matlab.ui.control.StateButton - ModelShowSettingLabel matlab.ui.control.Label ModelShowSettingStateButton matlab.ui.control.StateButton - ShowLegendStateButtonLabel matlab.ui.control.Label ShowLegendStateButton matlab.ui.control.StateButton - XAxisSettingLabel matlab.ui.control.Label XAxisSettingDropDown matlab.ui.control.DropDown - YAxisSettingLabel matlab.ui.control.Label YAxisSettingDropDown matlab.ui.control.DropDown XAxisRangeSelector ui.NumericRangeSelector @@ -100,10 +94,10 @@ function onSettingsChanged(obj, source, event) end return end + updateDropDowns(obj) [names, values] = getSteadyStateUserSelection(obj); s.SteadyStateNamesSelected = names; s.SteadyStateValuesSelected = values; - updateDropDowns(obj) updatePlot(obj) end end @@ -176,6 +170,7 @@ function findSteadyStateValues(obj) % the loaded measurements. For example that in the testing data % three inclination angles were tested: 0, 2 and 4 degrees. s = obj.Settings.View.TyreAnalysisPanel.TyrePlotCurvesPanel; + measurements = obj.Measurements; if isempty(measurements) mc = ?settings.TyrePlotCurvesPanelViewSettings; @@ -183,13 +178,25 @@ function findSteadyStateValues(obj) prop = findobj(mp, 'Name', 'SteadyStateValues'); steadyStateValues = prop.DefaultValue; end + + xAxis = obj.XAxisSettingDropDown.Value; + yAxis = obj.YAxisSettingDropDown.Value; + + isPlottable = false(numel(measurements),1); + for i = 1:numel(measurements) + measurement = measurements(i); + constantNames = {measurement.Constant.Name}; + isPlottable(i) = ~any(contains(constantNames, xAxis)); + end + measurementsPlottable = measurements(isPlottable); + obj.MeasurementsPlottable = measurementsPlottable; steadyStateNamesAll = obj.SteadyStateNamesAll; steadyStateValues = cell(1, numel(steadyStateNamesAll)); steadyStateValues(:) = {{}}; - for i = 1:numel(measurements) - measurement = measurements(i); + for i = 1:numel(measurementsPlottable) + measurement = measurementsPlottable(i); constantNames = {measurement.Constant.Name}; constantValues = [measurement.Constant.Value]; @@ -299,7 +306,7 @@ function updatePlot(obj) end model = obj.Model; - measurements = obj.Measurements; + measurements = obj.MeasurementsPlottable; showModel = obj.ModelShowSettingStateButton.Value; showData = obj.DataShowSettingStateButton.Value; @@ -445,12 +452,7 @@ function updatePlot(obj) return end - if isempty(yVal) - fig = ancestor(obj, 'figure'); - message = sprintf('No data for channel ''%s''!', yVar); - title = 'Tyre Analysis'; - uialert(fig, message, title) - else + if ~isempty(yVal) hData = plot(ax, xVal, yVal, ... 'Marker', '.', ... 'LineStyle', 'none'); @@ -609,91 +611,94 @@ function updatePlotSettings(obj) end methods(Access = protected) function setupMainGrid(obj) + s = obj.Settings.Layout; + w = s.DefaultSidebarWidth; obj.MainGrid = uigridlayout(obj, ... 'RowHeight', {'1x'}, ... - 'ColumnWidth', {'1x', 'fit'}, ... - 'ColumnSpacing', 10, ... + 'ColumnWidth', {'1x', w}, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... 'Padding', 0*ones(1,4), ... 'Scrollable', false); end function setupPlotSettingsPanel(obj) - s = obj.Settings.View.TyreAnalysisPanel.TyrePlotCurvesPanel; - + s = obj.Settings.Layout; + w = s.DefaultButtonWidthTextIcon; grid = uigridlayout(obj.PlotSettingsPanel, ... 'RowHeight', repmat({'fit'}, 9, 1), ... - 'ColumnWidth', {'fit', 'fit'}, ... - 'ColumnSpacing', 10, ... - 'Padding', 10*ones(1,4), ... + 'ColumnWidth', {w, 'fit'}, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... + 'Padding', s.DefaultPadding, ... 'Scrollable', false); - obj.PlotSettingsPanelGrid = grid; - obj.AutoRefreshStateButtonLabel = uilabel(grid, ... - 'Text', 'Refresh'); + s = obj.Settings.View.TyreAnalysisPanel.TyrePlotCurvesPanel; + obj.PlotSettingsPanelGrid = grid; + obj.AutoRefreshStateButton = uibutton(grid, 'state', ... 'Text', 'Auto', ... 'Value', s.AutoRefresh, ... 'Tag', 'AutoRefresh', ... 'Tooltip', 'Plot will update on new model changes.', ... 'ValueChangedFcn', @obj.onSettingsChanged); - - obj.ShowLegendStateButtonLabel = uilabel(grid, 'Text', 'Legend'); + uilabel(grid, 'Text', 'Auto-Refresh'); + obj.ShowLegendStateButton = uibutton(grid, 'state', ... 'Text', 'On', ... 'Value', s.LegendOn, ... 'Tag', 'LegendOn', ... 'ValueChangedFcn', @obj.onSettingsChanged); + uilabel(grid, 'Text', 'Show Legend'); - obj.HoldOnSettingLabel = uilabel(grid, 'Text', 'Hold'); obj.HoldOnSettingStateButton = uibutton(grid, 'state', ... 'Value', s.HoldOn, ... 'Text', 'On', ... 'ValueChangedFcn', @obj.onSettingsChanged, ... 'Tag', 'HoldOn'); + uilabel(grid, 'Text', 'Hold'); - obj.DataShowSettingLabel = uilabel(grid, 'Text', 'Data'); obj.DataShowSettingStateButton = uibutton(grid, 'state', ... 'Text', 'Show', ... 'Value', s.DataShow, ... 'ValueChangedFcn', @obj.onSettingsChanged, ... 'Tag', 'DataShow'); - - obj.ModelShowSettingLabel = uilabel(grid, 'Text', 'Model'); + uilabel(grid, 'Text', 'Measurement Data'); + obj.ModelShowSettingStateButton = uibutton(grid, 'state', ... 'Value', s.ModelShow, ... 'Text', 'Show', ... 'ValueChangedFcn', @obj.onSettingsChanged, ... 'Tag', 'ModelShow'); + uilabel(grid, 'Text', 'Tire Model'); - obj.XAxisSettingLabel = uilabel(grid, ... - 'Text', 'X-Axis', ... - 'Tag', 'XAxis'); obj.XAxisSettingDropDown = uidropdown(grid, ... 'Items', obj.SteadyStateNamesAll, ... 'Value', s.XAxis, ... 'Tag', 'XAxis', ... 'ValueChangedFcn', @obj.onSettingsChanged); + uilabel(grid, ... + 'Text', 'X-Axis', ... + 'Tag', 'XAxis'); - obj.YAxisSettingLabel = uilabel(grid, ... - 'Text', 'Y-Axis'); obj.YAxisSettingDropDown = uidropdown(grid, ... 'Items', {'FX','FYW','MXW','MYW','MZW'}, ... 'Tag', 'YAxis', ... 'ValueChangedFcn', @obj.onSettingsChanged); - - uilabel(grid, 'Text', 'X-Range'); + uilabel(grid, ... + 'Text', 'Y-Axis'); + selector = ui.NumericRangeSelector(grid, ... 'RangeChangedFcn', @obj.onXAxisRangeChanged); - uilabel(grid, 'Text', char.empty); % to keep grid flow + uilabel(grid, 'Text', 'X-Range'); selector.Layout.Row = [0 1] + selector.Layout.Row; obj.XAxisRangeSelector = selector; end function setupSteadyStateSettingsPanel(obj) - s = obj.Settings.View.TyreAnalysisPanel.TyrePlotCurvesPanel; + s = obj.Settings.Layout; + w = s.DefaultButtonWidthTextIcon; grid = uigridlayout(obj.SteadyStateSettingsPanel, ... 'RowHeight', repmat({'fit'}, 1, 6), ... - 'ColumnWidth', {'fit', 'fit', 'fit'}, ... - 'ColumnSpacing', 10, ... - 'Padding', 10*ones(1,4), ... + 'ColumnWidth', {'fit', w, 'fit'}, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... + 'Padding', s.DefaultPadding, ... 'Scrollable', false); names = obj.SteadyStateNamesAll; @@ -715,8 +720,8 @@ function setupSteadyStateSettingsPanel(obj) obj.SteadyStateSettingsPanelGrid = grid; end function setupSidePanel(obj) + s = obj.Settings; obj.SidePanel = uipanel(obj.MainGrid); - obj.SidePanel.Layout.Column = 2; obj.SidePanelGrid = uigridlayout(obj.SidePanel, ... 'RowHeight', repmat({'fit'}, 1, 2), ... @@ -727,10 +732,14 @@ function setupSidePanel(obj) obj.PlotSettingsPanel = uipanel(obj.SidePanelGrid, ... 'Title', 'Plot Settings', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'FontName', s.Text.FontNamePanel, ... 'BorderType', 'none'); obj.SteadyStateSettingsPanel = uipanel(obj.SidePanelGrid, ... 'Title', 'Steady-State Settings', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'FontName', s.Text.FontNamePanel, ... 'BorderType', 'none'); end function setupAxes(obj) diff --git a/src/+ui/TyrePlotFrictionEllipsePanel.m b/src/+ui/TyrePlotFrictionEllipsePanel.m index 3057072..32b9b10 100644 --- a/src/+ui/TyrePlotFrictionEllipsePanel.m +++ b/src/+ui/TyrePlotFrictionEllipsePanel.m @@ -108,16 +108,17 @@ function updateSidebarState(obj) end methods(Access = protected) function setupMainGrid(obj) + s = obj.Settings.Layout; + w = s.DefaultSidebarWidth; obj.MainGrid = uigridlayout(obj, ... 'RowHeight', {'1x'}, ... - 'ColumnWidth', {'1x', 'fit'}, ... - 'ColumnSpacing', 10, ... + 'ColumnWidth', {'1x', w}, ... + 'ColumnSpacing', s.DefaultColumnSpacing, ... 'Padding', 0*ones(1,4), ... 'Scrollable', false); end function setupSidePanel(obj) obj.SidePanel = uipanel(obj.MainGrid); - obj.SidePanel.Layout.Column = 2; obj.SidePanelGrid = uigridlayout(obj.SidePanel, ... 'RowHeight', repmat({'fit'}, 1, 2), ... @@ -128,113 +129,118 @@ function setupSidePanel(obj) end function setupPlotSettings(obj) s = obj.Settings; - s = s.View.TyreAnalysisPanel.TyrePlotFrictionEllipsePanel; p = uipanel(obj.SidePanelGrid, ... 'Title', 'Plot Settings', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'FontName', s.Text.FontNamePanel, ... 'BorderType', 'none'); + w = s.Layout.DefaultButtonWidthTextIcon; g = uigridlayout(p, ... 'RowHeight', repmat({'fit'}, 1, 9), ... - 'ColumnWidth', {'fit', 'fit'}, ... - 'ColumnSpacing', 10, ... - 'Padding', 10*ones(1,4), ... + 'ColumnWidth', {w, 'fit'}, ... + 'ColumnSpacing', s.Layout.DefaultColumnSpacing, ... + 'Padding', s.Layout.DefaultPadding, ... 'Scrollable', false); - uilabel(g, 'Text', 'Axis'); + s = s.View.TyreAnalysisPanel.TyrePlotFrictionEllipsePanel; uibutton(g, 'state', 'Text', 'Equal', 'Value', true, ... 'Tag', 'AxisEqual', ... 'Value', s.AxisEqual, ... 'ValueChangedFcn', @obj.onPlotSettingsChanged); - - uilabel(g, 'Text', 'Limits'); + uilabel(g, 'Text', 'Axis'); + uibutton(g, 'state', 'Text', 'Manual', 'Value', true, ... 'Tag', 'AxisManualLimits', ... 'Value', s.AxisManualLimits, ... - 'ValueChangedFcn', @obj.onPlotSettingsChanged); + 'ValueChangedFcn', @obj.onPlotSettingsChanged); + uilabel(g, 'Text', 'Limits'); - uilabel(g, 'Text', 'X-Limit'); uieditfield(g, 'numeric', 'Value', 4000, ... 'Tag', 'XLimit', ... 'ValueDisplayFormat', '%.0f [N]', ... 'Value', s.Limits(1), ... 'Limits', [0 100E3], 'LowerLimitInclusive', false, ... 'ValueChangedFcn', @obj.onPlotSettingsChanged); - - uilabel(g, 'Text', 'Y-Limit'); + uilabel(g, 'Text', 'X-Limit'); + uieditfield(g, 'numeric', 'Value', 4000, ... 'Tag', 'YLimit', ... 'ValueDisplayFormat', '%.0f [N]', ... 'Value', s.Limits(2), ... 'Limits', [0 100E3], 'LowerLimitInclusive', false, ... - 'ValueChangedFcn', @obj.onPlotSettingsChanged); - - uilabel(g, 'Text', 'SLIPANGL (Max)'); + 'ValueChangedFcn', @obj.onPlotSettingsChanged); + uilabel(g, 'Text', 'Y-Limit'); + uieditfield(g, 'numeric', 'Value', 15, ... 'Tag', 'MaxSLIPANGL', ... 'Limits', [-90 90], ... 'Value', s.SLIPANGL_Max, ... 'ValueDisplayFormat', '%.1f [deg]', ... 'ValueChangedFcn', @obj.onPlotSettingsChanged); - - uilabel(g, 'Text', 'SLIPANGL (Step)'); + uilabel(g, 'Text', 'SLIPANGL (Max)'); + uieditfield(g, 'numeric', 'Value', 1, ... 'Tag', 'StepSLIPANGL', ... 'Limits', [-90 90], ... 'Value', s.SLIPANGL_Step, ... 'ValueDisplayFormat', '%.1f [deg]', ... 'ValueChangedFcn', @obj.onPlotSettingsChanged); - - uilabel(g, 'Text', 'LONGSLIP (Max)'); + uilabel(g, 'Text', 'SLIPANGL (Step)'); + uieditfield(g, 'numeric', 'Value', 15, ... 'Tag', 'MaxLONGSLIP', ... 'Limits', [-1 1]*100, ... 'Value', s.LONGSLIP_Max, ... 'ValueDisplayFormat', '%.1f [%%]', ... 'ValueChangedFcn', @obj.onPlotSettingsChanged); + uilabel(g, 'Text', 'LONGSLIP (Max)'); - - uilabel(g, 'Text', 'LONGSLIP (Step)'); uieditfield(g, 'numeric', 'Value', 1, ... 'Tag', 'StepLONGSLIP', ... 'Limits', [-1 1]*100, ... 'Value', s.LONGSLIP_Step, ... 'ValueDisplayFormat', '%.1f [%%]', ... 'ValueChangedFcn', @obj.onPlotSettingsChanged); - - uilabel(g, 'Text', 'Marker'); + uilabel(g, 'Text', 'LONGSLIP (Step)'); + markers = {'none', '.', '+', 'o', '*', 'x', 'square', ... 'diamond', '^', 'v', '>', '<', 'pentagram', 'hexagram'}; uidropdown(g, 'Items', markers, ... 'Value', s.Marker, ... 'ValueChangedFcn', @obj.onPlotSettingsChanged, ... 'Tag', 'Marker'); - - uilabel(g, 'Text', 'Marker Size'); + uilabel(g, 'Text', 'Marker'); + uispinner(g, 'Value', 15, ... 'Limits', [0 100], 'LowerLimitInclusive', 'off', ... 'Value', s.MarkerSize, ... 'ValueChangedFcn', @obj.onPlotSettingsChanged, ... - 'Tag', 'MarkerSize'); - - uilabel(g, 'Text', 'Line Width'); + 'Tag', 'MarkerSize'); + uilabel(g, 'Text', 'Marker Size'); + uispinner(g, 'Value', 1, ... 'Limits', [0 10], 'LowerLimitInclusive', 'off', ... 'Value', s.LineWidth, ... 'ValueChangedFcn', @obj.onPlotSettingsChanged, ... - 'Tag', 'LineWidth'); + 'Tag', 'LineWidth'); + uilabel(g, 'Text', 'Line Width'); end function setupSteadyStateSettings(obj) s = obj.Settings; - s = s.View.TyreAnalysisPanel.TyrePlotFrictionEllipsePanel; p = uipanel(obj.SidePanelGrid, ... 'Title', 'Steady-State Settings', ... + 'FontWeight', s.Text.FontWeightPanel, ... + 'FontName', s.Text.FontNamePanel, ... 'BorderType', 'none'); + w = s.Layout.DefaultButtonWidthTextIcon; g = uigridlayout(p, ... 'RowHeight', repmat({'fit'}, 1, 6), ... - 'ColumnWidth', {'fit', 'fit', 'fit'}, ... - 'ColumnSpacing', 10, ... - 'Padding', 10*ones(1,4), ... + 'ColumnWidth', {'fit', w, 'fit'}, ... + 'ColumnSpacing', s.Layout.DefaultColumnSpacing, ... + 'Padding', s.Layout.DefaultPadding, ... 'Scrollable', false); + s = s.View.TyreAnalysisPanel.TyrePlotFrictionEllipsePanel; uilabel(g, 'Text', 'INCLANGL'); uispinner(g, 'Value', 0, 'Step', 1, 'Limits', [-90 90], ... 'Value', rad2deg(s.INCLANGL), ... diff --git a/src/MagicFormulaTyreTool.m b/src/MagicFormulaTyreTool.m index 1b58550..c5a8e1e 100644 --- a/src/MagicFormulaTyreTool.m +++ b/src/MagicFormulaTyreTool.m @@ -41,9 +41,9 @@ TyreModelPanel ui.TyreModelPanel TyreModelGrid matlab.ui.container.GridLayout - TyreMeasurementsTab matlab.ui.container.Tab - TyreMeasurementsTabGrid matlab.ui.container.GridLayout - TyreMeasurementsPanel ui.TyreMeasurementsPanel + TyreDataTab matlab.ui.container.Tab + TyreDataTabGrid matlab.ui.container.GridLayout + TyreDataPanel ui.TyreDataPanel TyreModelAnalysisTab matlab.ui.container.Tab TyreModelAnalysisGrid matlab.ui.container.GridLayout @@ -250,31 +250,45 @@ function onClearModelRequested(app, ~, ~) modelEmpty = MagicFormulaTyre.empty; app.setTyreModel(modelEmpty) end - function onImportMeasurementsRequested(app, ~, ~) + function onImportMeasurementsRequested(app, ~, event) fig = app.UIFigure; - - importer = ui.MeasurementImporter(fig); + title = 'Measurement Import'; + msg = 'Importing measurements...'; %TODO: not indeterminate + dlg = uiprogressdlg(fig, ... + 'Title', title,... + 'Message', msg, ... + 'Indeterminate','on', ... + 'Cancelable', 'off'); try - uiwait(fig); - measurements = importer.MeasurementImported; - delete(importer) - if isempty(measurements) - uiresume(fig) + measurements = app.TyreMeasurements; + files = event.Files; + if isempty(files) return end - measurements = [app.TyreMeasurements measurements]; + parser = event.Parser(); + for i = 1:numel(files) + file = files{i}; + measurement = parser.run(file); + measurements = [measurements measurement]; + end app.setTyreMeasurementData(measurements) - model = app.TyreModel; if ~isempty(model) nominalParams = app.extractNominalParametersFromMeasurements(); app.dialogApplyParamsFromMeasurements(nominalParams) end - catch ME - delete(importer) - uiresume(fig) - rethrow(ME) + catch cause + close(dlg) + msg = 'Import failed. See console/logfile for details'; + uialert(fig, msg, title, 'Icon', 'error') + + exception = exceptions.CouldNotExportTYDEX(); + exception = exception.addCause(cause); + throw(exception) end + notify(app.TyreDataPanel, 'MeasurementDataImportFinished') + msg = 'Import successful.'; + uialert(fig, msg, title, 'Icon', 'success') end function onExportMeasurementsRequested(app, ~, ~) measurements = app.TyreMeasurements; @@ -398,7 +412,7 @@ function onFitterMeasurementsLoaded(app, ~, event) event events.FitterMeasurementsLoadedEventData end flagsMap = event.FitModeFlags; - app.TyreMeasurementsPanel.addMeasurementFitModes(flagsMap); + app.TyreDataPanel.addMeasurementFitModes(flagsMap); end function onStartFittingRequested(app, ~, ~) import magicformula.FitMode @@ -441,6 +455,7 @@ function onStartFittingRequested(app, ~, ~) 'Title', title, ... 'Message', msg, ... 'Indeterminate','on', ... + 'CancelText', 'Stop', ... 'Cancelable', 'on'); outputFcn = @(x,optimValues,state) ... @@ -727,7 +742,7 @@ function createComponents(app) createGrid(app) createTabGroups(app) createTyreModelTab(app) - createTyreMeasurementsTab(app) + createTyreDataTab(app) createTyreAnalysisTab(app) createMenus(app) set(app.UIFigure, 'Visible', 'on'); @@ -786,17 +801,17 @@ function createTyreModelTab(app) 'FitterStartRequestedFcn', @app.onStartFittingRequested, ... 'TyreModelEditedFcn', @app.onTyreModelEdited); end - function createTyreMeasurementsTab(app) - app.TyreMeasurementsTab = uitab(... + function createTyreDataTab(app) + app.TyreDataTab = uitab(... app.TabGroupPrimary, ... 'Title', 'Tyre Data'); - app.TyreMeasurementsTabGrid = uigridlayout(... - app.TyreMeasurementsTab, ... + app.TyreDataTabGrid = uigridlayout(... + app.TyreDataTab, ... 'Padding', 10*ones(1,4), ... 'RowHeight', {'1x'}, ... 'ColumnWidth', {'1x'}); - app.TyreMeasurementsPanel = ui.TyreMeasurementsPanel(... - app.TyreMeasurementsTabGrid, ... + app.TyreDataPanel = ui.TyreDataPanel(... + app.TyreDataTabGrid, ... 'MeasurementDataImportRequestedFcn', ... @app.onImportMeasurementsRequested, ... 'MeasurementDataClearRequestedFcn', ... @@ -949,8 +964,8 @@ function addlisteners(app) @app.onFitterSettingsChanged); end function startupFcn(app) - appSettings = app.Settings; - lastTyreFile = appSettings.LastSession.TyreModelFile; + s = app.Settings; + lastTyreFile = s.LastSession.TyreModelFile; if ~isempty(lastTyreFile) fig = app.UIFigure; title = 'Load Last Tyre Model'; @@ -980,6 +995,7 @@ function startupFcn(app) app.setTyreModel(model) fitter = magicformula.v61.Fitter(); + fitter.Options = s.Fitter.OptimizerSettings; app.TyreModelFitter = fitter; app.TyreModelPanel.Fitter = fitter; @@ -1022,7 +1038,7 @@ function setTyreMeasurementData(app, measurements) app.TyreMeasurementsSelectionIndices = logical.empty; e = events.TyreMeasurementsChanged(measurements, flags); - notify(app.TyreMeasurementsPanel, 'MeasurementDataChanged', e); + notify(app.TyreDataPanel, 'MeasurementDataChanged', e); notify(app.TyreAnalysisPanel, 'TyreDataChanged', e); end end diff --git a/src/about.json b/src/about.json index 676931e..0b4969c 100644 --- a/src/about.json +++ b/src/about.json @@ -1,11 +1,10 @@ { "Name": "MagicFormulaTyreTool", - "Version": "1.4.0", + "Version": "1.5.0", "Authors": [ "Tom Teasdale" ], - "Source": "https://github.com/teasit/magic-formula-tyre-tool", - "FileExchange": "https://mathworks.com/matlabcentral/fileexchange/111375", - "Contact": "teasdale@lightsaber.red", + "Source": "github.com/teasit/magic-formula-tyre-tool", + "FileExchange": "mathworks.com/matlabcentral/fileexchange/111375", "CreatedWithRelease": "R2022b" } \ No newline at end of file