├── requirements.txt ├── mpl_animationmanager ├── test │ ├── __init__.py │ └── test_based_on_examples.py ├── examples │ ├── __init__.py │ ├── small_example.py │ ├── oscillation_2D.py │ ├── modif_wireframe_3D.py │ ├── README.rst │ ├── modif_randwalk_3D.py │ └── rot_graph_3D.py ├── images │ ├── icon.png │ ├── info.png │ ├── pause.png │ ├── play.png │ └── stop.png ├── __init__.py ├── animationmanager.py └── QDialogAnimManager.ui ├── docs ├── readme.rst └── index.rst ├── img_src ├── demo.gif ├── screenshot.png ├── example_graph3d.gif ├── example_randomwalk3d.gif └── example_oscillation2d.gif ├── TODO.rst ├── MANIFEST.in ├── run_examples.py ├── .coveragerc ├── LICENSE.txt ├── .travis (copy).yml ├── .gitignore ├── install.sh ├── .travis.yml ├── setup.py └── README.rst /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | -------------------------------------------------------------------------------- /mpl_animationmanager/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst -------------------------------------------------------------------------------- /mpl_animationmanager/examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img_src/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/img_src/demo.gif -------------------------------------------------------------------------------- /img_src/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/img_src/screenshot.png -------------------------------------------------------------------------------- /img_src/example_graph3d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/img_src/example_graph3d.gif -------------------------------------------------------------------------------- /img_src/example_randomwalk3d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/img_src/example_randomwalk3d.gif -------------------------------------------------------------------------------- /img_src/example_oscillation2d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/img_src/example_oscillation2d.gif -------------------------------------------------------------------------------- /mpl_animationmanager/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/mpl_animationmanager/images/icon.png -------------------------------------------------------------------------------- /mpl_animationmanager/images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/mpl_animationmanager/images/info.png -------------------------------------------------------------------------------- /mpl_animationmanager/images/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/mpl_animationmanager/images/pause.png -------------------------------------------------------------------------------- /mpl_animationmanager/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/mpl_animationmanager/images/play.png -------------------------------------------------------------------------------- /mpl_animationmanager/images/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchko/mpl_animationmanager/HEAD/mpl_animationmanager/images/stop.png -------------------------------------------------------------------------------- /TODO.rst: -------------------------------------------------------------------------------- 1 | TODO list 2 | ========= 3 | * Integration to matplotlib toolbar 4 | 5 | * Improve documentation 6 | 7 | * Create video tutorial 8 | 9 | * Improve test coverage -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include docs/* 2 | 3 | include mpl_animationmanager/*.ui 4 | include mpl_animationmanager/images/*.png 5 | 6 | include run_examples.py 7 | include LICENSE.txt 8 | include MANIFEST.in 9 | include README.rst 10 | include requirements.txt 11 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to the Lattice graph designer's documentation! 2 | ====================================================== 3 | 4 | NOTE: Documentation is curently in development!!! 5 | ------------------------------------------------- 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 3 10 | 11 | readme 12 | 13 | .. include:: ../README.rst 14 | -------------------------------------------------------------------------------- /run_examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """script runs examples of animation manager usage""" 4 | 5 | from mpl_animationmanager.examples import oscillation_2D 6 | from mpl_animationmanager.examples import rot_graph_3D 7 | from mpl_animationmanager.examples import modif_wireframe_3D 8 | from mpl_animationmanager.examples import modif_randwalk_3D 9 | 10 | 11 | def run_examples(): 12 | """Main entry point for the script.""" 13 | 14 | # oscillation_2D.run() 15 | # rot_graph_3D.run() 16 | modif_randwalk_3D.run() 17 | # modif_wireframe_3D.run() 18 | 19 | if __name__ == '__main__': 20 | run_examples() 21 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | 3 | omit = 4 | *__init__* 5 | */test/* 6 | */examples/* 7 | 8 | 9 | [report] 10 | 11 | # also exclude part of code that contain modal dialog call 12 | exclude_lines = 13 | 14 | if __name__ == .__main__.: 15 | 16 | pragma: no cover 17 | 18 | except ImportError 19 | except AttributeError 20 | 21 | msb_noWriters 22 | return 23 | 24 | #required for dealing with modal QFileDialog 25 | def getPathString 26 | 27 | # contain Filedialog call 28 | def browse_callback 29 | 30 | # required additional system tools 31 | def show_anim_callback 32 | 33 | def run 34 | 35 | def close_callback 36 | -------------------------------------------------------------------------------- /mpl_animationmanager/examples/small_example.py: -------------------------------------------------------------------------------- 1 | """script runs small example of the animation manager usage""" 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from mpl_toolkits.mplot3d import axes3d 6 | from mpl_animationmanager import AnimationManager 7 | 8 | def fAnim(j, ax, lineColl): 9 | '''define the modification animation function''' 10 | ax.collections = [] # clean axes 11 | ax.add_collection3d(lineColl[j]) # add new artist 12 | 13 | # create figure 14 | fig = plt.figure('3D wireframe example') 15 | ax = fig.gca(projection='3d') 16 | ax.set_axis_off() 17 | 18 | # generate modification frames (passed as fargs) 19 | numFrames = 300 20 | X, Y, Z = axes3d.get_test_data(0.05) 21 | for j in range(numFrames): 22 | ax.plot_wireframe(X, Y, Z*np.cos(2*np.pi/numFrames*j), rstride=5, cstride=5) 23 | fargs = ax.collections 24 | ax.collections = [] 25 | 26 | # pass figure to animation manager 27 | mng = AnimationManager(ax, fAnim, fargs, numFrames) 28 | mng.run() 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017, Ivan Luchko and Project Contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /.travis (copy).yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.6" 5 | 6 | before_install: 7 | - "export DISPLAY=:99.0" 8 | - "sh -e /etc/init.d/xvfb start" 9 | 10 | install: 11 | - sudo apt-get update 12 | # We do this conditionally because it saves us some downloading if the 13 | # version is the same. 14 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then 15 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; 16 | else 17 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 18 | fi 19 | - bash miniconda.sh -b -p $HOME/miniconda 20 | - export PATH="$HOME/miniconda/bin:$PATH" 21 | - hash -r 22 | - conda config --set always_yes yes --set changeps1 no 23 | - conda update -q conda 24 | # Useful for debugging any issues with conda 25 | - conda info -a 26 | 27 | # configure conda test-environment 28 | - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION 29 | - source activate test-environment 30 | - conda install pyqt 31 | - python setup.py install 32 | 33 | - pip install coveralls 34 | 35 | script: 36 | - coverage run --source mpl_animationmanager setup.py test 37 | 38 | after_success: 39 | coveralls 40 | 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /mpl_animationmanager/examples/oscillation_2D.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Mar 27 23:05:42 2017 5 | 6 | @author: Ivan Luchko (luchko.ivan@gmail.com) 7 | 8 | 2D oscillation: matplotlib animation manager usage example 9 | """ 10 | import sys 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | from mpl_animationmanager import AnimationManager 14 | 15 | 16 | def fAnim(i, ax, fargs): 17 | '''define modification animation function''' 18 | line, tdata, ydata = fargs 19 | line.set_data(tdata[:i], ydata[:i]) 20 | 21 | 22 | def get_animManager(): 23 | """Return axample of configured AnimationManager instance""" 24 | 25 | NUM_STEPS = 400 26 | STEP = 0.05 27 | 28 | fig = plt.figure('2D oscillation example') 29 | ax = fig.gca() 30 | ax.set_ylim(-1.1, 1.1) 31 | ax.set_xlim(0, STEP*NUM_STEPS) 32 | ax.grid() 33 | 34 | line, = ax.plot([], [], lw=2) 35 | tdata = [STEP*i for i in range(NUM_STEPS)] 36 | ydata = [np.sin(2*np.pi*t) * np.exp(-t/5.) for t in tdata] 37 | 38 | # pass figure to animation manager 39 | mng = AnimationManager(ax, fAnim=fAnim, fargs=(line, tdata, ydata), 40 | numFramesModif=NUM_STEPS) 41 | # set some initial parameters 42 | mng.dlg.spinBox_period_modif.setValue(10) 43 | 44 | return mng 45 | 46 | 47 | def run(): 48 | """run example""" 49 | 50 | mng = get_animManager() 51 | 52 | return mng.run() 53 | 54 | 55 | if __name__ == '__main__': 56 | sys.exit(run()) -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo apt-get update 4 | 5 | # Define the value to download 6 | if [ "$TRAVIS_OS_NAME" = "linux" ]; then 7 | MINICONDA_OS=$MINICONDA_LINUX; 8 | elif [ "$TRAVIS_OS_NAME" = "osx" ]; then 9 | MINICONDA_OS=$MINICONDA_OSX; 10 | fi 11 | 12 | # You may want to periodically update this, although the conda update 13 | # conda line below will keep everything up-to-date. We do this 14 | # conditionally because it saves us some downloading if the version is 15 | # the same. 16 | if [ "$TRAVIS_PYTHON_VERSION" = "2.7" ]; then 17 | wget "http://repo.continuum.io/miniconda/Miniconda2-$MINICONDA_VERSION-$MINICONDA_OS.sh" -O miniconda.sh; 18 | else 19 | wget "http://repo.continuum.io/miniconda/Miniconda3-$MINICONDA_VERSION-$MINICONDA_OS.sh" -O miniconda.sh; 20 | fi 21 | 22 | bash miniconda.sh -b -p $HOME/miniconda 23 | export PATH="$HOME/miniconda/bin:$PATH" 24 | hash -r 25 | conda config --set always_yes yes --set changeps1 no 26 | conda update -q conda 27 | # Useful for debugging any issues with conda 28 | conda info -a 29 | 30 | # setup evironment 31 | if [ "${USE_CONDA}" = "true" ]; then 32 | # configure conda test-environment 33 | # conda install conda-build=2.1.0; # needed to build conda 34 | conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION; 35 | source activate test-environment; 36 | # conda install mpl_animationmanager 37 | # we use this temporarly 38 | conda install pyqt; 39 | python setup.py install; 40 | else 41 | # we still use conda to install pyqt 42 | conda install pyqt; 43 | python setup.py install; 44 | fi 45 | 46 | #pip install coveralls 47 | -------------------------------------------------------------------------------- /mpl_animationmanager/examples/modif_wireframe_3D.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Mar 27 23:09:29 2017 5 | 6 | @author: Ivan Luchko (luchko.ivan@gmail.com) 7 | 8 | 3D wireframe: matplotlib animation manager usage example 9 | """ 10 | import sys 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | from mpl_toolkits.mplot3d import axes3d 14 | from mpl_animationmanager import AnimationManager 15 | 16 | 17 | def fAnim(j, ax, lineColl): 18 | '''define modification animation function''' 19 | ax.collections = [] # clean axes 20 | ax.add_collection3d(lineColl[j]) # add new artist 21 | 22 | 23 | def run(): 24 | """ 25 | run example 26 | 27 | Return 28 | ------ 29 | ax : 3D matplotlib axes object binded to the figure 30 | provides control over the animated figure example 31 | dlg : QDialog 32 | animation manager dialog 33 | 34 | example idea borrowed from: 35 | http://matplotlib.org/examples/mplot3d/rotate_axes3d_demo.html 36 | """ 37 | # create figure 38 | fig = plt.figure('3D wireframe example') 39 | ax = fig.gca(projection='3d') 40 | ax.set_axis_off() 41 | 42 | # generate modification frames (passed as fargs) 43 | numFrames = 300 44 | X, Y, Z = axes3d.get_test_data(0.05) 45 | for j in range(numFrames): 46 | ax.plot_wireframe(X, Y, Z*np.cos(2*np.pi/numFrames*j), rstride=5, cstride=5) 47 | fargs = ax.collections 48 | ax.collections = [] 49 | 50 | # pass figure to animation manager 51 | mng = AnimationManager(ax, fAnim, fargs, numFrames) 52 | 53 | mng.run() 54 | 55 | return ax, mng.dlg 56 | 57 | 58 | if __name__ == '__main__': 59 | sys.exit(run()) -------------------------------------------------------------------------------- /mpl_animationmanager/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2017, Ivan Luchko and Project Contributors 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | # import classes from modules in order to use them at widgets package level 28 | # e.g. from widgets import XMLHighlighter - imports class from module QCodeEditor 29 | 30 | from mpl_animationmanager.animationmanager import QDialogAnimManager 31 | from mpl_animationmanager.animationmanager import AnimationManager 32 | 33 | __author__ = "Ivan Luchko (luchko.ivan@gmail.com)" 34 | __version__ = "1.0a1" 35 | __license__ = __doc__ 36 | __copyright__ = "Copyright (c) 2017, Ivan Luchko and Project Contributors " 37 | __project_url__ = 'https://github.com/luchko/mpl_animationmanager' 38 | -------------------------------------------------------------------------------- /mpl_animationmanager/examples/README.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | List of the gif animation examples created with the `Matplotlib animation manager `_ 5 | 6 | 2D oscillation (`see script `_) 7 | ------------------------------------------------------------------------------------------------------------------------------------------------- 8 | 9 | .. figure:: https://github.com/luchko/mpl_animationmanager/blob/master/img_src/example_oscillation2d.gif?raw=true 10 | :align: center 11 | :figwidth: 100 % 12 | :alt: example_oscillation2d 13 | 14 | 3D rotated graph (`see script `_) 15 | ------------------------------------------------------------------------------------------------------------------------------------------------- 16 | 17 | .. figure:: https://github.com/luchko/mpl_animationmanager/blob/master/img_src/example_graph3d.gif?raw=true 18 | :align: center 19 | :figwidth: 100 % 20 | :alt: example_graph3d 21 | 22 | 3D random walk (`see script `_) 23 | ------------------------------------------------------------------------------------------------------------------------------------------------- 24 | 25 | .. figure:: https://github.com/luchko/mpl_animationmanager/blob/master/img_src/example_randomwalk3d.gif?raw=true 26 | :align: center 27 | :figwidth: 100 % 28 | :alt: example_randomwalk3d 29 | 30 | 3D wireframe (`see script `_) 31 | ------------------------------------------------------------------------------------------------------------------------------------------------- 32 | 33 | .. figure:: https://github.com/luchko/mpl_animationmanager/blob/master/img_src/demo.gif?raw=true 34 | :align: center 35 | :figwidth: 100 % 36 | :alt: example_wireframe3d 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | env: 4 | global: 5 | - MINICONDA_VERSION="latest" 6 | - MINICONDA_LINUX="Linux-x86_64" 7 | - MINICONDA_OSX="MacOSX-x86_64" 8 | 9 | matrix: 10 | include: 11 | # Linux using pip packages 12 | - python: "3.5" 13 | env: USE_QT_API="5" USE_CONDA=false 14 | os: linux 15 | # Linux using conda packages 16 | - python: "2.7" 17 | env: USE_QT_API="4" USE_CONDA=true 18 | os: linux 19 | - python: "2.7" 20 | env: USE_QT_API="5" USE_CONDA=true 21 | os: linux 22 | - python: "3.6" 23 | env: USE_QT_API="5" USE_CONDA=true 24 | os: linux 25 | 26 | before_install: 27 | - "export DISPLAY=:99.0" 28 | - "sh -e /etc/init.d/xvfb start" 29 | 30 | install: 31 | - sudo apt-get update; 32 | # Define the value to download 33 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then 34 | MINICONDA_OS=$MINICONDA_LINUX; 35 | elif [ "$TRAVIS_OS_NAME" = "osx" ]; then 36 | MINICONDA_OS=$MINICONDA_OSX; 37 | fi 38 | 39 | # You may want to periodically update this, although the conda update 40 | # conda line below will keep everything up-to-date. We do this 41 | # conditionally because it saves us some downloading if the version is 42 | # the same. 43 | - if [[ "$TRAVIS_PYTHON_VERSION" = "2.7" ]]; then 44 | wget "http://repo.continuum.io/miniconda/Miniconda2-$MINICONDA_VERSION-$MINICONDA_OS.sh" -O miniconda.sh; 45 | else 46 | wget "http://repo.continuum.io/miniconda/Miniconda3-$MINICONDA_VERSION-$MINICONDA_OS.sh" -O miniconda.sh; 47 | fi 48 | 49 | - bash miniconda.sh -b -p $HOME/miniconda; 50 | - export PATH="$HOME/miniconda/bin:$PATH"; 51 | - hash -r; 52 | - conda config --set always_yes yes --set changeps1 no; 53 | - conda update -q conda; 54 | # Useful for debugging any issues with conda 55 | - conda info -a; 56 | 57 | # setup evironment 58 | - if [[ "$USE_CONDA" = "true" ]]; then 59 | conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION; 60 | source activate test-environment; 61 | 62 | conda install pyqt=$USE_QT_API; 63 | python setup.py install; 64 | else 65 | conda install pyqt=$USE_QT_API; 66 | python setup.py install; 67 | fi 68 | 69 | - pip install coveralls; 70 | - sudo apt-get install ffmpeg 71 | - conda list 72 | 73 | script: 74 | - coverage run --source mpl_animationmanager setup.py test 75 | 76 | after_success: 77 | coveralls 78 | 79 | -------------------------------------------------------------------------------- /mpl_animationmanager/examples/modif_randwalk_3D.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Mar 27 23:05:42 2017 5 | 6 | @author: Ivan Luchko (luchko.ivan@gmail.com) 7 | 8 | 3D Random walk: matplotlib animation manager usage example 9 | """ 10 | import sys 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | from mpl_toolkits.mplot3d import axes3d 14 | from mpl_animationmanager import AnimationManager 15 | 16 | 17 | def Gen_RandLine(length, step_max, dims=2): 18 | """Create a line using a random walk algorithm """ 19 | 20 | lineData = np.empty((dims, length)) 21 | lineData[:, 0] = np.random.rand(dims) 22 | for index in range(1, length): 23 | step = ((np.random.rand(dims) - 0.5)*step_max) 24 | lineData[:, index] = lineData[:, index - 1] + step 25 | return lineData 26 | 27 | 28 | def update_lines(num, ax, fargs): 29 | """define modification animation function""" 30 | 31 | dataLines, lines = fargs 32 | for line, data in zip(lines, dataLines): 33 | # NOTE: there is no .set_data() for 3 dim data... 34 | line.set_data(data[0:2, :num]) 35 | line.set_3d_properties(data[2, :num]) 36 | return lines 37 | 38 | 39 | def get_animManager(): 40 | """ 41 | Return axample of configured AnimationManager instance 42 | 43 | example idea borrowed from: 44 | http://matplotlib.org/examples/animation/simple_3danim.html 45 | """ 46 | NUM_LINES = 50 47 | NUM_STEPS = 1000 48 | STEP_MAX = 0.1 49 | 50 | fig = plt.figure('3D Random walk example') 51 | ax = fig.gca(projection='3d') 52 | ax.set_axis_off() 53 | # Setting the axes properties 54 | d = 1 55 | ax.set_xlim3d([0.0 - d, 1.0 + d]) 56 | ax.set_ylim3d([0.0 - d, 1.0 + d]) 57 | ax.set_zlim3d([0.0 - d, 1.0 + d]) 58 | 59 | # generating random data and 3-D lines 60 | data = [Gen_RandLine(NUM_STEPS, STEP_MAX, dims=3) for index in range(NUM_LINES)] 61 | lines = [ax.plot(dat[0, 0:1], dat[1, 0:1], dat[2, 0:1])[0] for dat in data] 62 | 63 | # pass figure to animation manager 64 | mng = AnimationManager(ax, fAnim=update_lines, fargs=(data, lines), 65 | numFramesModif=NUM_STEPS) 66 | # set some initial parameters 67 | mng.dlg.spinBox_period_modif.setValue(30) 68 | 69 | return mng 70 | 71 | 72 | def run(): 73 | """run example""" 74 | 75 | mng = get_animManager() 76 | 77 | return mng.run() 78 | 79 | 80 | if __name__ == '__main__': 81 | sys.exit(run()) -------------------------------------------------------------------------------- /mpl_animationmanager/examples/rot_graph_3D.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Mar 27 23:07:30 2017 5 | 6 | @author: Ivan Luchko (luchko.ivan@gmail.com) 7 | 8 | 3D rotated graph: matplotlib animation manager example 9 | """ 10 | import sys 11 | import matplotlib.pyplot as plt 12 | from matplotlib.text import Annotation 13 | from mpl_toolkits.mplot3d import axes3d 14 | from mpl_toolkits.mplot3d.proj3d import proj_transform 15 | from mpl_toolkits.mplot3d.art3d import Line3DCollection 16 | from mpl_animationmanager import AnimationManager 17 | 18 | 19 | class Annotation3D(Annotation): 20 | '''Annotate the point xyz with text s''' 21 | 22 | def __init__(self, s, xyz, *args, **kwargs): 23 | Annotation.__init__(self,s, xy=(0,0), *args, **kwargs) 24 | self._verts3d = xyz 25 | 26 | def draw(self, renderer): 27 | '''overload parent method''' 28 | 29 | xs3d, ys3d, zs3d = self._verts3d 30 | xs, ys, zs = proj_transform(xs3d, ys3d, zs3d, renderer.M) 31 | self.xy=(xs,ys) 32 | Annotation.draw(self, renderer) 33 | 34 | 35 | def annotate3D(ax, s, *args, **kwargs): 36 | '''add anotation text s to to Axes3d ax''' 37 | 38 | tag = Annotation3D(s, *args, **kwargs) 39 | ax.add_artist(tag) 40 | 41 | 42 | def get_animManager(): 43 | """Return axample of configured AnimationManager instance""" 44 | 45 | # data: coordinates of nodes and links 46 | xn = [1.1, 1.9, 0.1, 0.3, 1.6, 0.8, 2.3, 1.2, 1.7, 1.0, -0.7, 0.1, 0.1, -0.9, 0.1, -0.1, 2.1, 2.7, 2.6, 2.0] 47 | yn = [-1.2, -2.0, -1.2, -0.7, -0.4, -2.2, -1.0, -1.3, -1.5, -2.1, -0.7, -0.3, 0.7, -0.0, -0.3, 0.7, 0.7, 0.3, 0.8, 1.2] 48 | zn = [-1.6, -1.5, -1.3, -2.0, -2.4, -2.1, -1.8, -2.8, -0.5, -0.8, -0.4, -1.1, -1.8, -1.5, 0.1, -0.6, 0.2, -0.1, -0.8, -0.4] 49 | group = [1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3] 50 | edges = [(1, 0), (2, 0), (3, 0), (3, 2), (4, 0), (5, 0), (6, 0), (7, 0), (8, 0), (9, 0), (11, 10), (11, 3), (11, 2), (11, 0), (12, 11), (13, 11), (14, 11), (15, 11), (17, 16), (18, 16), (18, 17), (19, 16), (19, 17), (19, 18)] 51 | xyzn = list(zip(xn, yn, zn)) 52 | segments = [(xyzn[s], xyzn[t]) for s, t in edges] 53 | 54 | # create figure 55 | fig = plt.figure('3D graph example') 56 | ax = fig.gca(projection='3d') 57 | ax.set_axis_off() 58 | 59 | # plot vertices 60 | ax.scatter(xn,yn,zn, marker='o', c = group, s = 64) 61 | # plot edges 62 | edge_col = Line3DCollection(segments, lw=0.2) 63 | ax.add_collection3d(edge_col) 64 | # add vertices annotation. CAUTION: might slow down animation 65 | for j, xyz_ in enumerate(xyzn): 66 | annotate3D(ax, s=str(j+1), xyz=xyz_, fontsize=10, xytext=(-3,3), 67 | textcoords='offset points', ha='right',va='bottom') 68 | 69 | # pass figure to animation manager 70 | mng = AnimationManager(ax) 71 | # set some initial parameters 72 | mng.dlg.spinBox_period_rot.setValue(20) 73 | mng.dlg.spinBox_elev.setValue(20) 74 | 75 | return mng 76 | 77 | 78 | def run(): 79 | """run example""" 80 | 81 | mng = get_animManager() 82 | 83 | return mng.run() 84 | 85 | 86 | if __name__ == '__main__': 87 | sys.exit(run()) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Thu Apr 6 02:24:06 2017 5 | 6 | @author: Ivan Luchko (luchko.ivan@gmail.com) 7 | 8 | setup latticegraph_designer package in your environment 9 | 10 | """ 11 | import os 12 | import sys 13 | import pip 14 | from setuptools import setup 15 | 16 | #with open(os.path.abspath('README.rst'), encoding='utf-8') as f: 17 | # long_description = f.read() 18 | 19 | 20 | long_description = ''' 21 | Matplotlib animation manager (GUI) 22 | ********************************************** 23 | 24 | .. image:: https://img.shields.io/pypi/status/mpl-animationmanager.svg 25 | :target: https://pypi.python.org/pypi/mpl-animationmanager 26 | :alt: status 27 | 28 | .. image:: https://img.shields.io/pypi/l/mpl-animationmanager.svg 29 | :target: https://github.com/luchko/mpl-animationmanager/blob/master/LICENSE.txt 30 | :alt: License 31 | 32 | .. image:: https://readthedocs.org/projects/mpl-animationmanager/badge/?version=latest 33 | :target: http://mpl-animationmanager.readthedocs.io/en/latest/?badge=latest 34 | :alt: Documentation Status 35 | 36 | It is a small convenient tool which allows to setup and save the gif/mp4-animations using the `PyQt `_ based GUI built on top of the `matplotlib animation module `_. Program can deal with both 2D and 3D animation. For 3D axes manager can add additional rotation of the view point resulting in both object modification and rotation animation. Also animation manager can be easily integrated in your larger PyQt project as a dialog. For more details see the Quitckstart section. 37 | 38 | - Git-hub repo: https://github.com/luchko/mpl_animationmanager 39 | - Documentation: https://mpl-animationmanager.readthedocs.io 40 | 41 | Tool is compatible with Python 2.7 or Python 3.3+ and PyQt4 4.6+ or PyQt5 5.2+. 42 | 43 | ------------------------- 44 | 45 | .. figure:: https://github.com/luchko/mpl_animationmanager/blob/master/img_src/demo.gif?raw=true 46 | :align: center 47 | :figwidth: 100 % 48 | 49 | ------------------------- 50 | 51 | Features 52 | ========= 53 | 54 | - ``mpl_animationmanager`` library contains two classes ``AnimationManager`` and ``QDialogAnimManager`` with the same input arguments. 55 | - ``QDialogAnimManager`` is inherited from the PyQt ``QDialog``. Using this class you can easily integrate animation manager as a QDialog into your larger PyQt application. 56 | - ``AnimationManager`` is a small class build on top of the ``QDialogAnimManager`` and uses the input arguments to initialize the ``QDialogAnimManager`` object and run a PyQt application using ``run()`` function. 57 | - After passing the required arguments to the manager, user can setup animation properties such as: dpi, fps (frames per second), modification period. 58 | - For 3D animation user can also setup the rotation period, elevation and initilal azimut angles. The resulting duration of the animation equals the least common multiple of modification and rotaion periods if both are provided. 59 | - Animation can be saved in gif or mp4 format by picking one of the preinstalled movie writers used by matplotlib (imagemagick, ffmpeg etc.). 60 | ''' 61 | 62 | # get list of dependencies from requirements.txt file 63 | with open(os.path.abspath('requirements.txt')) as f: 64 | install_requires = [p for p in f.read().splitlines() if p != ''] 65 | 66 | pip.main(['install', 'matplotlib']) 67 | ## trick required to install numpy 68 | #for package in install_requires: 69 | # pip.main(['install', package]) 70 | 71 | # define custom test runner 72 | from setuptools.command.test import test as TestCommand 73 | 74 | class MyUnitTest(TestCommand): 75 | def finalize_options(self): 76 | TestCommand.finalize_options(self) 77 | self.test_args = [] 78 | self.test_suite = True 79 | 80 | def run_tests(self): 81 | from mpl_animationmanager.test import test_based_on_examples 82 | errcode = test_based_on_examples.main() 83 | sys.exit(errcode) 84 | 85 | import mpl_animationmanager # need to be imported after matplotlib have been installed 86 | 87 | setup( 88 | name='mpl_animationmanager', 89 | version=mpl_animationmanager.__version__, 90 | description='Matplotlib animation manager (GUI).', 91 | long_description=long_description, 92 | url='https://github.com/luchko/mpl_animationmanager', 93 | author='Ivan Luchko', 94 | author_email='luchko.ivan@gmail.com', 95 | documentation='https://mpl-animationmanager.readthedocs.io', 96 | license='MIT', 97 | packages=['mpl_animationmanager', 98 | 'mpl_animationmanager.examples', 99 | 'mpl_animationmanager.test'], 100 | install_requires=install_requires, 101 | platforms='any', 102 | include_package_data=True, 103 | zip_safe=False, 104 | cmdclass = {'test': MyUnitTest}, 105 | keywords='matplotlib animation animation-3d visualization gui gif pdf', 106 | classifiers = [ 107 | 'Programming Language :: Python', 108 | 'Development Status :: 3 - Alpha', 109 | 'Operating System :: OS Independent', 110 | 'Intended Audience :: Developers', 111 | 'Intended Audience :: Science/Research', 112 | 'Intended Audience :: Education', 113 | 'Intended Audience :: Other Audience', 114 | 'Topic :: Desktop Environment', 115 | 'Topic :: Scientific/Engineering', 116 | 'Topic :: Scientific/Engineering :: Visualization', 117 | 'Topic :: Software Development :: Widget Sets', 118 | 'Topic :: Multimedia :: Graphics', 119 | 'Topic :: Multimedia :: Graphics :: Presentation', 120 | 'License :: OSI Approved :: MIT License', 121 | 'Natural Language :: English' 122 | ] 123 | ) 124 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Matplotlib animation manager (GUI) 1.0a1 2 | **************************************** 3 | 4 | .. image:: https://img.shields.io/pypi/v/mpl-animationmanager.svg 5 | :target: https://pypi.python.org/pypi/mpl-animationmanager 6 | :alt: PyPi 7 | 8 | .. image:: https://img.shields.io/pypi/status/mpl-animationmanager.svg 9 | :target: https://pypi.python.org/pypi/mpl-animationmanager 10 | :alt: status 11 | 12 | .. image:: https://img.shields.io/pypi/l/mpl-animationmanager.svg 13 | :target: https://github.com/luchko/mpl_animationmanager/blob/master/LICENSE.txt 14 | :alt: License 15 | 16 | .. image:: https://readthedocs.org/projects/mpl-animationmanager/badge/?version=latest 17 | :target: http://mpl-animationmanager.readthedocs.io/en/latest/?badge=latest 18 | :alt: Documentation Status 19 | 20 | .. image:: https://travis-ci.org/luchko/mpl_animationmanager.svg?branch=master 21 | :target: https://travis-ci.org/luchko/mpl_animationmanager 22 | :alt: travis-ci 23 | 24 | .. image:: https://coveralls.io/repos/github/luchko/mpl_animationmanager/badge.svg?branch=master 25 | :target: https://coveralls.io/github/luchko/mpl_animationmanager?branch=master 26 | :alt: coveralls 27 | 28 | It is a small convenient tool which allows to setup and save the gif/mp4-animations using the `PyQt `_ based GUI built on top of the `matplotlib animation module `_. Program can deal with both 2D and 3D animation. For 3D axes manager can add additional rotation of the view point resulting in both object modification and rotation animation. Also animation manager can be easily integrated in your larger PyQt project as a dialog. For more details see the Quitckstart section. 29 | 30 | - Git-hub repo: https://github.com/luchko/mpl_animationmanager 31 | - Documentation: https://mpl-animationmanager.readthedocs.io 32 | - Free software: MIT license 33 | 34 | Tool is compatible with Python 2.7 or Python 3.3+ and PyQt4 4.6+ or PyQt5 5.2+. 35 | 36 | ------------------------- 37 | 38 | .. figure:: https://github.com/luchko/mpl_animationmanager/blob/master/img_src/demo.gif?raw=true 39 | :align: center 40 | :figwidth: 100 % 41 | 42 | ------------------------- 43 | 44 | Features 45 | ======== 46 | 47 | - ``mpl_animationmanager`` library contains two classes ``AnimationManager`` and ``QDialogAnimManager`` with the same input arguments (`see API`_). 48 | - ``QDialogAnimManager`` is inherited from the PyQt ``QDialog``. Using this class you can easily integrate animation manager as a QDialog into your larger PyQt application. 49 | - ``AnimationManager`` is a small class build on top of the ``QDialogAnimManager`` and uses the input arguments to initialize the ``QDialogAnimManager`` object and run a PyQt application using ``run()`` function. 50 | - After passing the required arguments to the manager, user can setup animation properties such as: dpi, fps (frames per second), modification period. 51 | - For 3D animation user can also setup the rotation period, elevation and initilal azimut angles. The resulting duration of the animation equals the least common multiple of modification and rotaion periods if both are provided. 52 | - Animation can be saved in gif or mp4 format by picking one of the preinstalled movie writers used by matplotlib (imagemagick, ffmpeg etc.). 53 | 54 | Dependencies 55 | ============ 56 | 57 | - **Python** 2.7 or 3.3+ 58 | - **PyQt4** 4.6+ or **PyQt5** 5.2+ : PyQt4 is recommended. 59 | - **Matplotlib** 60 | 61 | **Important note**: *Most dependencies listed above are installed automatically, however in some cases you might need to istall them separately (see next section).* 62 | 63 | **Install PyQt4 or PyQt5** 64 | 65 | - in case you use conda type: ``$ conda install pyqt=4`` (or 5) 66 | - otherwise follow the links `PyQt4 `_ or `PyQt5 `_. 67 | 68 | Installation and usage 69 | ======================= 70 | 71 | This section explains how to install and use the latest stable release of the Matplotlib animation manager in one of the cross-platform ways listed bellow. If you prefer just to have a taste of the tool you may `jump to the example section`_. 72 | 73 | Installation using ``conda`` `scientific package manager `_ (recommended way) 74 | ----------------------------------------------------------------------------------------------------------------- 75 | 76 | *PROJECT IS NOT RELEASED YET* 77 | 78 | Type in your command prompt: 79 | 80 | ``$ pip install conda`` (if ``conda`` is not installed yet) 81 | 82 | ``$ conda install mpl_animationmanager`` 83 | 84 | **Note:** *All dependencies are installed by* ``conda`` *automatically.* 85 | 86 | Installation using ``pip`` package manager from `PyPI `_ 87 | -------------------------------------------------------------------------------------- 88 | 89 | *PROJECT IS NOT RELEASED YET* 90 | 91 | Type in your command prompt: 92 | 93 | ``$ pip install mpl_animationmanager`` 94 | 95 | **Important note:** *This also installs all dependencies except PyQt4 or PyQt5. Those have to be installed separately after installing Python.* 96 | 97 | Running from source 98 | ------------------- 99 | 100 | It is possible to use animation manager without installing it. 101 | 102 | 1. Make sure that PyQt4 or PyQt5 package is installed. 103 | 2. `Download a source `_ of the last stable package version. 104 | 3. Copy the ``./mpl_animationmanager/`` package source folder into the root directiory of your script. 105 | 4. Now you can import ``mpl_animationmanager`` module to your sript the same way as it would have been installed. 106 | 107 | You may want to do this for fixing bugs, adding new features, integrating the widget into your own PyQt project, learning how the tool works or just getting a taste of it. 108 | 109 | 110 | .. _`see API`: 111 | 112 | API 113 | --- 114 | 115 | Both ``AnimationManager`` and ``QDialogAnimManager`` classes take the same input arguments 116 | 117 | .. code-block:: python 118 | 119 | class AnimationManager(object): 120 | 121 | def __init__(self, ax, fAnim=None, fargs=None, numFramesModif=None, *args, **kwargs): 122 | ''' 123 | Parameters 124 | ---------- 125 | ax : 2D or 3D matplotlib axes object binded to the figure 126 | provides control over animated figure 127 | fAnim : function 128 | fAnim(i, ax, fargs) - modifies the "ax" at each "i" step 129 | fargs : any 130 | arguments used by the "fAnim" function during the "ax" modification 131 | numFramesModif : int 132 | number of modification frames 133 | ''' 134 | 135 | Small example 136 | -------------- 137 | 138 | Code below produces the same animation as one shown at the main demo above. 139 | 140 | .. code-block:: python 141 | 142 | """script runs a small example of the animation manager usage""" 143 | 144 | import numpy as np 145 | import matplotlib.pyplot as plt 146 | from mpl_toolkits.mplot3d import axes3d 147 | from mpl_animationmanager import AnimationManager 148 | 149 | def fAnim(j, ax, lineColl): 150 | '''define the modification animation function''' 151 | ax.collections = [] # clean axes 152 | ax.add_collection3d(lineColl[j]) # add new artist 153 | 154 | # create figure 155 | fig = plt.figure('3D wireframe example') 156 | ax = fig.gca(projection='3d') 157 | ax.set_axis_off() 158 | 159 | # generate modification frames (passed as fargs) 160 | numFrames = 300 161 | X, Y, Z = axes3d.get_test_data(0.05) 162 | for j in range(numFrames): 163 | ax.plot_wireframe(X, Y, Z*np.cos(2*np.pi/numFrames*j), rstride=5, cstride=5) 164 | fargs = ax.collections 165 | ax.collections = [] 166 | 167 | # pass figure to the animation manager 168 | mng = AnimationManager(ax, fAnim, fargs, numFrames) 169 | mng.run() 170 | 171 | .. _`jump to the example section`: 172 | 173 | More examples 174 | ------------- 175 | 176 | More examples with gif demo are included in the ``./mpl_animationmanager/examples/`` folder (`link `_). You might run them as a Python script after instalation ``mpl_animationmanager`` package. 177 | 178 | Second option is to run the python script ``run_examples.py`` located in the root project directory after `downloading the source code `_. In this script you can also pick the examples you would like to run. 179 | 180 | Following gif-animation examples were created with the Matplotlib animation manager: 181 | 182 | .. raw:: html 183 | 184 | 185 | 186 | 187 | 188 | 193 | 194 | 199 | 200 | 205 | 206 | 207 | 208 |
189 | example_oscillation2d 190 |
191 | 2D oscillation (see script) 192 |
195 | example_graph3d 196 |
197 | 3D rotated graph (see script) 198 |
201 | example_randomwalk3d 202 |
203 | 3D random walk (see script) 204 |
209 | 210 | You might also have a look at the larger PyQt project `Lattice graph designer `_ where ``QDialogAnimManager`` is integrated for exporting the rotating visualization of 3D model. 211 | 212 | Running ``unittest`` 213 | -------------------- 214 | 215 | After making any changes in the source code you can run ``unitittest`` to make sure that nothing is broken by typing in your command prompt: 216 | 217 | ``$ python setup.py test`` 218 | 219 | 220 | Contacts 221 | ======== 222 | 223 | About the feature extension or bugs report you can create `the issue or feature request `_ or feel free to contact me directly by e-mail: 224 | 225 | **Ivan Luchko** - luchko.ivan@gmail.com 226 | -------------------------------------------------------------------------------- /mpl_animationmanager/test/test_based_on_examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Unittest of the Matplotlib animation manager basic functionality 5 | 6 | Unittests is strongly binded with the example moudules and AnimationManager 7 | class itself which support TestCase running. 8 | 9 | Unittest should be run by passing the TestCase to manager instance (mng) 10 | via the nmg.runUnitTest(MyTestCase) method. 11 | 12 | TestCase gets access to the figure axes and the manager dialog during 13 | the TestCase instance creationg via __init__ method. 14 | 15 | You might want to decrease DELAY constant or signal processing in the main loop 16 | in order to make test run faster. Also you might want to icrease DELAY in case 17 | test fail due to larte delay in slots are processing. 18 | """ 19 | 20 | __author__ = "Ivan Luchko (luchko.ivan@gmail.com)" 21 | __version__ = "1.0a1" 22 | __date__ = "Apr 4, 2017" 23 | __copyright__ = "Copyright (c) 2017, Ivan Luchko and Project Contributors " 24 | 25 | import os 26 | import time 27 | import unittest 28 | 29 | from mpl_animationmanager.examples import oscillation_2D 30 | from mpl_animationmanager.examples import rot_graph_3D 31 | from mpl_animationmanager.examples import modif_wireframe_3D 32 | from mpl_animationmanager.examples import modif_randwalk_3D 33 | 34 | test_folder = "./mpl_animationmanager/test/" 35 | 36 | DELAY = 0.5 # [sec] delay for signal processing in the main loop 37 | 38 | def isRotated(ax, dlg): 39 | ''' 40 | returns True if 3D axes is rotated 41 | 42 | this function should be executed in separate thread 43 | ''' 44 | azim0 = ax.azim 45 | # wait some time until 3D axes are rotated in main thread 46 | time.sleep(1.1/dlg.fps) 47 | 48 | return azim0 != ax.azim 49 | 50 | class modif_oscillation_2D_Test(unittest.TestCase): 51 | '''Animation manager test based on the '2D oscillation' example''' 52 | 53 | def __init__(self, testname, ax, dlg): 54 | 55 | unittest.TestCase.__init__(self, testname) 56 | 57 | self.ax, self.dlg = ax, dlg 58 | self.fig = self.ax.get_figure() 59 | self.dlg.spinBox_period_modif.setValue(5) 60 | 61 | def test_initialization(self): 62 | ''' 63 | test widgets comonent according to type of figure and animation 64 | - 2D/3D axes 65 | - object modification present 66 | ''' 67 | # curent example has modification 68 | self.assertTrue(self.dlg.widget_modif.isVisible()) 69 | self.assertTrue(self.dlg.checkBox_modif.isChecked()) 70 | 71 | # curent example are axes 2D, thus object can be rotated 72 | self.assertTrue(not self.dlg.widget_rot.isVisible()) 73 | self.assertTrue(not self.dlg.checkBox_rot.isChecked()) 74 | 75 | self.assertAlmostEqual(self.dlg.period, self.dlg.period_modif) 76 | 77 | def test_quality_props(self): 78 | 79 | # change dpi 80 | dpi = 50 81 | self.dlg.spinBox_dpi.setValue(dpi) 82 | time.sleep(DELAY) # wait for signal handling in the main event loop 83 | self.assertEqual(self.dlg.dpi, dpi) 84 | self.assertEqual(self.fig.get_dpi(), dpi) 85 | 86 | # change fps 87 | fps = 10 88 | self.dlg.spinBox_fps.setValue(fps) 89 | time.sleep(DELAY) # wait for signal handling in the main event loop 90 | self.assertEqual(self.dlg.fps, fps) 91 | 92 | # restore defaults: 93 | self.dlg.spinBox_dpi.setValue(100) 94 | self.dlg.spinBox_fps.setValue(24) 95 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 96 | 97 | def test_modif_props(self): 98 | 99 | # change modification period 100 | t = 30 101 | self.dlg.spinBox_period_modif.setValue(t) 102 | time.sleep(DELAY) # wait for signal handling in the main event loop 103 | self.assertEqual(self.dlg.period_modif, t) 104 | self.assertEqual(self.dlg.period, t) 105 | 106 | def test_control_btns(self): 107 | 108 | # test pause button 109 | self.dlg.btnPause.click() 110 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 111 | 112 | # test start button 113 | self.dlg.btnStart.click() 114 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 115 | 116 | # test stop button 117 | self.dlg.btnStop.click() 118 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 119 | 120 | # test start button 121 | self.dlg.btnStart.click() 122 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 123 | 124 | def test_exportAnim(self): 125 | 126 | # make the animation small for faster export 127 | self.dlg.spinBox_dpi.setValue(30) 128 | self.dlg.spinBox_fps.setValue(10) 129 | self.dlg.spinBox_period_modif.setValue(2) 130 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 131 | 132 | # export animation 133 | path = os.path.abspath(test_folder+"test") 134 | self.dlg.lineEdit_name.setText(path) 135 | self.dlg.EXPORT_RUNNING = True 136 | self.dlg.btnExport.click() 137 | # wait until exporting is finished 138 | while self.dlg.EXPORT_RUNNING: time.sleep(2*DELAY) 139 | self.assertTrue(os.path.exists(self.dlg.filepath)) 140 | # remove testfile 141 | os.remove(self.dlg.filepath) 142 | 143 | # restore default settings 144 | self.dlg.btnStart.click() 145 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 146 | # make the animation small for faster export 147 | self.dlg.spinBox_dpi.setValue(100) 148 | self.dlg.spinBox_fps.setValue(24) 149 | self.dlg.spinBox_period_modif.setValue(10) 150 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 151 | 152 | def test_Info(self): 153 | 154 | # make the animation small for faster export 155 | self.dlg.btnAsk.click() 156 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 157 | self.assertTrue(self.dlg.msg.isVisible()) 158 | self.dlg.msg.accept() 159 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 160 | self.assertTrue(not self.dlg.msg.isVisible()) 161 | 162 | 163 | class rot_graph_3D_Test(unittest.TestCase): 164 | '''Animation manager test based on the '3D rotated graph' example''' 165 | 166 | def __init__(self, testname, ax, dlg): 167 | 168 | unittest.TestCase.__init__(self, testname) 169 | 170 | self.ax, self.dlg = ax, dlg 171 | self.fig = self.ax.get_figure() 172 | 173 | def test_initialization(self): 174 | ''' 175 | test widgets comonent according to type of figure and animation 176 | - 2D/3D axes 177 | - object modification present 178 | ''' 179 | # curent example has no modification 180 | self.assertTrue(not self.dlg.widget_modif.isVisible()) 181 | self.assertTrue(not self.dlg.checkBox_modif.isChecked()) 182 | 183 | # curent example are axes 3D, thus object can be rotated 184 | self.assertTrue(self.dlg.widget_rot.isVisible()) 185 | self.assertTrue(self.dlg.checkBox_rot.isChecked()) 186 | 187 | self.assertAlmostEqual(self.dlg.period, self.dlg.period_rot) 188 | 189 | def test_quality_props(self): 190 | 191 | # change dpi 192 | dpi = 50 193 | self.dlg.spinBox_dpi.setValue(dpi) 194 | time.sleep(DELAY) # wait for signal handling in the main event loop 195 | self.assertEqual(self.dlg.dpi, dpi) 196 | self.assertEqual(self.fig.get_dpi(), dpi) 197 | 198 | # change fps 199 | fps = 10 200 | self.dlg.spinBox_fps.setValue(fps) 201 | time.sleep(DELAY) # wait for signal handling in the main event loop 202 | self.assertEqual(self.dlg.fps, fps) 203 | 204 | # restore defaults: 205 | self.dlg.spinBox_dpi.setValue(100) 206 | self.dlg.spinBox_fps.setValue(24) 207 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 208 | 209 | def test_3D_props(self): 210 | 211 | # change rotation period 212 | t = 30 213 | self.dlg.spinBox_period_rot.setValue(t) 214 | time.sleep(DELAY) # wait for signal handling in the main event loop 215 | self.assertEqual(self.dlg.period_rot, t) 216 | self.assertEqual(self.dlg.period, t) 217 | 218 | # change elevation 219 | elev = 10 220 | self.dlg.spinBox_elev.setValue(elev) 221 | time.sleep(DELAY) # wait for signal handling in the main event loop 222 | self.assertEqual(self.dlg.elevation, elev) 223 | self.assertEqual(self.ax.elev, elev) 224 | 225 | # enable/diable rotation 226 | self.dlg.checkBox_rot.setChecked(False) 227 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 228 | self.assertTrue(not isRotated(self.ax, self.dlg)) 229 | self.dlg.checkBox_rot.setChecked(True) 230 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 231 | self.assertTrue(isRotated(self.ax, self.dlg)) 232 | 233 | # change initial azimut 234 | azim = -50 235 | self.dlg.btnStop.click() 236 | time.sleep(DELAY) # wait for signal handling in the main event loop 237 | self.dlg.spinBox_azim.setValue(azim) 238 | time.sleep(DELAY) # wait for signal handling in the main event loop 239 | self.assertEqual(self.dlg.zero_azim, azim) 240 | self.assertEqual(self.ax.azim, azim) 241 | 242 | def test_control_btns(self): 243 | 244 | # test pause button 245 | self.dlg.btnPause.click() 246 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 247 | self.assertTrue(not isRotated(self.ax, self.dlg)) 248 | 249 | # test start button 250 | self.dlg.btnStart.click() 251 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 252 | self.assertTrue(isRotated(self.ax, self.dlg)) 253 | 254 | # test stop button 255 | self.assertNotEqual(self.ax.azim, self.dlg.spinBox_azim.value()) 256 | self.dlg.btnStop.click() 257 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 258 | self.assertTrue(not isRotated(self.ax, self.dlg)) 259 | self.assertEqual(self.ax.azim, self.dlg.spinBox_azim.value()) 260 | 261 | # test start button 262 | self.dlg.btnStart.click() 263 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 264 | self.assertTrue(isRotated(self.ax, self.dlg)) 265 | 266 | def test_exportAnim(self): 267 | 268 | # make the animation small for faster export 269 | self.dlg.spinBox_dpi.setValue(30) 270 | self.dlg.spinBox_fps.setValue(10) 271 | self.dlg.spinBox_period_rot.setValue(2) 272 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 273 | 274 | # export animation 275 | path = os.path.abspath(test_folder+"test") 276 | self.dlg.lineEdit_name.setText(path) 277 | self.dlg.EXPORT_RUNNING = True 278 | self.dlg.btnExport.click() 279 | # wait until exporting is finished 280 | while self.dlg.EXPORT_RUNNING: time.sleep(2*DELAY) 281 | self.assertTrue(os.path.exists(self.dlg.filepath)) 282 | # remove testfile 283 | os.remove(self.dlg.filepath) 284 | 285 | # restore default settings 286 | self.dlg.btnStart.click() 287 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 288 | self.assertTrue(isRotated(self.ax, self.dlg)) 289 | # make the animation small for faster export 290 | self.dlg.spinBox_dpi.setValue(100) 291 | self.dlg.spinBox_fps.setValue(24) 292 | self.dlg.spinBox_period_rot.setValue(10) 293 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 294 | 295 | 296 | class randwalk_3D_Test(unittest.TestCase): 297 | '''Animation manager test based on the '3D Random walk' example''' 298 | 299 | def __init__(self, testname, ax, dlg): 300 | 301 | unittest.TestCase.__init__(self, testname) 302 | 303 | self.ax, self.dlg = ax, dlg 304 | self.fig = self.ax.get_figure() 305 | 306 | def test_initialization(self): 307 | ''' 308 | test widgets comonent according to type of figure and animation 309 | - 2D/3D axes 310 | - object modification present 311 | ''' 312 | # curent example has modification 313 | self.assertTrue(self.dlg.widget_modif.isVisible()) 314 | self.assertTrue(self.dlg.checkBox_modif.isChecked()) 315 | 316 | # curent example are axes 3D, thus object can be rotated 317 | self.assertTrue(self.dlg.widget_rot.isVisible()) 318 | self.assertTrue(self.dlg.checkBox_rot.isChecked()) 319 | 320 | # period is least commot multiple of period_modif and period_rot 321 | self.assertTrue(self.dlg.period % self.dlg.period_modif == 0) 322 | self.assertTrue(self.dlg.period % self.dlg.period_rot == 0) 323 | 324 | def test_quality_props(self): 325 | 326 | # change dpi 327 | dpi = 50 328 | self.dlg.spinBox_dpi.setValue(dpi) 329 | time.sleep(DELAY) # wait for signal handling in the main event loop 330 | self.assertEqual(self.dlg.dpi, dpi) 331 | self.assertEqual(self.fig.get_dpi(), dpi) 332 | 333 | # change fps 334 | fps = 10 335 | self.dlg.spinBox_fps.setValue(fps) 336 | time.sleep(DELAY) # wait for signal handling in the main event loop 337 | self.assertEqual(self.dlg.fps, fps) 338 | 339 | # restore defaults: 340 | self.dlg.spinBox_dpi.setValue(100) 341 | self.dlg.spinBox_fps.setValue(24) 342 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 343 | 344 | def test_modif_props(self): 345 | 346 | # change modification period 347 | t = 30 348 | self.dlg.spinBox_period_modif.setValue(t) 349 | time.sleep(DELAY) # wait for signal handling in the main event loop 350 | self.assertEqual(self.dlg.period_modif, t) 351 | # period is least commot multiple of period_modif and period_rot 352 | self.assertTrue(self.dlg.period % self.dlg.period_modif == 0) 353 | self.assertTrue(self.dlg.period % self.dlg.period_rot == 0) 354 | 355 | def test_3D_props(self): 356 | 357 | # change rotation period 358 | t = 25 359 | self.dlg.spinBox_period_rot.setValue(t) 360 | time.sleep(DELAY) # wait for signal handling in the main event loop 361 | self.assertEqual(self.dlg.period_rot, t) 362 | # period is least commot multiple of period_modif and period_rot 363 | self.assertTrue(self.dlg.period % self.dlg.period_modif == 0) 364 | self.assertTrue(self.dlg.period % self.dlg.period_rot == 0) 365 | 366 | # change elevation 367 | elev = 10 368 | self.dlg.spinBox_elev.setValue(elev) 369 | time.sleep(DELAY) # wait for signal handling in the main event loop 370 | self.assertEqual(self.dlg.elevation, elev) 371 | self.assertEqual(self.ax.elev, elev) 372 | 373 | # enable/diable rotation 374 | self.dlg.checkBox_rot.setChecked(False) 375 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 376 | self.assertTrue(not isRotated(self.ax, self.dlg)) 377 | self.dlg.checkBox_rot.setChecked(True) 378 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 379 | self.assertTrue(isRotated(self.ax, self.dlg)) 380 | 381 | # change initial azimut 382 | azim = -50 383 | self.dlg.btnStop.click() 384 | time.sleep(DELAY) # wait for signal handling in the main event loop 385 | self.dlg.spinBox_azim.setValue(azim) 386 | time.sleep(DELAY) # wait for signal handling in the main event loop 387 | self.assertEqual(self.dlg.zero_azim, azim) 388 | self.assertEqual(self.ax.azim, azim) 389 | 390 | def test_control_btns(self): 391 | 392 | # test pause button 393 | self.dlg.btnPause.click() 394 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 395 | self.assertTrue(not isRotated(self.ax, self.dlg)) 396 | 397 | # test start button 398 | self.dlg.btnStart.click() 399 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 400 | self.assertTrue(isRotated(self.ax, self.dlg)) 401 | 402 | # test stop button 403 | self.assertNotEqual(self.ax.azim, self.dlg.spinBox_azim.value()) 404 | self.dlg.btnStop.click() 405 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 406 | self.assertTrue(not isRotated(self.ax, self.dlg)) 407 | self.assertEqual(self.ax.azim, self.dlg.spinBox_azim.value()) 408 | 409 | # test start button 410 | self.dlg.btnStart.click() 411 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 412 | self.assertTrue(isRotated(self.ax, self.dlg)) 413 | 414 | def test_exportAnim(self): 415 | 416 | # make the animation small for faster export 417 | self.dlg.spinBox_dpi.setValue(30) 418 | self.dlg.spinBox_fps.setValue(10) 419 | self.dlg.spinBox_period_rot.setValue(2) 420 | self.dlg.spinBox_period_modif.setValue(2) 421 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 422 | 423 | # export animation 424 | path = os.path.abspath(test_folder+"test") 425 | self.dlg.lineEdit_name.setText(path) 426 | self.dlg.EXPORT_RUNNING = True 427 | self.dlg.btnExport.click() 428 | # wait until exporting is finished 429 | while self.dlg.EXPORT_RUNNING: time.sleep(2*DELAY) 430 | self.assertTrue(os.path.exists(self.dlg.filepath)) 431 | # remove testfile 432 | os.remove(self.dlg.filepath) 433 | 434 | # restore default settings 435 | self.dlg.btnStart.click() 436 | time.sleep(2*DELAY) # wait for signal handling in the main event loop 437 | self.assertTrue(isRotated(self.ax, self.dlg)) 438 | # make the animation small for faster export 439 | self.dlg.spinBox_dpi.setValue(100) 440 | self.dlg.spinBox_fps.setValue(24) 441 | self.dlg.spinBox_period_rot.setValue(10) 442 | self.dlg.spinBox_period_modif.setValue(20) 443 | time.sleep(3*DELAY) # wait for signal handling in the main event loop 444 | 445 | 446 | def main(): 447 | '''run test suits and return the error code''' 448 | 449 | TestExamples_list = [(oscillation_2D, modif_oscillation_2D_Test), 450 | (rot_graph_3D, rot_graph_3D_Test), 451 | (modif_randwalk_3D, randwalk_3D_Test)] 452 | 453 | WAS_SUCCESSFUL = True 454 | 455 | for example, exampleTestCase in TestExamples_list: 456 | mng = example.get_animManager() 457 | result = mng.runUnitTest(MyTestCase=exampleTestCase) #TestResult instance 458 | 459 | WAS_SUCCESSFUL = WAS_SUCCESSFUL and result.wasSuccessful() 460 | 461 | return not WAS_SUCCESSFUL 462 | 463 | 464 | if __name__ == "__main__": 465 | 466 | main() -------------------------------------------------------------------------------- /mpl_animationmanager/animationmanager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Copyright (c) 2017, Ivan Luchko and Project Contributors 5 | Licensed under the terms of the MIT License 6 | https://github.com/luchko/mpl_animationmanager 7 | luchko.ivan@gmail.com 8 | 9 | This module contains the definition of the matplotlib animation management tool. 10 | 11 | class QDialogAnimManager(QDilaog, Ui_QDialogAnimManager): 12 | class AnimationManager(object): 13 | 14 | Module is compatible with both pyQt4 and pyQt5 15 | 16 | """ 17 | from __future__ import division # to handle python2 int division 18 | 19 | import matplotlib 20 | 21 | # define PyQt version 22 | try: 23 | import PyQt4 as PyQt 24 | pyQtVersion = "PyQt4" 25 | 26 | except ImportError: 27 | try: 28 | import PyQt5 as PyQt 29 | pyQtVersion = "PyQt5" 30 | except ImportError: 31 | raise ImportError("neither PyQt4 or PyQt5 is found") 32 | 33 | # imports requied PyQt modules 34 | if pyQtVersion == "PyQt4": 35 | matplotlib.use('Qt4Agg') 36 | from PyQt4.uic import loadUiType 37 | from PyQt4.QtCore import pyqtSignal, Qt, QTimer, QThread 38 | from PyQt4.QtGui import QApplication, QFileDialog, QMessageBox, QIcon 39 | else: 40 | matplotlib.use('Qt5Agg') 41 | from PyQt5.uic import loadUiType 42 | from PyQt5.QtCore import pyqtSignal, Qt, QTimer, QThread 43 | from PyQt5.QtWidgets import QApplication, QFileDialog, QMessageBox 44 | from PyQt5.QtGui import QIcon 45 | 46 | def getPathString(output): 47 | ''' 48 | returns a path string of the QFileDialog output 49 | 50 | pyQt5 returns a tuple (path, filter) not just a path QString like pyQt4 51 | ''' 52 | return str(output if pyQtVersion == "PyQt4" else output[0]) 53 | 54 | #import python libs 55 | import os 56 | import sys 57 | import unittest 58 | import subprocess 59 | from matplotlib import animation 60 | import matplotlib.pyplot as plt 61 | 62 | # import UI created in designer 63 | sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 64 | ui_folder = os.path.dirname(__file__)+"/" 65 | #ui_folder = 'mpl_animationmanager/' 66 | Ui_QDialogAnimManager, QDilaog = loadUiType(ui_folder+'QDialogAnimManager.ui') 67 | 68 | # classes definition 69 | 70 | class QDialogAnimManager(QDilaog, Ui_QDialogAnimManager): 71 | ''' 72 | PyQt dialog for creation, setting up and exporting matplotlib animations. 73 | 74 | Atimation frames change according to fAnim(i, ax, fargs) funcition. 75 | 76 | Widget can deal with both 2D and 3D axes. For 3D axes manager can add 77 | additional rotation of the view point resulting in both object modification 78 | and rotation animation. If fAnim(i, ax, fargs) is not provided 3D object 79 | anyway can be animated with the rotation animation. 80 | ''' 81 | closed = pyqtSignal(object) # used for interaction with parent 82 | EXPORT_RUNNING = False # True while exporting the animation 83 | 84 | def __init__(self, ax, fAnim=None, fargs=None, numFramesModif=None, *args, **kwargs): 85 | ''' 86 | Parameters 87 | ---------- 88 | ax : 2D or 3D matplotlib axes object binded to the figure 89 | provides control over animated figure 90 | fAnim : function 91 | fAnim(i, ax, fargs) - modifies the "ax" at each "i" step 92 | fargs : any 93 | arguments used by the "fAnim" function during the "ax" modification 94 | numFramesModif : int 95 | number of modification frames 96 | 97 | ''' 98 | super(QDialogAnimManager, self).__init__() 99 | self.setAttribute(Qt.WA_DeleteOnClose) 100 | self.setupUi(self) 101 | 102 | # add button icons 103 | img_folder = os.path.join(os.path.dirname(__file__), 'images') 104 | self.btnAsk.setIcon(QIcon(os.path.join(img_folder, 'info.png'))) 105 | self.btnStart.setIcon(QIcon(os.path.join(img_folder, 'play.png'))) 106 | self.btnPause.setIcon(QIcon(os.path.join(img_folder, 'pause.png'))) 107 | self.btnStop.setIcon(QIcon(os.path.join(img_folder, 'stop.png'))) 108 | 109 | self.filepath = str(self.lineEdit_name.text()) 110 | self.setup_writers_comboBox() 111 | 112 | self.ax = ax 113 | self.fig = ax.get_figure() 114 | self.fig.subplots_adjust(left=0, bottom=0, right=1, top=1) 115 | self.fAnim, self.fargs = fAnim, fargs 116 | self.numFramesModif = numFramesModif 117 | self.args, self.kwargs = args, kwargs 118 | 119 | self.dpi = self.fig.get_dpi() 120 | self.spinBox_dpi.setValue(self.dpi) 121 | self.fps = self.spinBox_fps.value() 122 | 123 | # configure widgets according to type of animation 124 | if self.fAnim is not None: # if no changes are provided 125 | self.period_modif = self.spinBox_period_modif.value() 126 | self.spinBox_period_modif.valueChanged.connect(self.changeTiming_callback) 127 | self.checkBox_modif.toggled.connect(self.changeTiming_callback) 128 | else: 129 | self.checkBox_modif.setChecked(False) 130 | self.widget_modif.setVisible(False) 131 | 132 | if self.axesDimensions(ax) == 3: 133 | self.elevation = self.spinBox_elev.value() 134 | self.zero_azim = self.spinBox_azim.value() 135 | self.period_rot = self.spinBox_period_rot.value() 136 | self.dpf = 360/(self.period_rot*self.fps) # degrees per frame 137 | 138 | self.checkBox_rot.toggled.connect(self.changeTiming_callback) 139 | self.spinBox_period_rot.valueChanged.connect(self.changeTiming_callback) 140 | self.spinBox_azim.valueChanged.connect(self.changeAzim_callback) 141 | self.spinBox_elev.valueChanged.connect(self.changeElev_callback) 142 | else: 143 | self.checkBox_rot.setChecked(False) 144 | self.widget_rot.setVisible(False) 145 | 146 | # some widgets will not be displayed depending on type of animation 147 | self.adjustSize() 148 | 149 | # calculate the length of animation 150 | if self.checkBox_modif.isChecked() and self.checkBox_rot.isChecked(): 151 | self.period = self.lcm(self.period_modif, self.period_rot) 152 | elif self.checkBox_modif.isChecked() and not self.checkBox_rot.isChecked(): 153 | self.period = self.period_modif 154 | elif not self.checkBox_modif.isChecked() and self.checkBox_rot.isChecked(): 155 | self.period = self.period_rot 156 | 157 | self.frames = self.period*self.fps # total number of frames 158 | 159 | # create animation 160 | self.stop_callback() 161 | self.start_callback() 162 | 163 | self.spinBox_dpi.valueChanged.connect(self.change_dpi_callback) 164 | self.spinBox_fps.valueChanged.connect(self.changeTiming_callback) 165 | 166 | self.btnStart.clicked.connect(self.start_callback) 167 | self.btnPause.clicked.connect(self.pause_callback) 168 | self.btnStop.clicked.connect(self.stop_callback) 169 | 170 | self.btnBrowse.clicked.connect(self.browse_callback) 171 | self.btnAsk.clicked.connect(self.info_callback) 172 | self.btnClose.clicked.connect(self.close_callback) 173 | self.btnShow.clicked.connect(self.show_anim_callback) 174 | self.btnExport.clicked.connect(self.export_callback) 175 | 176 | self.lineEdit_name.textChanged.connect(self.fileNameChanged_slot) 177 | 178 | def rotate(self, i): 179 | '''called during creation animation frames''' 180 | 181 | if i == 0: # remember starting azimut (used for dynamic speed adjustment) 182 | self.j_modif_start = self.j_modif 183 | if self.checkBox_rot.isChecked(): 184 | self.azim_start = self.ax.azim 185 | 186 | # modify figure 187 | if self.checkBox_modif.isChecked(): 188 | self.j_modif = int(i/(self.period_modif*self.fps)*self.numFramesModif) 189 | self.j_modif += self.j_modif_start 190 | self.j_modif %= self.numFramesModif 191 | self.fAnim(self.j_modif, self.ax, self.fargs) 192 | 193 | # rotate figure 194 | if self.checkBox_rot.isChecked(): 195 | self.ax.view_init(elev=self.elevation, 196 | azim=self.azim_start + i*self.dpf) 197 | 198 | self.progressBar.setValue(round((i+1)/self.frames*100)) 199 | 200 | def change_dpi_callback(self): 201 | self.dpi = self.spinBox_dpi.value() 202 | self.fig.set_dpi(self.dpi) 203 | # hack used to update the figure size 204 | size = self.fig.get_size_inches() 205 | self.fig.set_size_inches(size, forward=True) 206 | 207 | def changeAzim_callback(self, azimVal): 208 | self.ax.view_init(elev=self.elevation, 209 | azim=self.ax.azim -self.zero_azim + azimVal) 210 | self.fig.canvas.draw() 211 | self.zero_azim = azimVal 212 | 213 | def changeElev_callback(self, elevVal): 214 | self.ax.view_init(elev=elevVal, azim=self.ax.azim) 215 | self.fig.canvas.draw() 216 | self.elevation = elevVal 217 | 218 | def changeTiming_callback(self): 219 | '''called when any parameter that influences frames flow is changed''' 220 | 221 | self.fps = self.spinBox_fps.value() 222 | self.period_modif = self.spinBox_period_modif.value() 223 | self.period_rot = self.spinBox_period_rot.value() 224 | self.dpf = 360/(self.period_rot*self.fps) # degrees per frame 225 | 226 | if self.checkBox_modif.isChecked() and self.checkBox_rot.isChecked(): 227 | self.period = self.lcm(self.period_modif, self.period_rot) 228 | elif self.checkBox_modif.isChecked() and not self.checkBox_rot.isChecked(): 229 | self.period = self.period_modif 230 | elif not self.checkBox_modif.isChecked() and self.checkBox_rot.isChecked(): 231 | self.period = self.period_rot 232 | else: 233 | self.pause_callback() 234 | return 235 | 236 | self.frames = self.period*self.fps 237 | 238 | try: # stop previsous animation before deleting creating new one 239 | self.anim._stop() 240 | del self.anim 241 | self.progressBar.setValue(0) 242 | except AttributeError: 243 | pass 244 | 245 | # create new animation 246 | self.anim = animation.FuncAnimation(self.fig, self.rotate, 247 | frames=self.frames, repeat=True, 248 | interval=1000/self.fps) 249 | 250 | self.fig.canvas.draw() # redraw to run the animation 251 | 252 | def start_callback(self): 253 | '''start animation''' 254 | self.anim.event_source.start() 255 | 256 | def pause_callback(self): 257 | '''pause animation''' 258 | self.anim.event_source.stop() 259 | 260 | def stop_callback(self): 261 | '''stop animation''' 262 | 263 | self.j_modif = 0 264 | 265 | try: # stop previsous animation before deleting creating new one 266 | self.anim._stop() 267 | del self.anim 268 | self.progressBar.setValue(0) 269 | except AttributeError: 270 | pass 271 | 272 | # change view according to starting deafalut values 273 | if self.checkBox_rot.isChecked(): 274 | self.ax.view_init(elev = self.elevation, azim = self.zero_azim) 275 | # create new animation 276 | self.anim = animation.FuncAnimation(self.fig, self.rotate, 277 | frames=self.frames, repeat=True, 278 | interval=1000/self.fps) 279 | self.fig.canvas.draw() # redraw to run the animation 280 | self.anim.event_source.stop() 281 | 282 | def setup_writers_comboBox(self): 283 | '''fill the VideoWriter comboBox''' 284 | 285 | w_list = animation.writers.list() 286 | self.comboBox_writers.currentIndexChanged.connect(self.set_extension) 287 | for writer in w_list: 288 | self.comboBox_writers.addItem(writer) 289 | if "imagemagick" in w_list: 290 | self.comboBox_writers.setCurrentIndex(w_list.index("imagemagick")) 291 | 292 | def set_extension(self): 293 | '''set file extension according to chosed VideoWriter''' 294 | 295 | # set extension in comboBox_ext 296 | self.comboBox_ext.clear() 297 | support_gif = ["imagemagick", "imagemagick_file"] 298 | if str(self.comboBox_writers.currentText()) in support_gif: 299 | self.comboBox_ext.addItem("gif") 300 | else: 301 | self.comboBox_ext.addItem("mp4") 302 | # set extension in filepath 303 | self.filepath = "{0}.{1}".format(os.path.splitext(str(self.lineEdit_name.text()))[0], 304 | self.comboBox_ext.currentText()) 305 | self.lineEdit_name.setText(self.filepath) 306 | 307 | def fileNameChanged_slot(self): 308 | '''preserve file extension during editing''' 309 | 310 | pos = self.lineEdit_name.cursorPosition() 311 | self.set_extension() 312 | self.lineEdit_name.setCursorPosition(pos) 313 | 314 | def browse_callback(self): 315 | '''get the file name where animation will be saved''' 316 | 317 | output = QFileDialog.getSaveFileName(self, 'Save model animation', '', 318 | filter="Animations (*.mp4 *.gif);;All files (*.*)") 319 | path = getPathString(output) 320 | if path != "": 321 | self.filepath = path 322 | self.set_extension() 323 | 324 | def info_callback(self): 325 | '''show information anbout animation management tool''' 326 | 327 | self.msg = QMessageBox() 328 | self.msg.setIcon(QMessageBox.Information) 329 | self.msg.setTextFormat(Qt.RichText) 330 | text = ''' 331 | Matplotlib animation manager 1.0a1 332 |
333 | Copyright © 2017, Ivan Luchko and Project Contributors 334 |
335 | Licensed under the terms of the MIT License 336 |

337 | This widget allows to manipulate and export matplotlib animations. 338 |

339 | For configuring the writers refer to the 340 | matplotlib website. 341 |

342 | For bug reports and feature requests, please go to our 343 | Github website. 344 | ''' 345 | self.msg.setText(text) 346 | self.msg.setWindowTitle("Info") 347 | self.msg.setStandardButtons(QMessageBox.Ok) 348 | self.msg.exec_() 349 | 350 | def show_anim_callback(self): 351 | '''opens exported animation file with default system tool''' 352 | 353 | if sys.platform.startswith('darwin'): 354 | subprocess.call(('open', self.filepath)) 355 | elif os.name == 'nt': 356 | os.startfile(self.filepath) 357 | elif os.name == 'posix': 358 | subprocess.call(('xdg-open', self.filepath)) 359 | 360 | def export_callback(self): 361 | '''export animation to file mp4 or gif file''' 362 | 363 | self.EXPORT_RUNNING = True 364 | 365 | if len(animation.writers.list()) == 0: 366 | self.msb_noWriters = QMessageBox() 367 | self.msb_noWriters.setIcon(QMessageBox.Critical) 368 | self.msb_noWriters.setWindowTitle("Message") 369 | self.msb_noWriters.setStandardButtons(QMessageBox.Ok) 370 | self.msb_noWriters.setText("No MovieWriter installed") 371 | self.msb_noWriters.exec_() 372 | return 373 | 374 | # disable btnShow and reset animation 375 | self.btnShow.setEnabled(False) 376 | self.stop_callback() 377 | 378 | _writerName = self.comboBox_writers.currentText() 379 | self.filepath = str(self.lineEdit_name.text()) 380 | self.anim.save(self.filepath, fps=self.fps, dpi=self.dpi, 381 | writer=_writerName, *self.args, **self.kwargs) 382 | 383 | self.btnShow.setEnabled(True) 384 | print(" animation exported into '{}'".format(os.path.basename(self.filepath))) 385 | 386 | self.EXPORT_RUNNING = False 387 | 388 | def close_callback(self): 389 | self.anim._stop() 390 | del self.anim 391 | self.closed.emit(True) 392 | self.reject() 393 | 394 | def closeEvent(self, event): 395 | self.anim._stop() 396 | del self.anim 397 | self.closed.emit(True) 398 | event.accept() 399 | 400 | def axesDimensions(self, ax): 401 | '''returns the dimension of matplotlib axes object''' 402 | if hasattr(ax, 'get_zlim'): 403 | return 3 404 | else: 405 | return 2 406 | 407 | def lcm(self, x, y): 408 | '''Returns least common multiple of x and y''' 409 | z = x if x > y else y 410 | while(True): 411 | if((z % x == 0) and (z % y == 0)): 412 | lcm = z 413 | break 414 | z += 1 415 | 416 | return lcm 417 | 418 | 419 | class AnimationManager(object): 420 | ''' 421 | A small class build on top of the 'QDialogAnimManager' and uses 422 | the input arguments to initialize the 'QDialogAnimManager' object 423 | and run a PyQt application using run() function. 424 | 425 | It allows creation, setting up and exporting matplotlib animations. 426 | 427 | Atimation frames change according to fAnim(i, ax, fargs) funcition. 428 | 429 | Widget can deal with both 2D and 3D axes. For 3D axes manager can add 430 | additional rotation of the view point resulting in both object modification 431 | and rotation animation. If fAnim(i, ax, fargs) is not provided 3D object 432 | anyway can be animated with the rotation animation. 433 | ''' 434 | app = QApplication([]) # start PyQt application 435 | 436 | class TestThread(QThread): 437 | '''separate thread for running the unittest suit''' 438 | 439 | def __init__(self, testSuite , *args, **kwargs): 440 | ''' 441 | Parameters 442 | ---------- 443 | testSuite : unittest.TestSuite 444 | prepared unittest suit which will be run to test QObject 445 | ''' 446 | QThread.__init__(self, *args, **kwargs) 447 | self.testSuite = testSuite 448 | 449 | def run(self): 450 | '''method which runs the TestCase''' 451 | self.result = unittest.TextTestRunner().run(self.testSuite) 452 | self.exit(not self.result.wasSuccessful()) 453 | 454 | def __init__(self, ax, fAnim=None, fargs=None, numFramesModif=None, *args, **kwargs): 455 | ''' 456 | Parameters 457 | ---------- 458 | ax : 2D or 3D matplotlib axes object binded to the figure 459 | provides control over animated figure 460 | fAnim : function 461 | fAnim(i, ax, fargs) - modifies the "ax" at each "i" step 462 | fargs : any 463 | arguments used by the "fAnim" function during the "ax" modification 464 | numFramesModif : int 465 | number of modification frames 466 | ''' 467 | self.ax = ax 468 | self.fig = ax.get_figure() 469 | self.dlg = QDialogAnimManager(ax, fAnim, fargs, numFramesModif, *args, **kwargs) 470 | 471 | # bind fig and dlg for close event 472 | self.dlg.closed.connect(lambda: plt.close(self.fig)) 473 | self.fig.canvas.mpl_connect('close_event', lambda e: self.dlg.reject()) 474 | 475 | def run(self): 476 | '''Open the QDialogAnimManager and run the animation''' 477 | 478 | self.dlg.show() 479 | self.fig.show() 480 | 481 | return self.app.exec_() 482 | 483 | def runUnitTest(self, MyTestCase): 484 | ''' 485 | hook for running the TestCase after launching the Qt app 486 | 487 | Parameters 488 | ---------- 489 | MyTestCase: unittest.TestCase 490 | class defining unittest case 491 | 492 | Return 493 | ------ 494 | result: unittest.TestResult 495 | instance containing the results of the running TestCase 496 | ''' 497 | # initialize test suite based on the MyTestCase 498 | test_loader = unittest.TestLoader() 499 | test_names = test_loader.getTestCaseNames(MyTestCase) 500 | suite = unittest.TestSuite() 501 | for test_name in test_names: 502 | suite.addTest(MyTestCase(test_name, self.ax, self.dlg)) 503 | 504 | # create test thread and pass test suite 505 | thread = self.TestThread(testSuite=suite, parent=self.app) 506 | thread.finished.connect(self.dlg.close) 507 | 508 | # start thread after the app.exec_() is called (main loop started) 509 | timer = QTimer(parent=self.app) 510 | timer.singleShot(0, thread.start) 511 | 512 | self.run() 513 | 514 | return thread.result -------------------------------------------------------------------------------- /mpl_animationmanager/QDialogAnimManager.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 345 10 | 400 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 345 22 | 0 23 | 24 | 25 | 26 | 27 | 345 28 | 16777215 29 | 30 | 31 | 32 | Animation manager 33 | 34 | 35 | 36 | mpl_animationmanager/images/icon.pngmpl_animationmanager/images/icon.png 37 | 38 | 39 | false 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 0 51 | 0 52 | 53 | 54 | 55 | dpi: 56 | 57 | 58 | 59 | 60 | 61 | 62 | 10 63 | 64 | 65 | 300 66 | 67 | 68 | 5 69 | 70 | 71 | 80 72 | 73 | 74 | Qt::Horizontal 75 | 76 | 77 | QSlider::NoTicks 78 | 79 | 80 | 81 | 82 | 83 | 84 | 10 85 | 86 | 87 | 300 88 | 89 | 90 | 5 91 | 92 | 93 | 80 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 0 106 | 0 107 | 108 | 109 | 110 | fps: 111 | 112 | 113 | 114 | 115 | 116 | 117 | 1 118 | 119 | 120 | 30 121 | 122 | 123 | 24 124 | 125 | 126 | Qt::Horizontal 127 | 128 | 129 | 130 | 131 | 132 | 133 | 1 134 | 135 | 136 | 100 137 | 138 | 139 | 24 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 0 152 | 0 153 | 154 | 155 | 156 | 157 | 0 158 | 159 | 160 | 0 161 | 162 | 163 | 0 164 | 165 | 166 | 0 167 | 168 | 169 | 170 | 171 | Qt::Horizontal 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | true 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 0 192 | 0 193 | 194 | 195 | 196 | Modification period, s: 197 | 198 | 199 | 200 | 201 | 202 | 203 | 1 204 | 205 | 206 | 60 207 | 208 | 209 | 15 210 | 211 | 212 | Qt::Horizontal 213 | 214 | 215 | 216 | 217 | 218 | 219 | 1 220 | 221 | 222 | 300 223 | 224 | 225 | 5 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 0 239 | 0 240 | 241 | 242 | 243 | 244 | 0 245 | 246 | 247 | 0 248 | 249 | 250 | 0 251 | 252 | 253 | 0 254 | 255 | 256 | 257 | 258 | Qt::Horizontal 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | true 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 0 279 | 0 280 | 281 | 282 | 283 | Rotation period, s: 284 | 285 | 286 | 287 | 288 | 289 | 290 | 1 291 | 292 | 293 | 60 294 | 295 | 296 | 15 297 | 298 | 299 | Qt::Horizontal 300 | 301 | 302 | 303 | 304 | 305 | 306 | 1 307 | 308 | 309 | 300 310 | 311 | 312 | 15 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 0 325 | 0 326 | 327 | 328 | 329 | Elevation: 330 | 331 | 332 | 333 | 334 | 335 | 336 | 90 337 | 338 | 339 | 15 340 | 341 | 342 | Qt::Horizontal 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 0 351 | 0 352 | 353 | 354 | 355 | 356 | 10 357 | 0 358 | 359 | 360 | 361 | 0 362 | 363 | 364 | 90 365 | 366 | 367 | 15 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 0 380 | 0 381 | 382 | 383 | 384 | Initial azimut: 385 | 386 | 387 | 388 | 389 | 390 | 391 | -180 392 | 393 | 394 | 180 395 | 396 | 397 | -30 398 | 399 | 400 | Qt::Horizontal 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 0 409 | 0 410 | 411 | 412 | 413 | 414 | 10 415 | 0 416 | 417 | 418 | 419 | -180 420 | 421 | 422 | 180 423 | 424 | 425 | -30 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | Qt::Horizontal 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 0 448 | 0 449 | 450 | 451 | 452 | 453 | 35 454 | 16777215 455 | 456 | 457 | 458 | Qt::NoFocus 459 | 460 | 461 | 462 | 463 | 464 | 465 | mpl_animationmanager/images/play.pngmpl_animationmanager/images/play.png 466 | 467 | 468 | 469 | 25 470 | 25 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 35 480 | 16777215 481 | 482 | 483 | 484 | Qt::NoFocus 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | mpl_animationmanager/images/pause.pngmpl_animationmanager/images/pause.png 495 | 496 | 497 | 498 | 25 499 | 25 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 0 509 | 0 510 | 511 | 512 | 513 | 514 | 0 515 | 0 516 | 517 | 518 | 519 | 520 | 35 521 | 16777215 522 | 523 | 524 | 525 | Qt::NoFocus 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | mpl_animationmanager/images/stop.pngmpl_animationmanager/images/stop.png 536 | 537 | 538 | 539 | 25 540 | 25 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 0 550 | 0 551 | 552 | 553 | 554 | 0 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | Qt::Horizontal 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | File name: 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 10 581 | false 582 | 583 | 584 | 585 | my_animation 586 | 587 | 588 | 589 | 590 | 591 | 592 | Browse... 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 0 605 | 0 606 | 607 | 608 | 609 | Writer: 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 60 621 | 16777215 622 | 623 | 624 | 625 | 626 | mp4 627 | 628 | 629 | 630 | 631 | gif 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | Qt::Horizontal 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 0 652 | 0 653 | 654 | 655 | 656 | 657 | 35 658 | 16777215 659 | 660 | 661 | 662 | Qt::TabFocus 663 | 664 | 665 | 666 | 667 | 668 | 669 | mpl_animationmanager/images/Info.pngmpl_animationmanager/images/Info.png 670 | 671 | 672 | 673 | 25 674 | 25 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | Qt::Horizontal 683 | 684 | 685 | QSizePolicy::Expanding 686 | 687 | 688 | 689 | 40 690 | 20 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | Close 699 | 700 | 701 | true 702 | 703 | 704 | 705 | 706 | 707 | 708 | false 709 | 710 | 711 | Show 712 | 713 | 714 | 715 | 716 | 717 | 718 | Export 719 | 720 | 721 | true 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | line 730 | line_2 731 | line_3 732 | widget_rot 733 | widget_modif 734 | 735 | 736 | 737 | 738 | spinBox_fps 739 | valueChanged(int) 740 | horizontalSlider 741 | setValue(int) 742 | 743 | 744 | 288 745 | 23 746 | 747 | 748 | 266 749 | 39 750 | 751 | 752 | 753 | 754 | horizontalSlider 755 | valueChanged(int) 756 | spinBox_fps 757 | setValue(int) 758 | 759 | 760 | 266 761 | 28 762 | 763 | 764 | 288 765 | 15 766 | 767 | 768 | 769 | 770 | horizontalSlider_2 771 | valueChanged(int) 772 | spinBox_period_rot 773 | setValue(int) 774 | 775 | 776 | 267 777 | 131 778 | 779 | 780 | 288 781 | 113 782 | 783 | 784 | 785 | 786 | spinBox_period_rot 787 | valueChanged(int) 788 | horizontalSlider_2 789 | setValue(int) 790 | 791 | 792 | 288 793 | 113 794 | 795 | 796 | 267 797 | 131 798 | 799 | 800 | 801 | 802 | horizontalSlider_3 803 | valueChanged(int) 804 | spinBox_elev 805 | setValue(int) 806 | 807 | 808 | 267 809 | 167 810 | 811 | 812 | 288 813 | 151 814 | 815 | 816 | 817 | 818 | spinBox_elev 819 | valueChanged(int) 820 | horizontalSlider_3 821 | setValue(int) 822 | 823 | 824 | 288 825 | 151 826 | 827 | 828 | 267 829 | 167 830 | 831 | 832 | 833 | 834 | horizontalSlider_4 835 | valueChanged(int) 836 | spinBox_azim 837 | setValue(int) 838 | 839 | 840 | 267 841 | 204 842 | 843 | 844 | 288 845 | 188 846 | 847 | 848 | 849 | 850 | spinBox_azim 851 | valueChanged(int) 852 | horizontalSlider_4 853 | setValue(int) 854 | 855 | 856 | 288 857 | 188 858 | 859 | 860 | 267 861 | 204 862 | 863 | 864 | 865 | 866 | horizontalSlider_5 867 | valueChanged(int) 868 | spinBox_period_modif 869 | setValue(int) 870 | 871 | 872 | 267 873 | 86 874 | 875 | 876 | 288 877 | 64 878 | 879 | 880 | 881 | 882 | spinBox_period_modif 883 | valueChanged(int) 884 | horizontalSlider_5 885 | setValue(int) 886 | 887 | 888 | 288 889 | 61 890 | 891 | 892 | 267 893 | 86 894 | 895 | 896 | 897 | 898 | checkBox_modif 899 | toggled(bool) 900 | horizontalSlider_5 901 | setEnabled(bool) 902 | 903 | 904 | 28 905 | 68 906 | 907 | 908 | 214 909 | 64 910 | 911 | 912 | 913 | 914 | checkBox_modif 915 | toggled(bool) 916 | spinBox_period_modif 917 | setEnabled(bool) 918 | 919 | 920 | 29 921 | 68 922 | 923 | 924 | 288 925 | 61 926 | 927 | 928 | 929 | 930 | checkBox_modif 931 | toggled(bool) 932 | label_10 933 | setEnabled(bool) 934 | 935 | 936 | 29 937 | 77 938 | 939 | 940 | 65 941 | 77 942 | 943 | 944 | 945 | 946 | checkBox_rot 947 | toggled(bool) 948 | spinBox_period_rot 949 | setEnabled(bool) 950 | 951 | 952 | 24 953 | 118 954 | 955 | 956 | 318 957 | 111 958 | 959 | 960 | 961 | 962 | checkBox_rot 963 | toggled(bool) 964 | horizontalSlider_2 965 | setEnabled(bool) 966 | 967 | 968 | 29 969 | 127 970 | 971 | 972 | 226 973 | 105 974 | 975 | 976 | 977 | 978 | checkBox_rot 979 | toggled(bool) 980 | label_2 981 | setEnabled(bool) 982 | 983 | 984 | 12 985 | 117 986 | 987 | 988 | 100 989 | 125 990 | 991 | 992 | 993 | 994 | horizontalSlider_7 995 | valueChanged(int) 996 | spinBox_dpi 997 | setValue(int) 998 | 999 | 1000 | 96 1001 | 13 1002 | 1003 | 1004 | 118 1005 | 18 1006 | 1007 | 1008 | 1009 | 1010 | spinBox_dpi 1011 | valueChanged(int) 1012 | horizontalSlider_7 1013 | setValue(int) 1014 | 1015 | 1016 | 112 1017 | 29 1018 | 1019 | 1020 | 78 1021 | 34 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | --------------------------------------------------------------------------------