visualdynamics 0.1.0a42__tar.gz → 0.1.0a43__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.0a42/src/visualdynamics.egg-info → visualdynamics-0.1.0a43}/PKG-INFO +1 -1
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/__init__.py +1 -1
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/data.py +3 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/shapes.py +6 -2
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/main_window.py +54 -61
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/movie.py +45 -38
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/object_tables.py +5 -5
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/__init__.py +22 -37
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/project.py +23 -8
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/report/__init__.py +2 -2
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/theme.py +6 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/animate.py +164 -247
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/marks.py +54 -40
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/paired.py +2 -11
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/waterfall.py +1 -3
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43/src/visualdynamics.egg-info}/PKG-INFO +1 -1
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/LICENSE +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/README.md +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/pyproject.toml +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/setup.cfg +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/__main__.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/compatibility.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/__init__.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/author.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/averaging.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/channel_table.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/compliance.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/correlate.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/detect.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/entities.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/fem.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/filters.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/geometry.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/kurtosis.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/matches.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/merge.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/mesh.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/modal_fit.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/octave.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/photos.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/progress.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/replication.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/report.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/rigid.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/shocks.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/sine.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/snr.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/srs.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/tables.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/transform.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/truncate.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/unit_choices.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/validate.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/core/wavelet.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/decimate.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/deform.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/demo/__init__.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/demo/barc.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/demo/drone.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/demo/frame.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/demo/plate.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/__init__.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/app_icon.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/ask.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/author_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/averaging_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/console.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/disclaimer.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/drawer_tab.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/editors.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/filter_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/glyphs.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/icons.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/mesh_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/octave_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/panes.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/preferences.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/project_tree.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/record_grid.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/report_editor.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/rigid_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/run_reports.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/settings_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/shock_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/sine_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/stage_drag.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/stream_window.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/tables.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/toolbars.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/truncate_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/updater.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/wavelet_panel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/gui/windows.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/__init__.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/adf.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_objects.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/excel.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/exodus.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/exporters.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/femap.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/matlab.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/nastran.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/native.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/notes.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/photo_files.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/punch.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/rattlesnake.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/report_template.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/sdynpy_data.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/sdynpy_npz.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/sep005.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/sniffing.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/step.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/stl.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/threemf.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/io/unv.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/names.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/averaging.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/bands.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/bars.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/filtering.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/scalogram.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/shocks.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/tracking_filter.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/plot/truncation.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/py.typed +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/report/markdown.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/report/page.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/rotate.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/units.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/update.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/__init__.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/_quiet.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/banded.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/geometry.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/mac_bars.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/pick.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/rigid.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/scalogram.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics/viz/sinespec.py +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics.egg-info/SOURCES.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics.egg-info/entry_points.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics.egg-info/requires.txt +0 -0
- {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a43}/src/visualdynamics.egg-info/top_level.txt +0 -0
|
@@ -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.0a43'
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
|
|
@@ -3032,6 +3032,9 @@ class Psd(_Bands, DataArray):
|
|
|
3032
3032
|
"""
|
|
3033
3033
|
spec = _recast(self, Specification)
|
|
3034
3034
|
spec.limits = {}
|
|
3035
|
+
# a requirement is not scaled to be compared: the test level
|
|
3036
|
+
# belongs to the measurement
|
|
3037
|
+
spec.__dict__.pop('scale_db', None)
|
|
3035
3038
|
spec.interpolation = ('bin' if self.bandwidth is not None
|
|
3036
3039
|
else Specification.reading_of(self.abscissa))
|
|
3037
3040
|
return spec
|
|
@@ -893,6 +893,7 @@ class ShapeSet:
|
|
|
893
893
|
other: ShapeSet, other_mode: int,
|
|
894
894
|
other_geometry: Geometry | None = None, *,
|
|
895
895
|
on_basis: bool = False,
|
|
896
|
+
projected: ShapeSet | None = None,
|
|
896
897
|
names: tuple[str, str] = ('first', 'second'),
|
|
897
898
|
**kwargs: Any) -> Any:
|
|
898
899
|
"""This mode and another set's overlaid, swinging together, as
|
|
@@ -920,6 +921,10 @@ class ShapeSet:
|
|
|
920
921
|
The other set's geometry; this one's when omitted.
|
|
921
922
|
on_basis : bool, default False
|
|
922
923
|
Draw the other set's projection on this geometry instead.
|
|
924
|
+
projected : ShapeSet, optional
|
|
925
|
+
The other set already carried onto this one's DOFs
|
|
926
|
+
(`correlate.project_shapes`), for a script animating many
|
|
927
|
+
pairs of the same two sets: made here when omitted.
|
|
923
928
|
names : tuple of str
|
|
924
929
|
What the caption calls the two sets.
|
|
925
930
|
**kwargs
|
|
@@ -934,8 +939,7 @@ class ShapeSet:
|
|
|
934
939
|
from ..viz.animate import animate_pair, pair_deflections
|
|
935
940
|
|
|
936
941
|
other_geometry = other_geometry or geometry
|
|
937
|
-
projected
|
|
938
|
-
if other_geometry is not geometry:
|
|
942
|
+
if other_geometry is not geometry and projected is None:
|
|
939
943
|
from .correlate import project_shapes
|
|
940
944
|
projected, _report = project_shapes(other, other_geometry,
|
|
941
945
|
self, geometry)
|
|
@@ -151,9 +151,14 @@ from ..rotate import (
|
|
|
151
151
|
snapped,
|
|
152
152
|
wrapped,
|
|
153
153
|
)
|
|
154
|
-
from ..theme import
|
|
154
|
+
from ..theme import PAIR_COLORS
|
|
155
155
|
from ..theme import theme as resolve_theme
|
|
156
156
|
from ..units import DEFAULT_SYSTEM, SYSTEMS, UnitSystem
|
|
157
|
+
|
|
158
|
+
# the playback clock is the viz layer's, so a saved movie and a script's
|
|
159
|
+
# `movie=` play at the window's own rate by construction
|
|
160
|
+
from ..viz.animate import MOVIE_FPS as FRAMES_PER_SECOND
|
|
161
|
+
from ..viz.animate import SECONDS_PER_CYCLE, SECONDS_PER_RECORD
|
|
157
162
|
from ..viz.geometry import (
|
|
158
163
|
AXIS_COLORS,
|
|
159
164
|
ENTITY_LABEL_LIMIT,
|
|
@@ -222,10 +227,7 @@ COMBO_CELL_PADDING = 44
|
|
|
222
227
|
#: a dragged turn snaps to whole degrees of this; a typed angle does not
|
|
223
228
|
ANGLE_STEP = 1.0
|
|
224
229
|
|
|
225
|
-
FRAMES_PER_SECOND = 30
|
|
226
230
|
PHASE_STEPS = 120 # frames in one mode-shape cycle
|
|
227
|
-
SECONDS_PER_CYCLE = 2.0 # every mode animates at this rate, whatever its Hz
|
|
228
|
-
SECONDS_PER_RECORD = 10.0 # a time record plays in about this long
|
|
229
231
|
|
|
230
232
|
# What Faster and Slower step through. Doublings, because that is how a
|
|
231
233
|
# speed reads — half as fast, twice as fast — and a ladder rather than a
|
|
@@ -1138,7 +1140,6 @@ class MainWindow(QMainWindow):
|
|
|
1138
1140
|
self._fit_parabola = None
|
|
1139
1141
|
self._fit_damping_label = None
|
|
1140
1142
|
self._fit_coherence_name = None
|
|
1141
|
-
self._fit_records = None
|
|
1142
1143
|
#: what the plot's top and bottom edges mean as damping, kept
|
|
1143
1144
|
#: across fits — an article's plausible range rarely changes
|
|
1144
1145
|
#: between two of its own FRF sets
|
|
@@ -3182,8 +3183,12 @@ class MainWindow(QMainWindow):
|
|
|
3182
3183
|
rewrites per line is in every frame; then playback is put back
|
|
3183
3184
|
where it was. `path` skips the dialog (scripts, tests).
|
|
3184
3185
|
"""
|
|
3185
|
-
from ..viz.animate import
|
|
3186
|
-
|
|
3186
|
+
from ..viz.animate import (
|
|
3187
|
+
cycle_parameters,
|
|
3188
|
+
movie_frames,
|
|
3189
|
+
sweep_parameters,
|
|
3190
|
+
)
|
|
3191
|
+
from .movie import write_movie
|
|
3187
3192
|
|
|
3188
3193
|
if self.animator is None or self.scene.plotter is None:
|
|
3189
3194
|
return None
|
|
@@ -3201,28 +3206,24 @@ class MainWindow(QMainWindow):
|
|
|
3201
3206
|
phase = self.phase_slider.value()
|
|
3202
3207
|
held = self._cursor.value() if self._cursor is not None else None
|
|
3203
3208
|
if self._shape_mode:
|
|
3204
|
-
parameters = cycle_parameters(SECONDS_PER_CYCLE / self.speed
|
|
3205
|
-
|
|
3209
|
+
parameters = cycle_parameters(SECONDS_PER_CYCLE / self.speed)
|
|
3210
|
+
step = self.animator.set_parameter
|
|
3206
3211
|
else:
|
|
3207
3212
|
parameters = sweep_parameters(len(self._cursor_abscissa),
|
|
3208
|
-
SECONDS_PER_RECORD / self.speed
|
|
3209
|
-
MOVIE_FPS)
|
|
3213
|
+
SECONDS_PER_RECORD / self.speed)
|
|
3210
3214
|
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
else:
|
|
3216
|
-
self._cursor.setValue(
|
|
3217
|
-
float(self._cursor_abscissa[parameter]))
|
|
3218
|
-
yield self.scene.plotter.screenshot(return_img=True)
|
|
3215
|
+
def step(index):
|
|
3216
|
+
# the cursor, not the animator: it is where an envelope
|
|
3217
|
+
# rewrites its caption, so every frame carries its line
|
|
3218
|
+
self._cursor.setValue(float(self._cursor_abscissa[index]))
|
|
3219
3219
|
|
|
3220
3220
|
# seconds on a large model or a Retina screen, with input held
|
|
3221
3221
|
# off: say so, rather than look hung
|
|
3222
3222
|
self._show_status(f'Saving {os.path.basename(path)}…')
|
|
3223
3223
|
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
|
3224
3224
|
try:
|
|
3225
|
-
count = write_movie(path,
|
|
3225
|
+
count = write_movie(path, movie_frames(self.scene.plotter, step,
|
|
3226
|
+
parameters))
|
|
3226
3227
|
except RuntimeError as error: # MovieUnavailable among them
|
|
3227
3228
|
self._show_status(str(error))
|
|
3228
3229
|
return None
|
|
@@ -3234,7 +3235,7 @@ class MainWindow(QMainWindow):
|
|
|
3234
3235
|
self._draw_frame(phase / PHASE_STEPS * 2 * np.pi)
|
|
3235
3236
|
self.set_playing(playing)
|
|
3236
3237
|
self._show_status(
|
|
3237
|
-
f'Saved {os.path.basename(path)} — {count /
|
|
3238
|
+
f'Saved {os.path.basename(path)} — {count / FRAMES_PER_SECOND:.3g} s, '
|
|
3238
3239
|
f'{os.path.getsize(path) / 1e6:.2g} MB')
|
|
3239
3240
|
return path
|
|
3240
3241
|
|
|
@@ -3774,7 +3775,7 @@ class MainWindow(QMainWindow):
|
|
|
3774
3775
|
|
|
3775
3776
|
# the Basis keeps the comparison overlay's baseline blue; other
|
|
3776
3777
|
# groups cycle the curve palette
|
|
3777
|
-
LINK_ROLE_COLORS: ClassVar[dict] = {'Basis':
|
|
3778
|
+
LINK_ROLE_COLORS: ClassVar[dict] = {'Basis': PAIR_COLORS[0]}
|
|
3778
3779
|
|
|
3779
3780
|
def _role_holder(self, role):
|
|
3780
3781
|
"""The real object group holding the type's `role`.
|
|
@@ -5375,14 +5376,8 @@ class MainWindow(QMainWindow):
|
|
|
5375
5376
|
said['origin'] = tuple(float(v) for v in matrix[3])
|
|
5376
5377
|
cs_id = int(geometry.cs_id[row])
|
|
5377
5378
|
geometry.place_coordinate_system(cs_id, **said)
|
|
5378
|
-
journal = self.project.journal
|
|
5379
|
-
before = len(journal)
|
|
5380
5379
|
self.project.record_call(geometry, 'place_coordinate_system', cs_id,
|
|
5381
|
-
**said)
|
|
5382
|
-
if len(journal) > before > 0:
|
|
5383
|
-
stem = journal[-1].split('=', 1)[0]
|
|
5384
|
-
if journal[-2].split('=', 1)[0] == stem:
|
|
5385
|
-
journal[-2:] = journal[-1:]
|
|
5380
|
+
settle=True, **said)
|
|
5386
5381
|
|
|
5387
5382
|
def _cursor_on_ring(self, matrix, axis, position):
|
|
5388
5383
|
"""Where the cursor's ray meets the plane the ring lies in."""
|
|
@@ -8336,7 +8331,7 @@ class MainWindow(QMainWindow):
|
|
|
8336
8331
|
color_a = self._link_color(a_name)
|
|
8337
8332
|
color_b = self._link_color(b_name)
|
|
8338
8333
|
if color_b is None or color_b == color_a:
|
|
8339
|
-
color_b = next(c for c in (
|
|
8334
|
+
color_b = next(c for c in (PAIR_COLORS[1], '#3fb950')
|
|
8340
8335
|
if c != color_a)
|
|
8341
8336
|
self._compare['colors'] = (color_a, color_b)
|
|
8342
8337
|
alphas = overlay_alphas(self.link_role(a_name),
|
|
@@ -8358,9 +8353,7 @@ class MainWindow(QMainWindow):
|
|
|
8358
8353
|
return None
|
|
8359
8354
|
|
|
8360
8355
|
def _build_comparison_animator(self, first_pair, second_pair,
|
|
8361
|
-
caption, showing,
|
|
8362
|
-
colors=(None, '#ff8c2b'),
|
|
8363
|
-
alphas=(1.0, OVERLAY_ALPHA)):
|
|
8356
|
+
caption, showing, colors, alphas):
|
|
8364
8357
|
"""Like _build_animator, twice: two moving copies in one
|
|
8365
8358
|
scene (`viz.animate.build_pair`), each (geometry, deflection)
|
|
8366
8359
|
pair on its own mesh, in its own color and at its own opacity."""
|
|
@@ -9809,20 +9802,21 @@ class MainWindow(QMainWindow):
|
|
|
9809
9802
|
plot another (Brandon, 2026-08-30).
|
|
9810
9803
|
"""
|
|
9811
9804
|
from ..plot import octave_preview
|
|
9805
|
+
from ..viz.marks import octave_steps
|
|
9812
9806
|
|
|
9813
9807
|
self._clear_octave()
|
|
9814
9808
|
panel = self.data_pane.octave_panel
|
|
9815
9809
|
try:
|
|
9816
|
-
|
|
9817
|
-
self.data_pane.graphics.ci.items, psd, panel.per_octave(),
|
|
9818
|
-
self.unit_system, resolve_theme(self.theme_name), records)
|
|
9819
|
-
banded = psd.to_octave(panel.per_octave())
|
|
9810
|
+
steps = octave_steps(psd, panel.per_octave(), self.unit_system)
|
|
9820
9811
|
except ValueError as refusal:
|
|
9821
9812
|
panel.show_bands(None)
|
|
9822
9813
|
panel.show()
|
|
9823
9814
|
self._show_status(f'No octave preview: {refusal}')
|
|
9824
9815
|
return
|
|
9825
|
-
|
|
9816
|
+
self.octave_previews = octave_preview(
|
|
9817
|
+
self.data_pane.graphics.ci.items, steps,
|
|
9818
|
+
resolve_theme(self.theme_name), records)
|
|
9819
|
+
panel.show_bands(len(steps[0].abscissa))
|
|
9826
9820
|
panel.show()
|
|
9827
9821
|
self._journal_octave_view(psd, panel.per_octave())
|
|
9828
9822
|
|
|
@@ -10320,7 +10314,7 @@ class MainWindow(QMainWindow):
|
|
|
10320
10314
|
return self._render_banded(
|
|
10321
10315
|
spec_name, spec, spec_records, m_data, m_name,
|
|
10322
10316
|
m_records,
|
|
10323
|
-
scale_db=self.
|
|
10317
|
+
scale_db=self.project.comparison_scale(spec, m_name))
|
|
10324
10318
|
# a specification bounds autospectra; drawn beside a full CPSD its 30
|
|
10325
10319
|
# cross terms have no limit near them and bury the six that do —
|
|
10326
10320
|
# and the window resolves each measured entry's comparison scale
|
|
@@ -11813,7 +11807,8 @@ class MainWindow(QMainWindow):
|
|
|
11813
11807
|
def read_as_specification_act(self) -> None:
|
|
11814
11808
|
"""The bar's Read as Specification: the selected PSD becomes the
|
|
11815
11809
|
requirement it is, in place (`Project.read_as_specification`)."""
|
|
11816
|
-
self._read_as(
|
|
11810
|
+
self._read_as('read_as_specification',
|
|
11811
|
+
'Select a PSD to read as a specification',
|
|
11817
11812
|
self.project.read_as_specification,
|
|
11818
11813
|
'read as a specification — compare a measurement '
|
|
11819
11814
|
'against it by selecting both')
|
|
@@ -11821,23 +11816,26 @@ class MainWindow(QMainWindow):
|
|
|
11821
11816
|
def read_as_psd_act(self) -> None:
|
|
11822
11817
|
"""The bar's Read as PSD: the selected specification is plain
|
|
11823
11818
|
data again, in place (`Project.read_as_psd`)."""
|
|
11824
|
-
self._read_as(
|
|
11819
|
+
self._read_as('read_as_psd',
|
|
11820
|
+
'Select a specification without limits to read as a PSD',
|
|
11825
11821
|
self.project.read_as_psd, 'read as a PSD')
|
|
11826
11822
|
|
|
11827
|
-
def _read_as(self,
|
|
11823
|
+
def _read_as(self, verb_name, refusal, verb, said) -> None:
|
|
11828
11824
|
"""Swap the selected object for its other reading, under the
|
|
11829
11825
|
same name: the tree row, its grid and the view follow, the way a
|
|
11830
|
-
recompute's in-place swap does.
|
|
11831
|
-
|
|
11826
|
+
recompute's in-place swap does. Whether it applies is the
|
|
11827
|
+
project's own answer (`Project.verbs`), the one the bar asked."""
|
|
11828
|
+
def applies(obj):
|
|
11829
|
+
return obj is not None and verb_name in dict(
|
|
11830
|
+
self.project.verbs(self.project.name_of(obj)))
|
|
11831
|
+
|
|
11832
|
+
acted = self._act_on(applies, refusal, verb)
|
|
11832
11833
|
if acted is None:
|
|
11833
11834
|
return
|
|
11834
11835
|
name = acted[0]
|
|
11835
|
-
obj = self.objects[name]
|
|
11836
11836
|
item = self._item_for_object(name)
|
|
11837
11837
|
if item is not None:
|
|
11838
|
-
self._refresh_item(item,
|
|
11839
|
-
item.setToolTip(0, self._object_tooltip(name, obj))
|
|
11840
|
-
self._build_children(item, obj, name)
|
|
11838
|
+
self._refresh_item(item, self.objects[name])
|
|
11841
11839
|
self._report_content_changed()
|
|
11842
11840
|
self.render_current()
|
|
11843
11841
|
self._show_status(f'{name} {said}')
|
|
@@ -12330,8 +12328,6 @@ class MainWindow(QMainWindow):
|
|
|
12330
12328
|
# every mode's residues. Chosen by the project's rule, and
|
|
12331
12329
|
# journaled with the records, so the fit_modes line replays
|
|
12332
12330
|
# this fit and not another (it did not, until 2026-10-09)
|
|
12333
|
-
self._fit_records = None if records is None else [
|
|
12334
|
-
int(i) for i in records]
|
|
12335
12331
|
coherence = self.project.fit_coherence(name, records)
|
|
12336
12332
|
self.fit = ModalFitSession(
|
|
12337
12333
|
obj, records,
|
|
@@ -13253,8 +13249,10 @@ class MainWindow(QMainWindow):
|
|
|
13253
13249
|
f'at={picks!r}'
|
|
13254
13250
|
+ (f', refine={self.fit.refined}' if self.fit.refined
|
|
13255
13251
|
else '')
|
|
13256
|
-
|
|
13257
|
-
|
|
13252
|
+
# the session's own rows: every record is the default
|
|
13253
|
+
+ (f', records={self.fit.rows!r}'
|
|
13254
|
+
if self.fit.rows != list(range(self.fit.frf.num_records))
|
|
13255
|
+
else '')
|
|
13258
13256
|
+ (f', coherence={self._fit_coherence_name!r}'
|
|
13259
13257
|
if self._fit_coherence_name else '')
|
|
13260
13258
|
+ f', name={self.fit_object_name!r})')
|
|
@@ -13291,17 +13289,12 @@ class MainWindow(QMainWindow):
|
|
|
13291
13289
|
if isinstance(data, Specification)]
|
|
13292
13290
|
if len(specs) != 1:
|
|
13293
13291
|
return None
|
|
13294
|
-
return {name: self.
|
|
13292
|
+
return {name: self.project.comparison_scale(specs[0], name)
|
|
13295
13293
|
for name, data, _r in series
|
|
13296
13294
|
if isinstance(data, Psd)
|
|
13297
13295
|
and not isinstance(data, Specification)
|
|
13298
13296
|
and name in self.objects}
|
|
13299
13297
|
|
|
13300
|
-
def _family_scale_db(self, spec, name):
|
|
13301
|
-
"""The comparison scale for `name`, by the project's rule
|
|
13302
|
-
(`Project.comparison_scale`), which the report reads too."""
|
|
13303
|
-
return self.project.comparison_scale(spec, name)
|
|
13304
|
-
|
|
13305
13298
|
def _show_comparison_scaling(self, series):
|
|
13306
13299
|
"""Put the comparison's scaling on the bar, or take it away.
|
|
13307
13300
|
|
|
@@ -13325,7 +13318,7 @@ class MainWindow(QMainWindow):
|
|
|
13325
13318
|
self.data_pane.show_scaling(None)
|
|
13326
13319
|
return
|
|
13327
13320
|
self._scaling_pair = (specs[0][0], measured[0][0])
|
|
13328
|
-
db = self.
|
|
13321
|
+
db = self.project.comparison_scale(specs[0][1], measured[0][0])
|
|
13329
13322
|
self.data_pane.show_scaling(f'{db:+d} dB' if db else '0 dB')
|
|
13330
13323
|
|
|
13331
13324
|
def _comparison_scale_edited(self, text):
|
|
@@ -13370,7 +13363,7 @@ class MainWindow(QMainWindow):
|
|
|
13370
13363
|
else f'{measured_name} and {len(family) - 1} linked '
|
|
13371
13364
|
f'PSD{"s" * (len(family) > 2)}')
|
|
13372
13365
|
if value is None:
|
|
13373
|
-
detected = self.
|
|
13366
|
+
detected = self.project.comparison_scale(spec, measured_name)
|
|
13374
13367
|
self._show_status('Scaling returned to automatic — detected '
|
|
13375
13368
|
f'{detected:+d} dB from the data')
|
|
13376
13369
|
else:
|
|
@@ -13399,7 +13392,7 @@ class MainWindow(QMainWindow):
|
|
|
13399
13392
|
return None
|
|
13400
13393
|
name, data, records = measured[0]
|
|
13401
13394
|
rows = compare_all(specs[0][0], data, specs[0][1], records,
|
|
13402
|
-
scale_db=self.
|
|
13395
|
+
scale_db=self.project.comparison_scale(specs[0][0], name))
|
|
13403
13396
|
if not rows:
|
|
13404
13397
|
return None
|
|
13405
13398
|
# a pair that is not compared says why, on the status line the
|
|
@@ -24,11 +24,12 @@ import struct
|
|
|
24
24
|
from collections.abc import Iterable
|
|
25
25
|
from functools import cache
|
|
26
26
|
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
27
28
|
|
|
28
29
|
import numpy as np
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
from ..plot import _application
|
|
32
|
+
from ..viz.animate import MOVIE_FPS
|
|
32
33
|
|
|
33
34
|
#: frames sent ahead of what the file holds. The encoder answers after
|
|
34
35
|
#: two or three (measured, VideoToolbox, Qt 6.12), so four never waits
|
|
@@ -44,19 +45,6 @@ class MovieUnavailable(RuntimeError):
|
|
|
44
45
|
"""This machine cannot encode H.264, so no movie is written."""
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
def _application():
|
|
48
|
-
"""A Qt application, made if a script has none — the recorder
|
|
49
|
-
needs one, and a headless script has no window to have made it.
|
|
50
|
-
|
|
51
|
-
A widgets application, not a QGuiApplication: Qt keeps the first
|
|
52
|
-
one made for the life of the process, and a script that later
|
|
53
|
-
opens a window would abort on its first widget.
|
|
54
|
-
"""
|
|
55
|
-
from PySide6.QtWidgets import QApplication
|
|
56
|
-
|
|
57
|
-
return QApplication.instance() or QApplication([])
|
|
58
|
-
|
|
59
|
-
|
|
60
48
|
@cache
|
|
61
49
|
def _mp4_codecs() -> list:
|
|
62
50
|
"""The video codecs this machine can encode into an MP4 — asked
|
|
@@ -99,12 +87,21 @@ def unavailable_reason() -> str | None:
|
|
|
99
87
|
'so an animation cannot be saved as a video here')
|
|
100
88
|
|
|
101
89
|
|
|
102
|
-
def
|
|
103
|
-
"""A screenshot as
|
|
104
|
-
frame
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
90
|
+
def _image(frame: np.ndarray) -> Any:
|
|
91
|
+
"""A screenshot as an RGBA QImage that owns its pixels — the encoder
|
|
92
|
+
reads a frame on its own thread, after the array may be gone. An
|
|
93
|
+
RGB screenshot is converted by Qt rather than padded in numpy: 0.07
|
|
94
|
+
against 3.5 ms a frame at 1024 × 768 (measured 2026-10-10)."""
|
|
95
|
+
from PySide6.QtGui import QImage
|
|
96
|
+
|
|
97
|
+
frame = np.ascontiguousarray(frame, dtype=np.uint8)
|
|
98
|
+
height, width, depth = frame.shape
|
|
99
|
+
if depth == 3:
|
|
100
|
+
return QImage(frame.data, width, height, 3 * width,
|
|
101
|
+
QImage.Format.Format_RGB888).convertToFormat(
|
|
102
|
+
QImage.Format.Format_RGBA8888)
|
|
103
|
+
return QImage(frame.data, width, height, 4 * width,
|
|
104
|
+
QImage.Format.Format_RGBA8888).copy()
|
|
108
105
|
|
|
109
106
|
|
|
110
107
|
def write_movie(path: str | Path, frames: Iterable[np.ndarray],
|
|
@@ -133,7 +130,6 @@ def write_movie(path: str | Path, frames: Iterable[np.ndarray],
|
|
|
133
130
|
if reason is not None:
|
|
134
131
|
raise MovieUnavailable(reason)
|
|
135
132
|
from PySide6.QtCore import QEventLoop, QSize, QTimer, QUrl
|
|
136
|
-
from PySide6.QtGui import QImage
|
|
137
133
|
from PySide6.QtMultimedia import (
|
|
138
134
|
QMediaCaptureSession,
|
|
139
135
|
QMediaFormat,
|
|
@@ -147,8 +143,8 @@ def write_movie(path: str | Path, frames: Iterable[np.ndarray],
|
|
|
147
143
|
first = next(source_frames, None)
|
|
148
144
|
if first is None:
|
|
149
145
|
raise ValueError('an animation needs at least one frame')
|
|
150
|
-
first =
|
|
151
|
-
|
|
146
|
+
first = _image(first)
|
|
147
|
+
width, height = first.width(), first.height()
|
|
152
148
|
|
|
153
149
|
path = Path(path).resolve()
|
|
154
150
|
|
|
@@ -191,8 +187,8 @@ def write_movie(path: str | Path, frames: Iterable[np.ndarray],
|
|
|
191
187
|
# still starting and keeps ten (2026-10-09). The next
|
|
192
188
|
# durationChanged sends on.
|
|
193
189
|
return
|
|
194
|
-
|
|
195
|
-
if
|
|
190
|
+
image = state['pending']
|
|
191
|
+
if image is None:
|
|
196
192
|
frame = next(state['frames'], None)
|
|
197
193
|
if frame is None:
|
|
198
194
|
# the end marker queues like a frame: refused when
|
|
@@ -200,22 +196,18 @@ def write_movie(path: str | Path, frames: Iterable[np.ndarray],
|
|
|
200
196
|
# stops — sent again on the next ready signal
|
|
201
197
|
state['ended'] = source.sendVideoFrame(QVideoFrame())
|
|
202
198
|
return
|
|
203
|
-
|
|
204
|
-
if
|
|
199
|
+
image = _image(frame)
|
|
200
|
+
if (image.width(), image.height()) != (width, height):
|
|
205
201
|
# raised here it would vanish into Qt's slot call
|
|
206
202
|
state['error'] = 'the frames changed size mid-movie'
|
|
207
203
|
state['frames'] = iter(())
|
|
208
204
|
continue
|
|
209
|
-
# copied: the encoder reads the frame on its own thread,
|
|
210
|
-
# after this array may be gone
|
|
211
|
-
image = QImage(frame.data, width, height, 4 * width,
|
|
212
|
-
QImage.Format.Format_RGBA8888).copy()
|
|
213
205
|
video = QVideoFrame(image)
|
|
214
206
|
video.setStartTime(state['sent'] * 1_000_000 // fps)
|
|
215
207
|
video.setEndTime((state['sent'] + 1) * 1_000_000 // fps)
|
|
216
208
|
if not source.sendVideoFrame(video):
|
|
217
209
|
# the encoder is full: keep this one for when it asks
|
|
218
|
-
state['pending'] =
|
|
210
|
+
state['pending'] = image
|
|
219
211
|
return
|
|
220
212
|
state['pending'] = None
|
|
221
213
|
state['sent'] += 1
|
|
@@ -240,18 +232,33 @@ def write_movie(path: str | Path, frames: Iterable[np.ndarray],
|
|
|
240
232
|
state['frames'], state['pending'] = iter(()), None
|
|
241
233
|
recorder.stop()
|
|
242
234
|
|
|
243
|
-
watchdog.timeout
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
235
|
+
connected = ((watchdog.timeout, stalled),
|
|
236
|
+
(source.readyToSendVideoFrame, send),
|
|
237
|
+
(recorder.durationChanged, lambda _duration: send()),
|
|
238
|
+
(recorder.recorderStateChanged, changed),
|
|
239
|
+
(recorder.errorOccurred, failed))
|
|
240
|
+
for signal, slot in connected:
|
|
241
|
+
signal.connect(slot)
|
|
248
242
|
recorder.record()
|
|
249
243
|
if not state['finished']:
|
|
250
244
|
loop.exec(QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents)
|
|
251
245
|
watchdog.stop()
|
|
246
|
+
# the slots hold the recorder, the input and the timer they are
|
|
247
|
+
# connected to — a cycle Python cannot see through Qt — so a save
|
|
248
|
+
# kept all of it alive for the life of the process until let go
|
|
249
|
+
for signal, slot in connected:
|
|
250
|
+
signal.disconnect(slot)
|
|
252
251
|
if state['error']:
|
|
253
252
|
raise RuntimeError(f'The movie could not be written: '
|
|
254
253
|
f'{state["error"]}')
|
|
254
|
+
# read back, not trusted: the pacing above leans on how one Qt
|
|
255
|
+
# buffers frames, and a Qt that buffers otherwise would write a
|
|
256
|
+
# short movie without a word (Principle 12 takes each Qt as it comes)
|
|
257
|
+
codec, kept = mp4_reading(path)
|
|
258
|
+
if codec != b'avc1' or kept != state['sent']:
|
|
259
|
+
raise RuntimeError(
|
|
260
|
+
f'The movie came out wrong: {kept} of {state["sent"]} frames '
|
|
261
|
+
f'as {(codec or b"nothing").decode(errors="replace")}')
|
|
255
262
|
return state['sent']
|
|
256
263
|
|
|
257
264
|
|
|
@@ -9,6 +9,7 @@ beside them is not.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
from collections.abc import Callable, Mapping, Sequence
|
|
12
|
+
from functools import partial
|
|
12
13
|
from typing import TYPE_CHECKING, Any
|
|
13
14
|
|
|
14
15
|
import numpy as np
|
|
@@ -78,11 +79,10 @@ def matched_modes_model(matched: MatchedModes, objects: Mapping[str, Any],
|
|
|
78
79
|
from ..core.tables import MATCHED_DECIMALS, cell_text, matched_rows
|
|
79
80
|
|
|
80
81
|
headers, rows = matched_rows(matched, objects)
|
|
81
|
-
columns = [
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for i, (title, decimals) in enumerate(zip(headers, MATCHED_DECIMALS))]
|
|
82
|
+
columns = [Column(title, lambda s, r, i=i: s[r][i],
|
|
83
|
+
format=partial(cell_text, decimals=decimals))
|
|
84
|
+
for i, (title, decimals)
|
|
85
|
+
in enumerate(zip(headers, MATCHED_DECIMALS))]
|
|
86
86
|
return TableModel(rows, columns, len, parent)
|
|
87
87
|
|
|
88
88
|
|
|
@@ -1977,17 +1977,19 @@ def _beyond(values, up):
|
|
|
1977
1977
|
|
|
1978
1978
|
|
|
1979
1979
|
def _application():
|
|
1980
|
+
"""A Qt application, made if a script has none — a plot rendered to a
|
|
1981
|
+
file, and a movie being encoded, both need one.
|
|
1982
|
+
|
|
1983
|
+
A widgets application, not a QGuiApplication: Qt keeps the first
|
|
1984
|
+
one made for the life of the process, and a script that later opens
|
|
1985
|
+
a window would abort on its first widget.
|
|
1986
|
+
"""
|
|
1980
1987
|
from PySide6.QtWidgets import QApplication
|
|
1981
1988
|
|
|
1982
1989
|
return QApplication.instance() or QApplication([])
|
|
1983
1990
|
|
|
1984
1991
|
|
|
1985
|
-
|
|
1986
|
-
FLAT_READINGS = ('averaging', 'shocks', 'truncation', 'filter', 'octave')
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
def octave_preview(items: Iterable[Any], psd: Any,
|
|
1990
|
-
per_octave: int | None, unit_system: Any,
|
|
1992
|
+
def octave_preview(items: Iterable[Any], steps: tuple[Any, Any, Any],
|
|
1991
1993
|
colors: Mapping[str, str],
|
|
1992
1994
|
records: Sequence[int] | None = None
|
|
1993
1995
|
) -> list[tuple[Any, Any]]:
|
|
@@ -1998,16 +2000,12 @@ def octave_preview(items: Iterable[Any], psd: Any,
|
|
|
1998
2000
|
window's octave view and `plot_data(marks='octave')`; returns the
|
|
1999
2001
|
(plot, curve) pairs drawn.
|
|
2000
2002
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
ValueError
|
|
2004
|
-
When the spectrum cannot be banded.
|
|
2003
|
+
`steps` is `viz.marks.octave_steps`' answer, banded once by the
|
|
2004
|
+
caller, which reads the band count off the same banding.
|
|
2005
2005
|
"""
|
|
2006
2006
|
import pyqtgraph as pg
|
|
2007
2007
|
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
banded, x, rows = octave_steps(psd, per_octave, unit_system)
|
|
2008
|
+
banded, x, rows = steps
|
|
2011
2009
|
wanted = (range(rows.shape[0]) if records is None
|
|
2012
2010
|
else [int(i) for i in records])
|
|
2013
2011
|
pen = pg.mkPen(colors['filter_preview'], width=2)
|
|
@@ -2032,7 +2030,8 @@ def octave_preview(items: Iterable[Any], psd: Any,
|
|
|
2032
2030
|
def _mark(layout, data, marks, theme=None, per_octave=None,
|
|
2033
2031
|
unit_system=None):
|
|
2034
2032
|
"""A reading over every plot in `layout` — the frames, the shocks,
|
|
2035
|
-
the cut, the filter's preview or the octave steps
|
|
2033
|
+
the cut, the filter's preview or the octave steps — as the window's
|
|
2034
|
+
toggles draw it (`viz.marks.held_reading`).
|
|
2036
2035
|
|
|
2037
2036
|
Locked, because a mark you can drag is a mark that reports the drag
|
|
2038
2037
|
to somebody, and in a standalone plot there is nobody. Returns the
|
|
@@ -2040,51 +2039,37 @@ def _mark(layout, data, marks, theme=None, per_octave=None,
|
|
|
2040
2039
|
"""
|
|
2041
2040
|
import pyqtgraph as pg
|
|
2042
2041
|
|
|
2043
|
-
from ..
|
|
2044
|
-
from ..core.data import Psd, TimeHistory
|
|
2042
|
+
from ..viz.marks import held_reading, octave_steps
|
|
2045
2043
|
|
|
2046
2044
|
if marks is None:
|
|
2047
2045
|
return []
|
|
2048
|
-
|
|
2049
|
-
raise ValueError(f'{marks!r} is not a reading of a plot: '
|
|
2050
|
-
+ ', '.join(repr(r) for r in FLAT_READINGS))
|
|
2046
|
+
held = held_reading(data, marks)
|
|
2051
2047
|
colors = resolve_theme(theme)
|
|
2052
2048
|
plots = [item for item in layout.ci.items if isinstance(item, pg.PlotItem)]
|
|
2053
2049
|
if marks == 'octave':
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
f'{type(data).__name__}')
|
|
2057
|
-
return octave_preview(plots, data, per_octave, unit_system, colors)
|
|
2058
|
-
if not isinstance(data, TimeHistory):
|
|
2059
|
-
raise TypeError(f'{marks} marks belong on a time history, not on '
|
|
2060
|
-
f'{type(data).__name__}')
|
|
2050
|
+
return octave_preview(
|
|
2051
|
+
plots, octave_steps(data, per_octave, unit_system), colors)
|
|
2061
2052
|
if marks == 'shocks':
|
|
2062
|
-
from ..core.shocks import suggest
|
|
2063
2053
|
from .shocks import ShockOverlay
|
|
2064
2054
|
|
|
2065
|
-
|
|
2066
|
-
return [ShockOverlay(plot, found, colors, locked=True)
|
|
2055
|
+
return [ShockOverlay(plot, held, colors, locked=True)
|
|
2067
2056
|
for plot in plots]
|
|
2068
2057
|
if marks == 'filter':
|
|
2069
2058
|
from .filtering import FilterOverlay
|
|
2070
2059
|
|
|
2071
|
-
|
|
2072
|
-
return [FilterOverlay(plot, filtering, data.sample_rate, colors)
|
|
2060
|
+
return [FilterOverlay(plot, held, data.sample_rate, colors)
|
|
2073
2061
|
for plot in plots]
|
|
2074
2062
|
if marks == 'truncation':
|
|
2075
2063
|
from .truncation import TruncationOverlay
|
|
2076
2064
|
|
|
2077
2065
|
abscissa = np.asarray(data.abscissa, dtype=float)
|
|
2078
|
-
|
|
2079
|
-
return [TruncationOverlay(plot, truncation, float(abscissa[0]),
|
|
2066
|
+
return [TruncationOverlay(plot, held, float(abscissa[0]),
|
|
2080
2067
|
float(abscissa[-1]), colors)
|
|
2081
2068
|
for plot in plots]
|
|
2082
2069
|
from .averaging import AveragingOverlay
|
|
2083
2070
|
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
return [AveragingOverlay(plot, averaging, data.sample_rate, samples,
|
|
2087
|
-
colors, locked=True)
|
|
2071
|
+
return [AveragingOverlay(plot, held, data.sample_rate,
|
|
2072
|
+
len(data.abscissa), colors, locked=True)
|
|
2088
2073
|
for plot in plots]
|
|
2089
2074
|
|
|
2090
2075
|
|