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.
Files changed (178) hide show
  1. {visualdynamics-0.1.0a40/src/visualdynamics.egg-info → visualdynamics-0.1.0a42}/PKG-INFO +7 -6
  2. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/README.md +6 -5
  3. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/__init__.py +1 -1
  4. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/channel_table.py +1 -1
  5. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/data.py +85 -0
  6. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/filters.py +19 -2
  7. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/geometry.py +43 -0
  8. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/shapes.py +130 -0
  9. visualdynamics-0.1.0a42/src/visualdynamics/core/tables.py +117 -0
  10. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/wavelet.py +41 -0
  11. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/__init__.py +9 -0
  12. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/disclaimer.py +4 -6
  13. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/icons.py +44 -1
  14. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/main_window.py +232 -234
  15. visualdynamics-0.1.0a42/src/visualdynamics/gui/movie.py +360 -0
  16. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/object_tables.py +6 -41
  17. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/project_tree.py +3 -2
  18. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/__init__.py +92 -34
  19. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/project.py +242 -5
  20. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/report/__init__.py +17 -59
  21. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/theme.py +11 -0
  22. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/animate.py +330 -40
  23. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/marks.py +112 -0
  24. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/paired.py +8 -4
  25. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/scalogram.py +39 -0
  26. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/sinespec.py +34 -0
  27. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/waterfall.py +49 -10
  28. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42/src/visualdynamics.egg-info}/PKG-INFO +7 -6
  29. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/SOURCES.txt +1 -0
  30. visualdynamics-0.1.0a40/src/visualdynamics/core/tables.py +0 -49
  31. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/LICENSE +0 -0
  32. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/pyproject.toml +0 -0
  33. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/setup.cfg +0 -0
  34. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/__main__.py +0 -0
  35. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/compatibility.py +0 -0
  36. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/__init__.py +0 -0
  37. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/author.py +0 -0
  38. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/averaging.py +0 -0
  39. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/compliance.py +0 -0
  40. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/correlate.py +0 -0
  41. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/detect.py +0 -0
  42. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/entities.py +0 -0
  43. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/fem.py +0 -0
  44. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/kurtosis.py +0 -0
  45. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/matches.py +0 -0
  46. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/merge.py +0 -0
  47. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/mesh.py +0 -0
  48. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/modal_fit.py +0 -0
  49. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/octave.py +0 -0
  50. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/photos.py +0 -0
  51. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/progress.py +0 -0
  52. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/replication.py +0 -0
  53. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/report.py +0 -0
  54. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/rigid.py +0 -0
  55. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/shocks.py +0 -0
  56. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/sine.py +0 -0
  57. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/sine_tracking.py +0 -0
  58. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/snr.py +0 -0
  59. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/srs.py +0 -0
  60. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/transform.py +0 -0
  61. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/truncate.py +0 -0
  62. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/unit_choices.py +0 -0
  63. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/core/validate.py +0 -0
  64. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/decimate.py +0 -0
  65. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/deform.py +0 -0
  66. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/__init__.py +0 -0
  67. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/barc.py +0 -0
  68. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/drone.py +0 -0
  69. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/frame.py +0 -0
  70. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/demo/plate.py +0 -0
  71. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/app_icon.py +0 -0
  72. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/ask.py +0 -0
  73. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/author_panel.py +0 -0
  74. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/averaging_panel.py +0 -0
  75. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/console.py +0 -0
  76. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/drawer_tab.py +0 -0
  77. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/editors.py +0 -0
  78. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/filter_panel.py +0 -0
  79. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/glyphs.py +0 -0
  80. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
  81. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
  82. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/mesh_panel.py +0 -0
  83. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/octave_panel.py +0 -0
  84. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/panes.py +0 -0
  85. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/preferences.py +0 -0
  86. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/record_grid.py +0 -0
  87. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/report_editor.py +0 -0
  88. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/rigid_panel.py +0 -0
  89. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/run_reports.py +0 -0
  90. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/settings_panel.py +0 -0
  91. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/shock_panel.py +0 -0
  92. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/sine_panel.py +0 -0
  93. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/stage_drag.py +0 -0
  94. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/stream_window.py +0 -0
  95. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/tables.py +0 -0
  96. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/toolbars.py +0 -0
  97. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/truncate_panel.py +0 -0
  98. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/updater.py +0 -0
  99. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/wavelet_panel.py +0 -0
  100. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/gui/windows.py +0 -0
  101. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/__init__.py +0 -0
  102. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/adf.py +0 -0
  103. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf.py +0 -0
  104. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_objects.py +0 -0
  105. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
  106. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
  107. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
  108. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
  109. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
  110. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
  111. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
  112. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
  113. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
  114. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
  115. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
  116. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
  117. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
  118. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
  119. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
  120. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
  121. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
  122. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
  123. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
  124. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
  125. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
  126. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
  127. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
  128. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
  129. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/excel.py +0 -0
  130. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/exodus.py +0 -0
  131. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/exporters.py +0 -0
  132. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/femap.py +0 -0
  133. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/matlab.py +0 -0
  134. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/nastran.py +0 -0
  135. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/native.py +0 -0
  136. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/notes.py +0 -0
  137. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/photo_files.py +0 -0
  138. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/punch.py +0 -0
  139. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/rattlesnake.py +0 -0
  140. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
  141. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
  142. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/report_template.py +0 -0
  143. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sdynpy_data.py +0 -0
  144. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sdynpy_npz.py +0 -0
  145. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
  146. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sep005.py +0 -0
  147. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/sniffing.py +0 -0
  148. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/step.py +0 -0
  149. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/stl.py +0 -0
  150. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/threemf.py +0 -0
  151. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/io/unv.py +0 -0
  152. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/names.py +0 -0
  153. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/averaging.py +0 -0
  154. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/bands.py +0 -0
  155. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/bars.py +0 -0
  156. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/filtering.py +0 -0
  157. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/scalogram.py +0 -0
  158. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/shocks.py +0 -0
  159. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/sine_tracking.py +0 -0
  160. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/tracking_filter.py +0 -0
  161. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/plot/truncation.py +0 -0
  162. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/py.typed +0 -0
  163. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/report/markdown.py +0 -0
  164. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/report/page.py +0 -0
  165. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/rotate.py +0 -0
  166. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/units.py +0 -0
  167. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/update.py +0 -0
  168. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/__init__.py +0 -0
  169. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/_quiet.py +0 -0
  170. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/banded.py +0 -0
  171. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/geometry.py +0 -0
  172. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/mac_bars.py +0 -0
  173. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/pick.py +0 -0
  174. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics/viz/rigid.py +0 -0
  175. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
  176. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/entry_points.txt +0 -0
  177. {visualdynamics-0.1.0a40 → visualdynamics-0.1.0a42}/src/visualdynamics.egg-info/requires.txt +0 -0
  178. {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.0a40
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. The project file,
77
- `.vdyn`, is provisional: the next alpha will save projects in the
78
- Engineering Sciences Common Data Format (`.h5`), a public standard,
79
- and will still open `.vdyn` files; the first release that is not an
80
- alpha will not. Open each `.vdyn` once in that alpha and save it again.
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. The project file,
20
- `.vdyn`, is provisional: the next alpha will save projects in the
21
- Engineering Sciences Common Data Format (`.h5`), a public standard,
22
- and will still open `.vdyn` files; the first release that is not an
23
- alpha will not. Open each `.vdyn` once in that alpha and save it again.
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.0a40'
93
+ __version__ = '0.1.0a42'
94
94
 
95
95
 
96
96
  def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
@@ -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( # noqa: DTZ007
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
- with ThreadPoolExecutor(min(8, os.cpu_count() or 1)) as pool:
101
- return np.stack(list(pool.map(lambda row: sosfiltfilt(sos, row), rows)))
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>The project file, <code>.vdyn</code>, is provisional. The next '
39
- 'alpha will save projects in the Engineering Sciences Common Data '
40
- 'Format (<code>.h5</code>), a public standard, and will still '
41
- 'open <code>.vdyn</code> files; the first release that is not an '
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
  )