visualdynamics 0.1.0a42__tar.gz → 0.1.0a44__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 (177) hide show
  1. {visualdynamics-0.1.0a42/src/visualdynamics.egg-info → visualdynamics-0.1.0a44}/PKG-INFO +1 -1
  2. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/__init__.py +1 -1
  3. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/data.py +3 -0
  4. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/shapes.py +6 -2
  5. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/main_window.py +135 -76
  6. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/movie.py +45 -38
  7. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/object_tables.py +5 -5
  8. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/__init__.py +22 -37
  9. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/project.py +23 -8
  10. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/report/__init__.py +2 -2
  11. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/rotate.py +15 -2
  12. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/theme.py +6 -0
  13. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/animate.py +164 -247
  14. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/marks.py +54 -40
  15. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/paired.py +2 -11
  16. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/pick.py +26 -14
  17. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/waterfall.py +1 -3
  18. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44/src/visualdynamics.egg-info}/PKG-INFO +1 -1
  19. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/LICENSE +0 -0
  20. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/README.md +0 -0
  21. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/pyproject.toml +0 -0
  22. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/setup.cfg +0 -0
  23. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/__main__.py +0 -0
  24. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/compatibility.py +0 -0
  25. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/__init__.py +0 -0
  26. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/author.py +0 -0
  27. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/averaging.py +0 -0
  28. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/channel_table.py +0 -0
  29. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/compliance.py +0 -0
  30. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/correlate.py +0 -0
  31. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/detect.py +0 -0
  32. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/entities.py +0 -0
  33. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/fem.py +0 -0
  34. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/filters.py +0 -0
  35. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/geometry.py +0 -0
  36. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/kurtosis.py +0 -0
  37. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/matches.py +0 -0
  38. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/merge.py +0 -0
  39. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/mesh.py +0 -0
  40. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/modal_fit.py +0 -0
  41. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/octave.py +0 -0
  42. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/photos.py +0 -0
  43. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/progress.py +0 -0
  44. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/replication.py +0 -0
  45. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/report.py +0 -0
  46. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/rigid.py +0 -0
  47. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/shocks.py +0 -0
  48. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/sine.py +0 -0
  49. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/sine_tracking.py +0 -0
  50. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/snr.py +0 -0
  51. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/srs.py +0 -0
  52. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/tables.py +0 -0
  53. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/transform.py +0 -0
  54. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/truncate.py +0 -0
  55. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/unit_choices.py +0 -0
  56. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/validate.py +0 -0
  57. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/core/wavelet.py +0 -0
  58. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/decimate.py +0 -0
  59. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/deform.py +0 -0
  60. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/demo/__init__.py +0 -0
  61. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/demo/barc.py +0 -0
  62. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/demo/drone.py +0 -0
  63. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/demo/frame.py +0 -0
  64. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/demo/plate.py +0 -0
  65. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/__init__.py +0 -0
  66. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/app_icon.py +0 -0
  67. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/ask.py +0 -0
  68. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/author_panel.py +0 -0
  69. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/averaging_panel.py +0 -0
  70. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/console.py +0 -0
  71. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/disclaimer.py +0 -0
  72. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/drawer_tab.py +0 -0
  73. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/editors.py +0 -0
  74. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/filter_panel.py +0 -0
  75. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/glyphs.py +0 -0
  76. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
  77. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
  78. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/icons.py +0 -0
  79. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/mesh_panel.py +0 -0
  80. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/octave_panel.py +0 -0
  81. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/panes.py +0 -0
  82. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/preferences.py +0 -0
  83. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/project_tree.py +0 -0
  84. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/record_grid.py +0 -0
  85. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/report_editor.py +0 -0
  86. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/rigid_panel.py +0 -0
  87. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/run_reports.py +0 -0
  88. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/settings_panel.py +0 -0
  89. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/shock_panel.py +0 -0
  90. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/sine_panel.py +0 -0
  91. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/stage_drag.py +0 -0
  92. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/stream_window.py +0 -0
  93. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/tables.py +0 -0
  94. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/toolbars.py +0 -0
  95. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/truncate_panel.py +0 -0
  96. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/updater.py +0 -0
  97. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/wavelet_panel.py +0 -0
  98. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/gui/windows.py +0 -0
  99. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/__init__.py +0 -0
  100. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/adf.py +0 -0
  101. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf.py +0 -0
  102. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_objects.py +0 -0
  103. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
  104. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
  105. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
  106. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
  107. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
  108. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
  109. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
  110. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
  111. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
  112. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
  113. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
  114. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
  115. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
  116. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
  117. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
  118. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
  119. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
  120. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
  121. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
  122. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
  123. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
  124. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
  125. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
  126. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
  127. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/excel.py +0 -0
  128. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/exodus.py +0 -0
  129. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/exporters.py +0 -0
  130. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/femap.py +0 -0
  131. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/matlab.py +0 -0
  132. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/nastran.py +0 -0
  133. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/native.py +0 -0
  134. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/notes.py +0 -0
  135. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/photo_files.py +0 -0
  136. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/punch.py +0 -0
  137. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/rattlesnake.py +0 -0
  138. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
  139. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
  140. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/report_template.py +0 -0
  141. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/sdynpy_data.py +0 -0
  142. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/sdynpy_npz.py +0 -0
  143. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
  144. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/sep005.py +0 -0
  145. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/sniffing.py +0 -0
  146. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/step.py +0 -0
  147. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/stl.py +0 -0
  148. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/threemf.py +0 -0
  149. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/io/unv.py +0 -0
  150. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/names.py +0 -0
  151. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/averaging.py +0 -0
  152. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/bands.py +0 -0
  153. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/bars.py +0 -0
  154. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/filtering.py +0 -0
  155. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/scalogram.py +0 -0
  156. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/shocks.py +0 -0
  157. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/sine_tracking.py +0 -0
  158. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/tracking_filter.py +0 -0
  159. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/plot/truncation.py +0 -0
  160. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/py.typed +0 -0
  161. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/report/markdown.py +0 -0
  162. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/report/page.py +0 -0
  163. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/units.py +0 -0
  164. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/update.py +0 -0
  165. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/__init__.py +0 -0
  166. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/_quiet.py +0 -0
  167. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/banded.py +0 -0
  168. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/geometry.py +0 -0
  169. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/mac_bars.py +0 -0
  170. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/rigid.py +0 -0
  171. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/scalogram.py +0 -0
  172. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics/viz/sinespec.py +0 -0
  173. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics.egg-info/SOURCES.txt +0 -0
  174. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
  175. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics.egg-info/entry_points.txt +0 -0
  176. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/src/visualdynamics.egg-info/requires.txt +0 -0
  177. {visualdynamics-0.1.0a42 → visualdynamics-0.1.0a44}/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.0a42
3
+ Version: 0.1.0a44
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
@@ -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.0a42'
93
+ __version__ = '0.1.0a44'
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 = None
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 OVERLAY_ALPHA
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
@@ -771,6 +773,7 @@ class MainWindow(QMainWindow):
771
773
  self._gizmo_reading = None # the drag's number, while it lasts
772
774
  self._sliding = None # the arrow gesture in progress (2026-10-02)
773
775
  self._rotate_observers = []
776
+ self._gizmo_hovered = None # ('ring' or 'arrow', axis) under the cursor
774
777
  self._last_axis = 2 # the ring a typed angle turns about
775
778
  self._pick_observers = []
776
779
  self._cursor = None
@@ -1138,7 +1141,6 @@ class MainWindow(QMainWindow):
1138
1141
  self._fit_parabola = None
1139
1142
  self._fit_damping_label = None
1140
1143
  self._fit_coherence_name = None
1141
- self._fit_records = None
1142
1144
  #: what the plot's top and bottom edges mean as damping, kept
1143
1145
  #: across fits — an article's plausible range rarely changes
1144
1146
  #: between two of its own FRF sets
@@ -3182,8 +3184,12 @@ class MainWindow(QMainWindow):
3182
3184
  rewrites per line is in every frame; then playback is put back
3183
3185
  where it was. `path` skips the dialog (scripts, tests).
3184
3186
  """
3185
- from ..viz.animate import cycle_parameters, sweep_parameters
3186
- from .movie import MOVIE_FPS, write_movie
3187
+ from ..viz.animate import (
3188
+ cycle_parameters,
3189
+ movie_frames,
3190
+ sweep_parameters,
3191
+ )
3192
+ from .movie import write_movie
3187
3193
 
3188
3194
  if self.animator is None or self.scene.plotter is None:
3189
3195
  return None
@@ -3201,28 +3207,24 @@ class MainWindow(QMainWindow):
3201
3207
  phase = self.phase_slider.value()
3202
3208
  held = self._cursor.value() if self._cursor is not None else None
3203
3209
  if self._shape_mode:
3204
- parameters = cycle_parameters(SECONDS_PER_CYCLE / self.speed,
3205
- MOVIE_FPS)
3210
+ parameters = cycle_parameters(SECONDS_PER_CYCLE / self.speed)
3211
+ step = self.animator.set_parameter
3206
3212
  else:
3207
3213
  parameters = sweep_parameters(len(self._cursor_abscissa),
3208
- SECONDS_PER_RECORD / self.speed,
3209
- MOVIE_FPS)
3214
+ SECONDS_PER_RECORD / self.speed)
3210
3215
 
3211
- def frames():
3212
- for parameter in parameters:
3213
- if self._shape_mode:
3214
- self._draw_frame(parameter)
3215
- else:
3216
- self._cursor.setValue(
3217
- float(self._cursor_abscissa[parameter]))
3218
- yield self.scene.plotter.screenshot(return_img=True)
3216
+ def step(index):
3217
+ # the cursor, not the animator: it is where an envelope
3218
+ # rewrites its caption, so every frame carries its line
3219
+ self._cursor.setValue(float(self._cursor_abscissa[index]))
3219
3220
 
3220
3221
  # seconds on a large model or a Retina screen, with input held
3221
3222
  # off: say so, rather than look hung
3222
3223
  self._show_status(f'Saving {os.path.basename(path)}…')
3223
3224
  QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
3224
3225
  try:
3225
- count = write_movie(path, frames(), fps=MOVIE_FPS)
3226
+ count = write_movie(path, movie_frames(self.scene.plotter, step,
3227
+ parameters))
3226
3228
  except RuntimeError as error: # MovieUnavailable among them
3227
3229
  self._show_status(str(error))
3228
3230
  return None
@@ -3234,7 +3236,7 @@ class MainWindow(QMainWindow):
3234
3236
  self._draw_frame(phase / PHASE_STEPS * 2 * np.pi)
3235
3237
  self.set_playing(playing)
3236
3238
  self._show_status(
3237
- f'Saved {os.path.basename(path)} — {count / MOVIE_FPS:.3g} s, '
3239
+ f'Saved {os.path.basename(path)} — {count / FRAMES_PER_SECOND:.3g} s, '
3238
3240
  f'{os.path.getsize(path) / 1e6:.2g} MB')
3239
3241
  return path
3240
3242
 
@@ -3774,7 +3776,7 @@ class MainWindow(QMainWindow):
3774
3776
 
3775
3777
  # the Basis keeps the comparison overlay's baseline blue; other
3776
3778
  # groups cycle the curve palette
3777
- LINK_ROLE_COLORS: ClassVar[dict] = {'Basis': '#4c92d9'}
3779
+ LINK_ROLE_COLORS: ClassVar[dict] = {'Basis': PAIR_COLORS[0]}
3778
3780
 
3779
3781
  def _role_holder(self, role):
3780
3782
  """The real object group holding the type's `role`.
@@ -5121,6 +5123,7 @@ class MainWindow(QMainWindow):
5121
5123
  self._rotate_observers = []
5122
5124
  self._rotating = None
5123
5125
  self._sliding = None
5126
+ self._gizmo_hovered = None
5124
5127
  if self.scene.plotter is not None:
5125
5128
  for axis in range(3):
5126
5129
  for kind in ('ring', 'arrow', 'head'):
@@ -5137,7 +5140,7 @@ class MainWindow(QMainWindow):
5137
5140
  span = np.ptp(_points, axis=0).max() if len(_points) > 1 else 1.0
5138
5141
  return 0.12 * (span or 1.0) * 1.15
5139
5142
 
5140
- def _draw_rings(self):
5143
+ def _draw_rings(self, render=True):
5141
5144
  """Three rings, one about each of the frame's own axes."""
5142
5145
  import pyvista as pv
5143
5146
 
@@ -5152,18 +5155,22 @@ class MainWindow(QMainWindow):
5152
5155
  self.scene.plotter.add_mesh(
5153
5156
  pv.lines_from_points(loop).tube(radius=radius * 0.03,
5154
5157
  n_sides=12),
5155
- color=AXIS_COLORS[axis], name=f'rotate-ring-{axis}')
5158
+ color=self._gizmo_color('ring', axis),
5159
+ name=f'rotate-ring-{axis}')
5156
5160
  # and an arrow along each axis, for sliding (2026-10-02)
5157
5161
  shaft = arrow_points(matrix, axis, self._arrow_length(), steps=2)
5158
5162
  self.scene.plotter.add_mesh(
5159
5163
  pv.lines_from_points(shaft).tube(radius=radius * 0.03,
5160
5164
  n_sides=12),
5161
- color=AXIS_COLORS[axis], name=f'rotate-arrow-{axis}')
5165
+ color=self._gizmo_color('arrow', axis),
5166
+ name=f'rotate-arrow-{axis}')
5162
5167
  self.scene.plotter.add_mesh(
5163
5168
  pv.Cone(center=shaft[-1], direction=matrix[axis],
5164
5169
  height=radius * 0.3, radius=radius * 0.1),
5165
- color=AXIS_COLORS[axis], name=f'rotate-head-{axis}')
5166
- self.scene.plotter.render()
5170
+ color=self._gizmo_color('arrow', axis),
5171
+ name=f'rotate-head-{axis}')
5172
+ if render:
5173
+ self.scene.plotter.render()
5167
5174
 
5168
5175
  def _arrow_length(self):
5169
5176
  """An arrow reaches past the rings, so its head is clear of
@@ -5207,11 +5214,64 @@ class MainWindow(QMainWindow):
5207
5214
  radius = self._ring_radius()
5208
5215
  rings = []
5209
5216
  for axis in range(3):
5217
+ points = ring_points(matrix, axis, radius)
5218
+ # closed, as drawn, so the last stretch of ring is a line too
5210
5219
  projector = ScreenProjector(self.scene.plotter.renderer,
5211
- ring_points(matrix, axis, radius))
5220
+ np.vstack([points, points[:1]]))
5212
5221
  rings.append((axis, projector.screen()[0]))
5213
5222
  return rings
5214
5223
 
5224
+ def _gizmo_under_cursor(self, position):
5225
+ """('arrow' or 'ring', axis) — what a press at `position` takes,
5226
+ or None. One answer for the press and the hover, so what lights
5227
+ up is what a press would grab. An arrow first: the arrows cross
5228
+ the rings, and a press there is a slide, the gesture that needs
5229
+ the origin."""
5230
+ reach = self._gizmo_reach()
5231
+ axis = ring_under_cursor(self._arrow_screen_points(), position, reach)
5232
+ if axis is not None:
5233
+ return 'arrow', axis
5234
+ axis = ring_under_cursor(self._ring_screen_points(), position, reach)
5235
+ return None if axis is None else ('ring', axis)
5236
+
5237
+ def _gizmo_color(self, kind, axis):
5238
+ """An axis's own color, or the scene's highlight — the color a
5239
+ hovered node takes — while that ring or arrow is under the
5240
+ cursor (2026-10-10)."""
5241
+ if self._gizmo_hovered == (kind, axis):
5242
+ return resolve_theme(self.theme_name)['scene_highlight']
5243
+ return AXIS_COLORS[axis]
5244
+
5245
+ def _hover_gizmo(self):
5246
+ """Light the ring or arrow under the cursor, so a press lands
5247
+ knowing what it will take. Not while a button is down: an orbit
5248
+ sweeps the rings past the cursor, and nothing there is on
5249
+ offer."""
5250
+ position = self._cursor_position()
5251
+ if position is None or self.scene.plotter is None:
5252
+ return
5253
+ part = None
5254
+ if QApplication.mouseButtons() == Qt.MouseButton.NoButton:
5255
+ part = self._gizmo_under_cursor(position)
5256
+ if part == self._gizmo_hovered:
5257
+ return # nothing changed, so nothing to redraw
5258
+ self._gizmo_hovered = part
5259
+ actors = self.scene.plotter.renderer.actors
5260
+ for axis in range(3):
5261
+ for kind, name in (('ring', 'ring'), ('arrow', 'arrow'),
5262
+ ('arrow', 'head')):
5263
+ actor = actors.get(f'rotate-{name}-{axis}')
5264
+ if actor is not None:
5265
+ actor.prop.color = self._gizmo_color(kind, axis)
5266
+ self.scene.plotter.render()
5267
+
5268
+ def _gizmo_reach(self):
5269
+ """How near a ring or an arrow a press must land: 14 points,
5270
+ counted in the device pixels VTK reports the cursor and projects
5271
+ the rings in. Counted in pixels until 2026-10-10, which on a
5272
+ Retina screen — two pixels to the point — was a 7-point target."""
5273
+ return 14.0 * self.devicePixelRatioF()
5274
+
5215
5275
  def _abort(self, caller, observer):
5216
5276
  """Stop VTK passing this event on to the camera."""
5217
5277
  with contextlib.suppress(Exception):
@@ -5221,10 +5281,11 @@ class MainWindow(QMainWindow):
5221
5281
  position = self._cursor_position()
5222
5282
  if position is None:
5223
5283
  return
5224
- # an arrow first: the arrows cross the rings at the origin, and
5225
- # a press there is a slide, the gesture that needs the origin
5226
- axis = ring_under_cursor(self._arrow_screen_points(), position)
5227
- if axis is not None:
5284
+ part = self._gizmo_under_cursor(position)
5285
+ if part is None:
5286
+ return # not on the gizmo: let the camera have it
5287
+ kind, axis = part
5288
+ if kind == 'arrow':
5228
5289
  row, matrix = self._rotating_frame()
5229
5290
  world = self._cursor_on_axis(matrix, axis, position)
5230
5291
  if world is None:
@@ -5235,9 +5296,6 @@ class MainWindow(QMainWindow):
5235
5296
  'from': distance_along(self._display_frame(matrix), axis, world)}
5236
5297
  self._abort(caller, self._rotate_observers[0])
5237
5298
  return
5238
- axis = ring_under_cursor(self._ring_screen_points(), position)
5239
- if axis is None:
5240
- return # not on a ring: let the camera have it
5241
5299
  row, matrix = self._rotating_frame()
5242
5300
  world = self._cursor_on_ring(matrix, axis, position)
5243
5301
  if world is None:
@@ -5265,6 +5323,7 @@ class MainWindow(QMainWindow):
5265
5323
  self._abort(caller, self._rotate_observers[1])
5266
5324
  return
5267
5325
  if self._rotating is None:
5326
+ self._hover_gizmo()
5268
5327
  return
5269
5328
  position = self._cursor_position()
5270
5329
  if position is None:
@@ -5375,14 +5434,8 @@ class MainWindow(QMainWindow):
5375
5434
  said['origin'] = tuple(float(v) for v in matrix[3])
5376
5435
  cs_id = int(geometry.cs_id[row])
5377
5436
  geometry.place_coordinate_system(cs_id, **said)
5378
- journal = self.project.journal
5379
- before = len(journal)
5380
5437
  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:]
5438
+ settle=True, **said)
5386
5439
 
5387
5440
  def _cursor_on_ring(self, matrix, axis, position):
5388
5441
  """Where the cursor's ray meets the plane the ring lies in."""
@@ -5799,7 +5852,7 @@ class MainWindow(QMainWindow):
5799
5852
 
5800
5853
  def hover_at(self, x: float, y: float) -> int | None:
5801
5854
  """Light up whatever is under this pixel; returns the entity."""
5802
- entity = self._picker.pick(x, y)
5855
+ entity = self._picker.pick(x, y, pixel_ratio=self.devicePixelRatioF())
5803
5856
  if entity == self._hovered:
5804
5857
  return entity # nothing changed, so nothing to redraw
5805
5858
  self._hovered = entity
@@ -6007,6 +6060,12 @@ class MainWindow(QMainWindow):
6007
6060
  # scene, and a rebuilt scene would otherwise re-frame itself
6008
6061
  self.scene.set_home_view(geometry.opening_view, geometry)
6009
6062
  self.scene.plotter.camera_position = camera
6063
+ if self._rotate_observers:
6064
+ # the clear above took the rings with it, and every committed
6065
+ # drag comes through here (its row refresh is an edit): until
6066
+ # 2026-10-10 they vanished after the first turn while still
6067
+ # answering clicks where they had been
6068
+ self._draw_rings(render=False)
6010
6069
  self.scene.plotter.render()
6011
6070
 
6012
6071
  def _add_hover_mesh(self, geometry, colors):
@@ -8336,7 +8395,7 @@ class MainWindow(QMainWindow):
8336
8395
  color_a = self._link_color(a_name)
8337
8396
  color_b = self._link_color(b_name)
8338
8397
  if color_b is None or color_b == color_a:
8339
- color_b = next(c for c in ('#ff8c2b', '#3fb950')
8398
+ color_b = next(c for c in (PAIR_COLORS[1], '#3fb950')
8340
8399
  if c != color_a)
8341
8400
  self._compare['colors'] = (color_a, color_b)
8342
8401
  alphas = overlay_alphas(self.link_role(a_name),
@@ -8358,9 +8417,7 @@ class MainWindow(QMainWindow):
8358
8417
  return None
8359
8418
 
8360
8419
  def _build_comparison_animator(self, first_pair, second_pair,
8361
- caption, showing,
8362
- colors=(None, '#ff8c2b'),
8363
- alphas=(1.0, OVERLAY_ALPHA)):
8420
+ caption, showing, colors, alphas):
8364
8421
  """Like _build_animator, twice: two moving copies in one
8365
8422
  scene (`viz.animate.build_pair`), each (geometry, deflection)
8366
8423
  pair on its own mesh, in its own color and at its own opacity."""
@@ -8495,7 +8552,9 @@ class MainWindow(QMainWindow):
8495
8552
  if start is None:
8496
8553
  return
8497
8554
  end = iren.interactor.GetEventPosition()
8498
- if abs(end[0] - start[0]) <= 3 and abs(end[1] - start[1]) <= 3:
8555
+ # 3 points of slop, in the device pixels VTK reports
8556
+ slop = 3 * self.devicePixelRatioF()
8557
+ if abs(end[0] - start[0]) <= slop and abs(end[1] - start[1]) <= slop:
8499
8558
  self._mac_bars_clicked(end)
8500
8559
 
8501
8560
  iren.add_observer('LeftButtonPressEvent', pressed)
@@ -9809,20 +9868,21 @@ class MainWindow(QMainWindow):
9809
9868
  plot another (Brandon, 2026-08-30).
9810
9869
  """
9811
9870
  from ..plot import octave_preview
9871
+ from ..viz.marks import octave_steps
9812
9872
 
9813
9873
  self._clear_octave()
9814
9874
  panel = self.data_pane.octave_panel
9815
9875
  try:
9816
- self.octave_previews = octave_preview(
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())
9876
+ steps = octave_steps(psd, panel.per_octave(), self.unit_system)
9820
9877
  except ValueError as refusal:
9821
9878
  panel.show_bands(None)
9822
9879
  panel.show()
9823
9880
  self._show_status(f'No octave preview: {refusal}')
9824
9881
  return
9825
- panel.show_bands(len(banded.abscissa))
9882
+ self.octave_previews = octave_preview(
9883
+ self.data_pane.graphics.ci.items, steps,
9884
+ resolve_theme(self.theme_name), records)
9885
+ panel.show_bands(len(steps[0].abscissa))
9826
9886
  panel.show()
9827
9887
  self._journal_octave_view(psd, panel.per_octave())
9828
9888
 
@@ -10320,7 +10380,7 @@ class MainWindow(QMainWindow):
10320
10380
  return self._render_banded(
10321
10381
  spec_name, spec, spec_records, m_data, m_name,
10322
10382
  m_records,
10323
- scale_db=self._family_scale_db(spec, m_name))
10383
+ scale_db=self.project.comparison_scale(spec, m_name))
10324
10384
  # a specification bounds autospectra; drawn beside a full CPSD its 30
10325
10385
  # cross terms have no limit near them and bury the six that do —
10326
10386
  # and the window resolves each measured entry's comparison scale
@@ -11813,7 +11873,8 @@ class MainWindow(QMainWindow):
11813
11873
  def read_as_specification_act(self) -> None:
11814
11874
  """The bar's Read as Specification: the selected PSD becomes the
11815
11875
  requirement it is, in place (`Project.read_as_specification`)."""
11816
- self._read_as(Psd, 'Select a PSD to read as a specification',
11876
+ self._read_as('read_as_specification',
11877
+ 'Select a PSD to read as a specification',
11817
11878
  self.project.read_as_specification,
11818
11879
  'read as a specification — compare a measurement '
11819
11880
  'against it by selecting both')
@@ -11821,23 +11882,26 @@ class MainWindow(QMainWindow):
11821
11882
  def read_as_psd_act(self) -> None:
11822
11883
  """The bar's Read as PSD: the selected specification is plain
11823
11884
  data again, in place (`Project.read_as_psd`)."""
11824
- self._read_as(Specification, 'Select a specification to read as a PSD',
11885
+ self._read_as('read_as_psd',
11886
+ 'Select a specification without limits to read as a PSD',
11825
11887
  self.project.read_as_psd, 'read as a PSD')
11826
11888
 
11827
- def _read_as(self, kind, refusal, verb, said) -> None:
11889
+ def _read_as(self, verb_name, refusal, verb, said) -> None:
11828
11890
  """Swap the selected object for its other reading, under the
11829
11891
  same name: the tree row, its grid and the view follow, the way a
11830
- recompute's in-place swap does."""
11831
- acted = self._act_on(kind, refusal, verb)
11892
+ recompute's in-place swap does. Whether it applies is the
11893
+ project's own answer (`Project.verbs`), the one the bar asked."""
11894
+ def applies(obj):
11895
+ return obj is not None and verb_name in dict(
11896
+ self.project.verbs(self.project.name_of(obj)))
11897
+
11898
+ acted = self._act_on(applies, refusal, verb)
11832
11899
  if acted is None:
11833
11900
  return
11834
11901
  name = acted[0]
11835
- obj = self.objects[name]
11836
11902
  item = self._item_for_object(name)
11837
11903
  if item is not None:
11838
- self._refresh_item(item, obj)
11839
- item.setToolTip(0, self._object_tooltip(name, obj))
11840
- self._build_children(item, obj, name)
11904
+ self._refresh_item(item, self.objects[name])
11841
11905
  self._report_content_changed()
11842
11906
  self.render_current()
11843
11907
  self._show_status(f'{name} {said}')
@@ -12330,8 +12394,6 @@ class MainWindow(QMainWindow):
12330
12394
  # every mode's residues. Chosen by the project's rule, and
12331
12395
  # journaled with the records, so the fit_modes line replays
12332
12396
  # 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
12397
  coherence = self.project.fit_coherence(name, records)
12336
12398
  self.fit = ModalFitSession(
12337
12399
  obj, records,
@@ -13253,8 +13315,10 @@ class MainWindow(QMainWindow):
13253
13315
  f'at={picks!r}'
13254
13316
  + (f', refine={self.fit.refined}' if self.fit.refined
13255
13317
  else '')
13256
- + (f', records={self._fit_records!r}'
13257
- if self._fit_records is not None else '')
13318
+ # the session's own rows: every record is the default
13319
+ + (f', records={self.fit.rows!r}'
13320
+ if self.fit.rows != list(range(self.fit.frf.num_records))
13321
+ else '')
13258
13322
  + (f', coherence={self._fit_coherence_name!r}'
13259
13323
  if self._fit_coherence_name else '')
13260
13324
  + f', name={self.fit_object_name!r})')
@@ -13291,17 +13355,12 @@ class MainWindow(QMainWindow):
13291
13355
  if isinstance(data, Specification)]
13292
13356
  if len(specs) != 1:
13293
13357
  return None
13294
- return {name: self._family_scale_db(specs[0], name)
13358
+ return {name: self.project.comparison_scale(specs[0], name)
13295
13359
  for name, data, _r in series
13296
13360
  if isinstance(data, Psd)
13297
13361
  and not isinstance(data, Specification)
13298
13362
  and name in self.objects}
13299
13363
 
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
13364
  def _show_comparison_scaling(self, series):
13306
13365
  """Put the comparison's scaling on the bar, or take it away.
13307
13366
 
@@ -13325,7 +13384,7 @@ class MainWindow(QMainWindow):
13325
13384
  self.data_pane.show_scaling(None)
13326
13385
  return
13327
13386
  self._scaling_pair = (specs[0][0], measured[0][0])
13328
- db = self._family_scale_db(specs[0][1], measured[0][0])
13387
+ db = self.project.comparison_scale(specs[0][1], measured[0][0])
13329
13388
  self.data_pane.show_scaling(f'{db:+d} dB' if db else '0 dB')
13330
13389
 
13331
13390
  def _comparison_scale_edited(self, text):
@@ -13370,7 +13429,7 @@ class MainWindow(QMainWindow):
13370
13429
  else f'{measured_name} and {len(family) - 1} linked '
13371
13430
  f'PSD{"s" * (len(family) > 2)}')
13372
13431
  if value is None:
13373
- detected = self._family_scale_db(spec, measured_name)
13432
+ detected = self.project.comparison_scale(spec, measured_name)
13374
13433
  self._show_status('Scaling returned to automatic — detected '
13375
13434
  f'{detected:+d} dB from the data')
13376
13435
  else:
@@ -13399,7 +13458,7 @@ class MainWindow(QMainWindow):
13399
13458
  return None
13400
13459
  name, data, records = measured[0]
13401
13460
  rows = compare_all(specs[0][0], data, specs[0][1], records,
13402
- scale_db=self._family_scale_db(specs[0][0], name))
13461
+ scale_db=self.project.comparison_scale(specs[0][0], name))
13403
13462
  if not rows:
13404
13463
  return None
13405
13464
  # a pair that is not compared says why, on the status line the