visualdynamics 0.1.0a40__tar.gz → 0.1.0a42__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {visualdynamics-0.1.0a40/src/visualdynamics.egg-info → visualdynamics-0.1.0a42}/PKG-INFO +7 -6
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/README.md +6 -5
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/__init__.py +1 -1
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/channel_table.py +1 -1
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/data.py +85 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/filters.py +19 -2
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/geometry.py +43 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/shapes.py +130 -0
- visualdynamics-0.1.0a42/src/visualdynamics/core/tables.py +117 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/wavelet.py +41 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/__init__.py +9 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/disclaimer.py +4 -6
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/icons.py +44 -1
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/main_window.py +232 -234
- visualdynamics-0.1.0a42/src/visualdynamics/gui/movie.py +360 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/object_tables.py +6 -41
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/project_tree.py +3 -2
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/__init__.py +92 -34
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/project.py +242 -5
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/report/__init__.py +17 -59
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/theme.py +11 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/animate.py +330 -40
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/marks.py +112 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/paired.py +8 -4
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/scalogram.py +39 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/sinespec.py +34 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/waterfall.py +49 -10
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42/src/visualdynamics.egg-info}/PKG-INFO +7 -6
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/SOURCES.txt +1 -0
- visualdynamics-0.1.0a40/src/visualdynamics/core/tables.py +0 -49
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/LICENSE +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/pyproject.toml +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/setup.cfg +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/__main__.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/compatibility.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/__init__.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/author.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/averaging.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/compliance.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/correlate.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/detect.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/entities.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/fem.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/kurtosis.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/matches.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/merge.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/mesh.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/modal_fit.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/octave.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/photos.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/progress.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/replication.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/report.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/rigid.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/shocks.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/sine.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/snr.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/srs.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/transform.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/truncate.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/unit_choices.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/validate.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/decimate.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/deform.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/__init__.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/barc.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/drone.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/frame.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/plate.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/app_icon.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/ask.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/author_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/averaging_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/console.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/drawer_tab.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/editors.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/filter_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/glyphs.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/mesh_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/octave_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/panes.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/preferences.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/record_grid.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/report_editor.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/rigid_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/run_reports.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/settings_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/shock_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/sine_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/stage_drag.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/stream_window.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/tables.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/toolbars.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/truncate_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/updater.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/wavelet_panel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/windows.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/__init__.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/adf.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_objects.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/excel.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/exodus.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/exporters.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/femap.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/matlab.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/nastran.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/native.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/notes.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/photo_files.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/punch.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/rattlesnake.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/report_template.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sdynpy_data.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sdynpy_npz.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sep005.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sniffing.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/step.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/stl.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/threemf.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/unv.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/names.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/averaging.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/bands.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/bars.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/filtering.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/scalogram.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/shocks.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/tracking_filter.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/truncation.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/py.typed +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/report/markdown.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/report/page.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/rotate.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/units.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/update.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/__init__.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/_quiet.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/banded.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/geometry.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/mac_bars.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/pick.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/rigid.py +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/entry_points.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/requires.txt +0 -0
- {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: visualdynamics
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a42
|
|
4
4
|
Summary: Structural Dynamics Visualization: a units-aware structural dynamics analysis toolset
|
|
5
5
|
Author-email: Brandon Zwink <bzwink@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,11 +73,12 @@ file.
|
|
|
73
73
|
|
|
74
74
|
**This is an alpha.** Every launch opens with a disclaimer to
|
|
75
75
|
acknowledge: the software is offered as is, and its numbers are the
|
|
76
|
-
user's to check against a tool they already trust.
|
|
77
|
-
`.vdyn
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
user's to check against a tool they already trust. Projects are
|
|
77
|
+
saved as `.vdyn` files, and Engineering Sciences Common Data Format
|
|
78
|
+
(`.h5`) files are imported and exported for exchanging data with
|
|
79
|
+
other tools. While Visual Dynamics is an alpha the `.vdyn` format may
|
|
80
|
+
still change; the promise that a later version opens every earlier
|
|
81
|
+
file starts with the first release that is not an alpha.
|
|
81
82
|
|
|
82
83
|
**[Core principles](PRINCIPLES.md)** — thirteen principles
|
|
83
84
|
that shape every part of it: data you can see and handle, a workflow
|
|
@@ -16,11 +16,12 @@ file.
|
|
|
16
16
|
|
|
17
17
|
**This is an alpha.** Every launch opens with a disclaimer to
|
|
18
18
|
acknowledge: the software is offered as is, and its numbers are the
|
|
19
|
-
user's to check against a tool they already trust.
|
|
20
|
-
`.vdyn
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
user's to check against a tool they already trust. Projects are
|
|
20
|
+
saved as `.vdyn` files, and Engineering Sciences Common Data Format
|
|
21
|
+
(`.h5`) files are imported and exported for exchanging data with
|
|
22
|
+
other tools. While Visual Dynamics is an alpha the `.vdyn` format may
|
|
23
|
+
still change; the promise that a later version opens every earlier
|
|
24
|
+
file starts with the first release that is not an alpha.
|
|
24
25
|
|
|
25
26
|
**[Core principles](PRINCIPLES.md)** — thirteen principles
|
|
26
27
|
that shape every part of it: data you can see and handle, a workflow
|
|
@@ -90,7 +90,7 @@ from .units import (
|
|
|
90
90
|
#: alpha, said in the version itself so the dialog, the About text, the
|
|
91
91
|
#: installers and latest.json all say the same thing, and the first
|
|
92
92
|
#: non-alpha release is offered as an upgrade (Brandon, 2026-09-11)
|
|
93
|
-
__version__ = '0.1.
|
|
93
|
+
__version__ = '0.1.0a42'
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
|
{visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/channel_table.py
RENAMED
|
@@ -161,7 +161,7 @@ def _parse_date(text: str) -> str:
|
|
|
161
161
|
value = value.split(' ')[0] # a spreadsheet's midnight timestamp
|
|
162
162
|
for form in _DATE_FORMATS:
|
|
163
163
|
try:
|
|
164
|
-
return _datetime.datetime.strptime(
|
|
164
|
+
return _datetime.datetime.strptime(
|
|
165
165
|
value, form).date().isoformat()
|
|
166
166
|
except ValueError:
|
|
167
167
|
continue
|
|
@@ -229,6 +229,18 @@ class _LogAbscissa:
|
|
|
229
229
|
return cls._log_abscissa_default
|
|
230
230
|
|
|
231
231
|
|
|
232
|
+
def _recast(obj: Any, cls: type) -> Any:
|
|
233
|
+
"""`obj` as an instance of `cls`, every field copied — what reading
|
|
234
|
+
a PSD as a specification, or back, changes is the kind and nothing
|
|
235
|
+
else, so nothing is rebuilt through a constructor that might not
|
|
236
|
+
know a field (a declared-later unit, a dimension hint, a band)."""
|
|
237
|
+
import copy
|
|
238
|
+
|
|
239
|
+
out = cls.__new__(cls)
|
|
240
|
+
out.__dict__.update(copy.deepcopy(obj.__dict__))
|
|
241
|
+
return out
|
|
242
|
+
|
|
243
|
+
|
|
232
244
|
class DataArray:
|
|
233
245
|
"""Base class; use a concrete subclass (TimeHistory, Spectrum, Frf, Psd).
|
|
234
246
|
|
|
@@ -1037,6 +1049,30 @@ class TimeHistory(DataArray):
|
|
|
1037
1049
|
abscissa_dim = 'time'
|
|
1038
1050
|
complex_ordinate = False
|
|
1039
1051
|
|
|
1052
|
+
def animate(self, geometry: Any, records: Any = None,
|
|
1053
|
+
**kwargs: Any) -> Any:
|
|
1054
|
+
"""The records moving on a geometry, sample by sample, as the
|
|
1055
|
+
GUI plays a time history selected with one.
|
|
1056
|
+
|
|
1057
|
+
Parameters
|
|
1058
|
+
----------
|
|
1059
|
+
geometry : Geometry
|
|
1060
|
+
The geometry to move.
|
|
1061
|
+
records : sequence of int, optional
|
|
1062
|
+
Which records. One per DOF either way (`animation_records`).
|
|
1063
|
+
**kwargs
|
|
1064
|
+
Passed through to the scene: `screenshot=` for the still at
|
|
1065
|
+
the furthest the model gets, `movie=` for the record once
|
|
1066
|
+
through as an MP4, `seconds=` for how long that takes.
|
|
1067
|
+
|
|
1068
|
+
Returns
|
|
1069
|
+
-------
|
|
1070
|
+
object
|
|
1071
|
+
The plotter, or the file written.
|
|
1072
|
+
"""
|
|
1073
|
+
from ..viz.animate import animate_time
|
|
1074
|
+
return animate_time(geometry, self, records, **kwargs)
|
|
1075
|
+
|
|
1040
1076
|
def compute_spectra(self) -> Spectrum:
|
|
1041
1077
|
"""The averaged spectrum of every channel — sdynpy's convention.
|
|
1042
1078
|
|
|
@@ -2976,6 +3012,30 @@ class Psd(_Bands, DataArray):
|
|
|
2976
3012
|
self.ordinate: np.ndarray = np.ascontiguousarray(self.ordinate.real)
|
|
2977
3013
|
self._take_bandwidth(bandwidth)
|
|
2978
3014
|
|
|
3015
|
+
def as_specification(self) -> Specification:
|
|
3016
|
+
"""This PSD read as a specification: the same numbers, records,
|
|
3017
|
+
units and bands, held as the requirement a measurement is judged
|
|
3018
|
+
against, with no limits.
|
|
3019
|
+
|
|
3020
|
+
Nothing in a file says a PSD is a requirement when the format
|
|
3021
|
+
has no word for one — ESCDF's data types do not, and a target
|
|
3022
|
+
written by another program arrives as a PSD with only its name
|
|
3023
|
+
to tell — so the person who knows says so (Brandon,
|
|
3024
|
+
2026-10-09). A banded PSD steps, as a banded specification does;
|
|
3025
|
+
otherwise its frequencies decide whether it reads as a
|
|
3026
|
+
controller's lines or as breakpoints (`Specification.reading_of`).
|
|
3027
|
+
|
|
3028
|
+
Returns
|
|
3029
|
+
-------
|
|
3030
|
+
Specification
|
|
3031
|
+
A copy; this PSD is unchanged.
|
|
3032
|
+
"""
|
|
3033
|
+
spec = _recast(self, Specification)
|
|
3034
|
+
spec.limits = {}
|
|
3035
|
+
spec.interpolation = ('bin' if self.bandwidth is not None
|
|
3036
|
+
else Specification.reading_of(self.abscissa))
|
|
3037
|
+
return spec
|
|
3038
|
+
|
|
2979
3039
|
def to_octave(self, per_octave: int | None = None,
|
|
2980
3040
|
low: float | None = None,
|
|
2981
3041
|
high: float | None = None) -> Psd:
|
|
@@ -3409,6 +3469,31 @@ class Specification(Bounded, Psd):
|
|
|
3409
3469
|
with np.errstate(invalid='ignore'):
|
|
3410
3470
|
return (np.isfinite(magnitude) & (magnitude > 0.0)).any(axis=0)
|
|
3411
3471
|
|
|
3472
|
+
def as_psd(self) -> Psd:
|
|
3473
|
+
"""This specification read as a plain PSD — `Psd.as_specification`
|
|
3474
|
+
undone, for a PSD read as a requirement by mistake. Its reading
|
|
3475
|
+
of its own points is kept.
|
|
3476
|
+
|
|
3477
|
+
Returns
|
|
3478
|
+
-------
|
|
3479
|
+
Psd
|
|
3480
|
+
A copy; this specification is unchanged.
|
|
3481
|
+
|
|
3482
|
+
Raises
|
|
3483
|
+
------
|
|
3484
|
+
ValueError
|
|
3485
|
+
When it has limits: a PSD has nowhere to keep them, and
|
|
3486
|
+
dropping them unasked would lose what the file said.
|
|
3487
|
+
`limit(name)` hands one out as a PSD of its own.
|
|
3488
|
+
"""
|
|
3489
|
+
if self.limits:
|
|
3490
|
+
raise ValueError(
|
|
3491
|
+
'it carries warning or abort limits, which a PSD cannot '
|
|
3492
|
+
'hold; take one out with limit(name) instead')
|
|
3493
|
+
plain = _recast(self, Psd)
|
|
3494
|
+
plain.__dict__.pop('limits', None)
|
|
3495
|
+
return plain
|
|
3496
|
+
|
|
3412
3497
|
def to_octave(self, per_octave: int | None = None,
|
|
3413
3498
|
low: float | None = None,
|
|
3414
3499
|
high: float | None = None) -> Specification:
|
|
@@ -54,6 +54,7 @@ double integration invents are set out.
|
|
|
54
54
|
from __future__ import annotations
|
|
55
55
|
|
|
56
56
|
from dataclasses import dataclass, replace
|
|
57
|
+
from functools import cache
|
|
57
58
|
from typing import TYPE_CHECKING, Any
|
|
58
59
|
|
|
59
60
|
import numpy as np
|
|
@@ -94,11 +95,27 @@ def threaded_sosfiltfilt(sos: Any, rows: Any, axis: int = -1) -> np.ndarray:
|
|
|
94
95
|
if rows.ndim < 2 or axis not in (-1, rows.ndim - 1) or rows.shape[0] < 2 \
|
|
95
96
|
or rows.size < 1_000_000:
|
|
96
97
|
return sosfiltfilt(sos, rows, axis=axis)
|
|
98
|
+
return np.stack(list(_filter_pool().map(
|
|
99
|
+
lambda row: sosfiltfilt(sos, row), rows)))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@cache
|
|
103
|
+
def _filter_pool() -> Any:
|
|
104
|
+
"""The threads `threaded_sosfiltfilt` shares, made once and kept.
|
|
105
|
+
|
|
106
|
+
A pool per call started up to eight threads every time the filter
|
|
107
|
+
preview redrew. One gate run stalled for an hour inside exactly
|
|
108
|
+
that: the main thread in `Thread.start`, waiting on a new worker
|
|
109
|
+
that never said it had begun, from a render of the filter view
|
|
110
|
+
(2026-10-09; CPython 3.13.2, not reproduced in 1 200 calls). A kept
|
|
111
|
+
pool starts its threads once a session, so no redraw waits on a
|
|
112
|
+
thread being born — and skips the start-up besides.
|
|
113
|
+
"""
|
|
97
114
|
import os
|
|
98
115
|
from concurrent.futures import ThreadPoolExecutor
|
|
99
116
|
|
|
100
|
-
|
|
101
|
-
|
|
117
|
+
return ThreadPoolExecutor(min(8, os.cpu_count() or 1),
|
|
118
|
+
thread_name_prefix='visualdynamics-filter')
|
|
102
119
|
|
|
103
120
|
|
|
104
121
|
@dataclass(frozen=True, kw_only=True)
|
|
@@ -950,6 +950,49 @@ class Geometry:
|
|
|
950
950
|
self.cs_matrix = np.concatenate([self.cs_matrix, matrix[np.newaxis]])
|
|
951
951
|
return cs_id
|
|
952
952
|
|
|
953
|
+
def place_coordinate_system(self, cs_id: int, *,
|
|
954
|
+
origin: ArrayLike | None = None,
|
|
955
|
+
angles: ArrayLike | None = None,
|
|
956
|
+
rotation: ArrayLike | None = None) -> None:
|
|
957
|
+
"""Turn a coordinate system, move it, or both — what the 3-D
|
|
958
|
+
view's rings and arrows do to one, as a call.
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
cs_id : int
|
|
963
|
+
Which coordinate system.
|
|
964
|
+
origin : array_like, optional
|
|
965
|
+
Where it sits, in the geometry's stored length unit (meters
|
|
966
|
+
once its units are declared). Kept when omitted.
|
|
967
|
+
angles : array_like, optional
|
|
968
|
+
Its turn as degrees about the geometry's fixed X, then Y,
|
|
969
|
+
then Z axes (`rotate.frame_from_angles`) — the angles the
|
|
970
|
+
view's angle box and the box panel speak. (0, 0, 0) undoes
|
|
971
|
+
any turn. Kept when omitted.
|
|
972
|
+
rotation : array_like, optional
|
|
973
|
+
Its turn as a 3x3 matrix whose rows are its axes, instead
|
|
974
|
+
of `angles`.
|
|
975
|
+
|
|
976
|
+
Returns
|
|
977
|
+
-------
|
|
978
|
+
None
|
|
979
|
+
"""
|
|
980
|
+
from ..rotate import frame_from_angles
|
|
981
|
+
|
|
982
|
+
rows = np.flatnonzero(self.cs_id == int(cs_id))
|
|
983
|
+
if not len(rows):
|
|
984
|
+
raise KeyError(f'no coordinate system {int(cs_id)}')
|
|
985
|
+
if angles is not None and rotation is not None:
|
|
986
|
+
raise ValueError('give the turn as angles or as a rotation, '
|
|
987
|
+
'not both')
|
|
988
|
+
row = int(rows[0])
|
|
989
|
+
if angles is not None:
|
|
990
|
+
self.cs_matrix[row, :3] = frame_from_angles(angles)[:3]
|
|
991
|
+
elif rotation is not None:
|
|
992
|
+
self.cs_matrix[row, :3] = np.asarray(rotation, dtype=np.float64)
|
|
993
|
+
if origin is not None:
|
|
994
|
+
self.cs_matrix[row, 3] = np.asarray(origin, dtype=np.float64)
|
|
995
|
+
|
|
953
996
|
def add_beams(self, node_ids: Ids, group: int | None = None,
|
|
954
997
|
color: int = 1, elem_type: int = 21) -> int:
|
|
955
998
|
"""A chain of two-node line elements through the given nodes, in
|
|
@@ -110,6 +110,76 @@ def aligned_mode(a: ShapeSet, i: int, b: ShapeSet, j: int) -> np.ndarray:
|
|
|
110
110
|
return apply_alignment(b.shape_matrix[j], alignment_factor(a, i, b, j))
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
def paired_mode(a: ShapeSet, i: int, b: ShapeSet, j: int,
|
|
114
|
+
projected: ShapeSet | None = None) -> np.ndarray:
|
|
115
|
+
"""b's mode j, phase-aligned to swing with a's mode i when the two
|
|
116
|
+
are overlaid — over b's own coordinates.
|
|
117
|
+
|
|
118
|
+
Sets on one geometry share DOFs and align directly. Sets on two
|
|
119
|
+
geometries share none, so the alignment is measured against
|
|
120
|
+
`projected` — b carried onto a's DOFs (`correlate.project_shapes`)
|
|
121
|
+
— and applied to b's dense original. The one rule the window's
|
|
122
|
+
overlay, the report's and `ShapeSet.animate_pair` all draw by.
|
|
123
|
+
"""
|
|
124
|
+
if projected is None:
|
|
125
|
+
return aligned_mode(a, i, b, j)
|
|
126
|
+
return apply_alignment(b.shape_matrix[j],
|
|
127
|
+
alignment_factor(a, i, projected, j))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def pair_scale_note(a_name: str, a: ShapeSet, b_name: str, b: ShapeSet,
|
|
131
|
+
i: int, j: int) -> str | None:
|
|
132
|
+
"""One line: how big this pair's second shape is against its first.
|
|
133
|
+
|
|
134
|
+
Overlaying scales each shape to its own peak, which is what makes a
|
|
135
|
+
sparse test set and a dense model comparable — and also what hides
|
|
136
|
+
one set being thirty times the other. So the number the overlay
|
|
137
|
+
cannot show is written above it.
|
|
138
|
+
|
|
139
|
+
**For the pair on screen, not for the sets.** A constant factor
|
|
140
|
+
across every mode is a unit or a normalization convention; a factor
|
|
141
|
+
that is 1.00 everywhere and 3.4 on one mode is that mode fitted
|
|
142
|
+
badly, and a figure averaged over the set hides exactly that.
|
|
143
|
+
`ScaleComparison.message` is the other reading — over a whole set,
|
|
144
|
+
at length — and the report still uses it.
|
|
145
|
+
|
|
146
|
+
Named by the objects rather than by their roles: a reader looking
|
|
147
|
+
at two animations wants to know which of the two things in front
|
|
148
|
+
of them is the bigger.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
ratios = compare_scaling(a, b, [(i, j)]).ratios
|
|
152
|
+
except (ValueError, IndexError):
|
|
153
|
+
return None
|
|
154
|
+
if not ratios.size or not np.isfinite(ratios[0]):
|
|
155
|
+
return None
|
|
156
|
+
# an unscaled fit has no drive point pinning its size, so the ratio
|
|
157
|
+
# is arithmetic rather than physics — said in a word, because the
|
|
158
|
+
# number is about to be read as if it meant one
|
|
159
|
+
unscaled = [name for name, shapes in ((a_name, a), (b_name, b))
|
|
160
|
+
if getattr(shapes, 'unscaled', False)]
|
|
161
|
+
note = f'{b_name}/{a_name} = {float(ratios[0]):.2f}'
|
|
162
|
+
return note + (f' ({", ".join(unscaled)} unscaled)'
|
|
163
|
+
if unscaled else '')
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def pair_caption(a_name: str, a: ShapeSet, b_name: str, b: ShapeSet,
|
|
167
|
+
i: int, j: int) -> str:
|
|
168
|
+
"""What the corner of an overlay says: each mode on a line of its
|
|
169
|
+
own — set, number, description, frequency, damping — and the scale
|
|
170
|
+
note under them (`pair_scale_note`)."""
|
|
171
|
+
def described(name: str, shape_set: ShapeSet, mode: int) -> str:
|
|
172
|
+
description = str(shape_set.description[mode]).strip()
|
|
173
|
+
return (f'{name} mode {mode + 1}'
|
|
174
|
+
+ (f' {description}' if description else '')
|
|
175
|
+
+ f' {float(shape_set.frequency[mode]):.4f} Hz'
|
|
176
|
+
f' {float(shape_set.damping[mode]) * 100:.3f} %')
|
|
177
|
+
|
|
178
|
+
caption = described(a_name, a, i) + '\n' + described(b_name, b, j)
|
|
179
|
+
scaling = pair_scale_note(a_name, a, b_name, b, i, j)
|
|
180
|
+
return caption + ('\n' + scaling if scaling else '')
|
|
181
|
+
|
|
182
|
+
|
|
113
183
|
#: how far the two sets' levels may differ before it is worth saying so,
|
|
114
184
|
#: as a fraction. Two properly mass-normalized sets of one structure in
|
|
115
185
|
#: one unit system land far closer than this; a tenth is loose enough
|
|
@@ -819,6 +889,66 @@ class ShapeSet:
|
|
|
819
889
|
from ..viz.animate import animate_shape
|
|
820
890
|
return animate_shape(geometry, self, mode, **kwargs)
|
|
821
891
|
|
|
892
|
+
def animate_pair(self, geometry: Geometry, mode: int,
|
|
893
|
+
other: ShapeSet, other_mode: int,
|
|
894
|
+
other_geometry: Geometry | None = None, *,
|
|
895
|
+
on_basis: bool = False,
|
|
896
|
+
names: tuple[str, str] = ('first', 'second'),
|
|
897
|
+
**kwargs: Any) -> Any:
|
|
898
|
+
"""This mode and another set's overlaid, swinging together, as
|
|
899
|
+
the window animates a picked cell of the two sets' cross-MAC.
|
|
900
|
+
|
|
901
|
+
The other mode is phase-aligned to this one (`paired_mode`) and
|
|
902
|
+
each is scaled to its own peak, so the picture compares shape;
|
|
903
|
+
the caption names both modes and says how their sizes compare
|
|
904
|
+
(`pair_caption`). On two geometries the other set is projected
|
|
905
|
+
onto this one's DOFs to measure the alignment
|
|
906
|
+
(`correlate.project_shapes`), and drawn on its own geometry —
|
|
907
|
+
or, `on_basis`, the projection on this one's.
|
|
908
|
+
|
|
909
|
+
Parameters
|
|
910
|
+
----------
|
|
911
|
+
geometry : Geometry
|
|
912
|
+
This set's geometry.
|
|
913
|
+
mode : int
|
|
914
|
+
This set's mode, by index.
|
|
915
|
+
other : ShapeSet
|
|
916
|
+
The set compared against.
|
|
917
|
+
other_mode : int
|
|
918
|
+
Its mode, by index.
|
|
919
|
+
other_geometry : Geometry, optional
|
|
920
|
+
The other set's geometry; this one's when omitted.
|
|
921
|
+
on_basis : bool, default False
|
|
922
|
+
Draw the other set's projection on this geometry instead.
|
|
923
|
+
names : tuple of str
|
|
924
|
+
What the caption calls the two sets.
|
|
925
|
+
**kwargs
|
|
926
|
+
Passed through to `viz.animate.animate_pair`: `colors`,
|
|
927
|
+
`alphas`, `scale`, `screenshot=`, `movie=`.
|
|
928
|
+
|
|
929
|
+
Returns
|
|
930
|
+
-------
|
|
931
|
+
object
|
|
932
|
+
The plotter, the still, or the movie's path.
|
|
933
|
+
"""
|
|
934
|
+
from ..viz.animate import animate_pair, pair_deflections
|
|
935
|
+
|
|
936
|
+
other_geometry = other_geometry or geometry
|
|
937
|
+
projected = None
|
|
938
|
+
if other_geometry is not geometry:
|
|
939
|
+
from .correlate import project_shapes
|
|
940
|
+
projected, _report = project_shapes(other, other_geometry,
|
|
941
|
+
self, geometry)
|
|
942
|
+
first, second = pair_deflections(self, mode, other, other_mode,
|
|
943
|
+
geometry, other_geometry,
|
|
944
|
+
projected, on_basis)
|
|
945
|
+
if not len(first[1].rows) or not len(second[1].rows):
|
|
946
|
+
raise ValueError('a set of the pair has no DOFs on its '
|
|
947
|
+
'geometry')
|
|
948
|
+
kwargs.setdefault('caption', pair_caption(
|
|
949
|
+
names[0], self, names[1], other, mode, other_mode))
|
|
950
|
+
return animate_pair(first, second, **kwargs)
|
|
951
|
+
|
|
822
952
|
def plot(self, geometry: Geometry | None = None, mode: int = 0,
|
|
823
953
|
**kwargs: Any) -> Any:
|
|
824
954
|
"""The set's own reading: its auto-MAC, or one mode animated
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""What an object reads as, as a table: headers and rows of text.
|
|
2
|
+
|
|
3
|
+
The app has table *models* — editable, colored, unit-aware, and Qt to
|
|
4
|
+
the core. This is the other kind: the read-only rendering that goes in a
|
|
5
|
+
report, and that a script wants when it asks what is in a channel table
|
|
6
|
+
without opening a window.
|
|
7
|
+
|
|
8
|
+
One implementation, because the report and a script asking the same
|
|
9
|
+
question must not answer it differently.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
#: decimals each matched-modes column is read to, None for as-is
|
|
18
|
+
MATCHED_DECIMALS = (None, 4, 3, None, 4, 3, None, 3, 2)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def matched_rows(matched: Any, objects: Mapping[str, Any]
|
|
22
|
+
) -> tuple[list[str], list[tuple[Any, ...]]]:
|
|
23
|
+
"""A MatchedModes object's table as values: headers, and a row per
|
|
24
|
+
pair — each set's mode number, frequency [Hz] and damping [%], the
|
|
25
|
+
frequency error against the first set (text, signed), the pair's
|
|
26
|
+
MAC, and the second set's size against the first's.
|
|
27
|
+
|
|
28
|
+
The sets are read live by name, so their parameters restate
|
|
29
|
+
themselves as the sets change, while the MAC is the object's own
|
|
30
|
+
stored value (it came from the comparison as displayed, possibly
|
|
31
|
+
projected). A set gone from `objects`, or a mode past its end,
|
|
32
|
+
reads None rather than a guess. The last column is the scale each
|
|
33
|
+
pair was normalized by, which an overlay — both shapes drawn to
|
|
34
|
+
their own peak — cannot show: 1.00 down it is the evidence that a
|
|
35
|
+
comparison is of shape alone.
|
|
36
|
+
|
|
37
|
+
The window's table and the report's draw these rows; `table_of`
|
|
38
|
+
is them as text.
|
|
39
|
+
"""
|
|
40
|
+
from .shapes import scale_ratios
|
|
41
|
+
|
|
42
|
+
a_name, b_name = matched.first, matched.second
|
|
43
|
+
a, b = objects.get(a_name), objects.get(b_name)
|
|
44
|
+
|
|
45
|
+
def parameters(shape_set: Any,
|
|
46
|
+
mode: int) -> tuple[float | None, float | None]:
|
|
47
|
+
if shape_set is None or not 0 <= mode < shape_set.num_shapes:
|
|
48
|
+
return None, None
|
|
49
|
+
return (float(shape_set.frequency[mode]),
|
|
50
|
+
float(shape_set.damping[mode]) * 100.0)
|
|
51
|
+
|
|
52
|
+
ratios = (scale_ratios(a, b, matched.pairs)
|
|
53
|
+
if a is not None and b is not None
|
|
54
|
+
else [None] * len(matched.pairs))
|
|
55
|
+
rows = []
|
|
56
|
+
for index, ((row, column), mac) in enumerate(
|
|
57
|
+
zip(matched.pairs, matched.macs)):
|
|
58
|
+
fa, da = parameters(a, row)
|
|
59
|
+
fb, db = parameters(b, column)
|
|
60
|
+
delta = (f'{(fb - fa) / fa * 100.0:+.2f}'
|
|
61
|
+
if fa and fb is not None else '—')
|
|
62
|
+
rows.append((row + 1, fa, da, column + 1, fb, db, delta,
|
|
63
|
+
float(mac), ratios[index]))
|
|
64
|
+
headers = [f'{a_name} Mode', 'Frequency [Hz]', 'Damping [%]',
|
|
65
|
+
f'{b_name} Mode', 'Frequency [Hz]', 'Damping [%]',
|
|
66
|
+
'Δf [%]', 'MAC', f'{b_name}/{a_name}']
|
|
67
|
+
return headers, rows
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cell_text(value: Any, decimals: int | None) -> str:
|
|
71
|
+
"""One matched-modes value as it is read: a dash for nothing."""
|
|
72
|
+
if value is None:
|
|
73
|
+
return '—'
|
|
74
|
+
return str(value) if decimals is None else f'{value:.{decimals}f}'
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def table_of(obj: Any, geometry: Any = None,
|
|
78
|
+
objects: Mapping[str, Any] | None = None
|
|
79
|
+
) -> tuple[list[str], list[list[str]]] | None:
|
|
80
|
+
"""(headers, rows) for an object that reads as a table, else None.
|
|
81
|
+
|
|
82
|
+
A shape set is its identified modal parameters; a channel table is
|
|
83
|
+
its instrumentation — with the direction columns a geometry
|
|
84
|
+
derives beside it, when one is given; matched modes are the pairs
|
|
85
|
+
beside both sets' parameters (`matched_rows`), read from the sets
|
|
86
|
+
in `objects`. Everything is text, formatted the way it is read
|
|
87
|
+
rather than the way it is stored — a damping of 0.0213 is '2.130'
|
|
88
|
+
percent, because that is the number an engineer quotes.
|
|
89
|
+
"""
|
|
90
|
+
from .channel_table import ChannelTable
|
|
91
|
+
from .matches import MatchedModes
|
|
92
|
+
from .shapes import ShapeSet
|
|
93
|
+
|
|
94
|
+
if isinstance(obj, MatchedModes):
|
|
95
|
+
headers, rows = matched_rows(obj, objects or {})
|
|
96
|
+
return headers, [[cell_text(value, decimals) for value, decimals
|
|
97
|
+
in zip(row, MATCHED_DECIMALS)] for row in rows]
|
|
98
|
+
|
|
99
|
+
if isinstance(obj, ShapeSet):
|
|
100
|
+
return (['Mode', 'Frequency [Hz]', 'Damping [%]', 'Description'],
|
|
101
|
+
[[str(m + 1), f'{float(obj.frequency[m]):.4f}',
|
|
102
|
+
f'{float(obj.damping[m]) * 100:.3f}', obj.description[m]]
|
|
103
|
+
for m in range(obj.num_shapes)])
|
|
104
|
+
if isinstance(obj, ChannelTable):
|
|
105
|
+
# every column, because the schema is already the curated set:
|
|
106
|
+
# a Rattlesnake save's coupling and feedback wiring stop at the
|
|
107
|
+
# importer now, so there is no acquisition plumbing left here to
|
|
108
|
+
# cut and no second list to keep in step with the first
|
|
109
|
+
from .channel_table import DERIVED_COLUMNS, title_of
|
|
110
|
+
|
|
111
|
+
names = obj.column_names
|
|
112
|
+
derived = list(DERIVED_COLUMNS) if geometry is not None else []
|
|
113
|
+
return ([title_of(name) for name in names + derived],
|
|
114
|
+
[[str(obj[name][r]) for name in names]
|
|
115
|
+
+ (obj.derived_cells(r, geometry) if derived else [])
|
|
116
|
+
for r in range(obj.num_channels)])
|
|
117
|
+
return None
|
|
@@ -113,6 +113,8 @@ influence are Torrence and Compo's.
|
|
|
113
113
|
|
|
114
114
|
from __future__ import annotations
|
|
115
115
|
|
|
116
|
+
from typing import Any
|
|
117
|
+
|
|
116
118
|
import numpy as np
|
|
117
119
|
from numpy.typing import ArrayLike
|
|
118
120
|
|
|
@@ -504,3 +506,42 @@ def default_range(sample_rate: float, duration: float) -> tuple[float, float]:
|
|
|
504
506
|
low = (8.0 / float(duration)) if duration else high / 100.0
|
|
505
507
|
low = min(max(low, nyquist / 1000.0), high / 2.0)
|
|
506
508
|
return low, high
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def reading(history: Any, channel: int = 0, *, low: float | None = None,
|
|
512
|
+
high: float | None = None, per_octave: int | None = None,
|
|
513
|
+
omega0: float | None = None
|
|
514
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, float]:
|
|
515
|
+
"""One channel's scalogram as the app draws it, flat or in 3-D:
|
|
516
|
+
(clock, magnitude, frequencies, omega0).
|
|
517
|
+
|
|
518
|
+
Settings left out take the defaults the panel opens on
|
|
519
|
+
(`default_range`, `PER_OCTAVE`, `OMEGA0`); lines at or over Nyquist
|
|
520
|
+
are dropped, since the record cannot carry them. Time is held to
|
|
521
|
+
`COLUMNS` columns by peak-hold (`scalogram_peaks`), so a long
|
|
522
|
+
record reads in bounded memory and a transient's ridge is not
|
|
523
|
+
strided past. The clock is the record's own.
|
|
524
|
+
|
|
525
|
+
One implementation for the window, `plot.plot_scalogram` and
|
|
526
|
+
`viz.scalogram.plot_scalogram_stage`.
|
|
527
|
+
|
|
528
|
+
Raises
|
|
529
|
+
------
|
|
530
|
+
ValueError
|
|
531
|
+
When fewer than two lines of the range lie under Nyquist.
|
|
532
|
+
"""
|
|
533
|
+
rate = float(history.sample_rate)
|
|
534
|
+
values = np.real(np.asarray(history.ordinate)[channel])
|
|
535
|
+
duration = len(history.abscissa) / rate
|
|
536
|
+
default_low, default_high = default_range(rate, duration)
|
|
537
|
+
frequencies = log_frequencies(
|
|
538
|
+
default_low if low is None else low,
|
|
539
|
+
default_high if high is None else high,
|
|
540
|
+
PER_OCTAVE if per_octave is None else per_octave)
|
|
541
|
+
frequencies = frequencies[frequencies < rate / 2.0]
|
|
542
|
+
if frequencies.size < 2:
|
|
543
|
+
raise ValueError('no frequencies this record can carry in that '
|
|
544
|
+
f'range; it reaches {rate / 2.0:g} Hz')
|
|
545
|
+
width = OMEGA0 if omega0 is None else float(omega0)
|
|
546
|
+
clock, magnitude = scalogram_peaks(values, rate, frequencies, width)
|
|
547
|
+
return clock + float(history.abscissa[0]), magnitude, frequencies, width
|
|
@@ -161,6 +161,15 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
161
161
|
QApplication.setApplicationName('Visual Dynamics')
|
|
162
162
|
QApplication.setApplicationDisplayName('Visual Dynamics')
|
|
163
163
|
app = QApplication.instance() or QApplication(sys.argv[:1])
|
|
164
|
+
if '--check-movie' in argv:
|
|
165
|
+
# the packaged build's self-check, no window: can this install
|
|
166
|
+
# save an animation? (gui.movie.check — 0 yes, 2 no H.264 here)
|
|
167
|
+
from .movie import check
|
|
168
|
+
|
|
169
|
+
where = argv[argv.index('--check-movie') + 1:][:1] or ['check.mp4']
|
|
170
|
+
code, message = check(where[0])
|
|
171
|
+
print(message, flush=True)
|
|
172
|
+
return code
|
|
164
173
|
from .icons import app_icon
|
|
165
174
|
|
|
166
175
|
app.setWindowIcon(app_icon())
|
|
@@ -35,12 +35,10 @@ TEXT = (
|
|
|
35
35
|
'set of tests. Numbers it produces — modal parameters, spectra, '
|
|
36
36
|
'levels, comparisons against a specification — are to be verified by '
|
|
37
37
|
'independent means before anything depends on them.</p>'
|
|
38
|
-
'<p>
|
|
39
|
-
'alpha
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'alpha will not. Open each <code>.vdyn</code> once in that alpha and '
|
|
43
|
-
'save it again.</p>'
|
|
38
|
+
'<p>Projects are saved as <code>.vdyn</code> files. While this is '
|
|
39
|
+
'an alpha the <code>.vdyn</code> format may still change; the '
|
|
40
|
+
'promise that a later version opens every earlier file starts with '
|
|
41
|
+
'the first release that is not an alpha.</p>'
|
|
44
42
|
'<p>Anything that looks wrong is worth reporting: '
|
|
45
43
|
'<a href="mailto:contact@visualdynamics.org">contact@visualdynamics.org</a>.</p>'
|
|
46
44
|
)
|