visualdynamics 0.1.0a67__tar.gz → 0.1.0a69__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.0a67/src/visualdynamics.egg-info → visualdynamics-0.1.0a69}/PKG-INFO +2 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/pyproject.toml +4 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/__init__.py +1 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/compatibility.py +24 -3
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/channel_table.py +39 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/geometry.py +34 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/instrumentation.py +104 -10
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/tables.py +31 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/__init__.py +37 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/icons.py +99 -25
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/instrumentation_panel.py +30 -12
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/main_window.py +376 -95
- visualdynamics-0.1.0a67/src/visualdynamics/gui/rigid_panel.py → visualdynamics-0.1.0a69/src/visualdynamics/gui/modes_panel.py +184 -22
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/object_tables.py +36 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/panes.py +38 -34
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/excel.py +7 -5
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/project.py +89 -10
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/geometry.py +72 -9
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69/src/visualdynamics.egg-info}/PKG-INFO +2 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics.egg-info/SOURCES.txt +1 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics.egg-info/requires.txt +1 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/LICENSE +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/README.md +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/setup.cfg +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/__main__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/author.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/averaging.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/classical_pulse.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/compliance.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/correlate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/data.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/detect.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/dwell.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/entities.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/fem.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/filters.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/half_power.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/hexmesh.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/kurtosis.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/matches.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/mates.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/merge.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/mesh.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/modal_fit.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/octave.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/photos.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/progress.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/replication.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/replication_fit.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/replication_segments.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/report.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/rigid.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/shaker.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/shapes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/shock_check.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/shocks.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/signature.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/sine.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/snr.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/solids.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/sparse_solve.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/srs.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/stroke.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/sweep_rate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/tetmesh.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/time_statistics.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/tone_readout.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/transform.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/truncate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/unit_choices.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/validate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/wavelet.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/decimate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/deform.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/demo/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/demo/barc.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/demo/drone.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/demo/frame_wing.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/demo/plate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/demo/shapes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/app_icon.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/ask.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/author_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/averaging_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/console.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/disclaimer.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/drawer_tab.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/dwell_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/editors.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/filter_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/glyphs.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/mate_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/merge_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/mesh_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/mesh_solids_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/message_bar.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/movie.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/octave_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/opengl.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/pattern_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/preferences.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/project_tree.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/pulse_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/record_grid.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/replication_fit_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/report_editor.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/run_reports.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/segments_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/settings_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/shock_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/signature_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/sine_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/srs_tolerance_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/stage_drag.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/statistics_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/stream_window.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/stroke_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/sweep_rate_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/tables.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/tone_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/toolbars.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/tooltips.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/truncate_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/updater.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/wavelet_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/gui/windows.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/adf.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_objects.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/exodus.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/exporters.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/femap.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/matlab.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/nastran.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/native.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/notes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/photo_files.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/punch.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/rattlesnake.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/report_template.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/sdynpy_data.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/sdynpy_npz.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/sep005.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/sniffing.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/step.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/stl.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/threemf.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/io/unv.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/names.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/averaging.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/bands.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/bars.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/classical_pulse.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/dwell.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/filtering.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/half_power.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/replication_fit.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/replication_segments.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/scalogram.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/shock_check.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/shocks.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/signature.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/srs_tolerance.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/stroke.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/sweep_rate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/time_statistics.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/tone_readout.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/tracking_filter.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/plot/truncation.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/py.typed +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/report/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/report/markdown.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/report/page.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/rotate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/theme.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/units.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/update.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/_quiet.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/animate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/banded.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/mac_bars.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/marks.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/paired.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/pick.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/rigid.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/scalogram.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/sinespec.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/viz/waterfall.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics.egg-info/entry_points.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: visualdynamics
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a69
|
|
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
|
|
@@ -32,6 +32,7 @@ Requires-Dist: pyvista
|
|
|
32
32
|
Requires-Dist: pyvistaqt
|
|
33
33
|
Requires-Dist: vtk
|
|
34
34
|
Requires-Dist: PySide6
|
|
35
|
+
Requires-Dist: shiboken6
|
|
35
36
|
Requires-Dist: pyqtgraph
|
|
36
37
|
Requires-Dist: openpyxl
|
|
37
38
|
Requires-Dist: pandas
|
|
@@ -79,6 +79,10 @@ dependencies = [
|
|
|
79
79
|
# pyvista's own cap is what decides which VTK is installed.
|
|
80
80
|
"vtk",
|
|
81
81
|
"PySide6",
|
|
82
|
+
# PySide6's own binding layer (LGPL-3.0, the same terms), installed
|
|
83
|
+
# with it: imported by name for the exit's window release
|
|
84
|
+
# (gui.release_widgets), so declared rather than relied on arriving
|
|
85
|
+
"shiboken6",
|
|
82
86
|
"pyqtgraph",
|
|
83
87
|
"openpyxl",
|
|
84
88
|
# A channel table is a spreadsheet, and a DataFrame is what carries a
|
|
@@ -97,7 +97,7 @@ from .units import (
|
|
|
97
97
|
#: alpha, said in the version itself so the dialog, the About text, the
|
|
98
98
|
#: installers and latest.json all say the same thing, and the first
|
|
99
99
|
#: non-alpha release is offered as an upgrade (Brandon, 2026-09-11)
|
|
100
|
-
__version__ = '0.1.
|
|
100
|
+
__version__ = '0.1.0a69'
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
|
|
@@ -137,6 +137,24 @@ def _listed(dofs: Sequence[str]) -> str:
|
|
|
137
137
|
return text
|
|
138
138
|
|
|
139
139
|
|
|
140
|
+
def _channel_named(table: Any, row: int) -> str:
|
|
141
|
+
"""A channel as the issue lists it: its DOF, or, before it has one,
|
|
142
|
+
its number and what it still lacks ('channel 3 (no node yet)'). A
|
|
143
|
+
channel with a node and no direction is listed only when the node is
|
|
144
|
+
not on the geometry, so its node is named: 'channel 5 at node 9 (no
|
|
145
|
+
direction yet)'."""
|
|
146
|
+
# A channel added blank listed as '', so the message ended at its
|
|
147
|
+
# colon with nothing named (Brandon, 2026-10-22).
|
|
148
|
+
node, direction = str(table['node'][row]), str(table['direction'][row])
|
|
149
|
+
number = int(table['channel'][row])
|
|
150
|
+
if node and direction:
|
|
151
|
+
return table.dof_strings()[row]
|
|
152
|
+
if node:
|
|
153
|
+
return f'channel {number} at node {node} (no direction yet)'
|
|
154
|
+
lacking = 'node' if direction else 'node or direction'
|
|
155
|
+
return f'channel {number} (no {lacking} yet)'
|
|
156
|
+
|
|
157
|
+
|
|
140
158
|
def modal_object(obj: Any) -> int | None:
|
|
141
159
|
"""The highest mode a data object's coordinates name, when every
|
|
142
160
|
one of its DOFs is a modal coordinate, else None."""
|
|
@@ -283,12 +301,15 @@ def check_object(name: str, obj: Any, geometry: Geometry | None,
|
|
|
283
301
|
if geometry.missing_dofs([dof])]
|
|
284
302
|
if not flagged:
|
|
285
303
|
return None
|
|
286
|
-
|
|
304
|
+
named = [_channel_named(obj, i) for i in flagged]
|
|
287
305
|
return Issue(
|
|
288
306
|
name=name, kind='dofs-not-in-geometry',
|
|
289
307
|
message=(f'{len(flagged)} of {len(dofs)} channels are not in '
|
|
290
|
-
f'geometry {geometry_name!r}: {_listed(
|
|
291
|
-
|
|
308
|
+
f'geometry {geometry_name!r}: {_listed(named)}'),
|
|
309
|
+
# only what is a DOF: a channel not yet placed names none
|
|
310
|
+
missing_dofs=[dofs[i] for i in flagged
|
|
311
|
+
if str(obj['node'][i]) and str(obj['direction'][i])],
|
|
312
|
+
sub_items=flagged)
|
|
292
313
|
|
|
293
314
|
return None
|
|
294
315
|
|
{visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/channel_table.py
RENAMED
|
@@ -359,7 +359,10 @@ class ChannelTable:
|
|
|
359
359
|
dtype='int64')
|
|
360
360
|
else:
|
|
361
361
|
frame[name] = pd.Series(values, dtype='string').fillna('')
|
|
362
|
-
|
|
362
|
+
# a Series of its own type, as the other text columns: a bare list
|
|
363
|
+
# with no channels in it read as an empty float column
|
|
364
|
+
frame['channel_type'] = pd.Series(cls._typed_by_unit(frame),
|
|
365
|
+
dtype='string')
|
|
363
366
|
return frame
|
|
364
367
|
|
|
365
368
|
@classmethod
|
|
@@ -559,6 +562,41 @@ class ChannelTable:
|
|
|
559
562
|
declared = str(self.frame.loc[row, 'channel_type'])
|
|
560
563
|
return list(ORDINATE_UNITS.get(declared, ALL_ORDINATE_UNITS))
|
|
561
564
|
|
|
565
|
+
def add_channels(self, count: int = 1) -> list[int]:
|
|
566
|
+
"""Add blank channels at the end of the table, numbered on from
|
|
567
|
+
the highest channel in it (from 1 in an empty table).
|
|
568
|
+
|
|
569
|
+
Every other cell takes its column's default, so the node,
|
|
570
|
+
direction and unit are blank until they are typed in.
|
|
571
|
+
|
|
572
|
+
Parameters
|
|
573
|
+
----------
|
|
574
|
+
count : int, default 1
|
|
575
|
+
How many channels to add.
|
|
576
|
+
|
|
577
|
+
Returns
|
|
578
|
+
-------
|
|
579
|
+
list of int
|
|
580
|
+
The new channels' numbers.
|
|
581
|
+
|
|
582
|
+
Raises
|
|
583
|
+
------
|
|
584
|
+
ValueError
|
|
585
|
+
If `count` is less than 1.
|
|
586
|
+
"""
|
|
587
|
+
import pandas as pd
|
|
588
|
+
|
|
589
|
+
count = int(count)
|
|
590
|
+
if count < 1:
|
|
591
|
+
raise ValueError(f'cannot add {count} channels; add at least one')
|
|
592
|
+
start = int(self.frame['channel'].max()) + 1 if self.num_channels else 1
|
|
593
|
+
numbers = list(range(start, start + count))
|
|
594
|
+
added = self._typed(pd.DataFrame(
|
|
595
|
+
{name: numbers if name == 'channel' else [spec.default] * count
|
|
596
|
+
for name, spec in self.COLUMNS.items()}))
|
|
597
|
+
self.frame = pd.concat([self.frame, added], ignore_index=True)
|
|
598
|
+
return numbers
|
|
599
|
+
|
|
562
600
|
def delete_channels(self, indices: Sequence[int]) -> None:
|
|
563
601
|
"""Remove the given rows in place. At least one row must remain.
|
|
564
602
|
|
|
@@ -2700,3 +2700,37 @@ class Geometry:
|
|
|
2700
2700
|
from ..viz.geometry import plot_dofs
|
|
2701
2701
|
return plot_dofs(self, source, quantity, unit_system=unit_system,
|
|
2702
2702
|
**kwargs)
|
|
2703
|
+
|
|
2704
|
+
def plot_channels(self, table: Any, role: str = 'all',
|
|
2705
|
+
unit_system: UnitSystem | None = None,
|
|
2706
|
+
**kwargs: Any) -> Any:
|
|
2707
|
+
"""Draw this geometry with a labeled arrow at each channel's DOF
|
|
2708
|
+
in a channel table, as the app does when the two are selected
|
|
2709
|
+
together.
|
|
2710
|
+
|
|
2711
|
+
A channel with no node or direction yet, or at a node this
|
|
2712
|
+
geometry does not have, has no arrow.
|
|
2713
|
+
|
|
2714
|
+
Parameters
|
|
2715
|
+
----------
|
|
2716
|
+
table : ChannelTable
|
|
2717
|
+
The channels to draw.
|
|
2718
|
+
role : str, default 'all'
|
|
2719
|
+
'reference', 'response' or 'monitor' to draw only the
|
|
2720
|
+
channels in that role, 'control' for the channels with
|
|
2721
|
+
Control checked, or 'all' for every channel.
|
|
2722
|
+
unit_system : UnitSystem, optional
|
|
2723
|
+
Units to draw in.
|
|
2724
|
+
**kwargs
|
|
2725
|
+
Passed through to the scene.
|
|
2726
|
+
|
|
2727
|
+
Returns
|
|
2728
|
+
-------
|
|
2729
|
+
object
|
|
2730
|
+
The plotter the scene is in.
|
|
2731
|
+
"""
|
|
2732
|
+
# Brandon, 2026-10-22: a channel table and a geometry selected
|
|
2733
|
+
# together draw the table's DOFs, picked by role.
|
|
2734
|
+
from ..viz.geometry import plot_channels
|
|
2735
|
+
return plot_channels(self, table, role, unit_system=unit_system,
|
|
2736
|
+
**kwargs)
|
{visualdynamics-0.1.0a67 → visualdynamics-0.1.0a69}/src/visualdynamics/core/instrumentation.py
RENAMED
|
@@ -23,7 +23,10 @@ surface allows. A node where surfaces meet at an edge or a corner is a
|
|
|
23
23
|
candidate on each of them, so that a sensor sits flat on one; within a
|
|
24
24
|
surface the faces' normals at the node are averaged. A plate's normal
|
|
25
25
|
points away from the middle of the model. Beams contribute every node,
|
|
26
|
-
measured in the global system.
|
|
26
|
+
measured in the global system. A place nearer than `edge_distance` to
|
|
27
|
+
its surface's edge is not a candidate, since a sensor's base needs flat
|
|
28
|
+
ground around the spot it is glued to; an edge here is wherever the
|
|
29
|
+
surface ends, at a sharp edge, a free edge, a hole or where parts touch.
|
|
27
30
|
|
|
28
31
|
`local_shapes` turns a shape set's translations into each candidate's
|
|
29
32
|
own directions, and `effective_independence` chooses the sensors:
|
|
@@ -52,7 +55,16 @@ import numpy as np
|
|
|
52
55
|
#: neighboring faces whose outward normals differ by more than this, in
|
|
53
56
|
#: degrees, are on separate surfaces: a block's edge is 90, a
|
|
54
57
|
#: cylinder's facets a few degrees each
|
|
55
|
-
|
|
58
|
+
# Brandon, 2026-10-22: 20 degrees, from 40
|
|
59
|
+
EDGE_ANGLE = 20.0
|
|
60
|
+
|
|
61
|
+
#: how far a sensor stays from its surface's edge, in meters: a quarter
|
|
62
|
+
#: of an inch (Brandon, 2026-10-22)
|
|
63
|
+
EDGE_DISTANCE = 0.25 * 0.0254
|
|
64
|
+
|
|
65
|
+
#: a surface of less than this share of the whole outside's area is
|
|
66
|
+
#: left out unless picked (`default_surfaces`)
|
|
67
|
+
SMALL_SURFACE = 0.01
|
|
56
68
|
|
|
57
69
|
#: removal in batches stops once this many times the wanted count
|
|
58
70
|
#: remain, and the rest is removed one at a time
|
|
@@ -231,8 +243,35 @@ def skin(geometry: Any, *, edge_angle: float = EDGE_ANGLE) -> list[Surface]:
|
|
|
231
243
|
return found
|
|
232
244
|
|
|
233
245
|
|
|
246
|
+
def default_surfaces(found: list[Surface]) -> list[int]:
|
|
247
|
+
"""The surfaces sensors go on unless told otherwise: every one of at
|
|
248
|
+
least `SMALL_SURFACE` of the whole outside's area. Sensors go on
|
|
249
|
+
the significant surfaces of a test article; a small one is most
|
|
250
|
+
often a sliver along an edge, a fillet or a hole's bore. When no
|
|
251
|
+
surface is that large, all of them.
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
found : list of Surface
|
|
256
|
+
From `skin`.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
list of int
|
|
261
|
+
Their ids, in order.
|
|
262
|
+
"""
|
|
263
|
+
# Brandon, 2026-10-22: at 20 degrees the cone split into 193
|
|
264
|
+
# surfaces, most of them a single element; "any surfaces less than
|
|
265
|
+
# 1% of the total surface area should be de-selected by default ...
|
|
266
|
+
# we just mount to significant surfaces usually"
|
|
267
|
+
total = sum(s.area for s in found)
|
|
268
|
+
kept = [s.id for s in found if s.area >= SMALL_SURFACE * total]
|
|
269
|
+
return kept or [s.id for s in found]
|
|
270
|
+
|
|
271
|
+
|
|
234
272
|
def candidates(geometry: Any, groups: Any = None, surfaces: Any = None, *,
|
|
235
|
-
edge_angle: float = EDGE_ANGLE, found: Any = None
|
|
273
|
+
edge_angle: float = EDGE_ANGLE, found: Any = None,
|
|
274
|
+
edge_distance: float = 0.0) -> Candidates:
|
|
236
275
|
"""Every place on `geometry` a sensor can be mounted, as an
|
|
237
276
|
experimental geometry with a coordinate system per place.
|
|
238
277
|
|
|
@@ -244,13 +283,19 @@ def candidates(geometry: Any, groups: Any = None, surfaces: Any = None, *,
|
|
|
244
283
|
The element groups sensors may go on. All of them when omitted.
|
|
245
284
|
A face shared with a group that is not picked is still inside.
|
|
246
285
|
surfaces : sequence of int, optional
|
|
247
|
-
The surfaces sensors may go on, by `Surface.id`.
|
|
248
|
-
`groups` when omitted
|
|
286
|
+
The surfaces sensors may go on, by `Surface.id`. Those of
|
|
287
|
+
`groups` that `default_surfaces` keeps when omitted: every one
|
|
288
|
+
of at least `SMALL_SURFACE` of the outside's area.
|
|
249
289
|
edge_angle : float, default EDGE_ANGLE
|
|
250
290
|
Degrees between two neighboring faces' normals beyond which they
|
|
251
291
|
are on separate surfaces.
|
|
252
292
|
found : list of Surface, optional
|
|
253
293
|
`skin(geometry, edge_angle=edge_angle)`, when already found.
|
|
294
|
+
edge_distance : float, default 0
|
|
295
|
+
How near a place may be to its surface's edge, in the
|
|
296
|
+
geometry's coordinates (meters once its units are defined),
|
|
297
|
+
measured in a straight line. Nothing is left out at 0. A
|
|
298
|
+
surface with no edge, such as a whole sphere, keeps every place.
|
|
254
299
|
|
|
255
300
|
Returns
|
|
256
301
|
-------
|
|
@@ -260,11 +305,12 @@ def candidates(geometry: Any, groups: Any = None, surfaces: Any = None, *,
|
|
|
260
305
|
|
|
261
306
|
picked = ({int(g) for g in geometry.group_id} if groups is None
|
|
262
307
|
else {int(g) for g in groups})
|
|
263
|
-
wanted = None if surfaces is None else {int(s) for s in surfaces}
|
|
264
308
|
xyz = geometry.node_xyz
|
|
265
309
|
row_of = {int(n): k for k, n in enumerate(geometry.node_id)}
|
|
310
|
+
by_id = np.argsort(geometry.node_id)
|
|
266
311
|
if found is None:
|
|
267
312
|
found = skin(geometry, edge_angle=edge_angle)
|
|
313
|
+
wanted = set(default_surfaces(found) if surfaces is None else (int(s) for s in surfaces))
|
|
268
314
|
|
|
269
315
|
# a place per node per surface, the surface's faces at the node
|
|
270
316
|
# averaged by area; the largest first, so a node's own id goes to it
|
|
@@ -278,6 +324,11 @@ def candidates(geometry: Any, groups: Any = None, surfaces: Any = None, *,
|
|
|
278
324
|
at, where = np.unique(corner_nodes, return_inverse=True)
|
|
279
325
|
summed = np.zeros((len(at), 3))
|
|
280
326
|
np.add.at(summed, where, np.repeat(weight, per_face, axis=0))
|
|
327
|
+
if edge_distance > 0:
|
|
328
|
+
rows = by_id[np.searchsorted(geometry.node_id, at, sorter=by_id)]
|
|
329
|
+
away = _away_from_edge(surface.faces, at, xyz[rows], xyz, by_id,
|
|
330
|
+
geometry.node_id, edge_distance)
|
|
331
|
+
at, summed = at[away], summed[away]
|
|
281
332
|
nodes.append(at)
|
|
282
333
|
sums.append(summed)
|
|
283
334
|
kinds.extend([surface.kind] * len(at))
|
|
@@ -623,7 +674,8 @@ class Plan:
|
|
|
623
674
|
|
|
624
675
|
def plan(shapes: Any, geometry: Any, triaxial: int, uniaxial: int = 0, *,
|
|
625
676
|
modes: Any = None, groups: Any = None, surfaces: Any = None,
|
|
626
|
-
edge_angle: float | None = None, axes: Any = (2,)
|
|
677
|
+
edge_angle: float | None = None, axes: Any = (2,),
|
|
678
|
+
edge_distance: float | None = None) -> Plan:
|
|
627
679
|
"""Plan a test's accelerometers from a finite element model.
|
|
628
680
|
|
|
629
681
|
Parameters
|
|
@@ -642,14 +694,20 @@ def plan(shapes: Any, geometry: Any, triaxial: int, uniaxial: int = 0, *,
|
|
|
642
694
|
groups : sequence of int, optional
|
|
643
695
|
The element groups sensors may go on. All when omitted.
|
|
644
696
|
surfaces : sequence of int, optional
|
|
645
|
-
The surfaces sensors may go on, by `Surface.id`.
|
|
646
|
-
`groups` when omitted.
|
|
697
|
+
The surfaces sensors may go on, by `Surface.id`. Those of
|
|
698
|
+
`groups` that `default_surfaces` keeps when omitted.
|
|
647
699
|
edge_angle : float, optional
|
|
648
700
|
Degrees between neighboring faces' normals beyond which they are
|
|
649
701
|
on separate surfaces; `EDGE_ANGLE` when omitted.
|
|
650
702
|
axes : sequence of int, default (2,)
|
|
651
703
|
The directions a uni-axial accelerometer may measure: 0, 1, 2
|
|
652
704
|
for a place's X, Y, Z. Z, the surface normal, by default.
|
|
705
|
+
edge_distance : float, optional
|
|
706
|
+
How near a sensor may be to its surface's edge, in the
|
|
707
|
+
geometry's coordinates. `EDGE_DISTANCE`, a quarter of an inch,
|
|
708
|
+
when omitted and the geometry's units are defined; 0 when they
|
|
709
|
+
are not, since a distance means nothing against coordinates
|
|
710
|
+
with no unit.
|
|
653
711
|
|
|
654
712
|
Returns
|
|
655
713
|
-------
|
|
@@ -665,8 +723,11 @@ def plan(shapes: Any, geometry: Any, triaxial: int, uniaxial: int = 0, *,
|
|
|
665
723
|
modes = [int(m) for m in modes]
|
|
666
724
|
if not modes:
|
|
667
725
|
raise ValueError('no modes to measure: every mode is a rigid-body mode at 0 Hz')
|
|
726
|
+
if edge_distance is None:
|
|
727
|
+
edge_distance = EDGE_DISTANCE if geometry.units_defined else 0.0
|
|
668
728
|
places = candidates(geometry, groups, surfaces,
|
|
669
|
-
edge_angle=EDGE_ANGLE if edge_angle is None else float(edge_angle)
|
|
729
|
+
edge_angle=EDGE_ANGLE if edge_angle is None else float(edge_angle),
|
|
730
|
+
edge_distance=float(edge_distance))
|
|
670
731
|
blocks, measured = local_shapes(shapes, modes, places, geometry)
|
|
671
732
|
placement = effective_independence(blocks, triaxial, uniaxial, axes=axes,
|
|
672
733
|
allowed=measured, sources=places.source,
|
|
@@ -770,6 +831,39 @@ def assemble(shapes: Any, modes: Any, places: Candidates, blocks: Any,
|
|
|
770
831
|
return Plan(kept_geometry, table, test_shapes, placement)
|
|
771
832
|
|
|
772
833
|
|
|
834
|
+
def _away_from_edge(faces: list, at: np.ndarray, points: np.ndarray, xyz: np.ndarray,
|
|
835
|
+
by_id: np.ndarray, node_id: np.ndarray, distance: float) -> np.ndarray:
|
|
836
|
+
"""Which of the nodes `at`, at `points`, are at least `distance`
|
|
837
|
+
from the edge of the surface made of `faces`: the sides only one of
|
|
838
|
+
its faces has."""
|
|
839
|
+
from scipy.spatial import cKDTree
|
|
840
|
+
|
|
841
|
+
sides = np.array([sorted((f[k], f[(k + 1) % len(f)])) for f in faces
|
|
842
|
+
for k in range(len(f))], dtype=np.int64).reshape(-1, 2)
|
|
843
|
+
unique, count = np.unique(sides, axis=0, return_counts=True)
|
|
844
|
+
edge = unique[count == 1]
|
|
845
|
+
if not len(edge):
|
|
846
|
+
return np.ones(len(at), dtype=bool)
|
|
847
|
+
ends = xyz[by_id[np.searchsorted(node_id, edge.ravel(), sorter=by_id)]].reshape(-1, 2, 3)
|
|
848
|
+
start, along = ends[:, 0], ends[:, 1] - ends[:, 0]
|
|
849
|
+
middle = start + along / 2
|
|
850
|
+
half = float(np.linalg.norm(along, axis=1).max()) / 2
|
|
851
|
+
# every side whose middle is near enough that the side could be
|
|
852
|
+
# nearer than `distance`, then its exact distance
|
|
853
|
+
near = cKDTree(middle).query_ball_point(points, distance + half, return_sorted=False)
|
|
854
|
+
which = np.repeat(np.arange(len(at)), [len(n) for n in near])
|
|
855
|
+
side = np.fromiter((k for n in near for k in n), dtype=np.int64, count=len(which))
|
|
856
|
+
if not len(side):
|
|
857
|
+
return np.ones(len(at), dtype=bool)
|
|
858
|
+
span = np.einsum('ij,ij->i', along[side], along[side])
|
|
859
|
+
t = np.einsum('ij,ij->i', points[which] - start[side], along[side])
|
|
860
|
+
t = np.clip(np.divide(t, span, out=np.zeros_like(t), where=span > 0), 0.0, 1.0)
|
|
861
|
+
gap = np.linalg.norm(points[which] - start[side] - t[:, None] * along[side], axis=1)
|
|
862
|
+
away = np.ones(len(at), dtype=bool)
|
|
863
|
+
away[which[gap < distance]] = False
|
|
864
|
+
return away
|
|
865
|
+
|
|
866
|
+
|
|
773
867
|
def _remove(kept: np.ndarray, loss: np.ndarray, target: int) -> np.ndarray:
|
|
774
868
|
"""`kept` less its weakest: a tenth of it while far more remain than
|
|
775
869
|
are wanted, otherwise the single weakest."""
|
|
@@ -76,6 +76,37 @@ def cell_text(value: Any, decimals: int | None) -> str:
|
|
|
76
76
|
return str(value) if decimals is None else f'{value:.{decimals}f}'
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def mode_heading(mode: int) -> str:
|
|
80
|
+
"""The column heading of mode `mode` (from 0) in a table of shape
|
|
81
|
+
values: 'M1' for the first."""
|
|
82
|
+
return f'M{mode + 1}'
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def shape_value_text(value: Any) -> str:
|
|
86
|
+
"""One shape value as text, to six significant figures; a complex
|
|
87
|
+
one as its real and imaginary parts, '0.5-0.25j'."""
|
|
88
|
+
import numpy as np
|
|
89
|
+
|
|
90
|
+
if np.iscomplexobj(value):
|
|
91
|
+
return f'{value.real:.6g}{value.imag:+.6g}j'
|
|
92
|
+
return f'{float(value):.6g}'
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def shape_values(shapes: Any) -> tuple[list[str], list[list[str]]]:
|
|
96
|
+
"""``(headers, rows)`` of a shape set's values: a row per DOF, the
|
|
97
|
+
DOF first, then a column per mode ('M1', 'M2', ...).
|
|
98
|
+
|
|
99
|
+
Rows are DOFs and columns modes, the way a mode shape matrix is
|
|
100
|
+
usually written, which is the transpose of `ShapeSet.shape_matrix`.
|
|
101
|
+
"""
|
|
102
|
+
# Brandon, 2026-10-22, with the table of them beside the mode list.
|
|
103
|
+
headers = ['DOF'] + [mode_heading(m) for m in range(shapes.num_shapes)]
|
|
104
|
+
rows = [[str(dof)] + [shape_value_text(value)
|
|
105
|
+
for value in shapes.shape_matrix[:, i]]
|
|
106
|
+
for i, dof in enumerate(shapes.coordinate)]
|
|
107
|
+
return headers, rows
|
|
108
|
+
|
|
109
|
+
|
|
79
110
|
def table_of(obj: Any, geometry: Any = None,
|
|
80
111
|
objects: Mapping[str, Any] | None = None
|
|
81
112
|
) -> tuple[list[str], list[list[str]]] | None:
|
|
@@ -244,4 +244,41 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
244
244
|
# while both still stand.
|
|
245
245
|
app.clipboard().clear()
|
|
246
246
|
app.processEvents()
|
|
247
|
+
release_widgets(app)
|
|
247
248
|
return code
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def release_widgets(app: Any) -> int:
|
|
252
|
+
"""Delete the windows Python still owns, one by one, while the
|
|
253
|
+
application stands, and return how many there were.
|
|
254
|
+
|
|
255
|
+
Left to the interpreter's exit, PySide deletes every widget it still
|
|
256
|
+
owns in one sweep over its own table, in no particular order. A
|
|
257
|
+
window deleted there takes its children with it, and the sweep then
|
|
258
|
+
reaches a child it still lists and deletes it again: the process
|
|
259
|
+
segfaults on its way out. Deleting each window here, through its
|
|
260
|
+
wrapper, leaves the sweep nothing to do.
|
|
261
|
+
"""
|
|
262
|
+
# a gate worker segfaulted at exit three times on 2026-10-22, in
|
|
263
|
+
# PySide's moduleShutdown sweep, on a QListWidget, a QComboBox and a
|
|
264
|
+
# QRadioButton of dialogs the tests had left open; PLAN.md, "Nothing
|
|
265
|
+
# of Python's left for PySide's sweep"
|
|
266
|
+
import gc
|
|
267
|
+
|
|
268
|
+
import shiboken6
|
|
269
|
+
from PySide6.QtCore import QCoreApplication, QEvent
|
|
270
|
+
|
|
271
|
+
# the cycles first: a window held by nothing but its own connections
|
|
272
|
+
# goes the way Python owns it, not the sweep's
|
|
273
|
+
gc.collect()
|
|
274
|
+
released = 0
|
|
275
|
+
for widget in app.topLevelWidgets():
|
|
276
|
+
# through shiboken, not deleteLater: a window Qt deletes leaves
|
|
277
|
+
# its children's wrappers standing, and the sweep still crashed
|
|
278
|
+
if (shiboken6.isValid(widget) and shiboken6.ownedByPython(widget)
|
|
279
|
+
and widget.parent() is None):
|
|
280
|
+
shiboken6.delete(widget)
|
|
281
|
+
released += 1
|
|
282
|
+
QCoreApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete)
|
|
283
|
+
app.processEvents()
|
|
284
|
+
return released
|
|
@@ -162,12 +162,7 @@ def _draw_geometry(painter, color):
|
|
|
162
162
|
def _draw_new_geometry(painter, color):
|
|
163
163
|
"""The geometry's frame with a plus: an empty geometry to build."""
|
|
164
164
|
_draw_geometry(painter, color)
|
|
165
|
-
painter
|
|
166
|
-
painter.setBrush(QBrush(QColor('#2ca02c')))
|
|
167
|
-
painter.drawEllipse(QPointF(50, 50), 13, 13)
|
|
168
|
-
painter.setBrush(QBrush(QColor('white')))
|
|
169
|
-
painter.drawRoundedRect(QRectF(48, 42, 4, 16), 1, 1)
|
|
170
|
-
painter.drawRoundedRect(QRectF(42, 48, 16, 4), 1, 1)
|
|
165
|
+
_plus(painter, 50, 50)
|
|
171
166
|
|
|
172
167
|
|
|
173
168
|
#: A pulse and its ringdown. The shape a time history wears, and the
|
|
@@ -284,6 +279,12 @@ def _draw_specification(painter, color):
|
|
|
284
279
|
_psd_body(painter, color)
|
|
285
280
|
|
|
286
281
|
|
|
282
|
+
def _draw_new_specification(painter, color):
|
|
283
|
+
"""The specification's glyph with a plus: a specification to write."""
|
|
284
|
+
_draw_specification(painter, color)
|
|
285
|
+
_plus(painter, 50, 50)
|
|
286
|
+
|
|
287
|
+
|
|
287
288
|
def _draw_srs(painter, color):
|
|
288
289
|
"""The shape every shock response spectrum has.
|
|
289
290
|
|
|
@@ -969,6 +970,65 @@ def _draw_channel_table(painter, color):
|
|
|
969
970
|
painter.drawLine(QPointF(32, 15), QPointF(32, 49))
|
|
970
971
|
|
|
971
972
|
|
|
973
|
+
def _plus(painter, x, y):
|
|
974
|
+
"""The green disc with a white plus that marks a control as making
|
|
975
|
+
something new, centered at `(x, y)`."""
|
|
976
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
977
|
+
painter.setBrush(QBrush(QColor('#2ca02c')))
|
|
978
|
+
painter.drawEllipse(QPointF(x, y), 13, 13)
|
|
979
|
+
painter.setBrush(QBrush(QColor('white')))
|
|
980
|
+
painter.drawRoundedRect(QRectF(x - 2, y - 8, 4, 16), 1, 1)
|
|
981
|
+
painter.drawRoundedRect(QRectF(x - 8, y - 2, 16, 4), 1, 1)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def _draw_new_channel_table(painter, color):
|
|
985
|
+
"""The channel table with a plus: an empty one to fill in."""
|
|
986
|
+
_draw_channel_table(painter, color)
|
|
987
|
+
_plus(painter, 50, 50)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def _draw_add_channel(painter, color):
|
|
991
|
+
"""A header and one row, with a plus on the row: a channel added to
|
|
992
|
+
the table."""
|
|
993
|
+
header = QColor(color)
|
|
994
|
+
header.setAlpha(110)
|
|
995
|
+
painter.fillRect(QRectF(8, 16, 48, 10), QBrush(header))
|
|
996
|
+
painter.setPen(_pen(color, 4))
|
|
997
|
+
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
998
|
+
painter.drawRoundedRect(QRectF(8, 16, 48, 22), 3, 3)
|
|
999
|
+
painter.drawLine(QPointF(8, 26), QPointF(56, 26))
|
|
1000
|
+
painter.drawLine(QPointF(32, 16), QPointF(32, 38))
|
|
1001
|
+
_plus(painter, 32, 50)
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def _draw_mac(painter, color):
|
|
1005
|
+
"""A three by three grid, its diagonal full and the rest faint: a
|
|
1006
|
+
set's MAC against itself."""
|
|
1007
|
+
faint = QColor(color)
|
|
1008
|
+
faint.setAlpha(70)
|
|
1009
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
1010
|
+
for row in range(3):
|
|
1011
|
+
for column in range(3):
|
|
1012
|
+
painter.setBrush(QBrush(QColor(color) if row == column else faint))
|
|
1013
|
+
painter.drawRect(QRectF(10 + 15 * column, 10 + 15 * row, 13, 13))
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def _draw_shape_values(painter, color):
|
|
1017
|
+
"""A table headed along its top and down its left side: values with
|
|
1018
|
+
a DOF for each row and a mode for each column."""
|
|
1019
|
+
header = QColor(color)
|
|
1020
|
+
header.setAlpha(110)
|
|
1021
|
+
painter.fillRect(QRectF(11, 15, 42, 10), QBrush(header))
|
|
1022
|
+
painter.fillRect(QRectF(11, 25, 12, 24), QBrush(header))
|
|
1023
|
+
painter.setPen(_pen(color, 4))
|
|
1024
|
+
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
1025
|
+
painter.drawRoundedRect(QRectF(11, 15, 42, 34), 3, 3)
|
|
1026
|
+
for y in (25, 37):
|
|
1027
|
+
painter.drawLine(QPointF(11, y), QPointF(53, y))
|
|
1028
|
+
for x in (23, 38):
|
|
1029
|
+
painter.drawLine(QPointF(x, 15), QPointF(x, 49))
|
|
1030
|
+
|
|
1031
|
+
|
|
972
1032
|
def _draw_shape_set(painter, color):
|
|
973
1033
|
"""A deformed mode shape with nodes riding on it."""
|
|
974
1034
|
points = [(10 + i, 32 - 16 * math.sin(i / 44 * 2 * math.pi))
|
|
@@ -1867,40 +1927,50 @@ def _draw_coordinate_systems(painter, color):
|
|
|
1867
1927
|
painter.drawEllipse(origin, 5, 5)
|
|
1868
1928
|
|
|
1869
1929
|
|
|
1930
|
+
def _depth(painter, color, *edges):
|
|
1931
|
+
"""The edges a solid's face is extruded along, thinner than the face
|
|
1932
|
+
so they read as going back.
|
|
1933
|
+
|
|
1934
|
+
`_element_shape` leaves the pen off to fill its node dots, so these
|
|
1935
|
+
take a pen of their own.
|
|
1936
|
+
"""
|
|
1937
|
+
# drawn with the dots' empty pen they were invisible: the tet wore
|
|
1938
|
+
# the triangle's icon and the brick the quad's (Brandon, 2026-10-22)
|
|
1939
|
+
painter.setPen(_pen(color, 5))
|
|
1940
|
+
for start, end in edges:
|
|
1941
|
+
painter.drawLine(QPointF(*start), QPointF(*end))
|
|
1942
|
+
|
|
1943
|
+
|
|
1870
1944
|
def _draw_tet(painter, color):
|
|
1871
|
-
"""A tetrahedron: a triangle with its
|
|
1945
|
+
"""A tetrahedron: a triangle with its far corner inside, joined to
|
|
1946
|
+
the three."""
|
|
1872
1947
|
_element_shape(painter, color, [(32, 10), (54, 50), (10, 50)])
|
|
1873
|
-
painter
|
|
1874
|
-
|
|
1875
|
-
painter.drawLine(QPointF(10, 50), QPointF(34, 36))
|
|
1948
|
+
_depth(painter, color, ((32, 10), (34, 36)), ((54, 50), (34, 36)),
|
|
1949
|
+
((10, 50), (34, 36)))
|
|
1876
1950
|
|
|
1877
1951
|
|
|
1878
1952
|
def _draw_wedge(painter, color):
|
|
1879
|
-
"""A wedge:
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
painter
|
|
1884
|
-
painter
|
|
1885
|
-
|
|
1886
|
-
painter.drawLine(QPointF(40, 42), QPointF(54, 42))
|
|
1953
|
+
"""A wedge: an upright triangle extruded back and to the right, a
|
|
1954
|
+
triangular prism, the hidden edges left out as the brick leaves
|
|
1955
|
+
them."""
|
|
1956
|
+
# Brandon, 2026-10-22: the old sideways ramp did not read as a wedge
|
|
1957
|
+
_element_shape(painter, color, [(24, 24), (40, 54), (8, 54)])
|
|
1958
|
+
_depth(painter, color, ((24, 24), (38, 10)), ((40, 54), (54, 40)),
|
|
1959
|
+
((38, 10), (54, 40)))
|
|
1887
1960
|
|
|
1888
1961
|
|
|
1889
1962
|
def _draw_hex(painter, color):
|
|
1890
1963
|
"""A brick: a square with its back face offset."""
|
|
1891
1964
|
_element_shape(painter, color, [(10, 22), (42, 22), (42, 54), (10, 54)])
|
|
1892
|
-
painter
|
|
1893
|
-
|
|
1894
|
-
painter.drawLine(QPointF(42, 54), QPointF(54, 42))
|
|
1895
|
-
painter.drawLine(QPointF(22, 10), QPointF(54, 10))
|
|
1896
|
-
painter.drawLine(QPointF(54, 10), QPointF(54, 42))
|
|
1965
|
+
_depth(painter, color, ((10, 22), (22, 10)), ((42, 22), (54, 10)),
|
|
1966
|
+
((42, 54), (54, 42)), ((22, 10), (54, 10)), ((54, 10), (54, 42)))
|
|
1897
1967
|
|
|
1898
1968
|
|
|
1899
1969
|
def _draw_pyramid(painter, color):
|
|
1900
1970
|
"""A pyramid: a quad base with an apex."""
|
|
1901
1971
|
_element_shape(painter, color, [(10, 50), (42, 50), (54, 40), (22, 40)])
|
|
1902
|
-
|
|
1903
|
-
|
|
1972
|
+
_depth(painter, color, *(((corner), (32, 10))
|
|
1973
|
+
for corner in ((10, 50), (42, 50), (54, 40), (22, 40))))
|
|
1904
1974
|
|
|
1905
1975
|
|
|
1906
1976
|
def _draw_elements(painter, color):
|
|
@@ -2150,6 +2220,10 @@ def control_icon(name: str) -> QIcon:
|
|
|
2150
2220
|
'faster': _draw_faster, 'slower': _draw_slower,
|
|
2151
2221
|
'up': _draw_up, 'down': _draw_down,
|
|
2152
2222
|
'add': _draw_add, 'new_geometry': _draw_new_geometry,
|
|
2223
|
+
'new_channel_table': _draw_new_channel_table,
|
|
2224
|
+
'mac': _draw_mac, 'shape_values': _draw_shape_values,
|
|
2225
|
+
'new_specification': _draw_new_specification,
|
|
2226
|
+
'add_channel': _draw_add_channel,
|
|
2153
2227
|
'beam': _draw_beam, 'tri': _draw_tri, 'quad': _draw_quad,
|
|
2154
2228
|
'edit': _draw_edit, 'edit_on': _draw_edit_on, 'trash': _draw_trash,
|
|
2155
2229
|
'colormap': _draw_colormap,
|