visualdynamics 0.1.0a67__tar.gz → 0.1.0a68__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.0a68}/PKG-INFO +2 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/pyproject.toml +4 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/__init__.py +1 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/instrumentation.py +104 -10
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/__init__.py +37 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/icons.py +29 -19
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/instrumentation_panel.py +30 -12
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/main_window.py +75 -17
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/project.py +19 -4
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68/src/visualdynamics.egg-info}/PKG-INFO +2 -1
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics.egg-info/requires.txt +1 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/LICENSE +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/README.md +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/setup.cfg +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/__main__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/compatibility.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/author.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/averaging.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/channel_table.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/classical_pulse.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/compliance.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/correlate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/data.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/detect.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/dwell.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/entities.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/fem.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/filters.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/geometry.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/half_power.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/hexmesh.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/kurtosis.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/matches.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/mates.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/merge.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/mesh.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/modal_fit.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/octave.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/photos.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/progress.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/replication.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/replication_fit.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/replication_segments.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/report.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/rigid.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/shaker.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/shapes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/shock_check.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/shocks.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/signature.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/sine.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/snr.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/solids.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/sparse_solve.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/srs.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/stroke.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/sweep_rate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/tables.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/tetmesh.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/time_statistics.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/tone_readout.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/transform.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/truncate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/unit_choices.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/validate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/core/wavelet.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/decimate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/deform.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/demo/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/demo/barc.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/demo/drone.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/demo/frame_wing.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/demo/plate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/demo/shapes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/app_icon.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/ask.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/author_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/averaging_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/console.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/disclaimer.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/drawer_tab.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/dwell_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/editors.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/filter_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/glyphs.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/mate_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/merge_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/mesh_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/mesh_solids_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/message_bar.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/movie.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/object_tables.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/octave_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/opengl.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/panes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/pattern_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/preferences.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/project_tree.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/pulse_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/record_grid.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/replication_fit_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/report_editor.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/rigid_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/run_reports.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/segments_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/settings_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/shock_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/signature_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/sine_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/srs_tolerance_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/stage_drag.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/statistics_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/stream_window.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/stroke_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/sweep_rate_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/tables.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/tone_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/toolbars.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/tooltips.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/truncate_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/updater.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/wavelet_panel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/windows.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/adf.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_objects.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/excel.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/exodus.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/exporters.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/femap.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/matlab.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/nastran.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/native.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/notes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/photo_files.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/punch.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/rattlesnake.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/report_template.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/sdynpy_data.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/sdynpy_npz.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/sep005.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/sniffing.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/step.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/stl.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/threemf.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/io/unv.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/names.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/averaging.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/bands.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/bars.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/classical_pulse.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/dwell.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/filtering.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/half_power.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/replication_fit.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/replication_segments.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/scalogram.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/shock_check.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/shocks.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/signature.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/srs_tolerance.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/stroke.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/sweep_rate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/time_statistics.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/tone_readout.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/tracking_filter.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/plot/truncation.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/py.typed +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/report/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/report/markdown.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/report/page.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/rotate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/theme.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/units.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/update.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/__init__.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/_quiet.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/animate.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/banded.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/geometry.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/mac_bars.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/marks.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/paired.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/pick.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/rigid.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/scalogram.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/sinespec.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/viz/waterfall.py +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics.egg-info/SOURCES.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics.egg-info/entry_points.txt +0 -0
- {visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/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.0a68
|
|
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.0a68'
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
|
{visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/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."""
|
|
@@ -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
|
|
@@ -1867,40 +1867,50 @@ def _draw_coordinate_systems(painter, color):
|
|
|
1867
1867
|
painter.drawEllipse(origin, 5, 5)
|
|
1868
1868
|
|
|
1869
1869
|
|
|
1870
|
+
def _depth(painter, color, *edges):
|
|
1871
|
+
"""The edges a solid's face is extruded along, thinner than the face
|
|
1872
|
+
so they read as going back.
|
|
1873
|
+
|
|
1874
|
+
`_element_shape` leaves the pen off to fill its node dots, so these
|
|
1875
|
+
take a pen of their own.
|
|
1876
|
+
"""
|
|
1877
|
+
# drawn with the dots' empty pen they were invisible: the tet wore
|
|
1878
|
+
# the triangle's icon and the brick the quad's (Brandon, 2026-10-22)
|
|
1879
|
+
painter.setPen(_pen(color, 5))
|
|
1880
|
+
for start, end in edges:
|
|
1881
|
+
painter.drawLine(QPointF(*start), QPointF(*end))
|
|
1882
|
+
|
|
1883
|
+
|
|
1870
1884
|
def _draw_tet(painter, color):
|
|
1871
|
-
"""A tetrahedron: a triangle with its
|
|
1885
|
+
"""A tetrahedron: a triangle with its far corner inside, joined to
|
|
1886
|
+
the three."""
|
|
1872
1887
|
_element_shape(painter, color, [(32, 10), (54, 50), (10, 50)])
|
|
1873
|
-
painter
|
|
1874
|
-
|
|
1875
|
-
painter.drawLine(QPointF(10, 50), QPointF(34, 36))
|
|
1888
|
+
_depth(painter, color, ((32, 10), (34, 36)), ((54, 50), (34, 36)),
|
|
1889
|
+
((10, 50), (34, 36)))
|
|
1876
1890
|
|
|
1877
1891
|
|
|
1878
1892
|
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))
|
|
1893
|
+
"""A wedge: an upright triangle extruded back and to the right, a
|
|
1894
|
+
triangular prism, the hidden edges left out as the brick leaves
|
|
1895
|
+
them."""
|
|
1896
|
+
# Brandon, 2026-10-22: the old sideways ramp did not read as a wedge
|
|
1897
|
+
_element_shape(painter, color, [(24, 24), (40, 54), (8, 54)])
|
|
1898
|
+
_depth(painter, color, ((24, 24), (38, 10)), ((40, 54), (54, 40)),
|
|
1899
|
+
((38, 10), (54, 40)))
|
|
1887
1900
|
|
|
1888
1901
|
|
|
1889
1902
|
def _draw_hex(painter, color):
|
|
1890
1903
|
"""A brick: a square with its back face offset."""
|
|
1891
1904
|
_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))
|
|
1905
|
+
_depth(painter, color, ((10, 22), (22, 10)), ((42, 22), (54, 10)),
|
|
1906
|
+
((42, 54), (54, 42)), ((22, 10), (54, 10)), ((54, 10), (54, 42)))
|
|
1897
1907
|
|
|
1898
1908
|
|
|
1899
1909
|
def _draw_pyramid(painter, color):
|
|
1900
1910
|
"""A pyramid: a quad base with an apex."""
|
|
1901
1911
|
_element_shape(painter, color, [(10, 50), (42, 50), (54, 40), (22, 40)])
|
|
1902
|
-
|
|
1903
|
-
|
|
1912
|
+
_depth(painter, color, *(((corner), (32, 10))
|
|
1913
|
+
for corner in ((10, 50), (42, 50), (54, 40), (22, 40))))
|
|
1904
1914
|
|
|
1905
1915
|
|
|
1906
1916
|
def _draw_elements(painter, color):
|
{visualdynamics-0.1.0a67 → visualdynamics-0.1.0a68}/src/visualdynamics/gui/instrumentation_panel.py
RENAMED
|
@@ -7,7 +7,8 @@ the selection does while the pane is open; the pane names them. The
|
|
|
7
7
|
element groups sensors may go on are checked here, and the surfaces
|
|
8
8
|
of the model are drawn in the view, where a click on one leaves it out
|
|
9
9
|
or puts it back. The numbers of tri-axial and uni-axial accelerometers
|
|
10
|
-
are typed, and
|
|
10
|
+
are typed, and so is how far a sensor stays from a surface's edge, in
|
|
11
|
+
the display unit system; the sensors Effective Independence chooses are drawn on
|
|
11
12
|
the model as they change, each tri-axial one as a triad and each
|
|
12
13
|
uni-axial one as an arrow, with how well they tell the modes apart: the
|
|
13
14
|
worst off-diagonal term of the auto-MAC at the chosen channels and the
|
|
@@ -33,7 +34,7 @@ from PySide6.QtWidgets import (
|
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
from ..core.instrumentation import EDGE_ANGLE
|
|
36
|
-
from .editors import DoubleSpinBox, SpinBox, commit_on_enter, number_box
|
|
37
|
+
from .editors import LENGTH_DECIMALS, DoubleSpinBox, SpinBox, commit_on_enter, number_box
|
|
37
38
|
from .settings_panel import panel_grid
|
|
38
39
|
|
|
39
40
|
# Brandon, 2026-10-21: plan the accelerometers of a modal or vibration
|
|
@@ -52,8 +53,9 @@ class InstrumentationPanel(QWidget):
|
|
|
52
53
|
#: the counts or the uni-axial directions changed: the window chooses
|
|
53
54
|
#: the sensors again
|
|
54
55
|
changed = Signal()
|
|
55
|
-
#: the element groups
|
|
56
|
-
#: places and reads the modes again,
|
|
56
|
+
#: the element groups, the edge angle or the distance from the edges
|
|
57
|
+
#: changed: the window finds the places and reads the modes again,
|
|
58
|
+
#: then chooses
|
|
57
59
|
places_changed = Signal()
|
|
58
60
|
#: every surface put back, or every one left out
|
|
59
61
|
all_asked = Signal()
|
|
@@ -97,6 +99,12 @@ class InstrumentationPanel(QWidget):
|
|
|
97
99
|
tip='Two neighboring faces are on one surface while their normals are within '
|
|
98
100
|
'this angle: a curved surface is one surface however far it turns. Raise it '
|
|
99
101
|
'for a coarse curved mesh that splits into strips.')
|
|
102
|
+
# Brandon, 2026-10-22: stay a quarter of an inch from the edges by
|
|
103
|
+
# default, shown in the display unit system
|
|
104
|
+
self.edge_distance: DoubleSpinBox = number_box(
|
|
105
|
+
0.0, 1e9, value=0.0, decimals=LENGTH_DECIMALS,
|
|
106
|
+
tip='How near a sensor may be to the edge of its surface: a sharp edge, a free '
|
|
107
|
+
'edge, a hole or where parts touch. 0 allows a sensor on the edge.')
|
|
100
108
|
self.triaxial: SpinBox = SpinBox()
|
|
101
109
|
self.triaxial.setRange(0, 100000)
|
|
102
110
|
self.triaxial.setToolTip('How many tri-axial accelerometers: placed first')
|
|
@@ -110,21 +118,22 @@ class InstrumentationPanel(QWidget):
|
|
|
110
118
|
self.axes.setToolTip('Which way a uni-axial accelerometer may point, in its '
|
|
111
119
|
"place's own system: Z is out of the surface")
|
|
112
120
|
for row, (label, widget) in enumerate(
|
|
113
|
-
(('Surface edge', self.edge_angle), ('
|
|
114
|
-
('
|
|
121
|
+
(('Surface edge', self.edge_angle), ('Away from edges', self.edge_distance),
|
|
122
|
+
('Tri-axial', self.triaxial), ('Uni-axial', self.uniaxial),
|
|
123
|
+
('Uni-axial along', self.axes)), start=7):
|
|
115
124
|
grid.addWidget(QLabel(label), row, 0)
|
|
116
125
|
grid.addWidget(widget, row, 1)
|
|
117
126
|
self.name_label: QLabel = QLabel('Name')
|
|
118
127
|
self.name: QLineEdit = QLineEdit()
|
|
119
128
|
self.name.setToolTip("The start of the three names Keep adds: '... Geometry', "
|
|
120
129
|
"'... Channels' and '... Shapes'")
|
|
121
|
-
grid.addWidget(self.name_label,
|
|
122
|
-
grid.addWidget(self.name,
|
|
130
|
+
grid.addWidget(self.name_label, 12, 0)
|
|
131
|
+
grid.addWidget(self.name, 12, 1)
|
|
123
132
|
self.reading_label: QLabel = QLabel()
|
|
124
133
|
self.reading_label.setWordWrap(True)
|
|
125
134
|
self.reading_label.setTextInteractionFlags(
|
|
126
135
|
Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
127
|
-
grid.addWidget(self.reading_label,
|
|
136
|
+
grid.addWidget(self.reading_label, 13, 0, 1, 2)
|
|
128
137
|
buttons = QHBoxLayout()
|
|
129
138
|
self.keep_button: QPushButton = QPushButton('Keep')
|
|
130
139
|
self.keep_button.setToolTip('Add the experimental geometry, the channel table '
|
|
@@ -134,13 +143,14 @@ class InstrumentationPanel(QWidget):
|
|
|
134
143
|
self.close_button.setToolTip('Put the pane away; nothing is added')
|
|
135
144
|
buttons.addWidget(self.keep_button)
|
|
136
145
|
buttons.addWidget(self.close_button)
|
|
137
|
-
grid.addLayout(buttons,
|
|
138
|
-
grid.setRowStretch(
|
|
139
|
-
commit_on_enter(self.triaxial, self.uniaxial, self.edge_angle)
|
|
146
|
+
grid.addLayout(buttons, 14, 0, 1, 2)
|
|
147
|
+
grid.setRowStretch(15, 1)
|
|
148
|
+
commit_on_enter(self.triaxial, self.uniaxial, self.edge_angle, self.edge_distance)
|
|
140
149
|
self.triaxial.valueChanged.connect(lambda _value: self.changed.emit())
|
|
141
150
|
self.uniaxial.valueChanged.connect(lambda _value: self.changed.emit())
|
|
142
151
|
self.axes.currentIndexChanged.connect(lambda _index: self.changed.emit())
|
|
143
152
|
self.edge_angle.valueChanged.connect(lambda _value: self.places_changed.emit())
|
|
153
|
+
self.edge_distance.valueChanged.connect(lambda _value: self.places_changed.emit())
|
|
144
154
|
self.all_button.clicked.connect(self.all_asked.emit)
|
|
145
155
|
self.none_button.clicked.connect(self.none_asked.emit)
|
|
146
156
|
self.groups.itemChanged.connect(lambda _item: self.places_changed.emit())
|
|
@@ -190,6 +200,14 @@ class InstrumentationPanel(QWidget):
|
|
|
190
200
|
or 0, 1 and 2."""
|
|
191
201
|
return tuple(self.axes.currentData())
|
|
192
202
|
|
|
203
|
+
def show_edge_distance(self, value: float, unit: str) -> None:
|
|
204
|
+
"""Show the distance from the edges, `value` in `unit` ('' for a
|
|
205
|
+
geometry whose units are not defined), without reporting it."""
|
|
206
|
+
self.edge_distance.blockSignals(True)
|
|
207
|
+
self.edge_distance.setSuffix(f' {unit}' if unit else '')
|
|
208
|
+
self.edge_distance.setValue(value)
|
|
209
|
+
self.edge_distance.blockSignals(False)
|
|
210
|
+
|
|
193
211
|
def show_modes(self, text: str) -> None:
|
|
194
212
|
"""Name the modes measured."""
|
|
195
213
|
self.modes_label.setText(text)
|
|
@@ -9114,7 +9114,11 @@ class MainWindow(QMainWindow):
|
|
|
9114
9114
|
return True
|
|
9115
9115
|
|
|
9116
9116
|
def _units_changed(self, name):
|
|
9117
|
+
planning = self.__dict__.get('_planning')
|
|
9118
|
+
distance = self._planning_distance() if planning is not None else None
|
|
9117
9119
|
self.unit_system = SYSTEMS[name]
|
|
9120
|
+
if planning is not None:
|
|
9121
|
+
self._show_plan_distance(distance) # the same distance, in the new unit
|
|
9118
9122
|
if self._merging is not None:
|
|
9119
9123
|
self._open_merge_panel() # its tolerance, in the new unit
|
|
9120
9124
|
self.render_current()
|
|
@@ -14295,6 +14299,7 @@ class MainWindow(QMainWindow):
|
|
|
14295
14299
|
triaxial = -(-len(modes) // 3) + 1
|
|
14296
14300
|
panel = self.scene.instrumentation_panel
|
|
14297
14301
|
panel.open_for(f'{name}, on {geometry_name}', groups, triaxial, 0, f'{name} Plan')
|
|
14302
|
+
self._show_plan_distance(self._plan_default_distance())
|
|
14298
14303
|
panel.show()
|
|
14299
14304
|
self._begin_plan_clicks()
|
|
14300
14305
|
self._plan_now()
|
|
@@ -14319,7 +14324,7 @@ class MainWindow(QMainWindow):
|
|
|
14319
14324
|
|
|
14320
14325
|
def _planning_skin(self):
|
|
14321
14326
|
"""The model's surfaces at the pane's edge angle, found once."""
|
|
14322
|
-
from ..core.instrumentation import skin
|
|
14327
|
+
from ..core.instrumentation import default_surfaces, skin
|
|
14323
14328
|
|
|
14324
14329
|
planning = self._planning
|
|
14325
14330
|
angle = float(self.scene.instrumentation_panel.edge_angle.value())
|
|
@@ -14330,12 +14335,41 @@ class MainWindow(QMainWindow):
|
|
|
14330
14335
|
finally:
|
|
14331
14336
|
QApplication.restoreOverrideCursor()
|
|
14332
14337
|
# a surface's number is its place in this angle's split: the
|
|
14333
|
-
# ones left out at another angle mean nothing here
|
|
14334
|
-
|
|
14335
|
-
|
|
14338
|
+
# ones left out at another angle mean nothing here, and each
|
|
14339
|
+
# split starts from the default, the small surfaces out
|
|
14340
|
+
planning['off'] = {s.id for s in found} - set(default_surfaces(found))
|
|
14336
14341
|
planning['skins'] = {angle: found}
|
|
14337
14342
|
return angle, planning['skins'][angle]
|
|
14338
14343
|
|
|
14344
|
+
def _plan_default_distance(self) -> float:
|
|
14345
|
+
"""The distance sensors keep from the edges unless told: a
|
|
14346
|
+
quarter of an inch, or none on a geometry whose units are not
|
|
14347
|
+
defined (`core.instrumentation.plan`)."""
|
|
14348
|
+
from ..core.instrumentation import EDGE_DISTANCE
|
|
14349
|
+
|
|
14350
|
+
geometry = self.objects[self._planning['geometry']]
|
|
14351
|
+
return EDGE_DISTANCE if geometry.units_defined else 0.0
|
|
14352
|
+
|
|
14353
|
+
def _planning_distance(self) -> float:
|
|
14354
|
+
"""The distance sensors keep from the surfaces' edges, in the
|
|
14355
|
+
geometry's coordinates: the pane's box, typed in the display unit
|
|
14356
|
+
system once the geometry's units are defined."""
|
|
14357
|
+
geometry = self.objects[self._planning['geometry']]
|
|
14358
|
+
typed = float(self.scene.instrumentation_panel.edge_distance.value())
|
|
14359
|
+
return (float(self.unit_system.to_si(typed, 'length')) if geometry.units_defined
|
|
14360
|
+
else typed)
|
|
14361
|
+
|
|
14362
|
+
def _show_plan_distance(self, distance: float) -> None:
|
|
14363
|
+
"""Put `distance`, in the geometry's coordinates, in the pane's
|
|
14364
|
+
box in the display unit system."""
|
|
14365
|
+
geometry = self.objects[self._planning['geometry']]
|
|
14366
|
+
panel = self.scene.instrumentation_panel
|
|
14367
|
+
if geometry.units_defined:
|
|
14368
|
+
panel.show_edge_distance(float(self.unit_system.from_si(distance, 'length')),
|
|
14369
|
+
self.unit_system.label_text('length'))
|
|
14370
|
+
else:
|
|
14371
|
+
panel.show_edge_distance(distance, '')
|
|
14372
|
+
|
|
14339
14373
|
def _planning_kept(self, found, groups) -> list[int]:
|
|
14340
14374
|
"""The surfaces sensors may go on: in a checked group and not
|
|
14341
14375
|
clicked out."""
|
|
@@ -14353,10 +14387,11 @@ class MainWindow(QMainWindow):
|
|
|
14353
14387
|
planning = self._planning
|
|
14354
14388
|
angle, found = self._planning_skin()
|
|
14355
14389
|
kept = self._planning_kept(found, groups)
|
|
14356
|
-
|
|
14390
|
+
distance = self._planning_distance()
|
|
14391
|
+
key = (angle, tuple(groups), tuple(kept), distance)
|
|
14357
14392
|
if planning['places'] is None or planning['places'][0] != key:
|
|
14358
14393
|
places = candidates(self.objects[planning['geometry']], groups, kept,
|
|
14359
|
-
edge_angle=angle, found=found)
|
|
14394
|
+
edge_angle=angle, found=found, edge_distance=distance)
|
|
14360
14395
|
planning['places'] = (key, places)
|
|
14361
14396
|
planning['blocks'] = None
|
|
14362
14397
|
places = planning['places'][1]
|
|
@@ -14558,16 +14593,29 @@ class MainWindow(QMainWindow):
|
|
|
14558
14593
|
def _begin_plan_clicks(self) -> None:
|
|
14559
14594
|
"""Watch the view's clicks: a press and release in one place on a
|
|
14560
14595
|
surface leaves it out or puts it back. The press goes on to the
|
|
14561
|
-
camera, so a drag turns the view and toggles nothing.
|
|
14596
|
+
camera, so a drag turns the view and toggles nothing.
|
|
14597
|
+
|
|
14598
|
+
The release is heard from the camera's style, as the end of its
|
|
14599
|
+
interaction, not from the interactor: the style takes the mouse
|
|
14600
|
+
to itself from the press (VTK's GrabFocus), so the interactor's
|
|
14601
|
+
observers hear neither the drag nor the release. Listening there,
|
|
14602
|
+
no click ever toggled a surface, and the press left behind ended
|
|
14603
|
+
the hover's light for good (Brandon, 2026-10-22)."""
|
|
14562
14604
|
interactor = getattr(self.scene.plotter, 'iren', None)
|
|
14563
14605
|
if interactor is None or self.__dict__.get('_plan_observers'):
|
|
14564
14606
|
return
|
|
14565
14607
|
raw = interactor.interactor
|
|
14566
14608
|
self._plan_observers = [
|
|
14567
|
-
raw.AddObserver('LeftButtonPressEvent', self._on_plan_press, 10.0),
|
|
14568
|
-
raw.AddObserver('
|
|
14569
|
-
raw.AddObserver('MouseMoveEvent', self._on_plan_move, 10.0),
|
|
14609
|
+
(raw, raw.AddObserver('LeftButtonPressEvent', self._on_plan_press, 10.0)),
|
|
14610
|
+
(raw, raw.AddObserver('MouseMoveEvent', self._on_plan_move, 10.0)),
|
|
14570
14611
|
]
|
|
14612
|
+
style = raw.GetInteractorStyle()
|
|
14613
|
+
if style is not None:
|
|
14614
|
+
self._plan_observers.append(
|
|
14615
|
+
(style, style.AddObserver('EndInteractionEvent', self._on_plan_release, 10.0)))
|
|
14616
|
+
else:
|
|
14617
|
+
self._plan_observers.append(
|
|
14618
|
+
(raw, raw.AddObserver('LeftButtonReleaseEvent', self._on_plan_release, 10.0)))
|
|
14571
14619
|
|
|
14572
14620
|
def _on_plan_press(self, _caller, _event) -> None:
|
|
14573
14621
|
planning = self.__dict__.get('_planning')
|
|
@@ -14601,7 +14649,12 @@ class MainWindow(QMainWindow):
|
|
|
14601
14649
|
"""Keep: the plan added to the project as the pane says it
|
|
14602
14650
|
(`Project.plan_instrumentation`), its three objects in the tree,
|
|
14603
14651
|
and the pane put away."""
|
|
14604
|
-
from ..core.instrumentation import
|
|
14652
|
+
from ..core.instrumentation import (
|
|
14653
|
+
EDGE_ANGLE,
|
|
14654
|
+
FewerDirectionsWarning,
|
|
14655
|
+
default_surfaces,
|
|
14656
|
+
elastic,
|
|
14657
|
+
)
|
|
14605
14658
|
|
|
14606
14659
|
planning = self.__dict__.get('_planning')
|
|
14607
14660
|
if planning is None:
|
|
@@ -14621,10 +14674,17 @@ class MainWindow(QMainWindow):
|
|
|
14621
14674
|
options['modes'] = modes
|
|
14622
14675
|
if not panel.all_groups:
|
|
14623
14676
|
options['groups'] = groups
|
|
14624
|
-
|
|
14677
|
+
# journaled only when the person changed them from the default
|
|
14678
|
+
offered = {s.id for s in found if s.group in set(groups)}
|
|
14679
|
+
if set(kept) != set(default_surfaces(found)) & offered:
|
|
14625
14680
|
options['surfaces'] = kept
|
|
14626
14681
|
if angle != EDGE_ANGLE:
|
|
14627
14682
|
options['edge_angle'] = angle
|
|
14683
|
+
distance = self._planning_distance()
|
|
14684
|
+
if not np.isclose(distance, self._plan_default_distance(), rtol=1e-4, atol=0.0):
|
|
14685
|
+
# in the geometry's own coordinates, which are meters once
|
|
14686
|
+
# its units are defined: the default unit
|
|
14687
|
+
options['edge_distance'] = distance
|
|
14628
14688
|
if panel.uniaxial_axes != (2,):
|
|
14629
14689
|
options['axes'] = list(panel.uniaxial_axes)
|
|
14630
14690
|
typed = panel.name.text().strip()
|
|
@@ -14656,11 +14716,9 @@ class MainWindow(QMainWindow):
|
|
|
14656
14716
|
"""Put Plan Instrumentation away; nothing is added."""
|
|
14657
14717
|
self.scene.instrumentation_panel.hide()
|
|
14658
14718
|
self._plan_timer.stop()
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
with contextlib.suppress(Exception):
|
|
14663
|
-
interactor.interactor.RemoveObserver(observer)
|
|
14719
|
+
for watched, observer in self.__dict__.get('_plan_observers') or []:
|
|
14720
|
+
with contextlib.suppress(Exception):
|
|
14721
|
+
watched.RemoveObserver(observer)
|
|
14664
14722
|
self._plan_observers = []
|
|
14665
14723
|
if self.__dict__.get('_planning') is None:
|
|
14666
14724
|
return
|