Repository: petercorke/bdsim Branch: master Commit: 9603af34d51d Files: 191 Total size: 5.2 MB Directory structure: gitextract_lbrlybs_/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── 0bdsim-bug-report.md │ │ └── 1bdedit-bug-report.md │ └── workflows/ │ ├── master.yml │ └── publish.yml ├── .gitignore ├── CONTRIBUTING.md ├── Icons.qrc ├── LICENSE ├── Makefile ├── README.md ├── bdsim/ │ ├── __init__.py │ ├── bdedit/ │ │ ├── Icons.py │ │ ├── Icons.qrc │ │ ├── LICENSE │ │ ├── README.md │ │ ├── TechReport.md │ │ ├── __init__.py │ │ ├── bdedit.py │ │ ├── block.py │ │ ├── block_connector_block.py │ │ ├── block_graphics_block.py │ │ ├── block_graphics_socket.py │ │ ├── block_graphics_wire.py │ │ ├── block_importer.py │ │ ├── block_main_block.py │ │ ├── block_param_window.py │ │ ├── block_socket.py │ │ ├── block_wire.py │ │ ├── docs/ │ │ │ ├── .buildinfo │ │ │ ├── _sources/ │ │ │ │ ├── bdedit.rst.txt │ │ │ │ ├── index.rst.txt │ │ │ │ └── modules.rst.txt │ │ │ ├── _static/ │ │ │ │ ├── alabaster.css │ │ │ │ ├── basic.css │ │ │ │ ├── css/ │ │ │ │ │ ├── badge_only.css │ │ │ │ │ └── theme.css │ │ │ │ ├── custom.css │ │ │ │ ├── doctools.js │ │ │ │ ├── documentation_options.js │ │ │ │ ├── jquery-3.5.1.js │ │ │ │ ├── jquery.js │ │ │ │ ├── js/ │ │ │ │ │ ├── badge_only.js │ │ │ │ │ └── theme.js │ │ │ │ ├── language_data.js │ │ │ │ ├── pygments.css │ │ │ │ ├── searchtools.js │ │ │ │ ├── underscore-1.13.1.js │ │ │ │ └── underscore.js │ │ │ ├── bdedit.html │ │ │ ├── genindex.html │ │ │ ├── index.html │ │ │ ├── modules.html │ │ │ ├── objects.inv │ │ │ ├── py-modindex.html │ │ │ ├── search.html │ │ │ └── searchindex.js │ │ ├── docstring.md │ │ ├── floating_label.py │ │ ├── floating_label_graphics.py │ │ ├── grouping_box.py │ │ ├── grouping_box_graphics.py │ │ ├── interface.py │ │ ├── interface_graphics_scene.py │ │ ├── interface_graphics_view.py │ │ ├── interface_manager.py │ │ ├── interface_scene.py │ │ ├── interface_scene_history.py │ │ └── interface_serialize.py │ ├── bdrun.py │ ├── bin/ │ │ └── stubgen.py │ ├── blockdiagram.py │ ├── blockdiagram.pyi │ ├── blocks/ │ │ ├── IO/ │ │ │ ├── Firmata.py │ │ │ └── README.md │ │ ├── Icons/ │ │ │ ├── README.md │ │ │ └── icons.sh │ │ ├── README.md │ │ ├── __init__.py │ │ ├── connections.py │ │ ├── discrete.py │ │ ├── displays.py │ │ ├── functions.py │ │ ├── linalg.py │ │ ├── sinks.py │ │ ├── sources.py │ │ ├── spatial.py │ │ ├── transfers.py │ │ └── vision.py │ ├── components.py │ ├── graphics.py │ ├── run_realtime.py │ ├── run_sim.py │ └── tk_editor/ │ ├── bdeditor.py │ ├── edit.py │ ├── editor.py │ └── editor2.py ├── docs/ │ ├── Makefile │ ├── make.bat │ └── source/ │ ├── bdsim.blocks.rst │ ├── bdsim.rst │ ├── conf.py │ ├── exts/ │ │ └── blockname.py │ ├── index.rst │ ├── internals.rst │ └── modules.rst ├── docs-aside/ │ ├── .buildinfo │ ├── .nojekyll │ ├── _modules/ │ │ ├── bdsim/ │ │ │ ├── bdsim.html │ │ │ ├── blockdiagram.html │ │ │ ├── blocks/ │ │ │ │ ├── connections.html │ │ │ │ ├── discrete.html │ │ │ │ ├── functions.html │ │ │ │ ├── linalg.html │ │ │ │ ├── robots.html │ │ │ │ ├── sinks.html │ │ │ │ ├── sources.html │ │ │ │ └── transfers.html │ │ │ ├── components.html │ │ │ └── graphics.html │ │ ├── index.html │ │ └── roboticstoolbox/ │ │ └── blocks/ │ │ ├── arm.html │ │ ├── mobile.html │ │ └── uav.html │ ├── _sources/ │ │ ├── bdsim.blocks.rst.txt │ │ ├── bdsim.rst.txt │ │ ├── index.rst.txt │ │ ├── internals.rst.txt │ │ └── modules.rst.txt │ ├── _static/ │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── css/ │ │ │ ├── badge_only.css │ │ │ └── theme.css │ │ ├── custom.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── fonts/ │ │ │ └── FontAwesome.otf │ │ ├── graphviz.css │ │ ├── jquery-3.4.1.js │ │ ├── jquery-3.5.1.js │ │ ├── jquery.js │ │ ├── js/ │ │ │ ├── badge_only.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── underscore-1.3.1.js │ │ └── underscore.js │ ├── bdsim.blocks.html │ ├── bdsim.html │ ├── genindex.html │ ├── index.html │ ├── internals.html │ ├── modules.html │ ├── objects.inv │ ├── py-modindex.html │ ├── search.html │ └── searchindex.js ├── examples/ │ ├── README.md │ ├── RVC2/ │ │ ├── README.md │ │ ├── rvc4_11.py │ │ ├── rvc4_2.py │ │ ├── rvc4_4.py │ │ ├── rvc4_6.py │ │ └── rvc4_8.py │ ├── deriv.py │ ├── eg1.bd │ ├── eg1.py │ ├── eg1_zoh.py │ ├── pid.py │ ├── rt_step.py │ ├── sine+sampler.py │ ├── sine+sampler2.py │ ├── subsys.py │ ├── vanderpol.py │ ├── viewsim.py │ └── waveform.py ├── figs/ │ ├── data_structures.ezdraw │ └── plugs_and_wires.ezdraw ├── pyproject.toml └── tests/ ├── __init__.py ├── test_bdsim.py ├── test_blockdiagram.py ├── test_components.py ├── test_connections.py ├── test_discrete.py ├── test_functions.py ├── test_linalg.py ├── test_sinks.py ├── test_sources.py ├── test_spatial.py └── test_transfers.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/0bdsim-bug-report.md ================================================ --- name: bdsim bug report about: Create a report to help us improve bdsim, the Python based block diagram simulator title: "[bdsim BUG]" labels: '' assignees: '' --- For bugs with the graphical tool `bdedit` please use the `bdedit` bug report template. **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Please provide a complete minimal stand-alone Python example. **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, for instance a problem with a SCOPE block, please add screenshots to help explain your problem. **Operating environment (please complete the following information):** - OS: [e.g. MacOS] - Version [e.g. Ventura] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/1bdedit-bug-report.md ================================================ --- name: bdedit bug report about: Create a report to help us improve bdedit, the graphical editor for block diagrams. title: "[bdedit BUG]" labels: '' assignees: petercorke --- **`bdedit` is an experimental editor for block diagrams that can be used in conjunction with `bdsim`. It is not a drop-in replacement for Simulink, if you want something reliable and supported buy it from The MathWorks.** **Before you post a bug please check that PyQt5 is properly installed on your computer, see the notes on the [wiki](https://github.com/petercorke/bdsim/wiki/Getting-going-with-PyQt5). If you cannot run the PyQt5 test example then you need to resolve that yourself, please don't post it here.** **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Operating environment (please complete the following information):** - OS: [e.g. MacOS] - Version [e.g. Ventura] **Your version of PyQT - please include the output of running `pip list | grep -I qt` **Additional context** Add any other context about the problem here. ================================================ FILE: .github/workflows/master.yml ================================================ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: build on: push: branches: [ master, future ] # pull_request: # branches: [ master ] jobs: # Run tests on different versions of python unittest: runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest, ubuntu-latest, macos-13] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install .[dev] - name: Test with pytest env: MPLBACKEND: TkAgg run: | pytest --capture=no --ignore=W605 --timeout=50 --timeout_method=thread codecov: # If all tests pass: # Run coverage and upload to codecov needs: unittest runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip - name: Run coverage run: | pip install .[dev] coverage run --omit='tests/*.py' -m pytest coverage report coverage xml - name: upload coverage to Codecov uses: codecov/codecov-action@v4 with: file: ./coverage.xml sphinx: # If the above worked: # Build docs and upload to GH Pages needs: unittest runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip pip install .[dev,docs] pip install git+https://github.com/petercorke/sphinx-autorun.git pip install sympy sudo apt-get install graphviz - name: Build docs run: | cd docs make html # Tell GitHub not to use jekyll to compile the docs touch build/html/.nojekyll cd ../ - name: Commit documentation changes run: | git clone https://github.com/petercorke/bdsim.git --branch gh-pages --single-branch gh-pages cp -r docs/build/html/* gh-pages/ cd gh-pages git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add . git commit -m "Update documentation" -a || true # The above command will fail if no changes were present, so we ignore # that. - name: Push changes uses: ad-m/github-push-action@master with: branch: gh-pages directory: gh-pages github_token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/publish.yml ================================================ # This workflows will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries # name: Upload Python Package on: release: types: [created] workflow_dispatch: jobs: deploy: runs-on: ${{ matrix.os }} strategy: max-parallel: 2 matrix: os: [ubuntu-latest] python-version: ['3.10'] steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -U setuptools wheel twine - name: Build and publish env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD_BDSIM }} run: | python setup.py sdist bdist_wheel ls ./dist/*.whl twine upload dist/*.gz twine upload dist/*.whl ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class untitled*.py # docs *.pdf *.dot # MATLAB *.m # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST *.json # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ .idea/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing This is a small-scale private project with a team size of about 0.01. I'd be delighted if you'd like to use and apply the tool, or even to contribute. ## Communicating GitHub Issues is a convenient means to discuss bugs or possible contributions. ## Bug notifications If you're using bdsim and encounter errors with the latest version from GitHub then please report it through GitHib Issues. Be sure to include: * a description of what the issue is, and the stack trace you get * the version of Python and numpy that you are using * a runnable code example that demonstrates the issue ## Specific contributions needed * The numerical integrator from SciPy has some limitations. It cannot handle: * hybrid continuous-discrete systems, * events associated with strong non-linearities or discontinuous inputs, * allow the state vector to be updated (ie. as would be required to renormalize a unit-quaternion state). * Extend to support bond graphs, or a hybrid of bond graphs and block diagrams. * There are many more blocks that could be created but of immediate interest are: * real-time blocks that interface to ADCs, DACs and PWM channels for use on a RaspberryPi * vision blocks that interface to cameras, displays and OpenCV operators ## Other contributions These are welcome but it'd be great to discuss through GitHub Issues before you start. You will be acknowledged as the author, but by contributing you are agreeing to your work being shared under the MIT Licence. Contributions should have unit tests and good quality documentation. ## Feature requests These are unlikely to be implemented by me, it's a time thing... ## Any contributions you make will be under the MIT Software License In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. ## Code of conduct So far there isn't one, but if there were it would embed principles from the [Contributor Covenant Code Of Conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct). ================================================ FILE: Icons.qrc ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Peter Corke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .FORCE: BLUE=\033[0;34m BLACK=\033[0;30m help: @echo "$(BLUE) make test - run all unit tests" @echo " make coverage - run unit tests and coverage report" @echo " make docs - build Sphinx documentation" @echo " make docupdate - upload Sphinx documentation to GitHub pages" @echo " make dist - build dist files" @echo " make upload - upload to PyPI" @echo " make clean - remove dist and docs build files" @echo " make help - this message$(BLACK)" test: pytest coverage: coverage run --source='bdsim' -m pytest coverage report coverage html open htmlcov/index.html docs: .FORCE (cd docs; make html) view: open docs/build/html/index.html dist: .FORCE #$(MAKE) test python -m build upload: .FORCE twine upload dist/* install: pip install -e . clean: .FORCE # (cd docsrc; make clean) -rm -r *.egg-info -rm -r dist build ================================================ FILE: README.md ================================================ [![A Python Robotics Package](https://raw.githubusercontent.com/petercorke/robotics-toolbox-python/master/.github/svg/py_collection.min.svg)](https://github.com/petercorke/robotics-toolbox-python) [![QUT Centre for Robotics Open Source](https://github.com/qcr/qcr.github.io/raw/master/misc/badge.svg)](https://qcr.github.io) [![PyPI version](https://badge.fury.io/py/bdsim.svg)](https://badge.fury.io/py/bdsim) ![Python Version](https://img.shields.io/pypi/pyversions/bdsim.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://github.com/petercorke/bdsim/workflows/build/badge.svg?branch=master)](https://github.com/petercorke/bdsim/actions?query=workflow%3Abuild) [![Coverage](https://codecov.io/gh/petercorke/bdsim/branch/master/graph/badge.svg)](https://codecov.io/gh/petercorke/bdsim) [![PyPI - Downloads](https://img.shields.io/pypi/dm/bdsim)](https://pypistats.org/packages/bdsim) [![GitHub stars](https://img.shields.io/github/stars/petercorke/bdsim.svg?style=social&label=Star)](https://GitHub.com/petercorke/bdsim/stargazers/)
A Python block diagram simulation package
`bdsim` is Python 3 package that enables modelling and simulation of continuous-time, discrete-time or hybrid dynamic systems. Systems are conceptualized in block diagram form, but represented in terms of Python objects.
  # define the blocks
  demand = bd.STEP(T=1, name='demand')
  sum = bd.SUM('+-')
  gain = bd.GAIN(10)
  plant = bd.LTI_SISO(0.5, [2, 1])
  scope = bd.SCOPE(styles=['k', 'r--'])
  # connect the blocks
  bd.connect(demand, sum[0], scope[1])
  bd.connect(plant, sum[1])
  bd.connect(sum, gain)
  bd.connect(gain, plant)
  bd.connect(plant, scope[0])
  
Key features include: * The block diagram can be created easily using Python code, rather than drawing boxes and wires. This enables use of your favourite IDE, standard version control tools and development workflows. * Wires can communicate *any* Python type such as scalars, lists, dicts, NumPy arrays, objects, and functions. For robotics and vision applications using the [Spatial Maths Toolbox for Python](https://github.com/petercorke/spatialmath-python) wires could send values such as `SE3`, `UnitQuaternion` or `Twist3` objects. * Over 70 blocks for linear, nonlinear functions, display blocks, as well as continuous- and discrete-time dynamics * Easy to add your own block, it's simply a class * Subsystems are supported, and a subsystem can be independently instantiated multiple times in a system. Subsystems can also be nested. * Blocks from other toolboxes are automatically discovered and included. There are blocks for some functions in the [Robotics Toolbox for Python](https://github.com/petercorke/robotics-toolbox-python) (such as arm, ground and aerial robots) and [Machine Vision Toolbox for Python](https://github.com/petercorke/machinevision-toolbox-python) (such as cameras). These are defined in the `blocks` folder of those toolboxes. * The diagram can be executed in a headless configuration, particularly useful on an embedded computer like a RaspberryPi. * A [python-based graphical editor](bdedit-the-graphical-editing-tool) * allows graphical creation of block diagrams * the diagram is stored in a human readable/editable JSON file with extension `.bd` * creates good-quality graphics for inclusion in publications * can launch `bdsim` to import and execute the model * automatically discovers all bsdim and toolbbox blocks and adds them to the block library menu * icons can be easily created using any image creation tool or a LaTeX expression # Getting started We first sketch the dynamic system we want to simulate as a block diagram, for example this simple first-order system ![block diagram](https://github.com/petercorke/bdsim/raw/master/figs/bd1-sketch.png) which we can express concisely with `bdsim` as (see [`bdsim/examples/eg1.py`](https://github.com/petercorke/bdsim/blob/master/examples/eg1.py)) ```python 1 #!/usr/bin/env python3 2 import bdsim 4 sim = bdsim.BDSim() # create simulator 5 bd = sim.blockdiagram() # create an empty block diagram 6 7 # define the blocks 8 demand = bd.STEP(T=1, name='demand') 9 sum = bd.SUM('+-') 10 gain = bd.GAIN(10) 11 plant = bd.LTI_SISO(0.5, [2, 1], name='plant') 12 scope = bd.SCOPE(styles=['k', 'r--']) 13 14 # connect the blocks 15 bd.connect(demand, sum[0], scope[1]) 17 bd.connect(sum, gain) 18 bd.connect(gain, plant) 19 bd.connect(plant, sum[1], scope[0]) 20 21 bd.compile() # check the diagram 22 23 sim.report(bd) # list the system 24 out = sim.run(bd, 5) # simulate for 5s ``` which is just 15 lines of executable code. The red block annotations on the hand-drawn diagram are used as the names of the variables holding references to the block instance. The blocks can also have user-assigned names, see lines 8 and 11, which are used in diagnostics and as labels in plots. After the blocks are created their input and output ports need to be connected. In `bdsim` all wires are point to point, a *one-to-many* connection is implemented by *many* wires, for example ``` bd.connect(source, dest1, dest2, ...) ``` creates individual wires from `source` -> `dest1`, `source` -> `dest2` and so on. Ports are designated using Python indexing notation, for example `block[2]` is port 2 (the third port) of `block`. Whether it is an input or output port depends on context. In the example above an index on the first argument refers to an output port, while on the second (or subsequent) arguments it refers to an input port. If a block has only a single input or output port then no index is required, 0 is assumed. A group of ports can be denoted using slice notation, for example ``` bd.connect(source[2:5], dest[3:6) ``` will connect `source[2]` -> `dest[3]`, `source[3]` -> `dest[4]`, `source[4]` -> `dest[5]`. The number of wires in each slice must be consistent. You could even do a cross over by connecting `source[2:5]` to `dest[6:3:-1]`. Line 21 assembles all the blocks and wires, instantiates subsystems, checks connectivity to create a flat wire list, and then builds the dataflow execution plan. Line 23 generates a report, in tabular form, showing a summary of the block diagram: ``` ┌────────┬──────────┬────────┬────────┬─────────────┐ │ block │ type │ inport │ source │ source type │ ├────────┼──────────┼────────┼────────┼─────────────┤ │demand@ │ step │ │ │ │ ├────────┼──────────┼────────┼────────┼─────────────┤ │gain.0 │ gain │ 0 │ sum.0 │ float64 │ ├────────┼──────────┼────────┼────────┼─────────────┤ │plant │ lti_siso │ 0 │ gain.0 │ float64 │ ├────────┼──────────┼────────┼────────┼─────────────┤ │scope.0 │ scope │ 0 │ plant │ float64 │ │ │ │ 1 │ demand │ int │ ├────────┼──────────┼────────┼────────┼─────────────┤ │sum.0 │ sum │ 0 │ demand │ int │ │ │ │ 1 │ plant │ float64 │ └────────┴──────────┴────────┴────────┴─────────────┘ ``` Line 24 runs the simulation for 5 seconds using the default variable-step RK45 solver and saves output values at least every 0.05s. It causes the following output ``` >>> Start simulation: T = 5.00, dt = 0.050 Continuous state variables: 1 x0 = [0.] Discrete state variables: 0 no graphics backend specified: Qt5Agg found, using instead of MacOSX bdsim ◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉◉ 100.0% - 0s <<< Simulation complete block diagram evaluations: 784 block diagram exec time: 0.075 ms time steps: 123 integration intervals: 2 ``` This provides a summary of the number of states for the complete system: the number of continuous states, the number of discrete states, and the initial value of the state vectors. During execution a progress bar is updated and scope blocks pops up a graphical window ![bdsim output](https://github.com/petercorke/bdsim/raw/master/figs/Figure_1.png) The simulation results are in a container object (`BDStruct`) ``` >>> out t = ndarray:float64 (123,) x = ndarray:float64 (123, 1) xnames = ['plantx0'] (list) ynames = [] (list) ``` which contains an array of time values, an array of state values, and a list of the names of the state variables. By default the `.run()` method at line 24 blocks blocks the script until all figure windows are closed (by pressing the operating system close button or typing "q"), or the script is killed with SIGINT. If you want to continue the script with the figures still active then the `hold=False` option should be set. If we wished to also record additional outputs, we can add them as _watched_ signals ``` out = sim.run(bd, watch=[demand, sum]) # simulate for 5s ``` and now the output is ``` >>> out t = ndarray:float64 (123,) x = ndarray:float64 (123, 1) xnames = ['plantx0'] (list) y0 = ndarray:float64 (123,) y1 = ndarray:int64 (123,) ynames = ['plant[0]', 'demand[0]'] (list) ``` where - `t` the time vector: ndarray, shape=(M,) - `x` is the state vector: ndarray, shape=(M,N), one row per timestep - `xnames` is a list of the names of the states corresponding to columns of `x`, eg. "plant.x0" The `watch` argument is a list of outputs to log, in this case `plant` defaults to output port 0. This information is saved in additional variables `y0`, `y1` etc. `ynames` is a list of the names of the watched variables. An alternative system report, created by `sim.report(bd, type="lists")` is more detailed ``` Blocks:: ┌───┬─────────┬─────┬──────┬────────┬─────────┬───────┐ │id │ name │ nin │ nout │ nstate │ ndstate │ type │ ├───┼─────────┼─────┼──────┼────────┼─────────┼───────┤ │ 0 │ demand │ 0 │ 1 │ 0 │ 0 │ step │ │ 1 │ sum.0 │ 2 │ 1 │ 0 │ 0 │ sum │ │ 2 │ gain.0 │ 1 │ 1 │ 0 │ 0 │ gain │ │ 3 │ plant │ 1 │ 1 │ 1 │ 0 │ LTI │ │ 4 │ scope.0 │ 2 │ 0 │ 0 │ 0 │ scope │ └───┴─────────┴─────┴──────┴────────┴─────────┴───────┘ Wires:: ┌───┬──────┬──────┬──────────────────────────┬─────────┐ │id │ from │ to │ description │ type │ ├───┼──────┼──────┼──────────────────────────┼─────────┤ │ 0 │ 0[0] │ 1[0] │ demand[0] --> sum.0[0] │ int │ │ 1 │ 0[0] │ 4[1] │ demand[0] --> scope.0[1] │ int │ │ 2 │ 3[0] │ 1[1] │ plant[0] --> sum.0[1] │ float64 │ │ 3 │ 1[0] │ 2[0] │ sum.0[0] --> gain.0[0] │ float64 │ │ 4 │ 2[0] │ 3[0] │ gain.0[0] --> plant[0] │ float64 │ │ 5 │ 3[0] │ 4[0] │ plant[0] --> scope.0[0] │ float64 │ └───┴──────┴──────┴──────────────────────────┴─────────┘ ``` In the first table we can see key information about each block, its `id` (used internally), name, the number of input and output ports, the number of continuous- and discrete-time states, and the type which is the block class. Note that the name is auto-generated based on the type, except if it has been set explicitly as for the blocks `demand` and `plant`. The second table shows all wires in point-to-point form, showing the start and end block and port (the block is represented here by its `id`) and the type of the object sent along the wire. To save figures we need to make two modifications, changing line 4 to ``` 4 sim = bdsim.BDSim(hold=False) # create simulator ``` which prevents `.run()` from blocking and then deleting all the figures. Then, after the `.run()` we add ``` 25 scope.savefig() # save scope figure ``` If the filename is not given it defaults to the block name, in this case `scope.0.pdf`. The output can be pickled and written to a file ```[shell] examples/eg1.py -o python -mpickle bd.out t = ndarray:float64 (123,) x = ndarray:float64 (123, 1) xnames = ['plantx0'] (list) y0 = ndarray:float64 (123,) y1 = ndarray:int64 (123,) ynames = ['plant[0]', 'demand[0]'] (list) ``` by default the results are written to `bd.out`, use the option `--out FILE` to set it to a specific value. The block parameters can also be overridden from the command line without having to edit the code. To increase the loop gain we could write: ```[shell] examples/eg1.py --set gain.0:K=20 ``` More details on this Wiki about: - [Adding blocks](https://github.com/petercorke/bdsim/wiki/Adding-blocks) - [Connecting blocks](https://github.com/petercorke/bdsim/wiki/Connecting-blocks) - [Running the simulation](https://github.com/petercorke/bdsim/wiki/Running) - [Command line options](https://github.com/petercorke/bdsim/wiki/Runtime-options) ## Other examples In the folder `bdsim/examples` you can find a few other runnable examples: - [`eg1.py`](https://github.com/petercorke/bdsim/blob/master/examples/eg1.py) the example given above - [`waveform.py`](https://github.com/petercorke/bdsim/blob/master/examples/waveform.py) two signal generators connected to two scopes Examples from Chapter four of _Robotics, Vision & Control 2e (2017)_: - [`rvc4_2.py`](https://github.com/petercorke/bdsim/blob/master/examples/rvc4_2.py) Fig 4.2 - a car-like vehicle with bicycle kinematics driven by a rectangular pulse steering signal - [`rvc4_4.py`](https://github.com/petercorke/bdsim/blob/master/examples/rvc4_4.py) Fig 4.4 - a car-like vehicle driving to a point ![RVC Figure 4.4](https://github.com/petercorke/bdsim/raw/master/figs/rvc4_4.gif) - [`rvc4_6.py`](https://github.com/petercorke/bdsim/blob/master/examples/rvc4_6.py) Fig 4.6 - a car-like vehicle driving to/along a line ![RVC Figure 4.6](https://github.com/petercorke/bdsim/raw/master/figs/rvc4_6.gif) - [`rvc4_8.py`](https://github.com/petercorke/bdsim/blob/master/examples/rvc4_8.py) Fig 4.8 - a car-like vehicle using pure-pursuit trajectory following ![RVC Figure 4.6](https://github.com/petercorke/bdsim/raw/master/figs/rvc4_8.gif) - [`rvc4_11.py`](https://github.com/petercorke/bdsim/blob/master/examples/rvc4_11.py) Fig 4.11 a car-like vehicle driving to a pose ![RVC Figure 4.11](https://github.com/petercorke/bdsim/raw/master/figs/rvc4_11.gif) Figs 4.8 (pure pursuit) and Fig 4.21 (quadrotor control) are yet to be done. # A more concise way Wiring, and some simple arithmetic blocks like `GAIN`, `SUM` and `PROD` can be implicitly generated by overloaded Python operators. This strikes a nice balance between block diagram coding and Pythonic programming. ``` 1 #!/usr/bin/env python3 2 3 import bdsim 4 5 sim = bdsim.BDSim() # create simulator 6 bd = sim.blockdiagram() # create an empty block diagram 7 8 # define the blocks 9 demand = bd.STEP(T=1, name='demand') 10 plant = bd.LTI_SISO(0.5, [2, 1], name='plant') 11 scope = bd.SCOPE(styles=['k', 'r--'], movie='eg1.mp4') 12 13 # connect the blocks using Python syntax 14 scope[0] = plant 15 scope[1] = demand 16 plant[0] = 10 * (demand - plant) 17 18 bd.compile() # check the diagram 19 bd.report() # list all blocks and wires 20 22 out = sim.run(bd, 5, watch=[plant,]) # simulate for 5s ``` This requires fewer lines of code and the code is more readable. Importantly, it results in in *exactly the same* block diagram in terms of blocks and wires ``` Wires:: ┌───┬──────┬──────┬──────────────────────────────┬─────────┐ │id │ from │ to │ description │ type │ ├───┼──────┼──────┼──────────────────────────────┼─────────┤ │ 0 │ 1[0] │ 2[0] │ plant[0] --> scope.0[0] │ float64 │ │ 1 │ 0[0] │ 2[1] │ demand[0] --> scope.0[1] │ int │ │ 2 │ 0[0] │ 3[0] │ demand[0] --> _sum.0[0] │ int │ │ 3 │ 1[0] │ 3[1] │ plant[0] --> _sum.0[1] │ float64 │ │ 4 │ 3[0] │ 4[0] │ _sum.0[0] --> _gain.0(10)[0] │ float64 │ │ 5 │ 4[0] │ 1[0] │ _gain.0(10)[0] --> plant[0] │ float64 │ └───┴──────┴──────┴──────────────────────────────┴─────────┘ ``` The implicitly created blocks have names prefixed with an underscore. # bdedit: the graphical editing tool ![block diagram](https://github.com/petercorke/bdsim/raw/master/figs/eg1-bdedit.png) `bdedit` is a multi-platform PyQt5-based graphical tool to create, edit, render and execute block diagram models. From the examples folder ``` % bdedit eg1.bd ``` will create a display like that shown above. Pushing the run button, top left (triangle in circle) will spawn `bdrun` as a subprocess which will: * parse the JSON file * instantiate all blocks and wires * compile and run the diagram Screencast showing bdedit usage. # Article I published [this article on LinkedIn](https://www.linkedin.com/pulse/journey-toward-open-source-block-diagram-simulation-peter-corke/?trackingId=wrJYinHUgAHDq63Nv65PnA%3D%3D), which describes the thought process behind bdsim. # Limitations There are lots! The biggest is that `bdsim` is based on a very standard variable-step integrator from the scipy library. For discontinuous inputs (step, square wave, triangle wave, piecewise constant) the transitions get missed. This also makes it inaccurate to simulate hybrid discrete-continuous time systems. We really need a better integrator, perhaps [`odedc`](https://help.scilab.org/docs/6.1.0/en_US/odedc.html) from SciLab could be integrated. ================================================ FILE: bdsim/__init__.py ================================================ # from bdsim.bdsim import * # from bdsim.blockdiagram import * # from bdsim.components import * # from bdsim.graphics import GraphicsBlock # from bdsim.bdrun import bdrun, bdload from .run_sim import * from .run_realtime import * from .blockdiagram import * from .components import * from .graphics import GraphicsBlock from .bdrun import bdrun, bdload try: import importlib.metadata __version__ = importlib.metadata.version("bdsim") except: pass ================================================ FILE: bdsim/bdedit/Icons.py ================================================ # -*- coding: utf-8 -*- # Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.15.2) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ \x00\x00\x0e\xc4\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x64\x00\x00\x00\x64\x08\x06\x00\x00\x00\x70\xe2\x95\x54\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ \xa8\x64\x00\x00\x0e\x59\x49\x44\x41\x54\x78\x5e\xed\xdd\x05\x8c\ \x34\x49\x19\x06\xe0\x1f\x77\x77\x77\x77\x77\x38\xdc\xdd\xed\x70\ \x08\x12\x24\x40\xb0\x40\xd0\x10\x0e\x77\x77\x87\x10\x3c\x38\x07\ \x1c\x76\x38\x1c\xee\xee\xee\x0e\xef\xc3\x6d\x91\x61\xd2\x5d\xd3\ \x3d\xdb\xdd\xd3\xcb\xcd\x9b\xbc\xd9\xbd\xbd\xdd\x7f\xaa\xeb\xab\ \xfa\xbc\xaa\xf7\x6d\xb1\xc5\x16\x5b\x6c\xb1\xc5\x16\x5b\x6c\x31\ \x02\x0e\xb7\xf3\x75\x2f\xe0\x24\xe1\xe9\x76\x78\xa2\xf0\x98\x0b\ \x3c\x52\xf8\xb7\xf0\xf7\xe1\xef\x76\xbe\xfe\x2c\xfc\xd6\x0e\x7f\ \x1a\xee\x09\xcc\x51\x20\x87\x0f\x8f\x1b\x9e\x3f\xbc\x50\x78\xc1\ \xf0\x0c\xe1\xb1\xc3\x23\x87\x26\xff\x08\xa1\xdf\x43\xcf\x80\xff\ \xda\xe1\x3f\x77\xf8\x8f\x90\x90\xfe\x12\xfe\x26\xfc\x5a\xf8\x89\ \xf0\xe3\xe1\xa7\xc3\xdf\x86\x7e\x7f\x56\x98\x8b\x40\x8e\x18\x9e\ \x2a\xbc\x5c\x78\xd9\xf0\xec\x21\x01\x94\x1d\x70\xd4\xb0\x4c\x7e\ \x5f\x14\x21\xfd\x29\xfc\x43\x68\xf7\xfc\x3a\xfc\x7c\xf8\x9e\xf0\ \xc0\xf0\x47\x21\x01\x6e\x1c\x9b\x16\xc8\x89\xc3\x8b\x87\x97\x0f\ \xed\x88\x93\x85\x27\x08\x09\x81\x00\xc6\x02\x01\x51\x6d\x3f\x0f\ \x09\xe3\x93\xe1\xbb\xc2\x8f\x86\xbf\x08\x37\x86\x4d\x09\xe4\xa4\ \xa1\x9d\x70\xd5\xf0\xbc\xe1\xc9\xc3\xe3\x85\x63\x0a\xa1\x0d\x76\ \xc6\xaf\xc2\xef\x87\x87\x84\x04\xf3\xbe\xf0\x7b\xe1\xe4\x98\x5a\ \x20\xa7\x08\xaf\x1c\xee\x17\x9e\x27\x64\xa0\x8f\x15\xce\x05\x76\ \xcd\x77\xc2\xcf\x84\xef\x08\xdf\x1d\xfe\x38\x9c\x0c\x53\x09\x84\ \x0d\xb8\x54\x78\xed\x90\x30\x4e\x1f\x1e\x2d\x9c\x2b\xd8\x9a\xaf\ \x87\xef\x0f\xdf\x1a\x1e\x14\xb2\x41\xa3\x83\xb7\x32\x36\xce\x11\ \xde\x32\xbc\x55\x78\x95\xf0\x94\x21\x4f\x69\xce\xe0\xcd\x71\xb3\ \xcf\x1a\x5a\x3c\xd4\xe9\x2f\xc3\xd1\xed\xcb\x98\x3b\xc4\xa4\xb3\ \x13\x37\x0e\xaf\x16\x8a\x1d\x86\x58\x00\xc5\x20\xff\x39\xe4\xd2\ \xfe\x75\xe7\x67\xec\x8f\x89\xb4\x1b\xed\xbe\x63\xec\xfc\x6c\xb7\ \x60\x63\x18\xfe\xb7\x84\xaf\x09\xed\x96\xbf\x87\xa3\x60\x0c\x81\ \xf8\x37\xad\xa8\xab\x87\xb7\x0d\x2f\x1a\x9a\xa4\xbe\x28\xee\x2a\ \x4f\xe8\x87\xe1\x4f\x42\xc6\x97\xcb\x6a\xa5\x52\x2b\xd4\x08\xa1\ \x98\x34\xc2\xf6\x39\xc8\x4b\xe3\xad\x89\x67\x8c\xc5\x6a\xe7\x38\ \x9c\x30\x34\xbe\x75\x9e\xdb\xe7\x7d\x28\x7c\x61\xf8\xf6\xd0\x38\ \x06\xc7\xd0\x02\xb1\x22\x19\x6e\xbb\xe2\x6e\x21\xf5\xd4\x67\x57\ \x10\x82\x15\xcf\x90\x7e\x3b\xe4\xe9\xd0\xe5\x5f\x09\xbf\x11\x5a\ \xa9\x22\x70\x42\x58\x05\x82\xb1\x2b\x09\x42\x60\x79\x96\xf0\x8c\ \xa1\x78\xe7\xb4\x21\x4f\x4f\xfc\xd3\x67\x0e\xec\x8c\x6f\x86\x4f\ \x0f\x5f\x1d\xca\x00\x0c\x1a\x5c\x0e\x29\x10\x0f\xe7\x41\x6f\x11\ \xde\x27\x3c\x7a\xd8\xf5\xdf\xb7\x13\x44\xce\x84\x20\xa2\x16\x4d\ \x7f\x30\xe4\xed\x0c\x69\x4c\x8d\xe9\x02\xe1\x25\x43\x59\x00\x82\ \x32\xe6\x3e\x71\x0f\x01\x88\xfc\x0f\x08\x5f\x11\x72\x97\x67\x11\ \x54\x2e\x82\xbd\x38\x5b\xf8\xd8\xd0\xea\x35\xe8\x2e\xf4\x20\x8c\ \xe5\xa7\xc2\xe7\x84\xd7\x0c\x8f\x13\x4e\x01\xaa\xec\x7a\xe1\x8b\ \x42\x82\xa7\x0e\x8d\xa7\x69\x9c\xcb\xb4\x80\x3c\xe7\x23\x43\xbb\ \xce\x62\x9c\x0d\x0c\x86\x37\x62\x1b\xdb\xd2\x4d\x0f\xd0\x44\x3a\ \x99\x2a\x7a\x76\xc8\xf8\xaf\x63\x67\x86\x00\x07\xe0\x4a\x21\xdb\ \x60\x77\x1a\x97\x09\x6f\x1a\xf3\x32\x3d\xef\x13\x42\x3b\x6d\x0a\ \x8f\x75\x25\x0c\xc2\x60\x1e\x1d\x76\x15\x86\xdf\x63\x94\x19\xc6\ \x1b\x86\xbc\xa1\x39\x80\xda\x62\xfb\x8c\xcb\xae\xed\xf3\x3c\x8f\ \x08\x4f\x1d\x6e\x22\xd3\xf0\x5f\xb0\x0f\x8c\xf6\x43\xc2\xae\x6a\ \x8a\xc1\x96\x0e\xbf\x7f\xc8\xf8\xcf\x11\xa7\x09\x1f\x16\x8a\xd8\ \x65\x8b\x9b\x9e\x63\x99\x5c\x70\x76\x93\xa3\x30\xb4\xa3\xd4\x19\ \xc7\x0f\xef\x19\xca\x9e\x36\x0d\x72\x99\x56\xd2\x87\x43\xee\xf0\ \x51\xc2\x39\x83\x1a\xbb\x41\xc8\xb9\xe8\xba\x53\xc4\x46\x77\x08\ \xa7\xb2\x81\xff\x03\x76\xe3\x26\xa1\x55\xd4\x55\xdf\xbe\x24\x94\ \x56\xdf\xd8\x0a\xea\x09\xea\x47\x06\x5a\x30\xd8\xf4\x3c\xcb\x34\ \x0f\x5c\xe2\x6b\x84\x93\xdb\x13\x46\x58\x2d\xa1\x8b\x57\x62\xa0\ \x0c\xfe\x99\xc2\x8d\xea\xd8\x35\x60\x62\x79\x8f\xcf\x08\xbb\x3c\ \xab\xdd\x24\xf7\x75\xb1\x70\x32\xf0\xa8\x78\x46\xe2\x83\xa6\x41\ \x2d\xd2\x43\x30\x78\xb2\xba\xb3\xf0\x42\x76\x01\x71\x47\x17\xa1\ \xfc\x31\x7c\x62\xc8\xd9\x19\x1d\x5c\xd3\x7b\x84\x3f\x08\x9b\x06\ \xb3\xcc\xa7\x86\xff\x0f\xc2\x80\x33\x87\x4f\x0b\x9b\x9e\x73\x99\ \x82\xc5\x3b\x85\xa3\xbb\xf2\x57\x0c\x15\x70\x56\x19\x3a\x2b\x89\ \xcd\xf0\x10\x5d\x85\x21\xc5\x71\xa3\x9d\xaf\x73\x85\xcc\xb5\x94\ \x49\xd3\x33\x2f\xd2\xfc\x48\x46\x2a\x39\x8c\x06\xe5\x55\x5b\x51\ \x52\xad\x69\x10\x85\x5c\xc5\x83\xc3\x73\x86\x7d\x76\x06\x61\xf8\ \x7b\xb5\x6e\x0e\xc3\x1c\x05\x23\x23\x21\xe5\xf2\xb1\x70\x95\x33\ \x23\x96\x11\xc9\x2b\x53\x8f\x02\x39\xaa\xcf\x86\xb5\x81\x58\x19\ \xdf\x0d\xb9\xb6\x7d\x0d\x78\x11\x48\xe1\x2b\xc3\x39\x0a\x45\x8a\ \xff\x3a\xa1\xc4\x62\xcd\xa6\x98\x27\x82\x93\x9e\x19\x1c\x02\xc0\ \xe7\x87\xd2\x0a\x4d\x1f\x8e\x06\x20\x1f\xf4\xc0\x70\x9d\x38\x63\ \x59\x20\xc8\x71\xb8\x57\x38\x37\xc1\x88\xea\x05\x8f\x0c\x78\x6d\ \x81\x8a\x4d\x9e\x12\x0a\x18\x07\xc5\xcd\x42\x09\xb8\xa6\x0f\x2d\ \x34\xb8\xb7\x85\xeb\x46\xe0\x4d\x02\x29\x94\x72\xbf\x75\x38\x17\ \xc1\x88\xa5\xa4\x4a\x94\x78\x45\xe9\x4d\x63\x2e\xa4\xbe\xaf\x1b\ \x0e\x06\x35\x05\xbb\xa3\x34\x96\x35\xd1\xd6\x55\xaf\xb8\x7e\xb8\ \x2e\x6a\x02\x29\xd4\xe8\xe6\xf7\xe6\x22\x18\x0b\x95\xc7\x59\x53\ \x5d\x6c\xee\x93\x43\x05\xb3\x41\x20\xf2\x94\x42\x68\xfa\xb0\x42\ \xaa\xea\x05\xe1\x6e\x12\x85\x5d\x04\x52\x28\x65\x3e\x07\xa1\xe8\ \x98\x79\x55\x48\x35\x35\x8d\xb3\x50\xd9\xf7\x0a\xe1\x4a\xac\x32\ \xbc\x52\x24\xba\x44\x24\xdc\xda\x40\x87\xf2\xbb\x5f\x1e\xb2\x31\ \xab\x30\x44\x4c\xa2\x61\x42\xea\x5e\x55\x72\x93\x82\x21\x08\x8b\ \x43\x79\xd9\x3c\xb4\x41\x90\xa8\x28\xb6\xeb\x67\x27\x88\x55\x71\ \x87\xea\xd9\x8b\xc3\xae\x6d\x3d\x6d\x6e\x60\x9f\x1d\xb2\x48\x55\ \x46\x1e\xe0\xa6\x04\x23\xf8\x7b\x6d\x58\xdb\x25\xe6\xef\x0d\xe1\ \x4a\xfb\xba\x6a\x87\x08\x6c\xd4\xa0\xdb\x24\xeb\xc3\xd4\xbd\x5f\ \x1f\x76\x2d\xb5\x6a\x3c\x18\x12\x16\xcd\x4b\x43\x8b\x62\x13\xf6\ \x85\x51\x27\x10\xbb\xc4\x7c\x34\xc1\xfc\x29\x15\x6b\x9b\x5d\x1b\ \x3c\x09\x65\x55\xf6\x61\x51\xda\x8b\x54\x07\x79\x63\x38\xc4\x24\ \xaf\xbb\x43\x96\x29\xcf\x36\xb5\x50\x94\x22\x24\x5b\xd5\x7b\x9a\ \xc6\x84\xba\x67\x04\xd6\xd5\x6c\x77\x6d\x87\xe8\x3e\x97\x48\xf4\ \xb5\x0d\x82\x23\xc1\xcf\x28\x2d\x31\x6b\xe2\x8e\xe1\x17\x42\xb9\ \xa4\xa9\x04\x23\x2a\xe7\xde\xd6\x1a\xe9\xd4\xf0\x57\xcd\x67\x55\ \x20\xe7\x0b\x49\xbe\xed\x77\x48\x9d\xba\xe2\x8b\x8f\x0d\xab\x8b\ \xa7\xd7\x15\x76\xec\xb3\x42\xf6\xef\xa6\xe1\x14\x82\xb1\x43\xb4\ \x2f\x99\x97\x26\x98\x47\x7d\x61\x6a\x42\xad\x58\x25\x90\x5a\xf5\ \xcb\x07\xf3\xc1\x75\x8c\x8c\x0d\x3b\x51\xba\x82\x77\xa5\x28\xd6\ \x15\x1e\x5e\xab\xce\x73\xc3\xb1\xed\x8b\x1d\x52\x13\x08\x58\xe0\ \xba\xfd\x5b\x51\x13\x88\x6a\x59\x4d\x20\xb6\xa7\x2e\x0d\x11\xfa\ \x14\xd0\xbd\x28\x83\xcc\x30\xea\xf4\xe8\x03\xad\xac\xb2\xb4\xf7\ \x0b\xc7\x12\x8a\x52\xb6\xf9\xa8\xa9\x6f\x6a\xeb\xdc\x87\x7e\xdb\ \x8c\x9a\x40\x78\x05\x35\x57\xd6\x6a\xf8\xd2\xa1\xdf\x4e\x0a\x82\ \x79\x7c\xa8\x43\x84\x2b\xd9\x07\x77\x0f\xa5\x80\x6e\x13\x8e\x21\ \x18\x99\x6a\xea\xb5\x0d\x72\x60\xe6\xb5\x15\x6d\x02\x11\x2b\xd0\ \xc3\xb5\x40\x46\x7e\xc9\x8a\xd8\x04\x08\x45\xad\xfb\xae\x21\x1b\ \xe1\x04\x54\x57\x48\x05\xc9\x2a\xbc\x33\x1c\x3a\xcd\xff\xe5\xb0\ \x66\xd8\x05\xda\xe6\xb5\x35\x8d\xd2\x26\x10\xbe\xbd\x80\xa7\xe6\ \xa2\x09\x08\x4d\xcc\x26\xe1\xf3\xa5\x2e\xae\x15\xde\x3e\xac\xad\ \xce\x65\x28\x36\x49\xf1\xab\x97\x0f\x25\x14\x19\x0b\x01\x62\x1b\ \xcc\x27\xad\x23\xb6\x6b\x44\x4d\x20\xf2\xfe\x6d\x90\x26\x20\x90\ \xb9\x1c\x37\x26\x18\x09\x50\xa7\xb2\x4c\x70\x1f\x38\x44\xa4\x5b\ \x44\x5f\xd5\x6e\x05\xa3\x19\x9c\x40\x6a\x69\x14\x02\x91\x29\x6e\ \x44\x9b\x40\xb8\x67\xb5\x43\x35\x72\x56\x56\xa3\x28\x75\x4e\x20\ \x98\x47\x85\xa5\x03\xb1\x2b\xd4\x6f\xf4\x25\x8b\xa9\x78\x72\xeb\ \x0a\xc6\x7c\x98\x97\x5a\xd6\xc2\xbc\xf2\xb6\x1a\xd1\x26\x90\x55\ \xdd\xe0\x3e\xb0\xb6\x35\x37\x89\x62\x5f\x6e\x17\x4a\x8f\x0b\x12\ \xbb\x42\xae\x49\xb2\xf0\x4d\xe1\x3a\x6e\x32\x97\xd7\xbc\xd4\x8e\ \x4b\xb0\x23\xe6\xb7\x11\x6d\x93\x2e\x8d\x5e\x33\xe8\x52\x04\x73\ \xdb\x1d\xcb\x20\x18\x36\x42\x23\xb5\xa8\xbd\x8f\x7d\x71\x64\x81\ \x9b\xfc\xb8\xb0\xaf\x50\x2c\x56\x7d\x05\x6d\x30\xaf\x8e\x45\x34\ \xa2\x4d\x20\xfe\xa0\xb6\x43\xca\x0d\x09\x7b\x01\x04\x23\x27\xc7\ \xbe\xe8\x70\xef\x03\x1e\xdc\x47\xc2\xd6\x15\xdd\x80\x55\x02\x31\ \xaf\xbd\x05\x42\xcf\xd5\x3c\x2c\x46\x4b\x95\x6c\xaf\xc0\x2a\xbf\ \x74\x78\xe1\xff\xfc\x57\x3f\x94\x23\x0a\x5d\x21\xd5\x5e\x9b\x1b\ \x73\xde\x6a\x9f\xdb\x04\x52\xba\xbe\xdb\xe0\xef\x6a\x2a\x6d\x2e\ \x20\x08\xb6\x40\xc3\x1e\xf5\xc5\xd5\xed\x0a\x2a\xce\x19\xc9\xfd\ \xc3\xda\x5c\x2c\x83\x8d\xa8\xcd\x8d\xc5\xdc\xba\x83\xda\x04\x52\ \xba\x29\xda\x40\xc2\x73\xef\x60\x27\x8c\xfb\x86\x6c\x41\xdf\x56\ \x1c\xae\x73\x51\x71\x54\x5e\x1f\x70\x6b\x6b\x1e\xaa\x79\x6d\x4d\ \x37\xb5\x09\xc4\x16\xad\x6d\x3b\x31\x4a\xd7\x0a\xe1\xd4\x20\x08\ \xa9\x11\x29\x12\xc7\x25\xfa\x40\x6d\x87\xcb\xcc\x75\xee\x2b\x88\ \x82\x55\x02\x31\xaf\xbd\x05\x22\x51\xb6\x2a\xb8\x99\xd3\x95\x18\ \x50\xd4\x13\x97\x57\x6a\x44\x8a\xa4\x2b\xd8\x09\x2e\xf2\x5d\x42\ \x7f\xbf\xae\x30\xd8\x5d\xf5\x8e\x9a\xf6\x60\x63\x5a\x6d\x52\x9b\ \x40\xe8\xcf\x9a\xa7\xc0\x2d\x96\x8f\xd9\xd4\xb9\xc0\x65\x10\x86\ \x6a\x1c\xf5\x74\x09\x3f\xe8\x08\x0b\x8f\x10\x34\x72\xb0\x31\xeb\ \x0a\xa2\xc0\x7c\x98\x97\x9a\xf6\x30\xaf\xad\xf9\xae\x36\x81\x68\ \x1c\xa8\xb9\xb5\xfe\x4e\x6a\x7e\xb4\xbe\xd5\x8e\x20\x08\x29\x0f\ \xa9\x0f\xbb\xa3\x0f\x14\xb0\x9c\x5d\x7f\x66\xb8\x5b\x41\x14\xe8\ \x7f\xa6\x39\xda\xe6\x15\xb8\xc5\xad\x35\x9d\xb6\x3f\xf4\x07\xe5\ \xdc\x60\x1b\x08\x64\x53\xe7\x04\x09\x82\xf7\xe3\x88\x9c\x94\x47\ \x1f\x07\xe3\x03\x21\x3b\xe1\xdc\xca\x50\x82\x28\xd0\x72\x5b\x53\ \xe5\xe6\x93\x40\x24\x21\x1b\xd1\x26\x10\xa9\x75\xc9\xc3\x9a\x61\ \xa7\xa3\x9d\xd1\x9e\x12\x8b\x76\x42\x97\x49\xad\x5f\x6c\x19\x6e\ \x84\xb8\x79\x28\xd8\xdb\x8d\x9d\xa8\x41\xcd\xbc\xd6\xa1\xc8\x7e\ \x28\x60\xf5\x56\x59\x40\x0d\xd4\xaa\x81\x1a\x88\xab\xf5\xe1\x81\ \x41\x18\xda\xfb\xfb\xda\x09\x29\x1e\x0d\xdb\x97\x09\x95\x73\xc7\ \x10\x44\x81\x23\x18\x35\x67\x82\xcd\x62\x0e\x5a\x51\x13\x48\xb9\ \x28\xb2\x0d\x32\x96\xce\x0d\xb6\xa6\x01\x06\x84\x74\xb5\x4e\x45\ \xee\x6c\x1f\xd8\x45\xba\x06\x9f\x14\x8e\x29\x08\xa0\xaa\x1c\x50\ \xaa\x95\xbd\x75\xa7\x38\xd2\xd1\x8a\x9a\x40\x34\x2f\x50\x5b\x6d\ \xf0\xb7\x56\xad\x44\xdc\xd8\x90\x4b\xea\x93\x4f\x72\x4f\x0a\x3b\ \xe1\x68\xc4\xd8\x82\x28\xb8\x48\xe8\xd6\xa1\x5a\xca\x49\x8f\xdb\ \xda\x02\xf1\x87\x74\x5d\x5b\x3c\xe2\x83\x55\xbe\xe4\x88\xe6\x02\ \xee\xba\x5d\x44\x18\x63\xd9\x89\x36\xb8\xc8\x93\x97\xd5\x26\x10\ \xf6\x98\x6d\xae\x96\x03\x6a\x02\xb1\x3b\xd4\x88\x6b\x6a\x8b\xdb\ \x6b\x65\xe8\xa6\xd8\x24\xd8\x89\x7b\x87\xd2\x1d\xea\x19\x53\x0a\ \x02\xa8\x6f\xf3\xd0\x5a\x78\x0a\xec\x0e\xf3\x59\xad\x23\xd5\x04\ \xc2\x45\x3b\x28\xd4\xb3\xda\x06\x29\x02\x57\xe0\x09\xac\xba\x82\ \x9e\x1d\x12\x6a\xea\xec\x84\xd6\xa0\xa9\x05\x51\xa0\xe6\xc2\xce\ \x49\x2c\xb6\x41\x0f\x1b\x37\xbd\x9a\xa8\xac\x09\x04\x74\x25\x3a\ \x33\xd8\xe6\xfe\x16\xb5\xe5\x84\x50\xd7\xdc\x56\xcd\x2e\xf5\x81\ \x4e\x13\xaa\xc9\xce\xd8\x94\x20\x40\x74\xee\xa0\x52\xcd\x7e\x98\ \x3f\x77\xbc\xa8\xad\x54\xb1\x4a\x20\x1e\x94\x2d\xe1\x1d\xb4\x81\ \xb1\xd5\xe5\xd8\xd5\x15\xed\x53\xb9\x6b\x82\x1d\x2b\x2d\xae\xd3\ \x64\x6a\x3b\xd1\x04\xb7\x5a\x78\xfe\x9a\xb7\xa9\x19\xc4\x3c\x0e\ \x72\xe5\xac\x13\xb5\xab\x4e\x50\x09\x76\xa4\xaa\xfb\x78\x42\xcb\ \x10\xf0\x35\xfd\xdb\x8b\x74\x45\x07\xcf\x6e\x2e\xe0\xea\x8a\x8b\ \x56\x5d\xc0\x23\x3b\xd0\xe9\x04\x55\x17\x08\x74\x9e\x17\x76\x39\ \x63\xe8\x06\x9d\x75\x51\x13\x88\x43\xf8\xfe\xff\x9c\x84\x01\x32\ \xc4\x76\xa8\xe7\x6f\x1a\x37\x0e\x7e\xc6\x10\xba\x9e\xc2\xd5\x7a\ \x23\x9f\xb3\x0e\x9a\x04\x32\xd7\x4b\x04\xd8\x0a\x46\x9c\xd3\x53\ \x72\x7e\x6d\x74\x9f\xfc\xa0\xa7\x70\x41\x12\xb1\xeb\x39\xf5\x07\ \x85\xeb\xa4\xe5\x17\x05\xc2\x35\xbc\x73\x38\x37\x41\x14\x50\xcd\ \x0f\x0f\x2d\xc2\xc5\x39\x58\xa6\xe7\x18\xe5\x9c\x3a\x48\xcc\xd9\ \x25\x26\xbe\xe9\xc3\x51\xf2\xac\xdc\xe4\xd0\xb7\xe6\x6e\xf2\x79\ \x4e\x0f\xdd\xf9\x7e\xae\x90\x59\x56\x12\x16\xe4\xd5\x54\x95\x79\ \x72\x44\x61\x94\x9b\x1c\x40\x14\xca\xd7\xa7\x13\x9b\x06\x50\xa8\ \x00\x63\x20\xe7\x0a\xf7\x42\x23\x44\x57\x58\x24\xe2\x2e\x9d\x2b\ \xd2\x4a\xb5\x85\x89\xb2\x1c\x52\xfc\xa3\xd6\x8c\x78\x0a\x5d\x6f\ \x03\x7a\x59\xd8\xe7\x36\xa0\xb9\x43\xd0\xa7\x6b\x85\xab\xdd\xf4\ \xcc\x8b\x34\x3f\x6f\x0e\x47\xbd\x0d\x08\xd8\x86\xc3\xe2\x7d\x59\ \xc6\x6f\x71\x71\xbb\x9b\x9e\x73\x99\x8e\xfa\x39\xeb\x38\x49\x89\ \x5b\x11\x46\xf9\x53\xe5\xab\x69\x30\x8b\xb4\x53\xf6\xfa\x8d\x72\ \xc6\xad\xcc\xd0\xe7\x46\xb9\x72\x97\xef\x64\xf0\xae\x28\x2f\x3b\ \x59\xa5\xba\x90\xae\xd5\xe7\x74\x58\xba\x73\xd1\x0b\x08\x26\x05\ \x7d\x2a\x8f\xa4\xfa\xb5\xca\xb8\x15\x3a\xdc\xbf\x97\x6e\x25\x35\ \x4e\xb5\x1e\x97\x02\x34\x3d\xcf\x32\xcd\x83\xe0\x78\x1d\x0f\x73\ \x10\x48\x35\x3b\xb3\xc7\xd7\x6e\x1a\xe0\x32\x79\x5f\x92\x6b\x2e\ \xb3\x99\x44\xb7\xee\x02\x12\xa5\xb2\x0e\x6e\x1f\xea\xa2\x05\x50\ \x26\xc3\x29\xae\x5a\xc5\x70\x54\x58\x41\x02\xc6\x07\x87\xab\xa2\ \xd5\xc2\x72\xb3\xf5\x03\xc2\x75\x23\xfa\xb1\xe1\x50\xa6\xa0\x4f\ \xe7\x4d\xed\x66\x86\x45\x96\x7a\xcc\xaa\x8a\xe1\xe8\x60\x13\xd4\ \x43\xd6\xb9\xfb\x9d\xfb\x2c\xd8\xac\xde\x6c\x30\x21\x24\x0a\xa9\ \xe1\x3d\x7b\xf7\x7b\x01\x7b\xc2\xf3\x72\x85\x6a\xd7\x87\x40\xde\ \xc8\x57\x43\x67\x37\x38\x09\x9b\x52\x63\xd4\x93\x37\xc7\xa9\x34\ \x96\xa3\x07\x5d\xed\xa2\xe7\x9d\xd5\xdb\x11\x0a\x44\xb0\x43\xbd\ \x3f\x64\xe8\xdb\x82\xda\xb0\xf8\xfe\x10\xb5\x0a\x19\x88\x2e\x9e\ \x14\x12\x18\x35\x35\xf8\xfb\x43\x86\xd4\x77\x06\x45\xff\x0e\xf1\ \x86\x1d\xc6\xd4\xfb\x9e\xe4\xce\xfa\x1c\x96\x59\x05\x3d\xc9\xe5\ \x0d\x3b\xbe\x9a\x4c\x63\x96\x2c\xec\xaa\x6e\xca\x58\x1f\x13\xea\ \x07\x1e\xf4\x0d\x3b\x43\x1b\x20\x0f\x25\xe7\x43\x17\xbb\xed\xad\ \x76\xd7\x56\x13\xac\x3e\xde\x98\xe3\xc5\x84\x23\xe2\xe5\x4a\x2e\ \xbf\x83\x6a\xd5\xf9\x46\xcf\x45\x05\xca\x23\xc9\xc1\x51\x29\xfa\ \x78\x7d\x35\x26\x42\x58\xf7\x1d\x54\xc6\x21\x2e\x51\x98\x52\x09\ \x34\xe6\xc1\x30\xb4\x40\xc0\xbf\x49\x1d\xf0\xc7\xdd\x24\xea\x62\ \xfa\xae\xf5\xf6\x45\x14\xd5\xc0\x01\x90\xaa\x59\x7c\x4b\x1b\x35\ \xa7\x4a\x27\x5b\x40\x45\x5a\xa1\x04\x2f\x13\xeb\xb3\x18\x68\x6e\ \x39\xf5\x67\x2c\x26\xdf\x42\x71\xdc\x1b\xd6\x79\x6e\x3b\x55\xfd\ \x83\x8a\xf3\x16\x50\xe3\x18\x1c\x63\x08\xa4\xc0\xea\x5b\x7c\x8f\ \xa1\xd5\x3a\x84\xe1\x23\x24\x93\x43\x18\x76\x0a\xd7\xd4\xcf\xec\ \x4e\x02\x41\xea\x12\x87\xf0\x7a\x08\x5b\x55\x50\x04\x2e\xb1\xa8\ \x1c\x3b\x98\x8a\x5a\xc6\x98\x02\x29\x10\x9d\xdb\x2d\xee\x8d\xf7\ \x6e\x74\xab\x76\x8a\xcf\xdd\x2d\xec\x50\x1d\x32\x1c\x0e\xc2\x50\ \x46\xe6\x15\x8e\x8a\x29\x5c\x35\x3a\xdf\x43\x95\x40\x8b\x61\x65\ \x44\x6b\xc7\xbe\x36\x0d\x3b\xf0\x8b\xa1\x0b\x04\x34\x6f\xbc\x2e\ \xac\xf5\xa7\x0d\x86\xa9\x7c\x67\xc6\x50\x84\xce\xbd\x64\x08\x8b\ \x51\xa6\xef\xe7\x94\x46\x61\x97\x78\x79\xef\x0d\xdd\xcd\x85\x9f\ \x0b\x8d\x7f\x12\x6c\x4a\x75\x30\xb2\x7a\x82\xbd\x4f\x9d\xfb\xc9\ \xe0\xae\xba\x0e\x6a\x2c\xb0\x07\x9c\x05\x8e\x83\xc9\x97\xc5\x3e\ \x30\xe4\xe1\x4d\x8e\x4d\xeb\x72\x2d\x46\xbc\x30\xf6\x45\xb3\x19\ \x17\x95\x27\xd4\x27\x2e\x58\x07\x9c\x00\x49\x51\x4d\x7b\x5c\x69\ \x71\x8f\x54\xce\xaa\x8b\x2c\x47\xc7\x5c\x8c\xab\x9d\x21\x3e\xd0\ \x23\x2c\x8d\xa2\x54\x6a\xc7\x14\x7b\x43\xad\x11\xd0\x3a\xe3\x65\ \x9c\xed\x02\x6a\x92\x6d\xa0\x96\xb8\xac\x87\x84\x2e\xae\xb4\x1b\ \x74\x14\x8e\xe6\x39\xf5\xc1\x1c\xbd\x1d\x63\x22\x0c\x3b\xc6\xcb\ \x53\xca\x3b\x6b\xfd\x8c\x23\x80\x04\x48\x40\x85\x05\x56\x7e\xa1\ \x09\x16\x64\x72\x24\xa8\xa4\x92\x01\x70\x05\x93\xc3\x48\xa5\xdb\ \x70\x56\x98\xa3\x40\xda\x20\x8e\x51\x0a\x96\x5d\x2e\x6a\x4d\x00\ \x58\x3c\x36\x13\x6f\x92\xa9\x22\xe4\xdd\x71\x24\x44\xfc\xbe\xdf\ \x62\x8b\x2d\xb6\xd8\x62\x8b\x2d\xb6\x98\x0a\xfb\xf6\xfd\x1b\x7a\ \x3c\x86\x67\xee\xbb\xee\x69\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ \x00\x00\x25\x17\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\xb2\x00\x00\x00\xb2\x08\x02\x00\x00\x00\x69\xe8\x02\x3f\ \x00\x00\x01\x35\x69\x43\x43\x50\x49\x43\x43\x20\x50\x72\x6f\x66\ \x69\x6c\x65\x00\x00\x78\x9c\x63\x60\x60\x32\x61\x80\x82\xdc\xbc\ \x92\xa2\x20\x77\x27\x85\x88\xc8\x28\x05\xf6\x7b\x0c\x6c\x0c\x2c\ \x0c\x82\x0c\xda\x0c\x16\x89\xc9\xc5\x05\x0c\x98\x80\x11\xc1\xfc\ \x76\x0d\xc2\xbb\xac\x8b\x45\x1d\x21\xc0\x95\x92\x5a\x9c\x0c\xa4\ \xff\x00\x71\x65\x52\x76\x41\x09\xd0\xe8\x0a\x20\x5b\xa4\xbc\x04\ \xcc\xee\x01\xb1\x93\x0b\x8a\x40\xec\x05\x40\xb6\x68\x11\xd0\x81\ \x40\xf6\x0e\x90\x78\x3a\x84\x7d\x06\xc4\x4e\x82\xb0\x1f\x80\xd8\ \x45\x21\x41\xce\x40\xf6\x17\x20\xdb\x21\x1d\x89\x9d\x84\xc4\x86\ \xda\x0b\x02\xf2\xc5\x20\x8f\x7b\xba\x3a\x9b\x19\x5a\x9a\x99\xe9\ \x1a\xe9\x1a\x2a\x24\xe5\x24\x26\x67\x2b\x14\x27\x27\xe6\xa4\xa6\ \x90\xe1\x2b\x02\x00\x14\xc6\x10\x16\xb3\x18\x10\x1b\x33\x30\x30\ \x2d\x41\x88\x21\xc2\xb3\x24\xb5\xa2\x04\xc4\x72\x29\xca\x2f\x48\ \xca\xaf\xd0\x51\xf0\xcc\x4b\xd6\x43\xd1\x9f\xbf\x88\x81\xc1\xe2\ \x2b\xd0\x8c\x09\x08\xb1\xa4\x99\x0c\x0c\xdb\x5b\x19\x18\x24\x6e\ \x21\xc4\x54\x80\x61\xc7\xdf\xc2\xc0\xb0\xed\x7c\x72\x69\x51\x19\ \xd4\x19\x52\x40\x7c\x94\xf1\x0c\x73\x12\xeb\x64\x8e\x6c\xee\x6f\ \x02\x0e\xa2\x81\xd2\x26\x8a\x1f\x35\x27\x18\x49\x58\x4f\x72\x63\ \x0d\x2c\x8b\x7d\x9b\x5d\x50\xc5\xda\xb9\x69\x56\xcd\x9a\xcc\xfd\ \x75\x97\x0f\xbe\x34\xff\xff\x1f\x00\x81\x49\x5c\xa9\xc6\xc2\x24\ \x8e\x00\x00\x23\x9d\x49\x44\x41\x54\x78\x9c\xed\x7d\x79\x74\x14\ \x55\xfe\xef\xf7\xde\xaa\x5e\x92\x74\x56\x02\x21\x40\x82\x09\x09\ \x24\x20\x61\x40\xa2\x6c\x13\xe1\x08\x8e\x22\x22\xb8\xa0\x1c\x1d\ \x67\x64\x66\xde\x19\xfc\xe9\x1c\x70\x3c\x8a\x8c\x0a\xe8\x30\xa8\ \x38\xa3\x8e\x07\x75\x5c\xdf\x3c\xdc\x1f\x8c\x4f\x07\x65\x11\xd1\ \x04\x09\x61\x58\x4d\x24\x0c\x10\xc8\x4a\x42\xf6\x90\xa4\xf7\xaa\ \xfa\xbe\x3f\xbe\xe9\xb2\x49\x3a\x49\x27\xe9\x2d\xa1\x3e\x27\xc7\ \x23\xdd\xd5\x55\x75\xef\xfd\xd4\xe7\xbb\xd4\xf7\xde\xcb\x10\x11\ \x86\x0a\x14\x45\x41\x44\x44\x64\x8c\x09\x82\xd0\xf5\xdb\xf6\xf6\ \xf6\xe6\xe6\xe6\xba\xba\xba\xc6\xc6\xc6\x96\x96\x96\xd6\xd6\xd6\ \x4b\x97\x2e\x59\xad\x56\x87\xc3\x61\xb7\xdb\x19\x63\x00\x60\x34\ \x1a\x0d\x06\x83\xc9\x64\x8a\x8c\x8c\x8c\x89\x89\x89\x8d\x8d\x1d\ \x36\x6c\xd8\xf0\xe1\xc3\xa3\xa3\xa3\xc3\xc3\xc3\xbb\x5e\x54\x92\ \x24\x00\x60\x8c\x71\xce\xe9\x0c\x43\x00\x6c\xb0\xd3\x02\x11\x15\ \x45\x01\x80\x4e\x3c\xb0\xd9\x6c\x17\x2e\x5c\x38\x7d\xfa\x74\x69\ \x69\x69\x71\x71\x71\x59\x59\xd9\xc5\x8b\x17\x6b\x6b\x6b\x5b\x5b\ \x5b\xad\x56\x2b\x8d\xa5\x97\x30\x18\x0c\x61\x61\x61\xc3\x86\x0d\ \x4b\x4c\x4c\x4c\x4c\x4c\x4c\x4f\x4f\x4f\x4f\x4f\x4f\x4b\x4b\x4b\ \x4b\x4b\x1b\x39\x72\xa4\xfb\x91\x8a\xa2\x28\x8a\x42\xfc\x18\xd4\ \x14\x19\xac\xb4\x40\x44\x59\x96\x3b\xa9\xc2\x85\x0b\x17\x8e\x1f\ \x3f\x7e\xf8\xf0\xe1\xa3\x47\x8f\x16\x17\x17\x57\x55\x55\x39\x9d\ \x4e\x8f\x3f\x67\x2e\xf4\x7a\x15\xe2\x9c\x47\x98\x4c\xa6\xf1\xe3\ \xc7\x4f\x9c\x38\xf1\xda\x6b\xaf\x9d\x3e\x7d\xfa\xa4\x49\x93\xa2\ \xa2\xa2\xd4\x6f\x65\x59\x06\x80\x41\x2a\x21\x83\x8c\x16\xc4\x06\ \xce\x39\xe7\x9c\x3e\x69\x6e\x6e\x2e\x28\x28\xf8\xf6\xdb\x6f\xf3\ \xf2\xf2\x4e\x9e\x3c\xd9\xde\xde\xee\x7e\xbc\x7a\x24\xba\xa0\x9e\ \xc7\xfb\x8b\xd2\xb8\xaa\x34\xa2\xff\xca\xb2\xdc\xe9\x24\xa3\x46\ \x8d\xba\xe6\x9a\x6b\xe6\xce\x9d\x3b\x77\xee\xdc\xc9\x93\x27\xeb\ \x74\x3a\xfa\x7c\x30\xf2\x63\xd0\xd0\x82\x3a\x57\xd5\x86\x0b\x17\ \x2e\xec\xdd\xbb\xf7\x8b\x2f\xbe\xc8\xcf\xcf\xbf\x78\xf1\xa2\x7a\ \x18\xf1\x00\xdd\xe0\xbf\x5b\x22\xa2\x10\xed\x3a\x59\xa5\x89\x13\ \x27\xce\x9b\x37\x6f\xf1\xe2\xc5\xb3\x67\xcf\x8e\x88\x88\x50\x9b\ \xa0\x1e\x1f\xe2\x08\x75\x5a\x90\x8c\xab\x6c\x68\x68\x68\xd8\xb9\ \x73\xe7\xf6\xed\xdb\x73\x73\x73\x5b\x5a\x5a\xe8\x43\x95\x0a\xe4\ \x72\x06\xeb\x56\x55\xaf\xd3\x9d\x22\x29\x29\x29\x37\xdf\x7c\xf3\ \xd2\xa5\x4b\xe7\xce\x9d\x2b\x8a\x22\x74\xe1\x77\x88\x02\x43\x15\ \x8a\xa2\x48\x92\xa4\xfe\x73\xef\xde\xbd\x2b\x56\xac\x48\x48\x48\ \x50\xef\x5c\x10\x04\x41\x10\x42\x50\x99\x89\x1f\xa2\x28\xba\x0b\ \xc3\xa4\x49\x93\x9e\x7d\xf6\xd9\x92\x92\x12\xb5\x45\x92\x24\xa9\ \xa1\x53\xa8\x21\x14\x69\x21\xcb\xb2\x4a\x88\x96\x96\x96\x2d\x5b\ \xb6\x64\x67\x67\xab\xfd\x1b\xb2\x6c\xf0\x88\x4e\xfc\x30\x99\x4c\ \xcb\x97\x2f\xdf\xb3\x67\x8f\xda\xd8\xd0\x24\x47\x68\xd1\xc2\x5d\ \x21\x2a\x2a\x2a\x1e\x7f\xfc\xf1\x31\x63\xc6\x50\x87\x32\xc6\x44\ \x51\x1c\x2c\x6c\xe8\x0a\xce\xb9\xbb\xe1\x98\x39\x73\xe6\x07\x1f\ \x7c\x60\xb1\x58\xa8\xb1\xa1\x46\x8e\x50\xa1\x85\x3b\x21\xce\x9e\ \x3d\xfb\xf0\xc3\x0f\xc7\xc6\xc6\x52\x0f\x0a\x82\x30\x28\xdc\x34\ \x6f\x40\x11\xb5\xda\x9c\x8c\x8c\x8c\xd7\x5e\x7b\xad\xbd\xbd\x9d\ \x1a\xee\x6e\x34\x83\x8b\x90\xa0\x85\xbb\x42\xac\x5e\xbd\xda\x64\ \x32\x51\xaf\x0d\x22\x63\xd1\x57\xb8\x8b\xc7\xf8\xf1\xe3\xdf\x7c\ \xf3\x4d\xea\x04\x59\x96\x29\xf4\x0d\x2e\x82\x4c\x0b\x59\x96\x49\ \x3c\x9b\x9b\x9b\xd7\xaf\x5f\xef\xae\x10\x43\x95\x10\xee\x70\x27\ \x47\x76\x76\xf6\xe7\x9f\x7f\x4e\xdd\x12\x74\x9b\x12\x34\x5a\xb8\ \x5b\x8d\xad\x5b\xb7\x8e\x1b\x37\x8e\x7a\xe7\x0a\x21\x84\x3b\xdc\ \xc9\x71\xcb\x2d\xb7\x1c\x3b\x76\x8c\xba\x25\x88\x36\x25\x38\xb4\ \x50\x1b\x7c\xf4\xe8\xd1\x05\x0b\x16\x50\x8f\x5c\x81\x84\x70\x87\ \x9a\x90\xd5\xeb\xf5\x8f\x3d\xf6\xd8\xa5\x4b\x97\x30\x78\xb2\x11\ \x68\x5a\xa8\x22\x61\xb5\x5a\xd7\xae\x5d\xab\xd7\xeb\x61\x68\x39\ \x95\x03\x84\x2a\x1b\x13\x26\x4c\xf8\xf2\xcb\x2f\xa9\xd3\x02\x2f\ \x1b\x01\xa5\x85\xea\x49\xec\xdf\xbf\x7f\xea\xd4\xa9\x9d\x3a\x42\ \x03\x81\x42\x71\xfa\xff\x95\x2b\x57\x36\x37\x37\x63\xc0\x65\x23\ \x70\xb4\x70\x3a\x9d\x88\x28\x49\xd2\x53\x4f\x3d\x45\x54\x18\xd4\ \x79\x08\x7f\x43\xb5\x29\x69\x69\x69\x3b\x77\xee\x44\x44\x45\x51\ \x02\x16\xa4\x04\x82\x16\x8a\xa2\x10\x27\xce\x9e\x3d\x3b\x6f\xde\ \x3c\x70\xa5\x87\x83\xdd\xf3\x83\x00\xaa\x6c\x3c\xf9\xe4\x93\xea\ \x73\x15\x80\x21\xf3\x3b\x2d\x54\x8e\x6f\xdf\xbe\x3d\x3e\x3e\x1e\ \x34\x91\xe8\x23\x54\xd9\x98\x3f\x7f\x7e\x59\x59\x19\xba\x74\xd7\ \xaf\xf0\x2f\x2d\x54\xd1\x5b\xbb\x76\x2d\x35\x52\xf3\x24\xfa\x07\ \x92\x8d\xc4\xc4\xc4\xaf\xbf\xfe\x1a\xfd\xef\x6a\xf8\x91\x16\x24\ \x77\xcd\xcd\xcd\xb7\xde\x7a\x2b\x68\xe1\xc6\x80\x41\x4f\x94\x20\ \x08\xaf\xbc\xf2\x0a\x22\x52\x81\xa0\x9f\xc6\xce\x5f\xb4\x20\xa1\ \x3b\x73\xe6\x4c\x56\x56\x16\xb8\xd9\x48\x0d\x03\x81\x5a\xe2\xf5\ \x87\x3f\xfc\x81\xfa\xd9\x4f\x4e\xa8\x5f\x68\x41\x9c\xc8\xcd\xcd\ \xa5\x0a\x58\x8d\x13\x3e\x84\x1a\xbe\xde\x76\xdb\x6d\x6a\xca\xcb\ \xe7\x23\xe8\x7b\x5a\x10\x27\x3e\xfb\xec\x33\x2a\x9f\xd7\x9c\x09\ \x7f\x80\x0a\x45\x67\xcd\x9a\x55\x57\x57\x87\x7e\x60\x86\x8f\x69\ \x41\x9c\xf8\xe7\x3f\xff\x49\x5a\xa7\x39\x13\xfe\x03\x69\x46\x66\ \x66\xe6\xf9\xf3\xe7\xd1\xd7\xcc\xf0\x25\x2d\x88\x13\x6f\xbf\xfd\ \x36\xb8\x85\x55\x1a\xfc\x07\x62\x46\x6a\x6a\xea\xb9\x73\xe7\xd0\ \xa7\x81\xab\xcf\x68\x41\xf7\xf4\xc6\x1b\x6f\xc0\x60\x2b\x7e\x1f\ \xd4\x20\x1b\xad\x32\xc3\x57\x9a\xe1\x1b\x5a\x10\x27\xde\x7b\xef\ \x3d\xd0\x38\x11\x70\xf8\x83\x19\x3e\xa0\x85\xea\x4f\xc0\x15\xff\ \x72\x3c\x58\x20\x6b\x32\x6e\xdc\xb8\xea\xea\x6a\xf4\x45\xd4\x3a\ \x50\x5a\x10\x27\xf6\xec\xd9\xc3\x5d\x08\x76\x17\x5d\xa1\x20\x66\ \x64\x67\x67\x37\x37\x37\x0f\x3c\xd3\x35\x20\x5a\x90\x5e\x15\x14\ \x14\x98\x4c\x26\xed\xed\x57\xd0\x41\xcc\x98\x3f\x7f\xbe\xd3\xe9\ \x54\x6b\x18\x02\x4d\x0b\x52\xaa\xb2\xb2\xb2\xd1\xa3\x47\x83\x16\ \x8b\x86\x06\x88\x19\xf7\xdf\x7f\x3f\x0e\x2c\x30\xe9\x27\x2d\xe8\ \xbd\x68\x5b\x5b\xdb\xf4\xe9\xd3\x41\xcb\x59\x85\x12\x88\x19\xcf\ \x3c\xf3\xcc\x40\x98\xd1\x4f\x5a\xd0\xf5\xee\xb9\xe7\x1e\xd0\x72\ \xdb\x21\x06\x35\x3b\xbe\x7d\xfb\xf6\x7e\x33\xa3\x3f\xb4\xa0\x2b\ \x3d\xf7\xdc\x73\xe0\xca\xc2\x6a\x08\x29\x50\x8e\x20\x32\x32\xb2\ \xb8\xb8\x18\xfb\x15\x98\xf4\x99\x16\xe4\x66\x7e\xfd\xf5\xd7\x34\ \x41\x4a\x0b\x47\x43\x13\x64\xd6\xb3\xb2\xb2\x2c\x16\x4b\x3f\x8a\ \x33\xfa\x46\x0b\xf2\x6f\x6b\x6a\x6a\x46\x8f\x1e\xad\x85\x1e\x21\ \x0e\x32\x25\xbf\xfd\xed\x6f\xb1\xef\xa6\xa4\x6f\xb4\x20\xa9\x58\ \xb4\x68\x11\x68\x6e\xe6\x60\x00\x31\xe3\xa3\x8f\x3e\xc2\x3e\x66\ \x3f\xfb\x40\x0b\x3a\xef\x96\x2d\x5b\x40\x73\x33\x07\x09\x48\xd1\ \xe3\xe2\xe2\xca\xcb\xcb\xb1\x2f\x4e\x86\xb7\xb4\x20\x4e\x9c\x3c\ \x79\x32\x3c\x3c\x5c\x73\x29\x06\x11\x48\xd4\x6f\xbc\xf1\xc6\x4e\ \xcb\xc8\xf8\x80\x16\x74\x46\x45\x51\xe6\xcc\x99\x03\x9a\xf9\x18\ \x6c\x20\x69\x7f\xfd\xf5\xd7\xd1\x6b\x27\xc3\x2b\x5a\x10\xcb\x5e\ \x7b\xed\x35\xd0\x38\x31\x08\x41\xef\xaa\xe2\xe2\xe2\x2a\x2a\x2a\ \xbc\x9c\x83\xd4\x3b\x2d\x28\xfa\xa8\xac\xac\x8c\x8e\x8e\xd6\x5e\ \x9a\x0f\x52\xd0\xc3\x7c\xe7\x9d\x77\xa2\x77\xbe\x67\xef\xb4\xa0\ \xb3\x2c\x5b\xb6\x0c\x34\xa9\x18\xcc\xa0\xb1\xa3\xe9\xce\xbd\x32\ \xa3\x17\x5a\xd0\xef\xf7\xed\xdb\x07\xda\xcb\xb0\x41\x0e\x1a\xbe\ \xcc\xcc\x4c\xab\xd5\xda\xeb\xfb\xd5\x5e\x68\x21\xcb\xb2\xc3\xe1\ \x98\x36\x6d\x1a\x68\xb4\x18\xfc\x20\xc1\x78\xf1\xc5\x17\xb1\x37\ \xdf\xb3\x27\x5a\x90\x54\x50\x29\x9e\x66\x3e\x86\x00\xc8\x35\x1c\ \x3e\x7c\x78\x5d\x5d\x5d\xcf\xa5\x3a\xdd\xd2\x82\x7e\xd6\xd6\xd6\ \x36\x76\xec\x58\x2d\xcf\x3d\x64\x40\x8f\xf7\x23\x8f\x3c\x82\x3d\ \x7a\x18\xdd\xd2\x82\x44\xe6\xa5\x97\x5e\x02\x4d\x2a\x86\x10\x68\ \x9d\xf2\xc8\xc8\xc8\xca\xca\xca\x1e\x82\x55\xcf\xb4\x50\xa5\x22\ \x29\x29\x49\x93\x8a\x21\x06\xca\x6e\xad\x5a\xb5\xaa\x07\xc1\xf0\ \x4c\x0b\x92\x8a\xbf\xfd\xed\x6f\xa0\x49\xc5\x90\x03\x79\x18\x26\ \x93\xa9\xbc\xbc\xbc\x3b\xc1\xf0\x40\x0b\x92\x0a\xb3\xd9\x9c\x9c\ \x9c\xac\x49\xc5\x90\x04\x09\xc6\xea\xd5\xab\xbb\x13\x0c\x0f\xb4\ \xa0\xe3\xd4\x79\x1f\xc1\x6e\x82\x06\xdf\x83\x3c\x8c\xf8\xf8\xf8\ \x86\x86\x06\x8f\x21\x89\x67\xb5\x90\x24\x89\x72\x15\x1a\x2d\x86\ \x2a\x68\x64\xff\xfa\xd7\xbf\xa2\xa7\x1c\x46\x67\x5a\x68\x69\xcd\ \x2b\x04\x6a\xd2\xd3\x6e\xb7\xf7\xae\x16\x44\x8b\xbb\xee\xba\x0b\ \xb4\x5a\x9b\xa1\x0e\x62\xc6\x8e\x1d\x3b\xba\x96\x62\x5c\x46\x0b\ \x72\x4a\xcb\xcb\xcb\x69\x7b\x2d\xed\x65\xe9\xd0\x06\xd9\x91\x25\ \x4b\x96\x60\x97\xc2\xad\xcb\x68\x41\x36\xe6\x85\x17\x5e\x00\x4d\ \x2a\xae\x00\xd0\x63\x1f\x11\x11\x41\xeb\x3a\xba\x33\xe3\x27\x5a\ \x90\x47\x2a\x49\x12\x2d\xbb\xac\x39\x9b\x57\x02\xe8\xe1\xef\xfa\ \xf2\xec\x27\x5a\x10\x59\x8e\x1c\x39\xa2\xd5\xda\x5c\x39\xa0\x87\ \x7f\xda\xb4\x69\x9d\x5e\xb5\xff\x14\x6b\xd0\xf6\xc0\xff\xfa\xd7\ \xbf\xdc\x37\x98\xd4\x30\xb4\xa1\x28\x0a\x63\xec\xc4\x89\x13\x45\ \x45\x45\x8c\x31\x75\x8b\xe8\x0e\x5a\x20\xa2\x20\x08\xb2\x2c\x7f\ \xf1\xc5\x17\xe0\xa2\x88\x86\x21\x0f\x1a\x77\x45\x51\x3e\xff\xfc\ \x73\x70\x1f\xf7\x4e\x16\xc4\xcb\xcd\xc7\x35\x0c\x19\x90\x65\xc8\ \xce\xce\x76\x4f\x77\x76\x84\x1b\x8a\xa2\x70\xce\x69\x83\x02\x51\ \x14\x3b\x6d\x0d\x1d\x9a\x08\xf1\xf7\x35\xc1\xdd\xc2\xd9\x7b\x90\ \x1d\xf9\xe1\x87\x1f\xce\x9e\x3d\x3b\x7e\xfc\x78\x62\x02\xa8\x61\ \x08\x22\x6a\xd3\x40\xae\x4c\x50\x3c\xb2\x65\xcb\x16\x35\x1e\x61\ \xc4\x09\xce\xf9\x85\x0b\x17\x26\x4c\x98\x60\x36\x9b\x19\x0b\xf5\ \x8d\xd7\xe9\x0e\x23\x23\x23\xe7\xcc\x99\x43\xdb\x5a\x85\x8e\xd5\ \x53\x1f\xb3\xa3\x47\x8f\xd6\xd4\xd4\x84\x7e\x67\x02\x00\xb9\x95\ \x4b\x97\x2e\xa5\x80\x83\x73\x2e\x82\xcb\x82\xe4\xe7\xe7\x9b\xcd\ \x66\x3a\x22\xd8\xf7\xd9\x0b\x38\xe7\xb2\x2c\x67\x67\x67\xaf\x5e\ \xbd\x7a\xe7\xce\x9d\xa2\x28\x86\x8e\x8f\xcc\x39\x77\x3a\x9d\x33\ \x67\xce\x4c\x49\x49\x79\xf5\xd5\x57\x05\x41\x08\x7d\x8b\x4c\xc4\ \x2d\x28\x28\x68\x6f\x6f\x37\x99\x4c\x3f\xf9\x16\x00\x90\x9b\x9b\ \x0b\xa1\xf4\xd8\xf5\x0a\x83\xc1\xf0\xd5\x57\x5f\xbd\xfc\xf2\xcb\ \xc1\xbe\x11\x0f\x38\x7d\xfa\x74\x7a\x7a\x3a\xb8\x7a\x3c\xc4\x41\ \xee\x45\x4d\x4d\x4d\x61\x61\xe1\xac\x59\xb3\x14\x45\x11\x01\x40\ \x10\x04\x44\x2c\x28\x28\x00\xff\x84\xa6\x9c\xf3\x8e\xdc\x99\x4f\ \x81\x88\x26\x93\x49\x10\x04\x0a\xb1\x7c\x7b\xf2\x7e\x83\x94\x2c\ \x3a\x3a\x7a\xe0\xa7\x52\x95\x3b\x00\x12\x4e\xaa\x76\xf0\xe0\xc1\ \x59\xb3\x66\x21\xa2\x48\x16\xa4\xaa\xaa\xaa\xb8\xb8\x18\xfc\xc3\ \x6e\x1a\x33\x7f\x58\x59\xaa\x39\x03\x80\xd0\x31\x7c\x34\x84\x3e\ \xa1\xa9\x2c\xcb\x46\xa3\x51\xa7\xd3\xb5\xb5\xb5\x0d\xfc\x6c\xde\ \xe0\xe0\xc1\x83\x40\xab\x6f\xd1\x50\xfd\xf8\xe3\x8f\x56\xab\x95\ \x73\xee\xdb\xc7\x8e\x96\xf7\x5a\xb8\x70\xe1\xb2\x65\xcb\xf6\xef\ \xdf\xff\xc6\x1b\x6f\x0c\x0a\xdf\x25\xe8\xa0\x81\x58\xbe\x7c\x79\ \x66\x66\x66\x6c\x6c\x6c\x43\x43\xc3\xb3\xcf\x3e\x0b\xfe\x4c\x33\ \xd2\x99\x7f\xf8\xe1\x07\x87\xc3\xa1\xd7\xeb\x39\xd1\xe2\xf8\xf1\ \xe3\xe0\x87\xba\x1b\x44\xe4\x9c\x9f\x3c\x79\xb2\xb6\xb6\x36\x25\ \x25\x05\x06\x95\xef\x12\x44\x50\xbf\x2d\x5b\xb6\xec\xbd\xf7\xde\ \x7b\xf8\xe1\x87\xef\xbf\xff\xfe\x29\x53\xa6\x74\x64\x14\xfc\x76\ \x45\x00\x28\x2d\x2d\x2d\x2f\x2f\x07\x35\xf9\x4d\xb4\xf0\x07\x24\ \x49\x2a\x29\x29\xa9\xae\xae\x0e\x80\x43\xee\x3d\xe7\xbc\xcc\xe4\ \x06\x6b\x7f\x35\x0a\x71\xef\xbc\xf3\xce\x8b\x17\x2f\x4e\x9e\x3c\ \x39\x37\x37\xb7\xba\xba\xda\xaf\xb1\x2e\x11\x51\x96\xe5\xff\xfe\ \xf7\xbf\x00\xc0\x29\xba\xa3\x7f\xf8\x43\xa3\x68\x00\xf4\x7a\x7d\ \x00\x74\xc2\xfb\x5e\xf3\xd2\x05\xf6\x95\x97\xd0\x0f\x10\x1d\xaf\ \xbb\xee\xba\xe7\x9e\x7b\xae\xaa\xaa\xaa\xae\xae\x0e\xfc\x1c\xd7\ \xd0\x15\x4f\x9e\x3c\x09\xa4\x16\x4d\x4d\x4d\x24\x1d\xfe\xbb\x2a\ \x65\x51\xbd\x65\x06\xf3\xf4\xd7\x3d\x28\x2d\xfb\xc0\x03\x0f\xe4\ \xe6\xe6\xd2\x3a\xa1\xbd\x5e\xe8\x93\x4f\x3e\x79\xe6\x99\x67\xa0\ \x7b\xbb\x49\x67\xb8\xee\xba\xeb\xae\xbe\xfa\x6a\x6f\x4e\xe8\x73\ \x90\x37\x9d\x9b\x9b\x7b\xcb\x2d\xb7\x64\x64\x64\xfc\xe6\x37\xbf\ \xa1\xf7\x12\xfe\xbe\xee\xa9\x53\xa7\x80\x68\x51\x56\x56\xd6\xda\ \xda\x0a\x3d\xd2\x82\x31\x60\x9c\xf5\xf9\x8f\x01\xe3\x0c\x11\x75\ \x7a\x9d\x4e\x27\x22\xa2\xfa\x61\x37\xc7\x33\xc6\x19\x20\x74\xfd\ \x63\x9c\x75\x47\x0e\x1a\xb3\xd4\xd4\xd4\x9c\x9c\x1c\x8a\x57\x45\ \x51\xec\x9a\xc2\xe7\x9c\x8b\xa2\x48\x9b\xf3\xce\x9e\x3d\x7b\xca\ \x94\x29\xea\x6f\xc9\x35\xa6\x5f\xa9\xf6\x45\x10\x84\xbd\x7b\xf7\ \xbe\xf5\xd6\x5b\xe0\x5a\x50\x86\xbe\x55\x8f\xa4\x1f\x76\x77\x39\ \x2f\xd1\xd1\x64\x76\xd9\x27\x00\x10\x19\x19\xf9\xfc\xf3\xcf\x13\ \xcb\x23\x22\x22\x6c\x36\x5b\xcf\x03\x04\x00\xc0\x80\x09\xac\xf3\ \x1f\xf7\x96\xd0\x74\x72\xda\xe2\x4a\x04\x00\x92\x8a\x9e\x62\x04\ \x06\x88\x00\xfd\xd2\x12\xa7\xc3\x79\xfd\x9c\xb9\x29\x63\x52\x19\ \x67\xf3\xe7\x2e\xd8\xfb\xdd\xd7\x74\x0b\xdd\xdf\x1d\x30\xce\x0c\ \x91\x3a\x44\x84\x8e\xee\x62\xb2\x43\x71\x5a\x7b\x71\x4d\x6c\x36\ \x9b\xc3\xe1\x90\x5d\x80\xcb\x43\x62\xf2\xed\x55\x8b\x60\x36\x9b\ \x2d\x16\x4b\x47\xe3\x18\x43\x44\x77\xd7\x87\x3e\x91\x65\x79\xc6\ \x8c\x19\x74\x98\xfb\x7b\x2f\xf5\x48\xea\x31\xb5\xd3\x3c\xda\x7e\ \xc6\x19\x07\xd6\x6d\x6b\x15\x40\x44\x40\x00\x00\x26\x74\x8c\x1f\ \x3d\x35\x4e\xd9\x69\x8a\x32\xad\x5b\xff\xb4\x53\x92\x0e\x1c\x3c\ \xf0\xe1\xc7\x1f\x72\x91\x23\x2a\xea\x61\x1e\x7a\x4e\x46\x94\xfb\ \xaf\xf7\xd4\x39\xe5\xe5\xe5\x66\xb3\x59\x04\x80\xb2\xb2\x32\xe8\ \x59\x27\x11\x22\x13\xc3\xb8\x9e\x43\x5f\x2f\xca\x00\x10\xce\x34\ \x9e\x7c\xe2\xc5\x47\x11\x50\x14\xc5\xa8\xe4\x70\xc6\x98\xe7\xf3\ \x70\x40\x19\x13\x26\xc7\x4e\xf9\x55\x9a\x31\x46\x87\x0a\x00\x03\ \x05\x99\xc0\x50\x91\xf1\xdc\xee\xea\x23\x6f\x9c\x56\x9c\x08\x88\ \x02\x78\x38\x01\x25\xb5\x8c\x46\xe3\xbd\xf7\xde\x3b\x72\xe4\xc8\ \xbc\xbc\xbc\xfc\xfc\x7c\x1a\x2a\x2a\x30\x49\x4b\x4b\x5b\xb4\x68\ \x11\x22\x7e\xf2\xc9\x27\x4e\xa7\x93\x04\x99\x0e\x88\x8e\x8e\x5e\ \xba\x74\x69\x52\x52\x52\x71\x71\xf1\x17\x5f\x7c\xe1\x74\x3a\xe9\ \x73\xbd\x5e\x6f\xb7\xdb\x01\x00\x11\xb3\xb2\xb2\x8c\x46\xe3\xe9\ \xd3\xa7\xef\xbe\xfb\xee\x98\x98\x98\x03\x07\x0e\x1c\x38\x70\x20\ \x22\x22\x62\xf9\xf2\xe5\xf1\xf1\xf1\x87\x0e\x1d\xfa\xf6\xdb\x6f\ \x3b\x79\xb2\x8c\x81\xe4\xec\x85\xcd\x91\xa3\xc2\x74\xe1\xba\x96\ \xd2\x36\xc5\x35\xa2\x44\x13\x59\xb2\xfd\xcf\xca\xff\x89\x32\xc4\ \xc8\xb2\x6c\x96\xbc\xca\x5b\xe8\x22\xc4\xd1\xd9\xf1\x86\x28\x1d\ \x43\x04\x06\x0a\x02\x63\xac\xad\xda\x52\x7d\xa4\x91\xc6\x82\x61\ \xef\x03\xd8\xd4\xd4\x54\x57\x57\x27\x02\x40\x45\x45\x45\xb7\x47\ \x31\x10\x74\xfc\xfa\x0d\x53\x52\xe7\x8f\x54\x24\xec\x9f\x85\x95\ \x29\x9d\x05\x80\x80\x02\xef\x5e\x6c\x19\x28\x12\x88\x06\x81\x31\ \xa6\x38\x15\x04\xc6\x00\x0d\x82\x62\x91\x04\x81\xe1\xb5\x0f\x67\ \xc8\x76\xf9\xd8\x3f\xce\x80\xc0\x1c\x1d\x87\x5f\x06\xca\xe0\xe6\ \xe6\xe6\x1a\x8d\x46\x51\x14\x37\x6d\xda\xb4\x71\xe3\xc6\x27\x9f\ \x7c\x92\xea\x04\x96\x2c\x59\xb2\x6d\xdb\xb6\xa6\xa6\xa6\xda\xda\ \xda\x07\x1f\x7c\x70\xe4\xc8\x91\xc7\x8e\x1d\xa3\x5f\xa5\xa7\xa7\ \xef\xda\xb5\x2b\x26\x26\xe6\xf4\xe9\xd3\xeb\xd7\xaf\x3f\x72\xe4\ \xc8\xa2\x45\x8b\x1a\x1a\x1a\x00\xe0\xcb\x2f\xbf\xcc\xcf\xcf\xbf\ \xf3\xce\x3b\x01\x60\xd5\xaa\x55\xcb\x97\x2f\x2f\x2e\x2e\xd6\xeb\ \xf5\xb1\xb1\xb1\xcf\x3f\xff\xfc\xfa\xf5\xeb\x17\x2e\x5c\x18\x15\ \x15\x05\x00\x9b\x36\x6d\xda\xb4\x69\xd3\xda\xb5\x6b\x55\x67\x85\ \x31\x40\x84\x31\xd7\x8e\x88\xba\x2a\x0c\x65\xec\x7c\xbb\x0c\x50\ \x86\x51\x53\x87\x25\xe7\x24\x08\x3a\xde\x54\xd2\x5a\xb2\xfb\x82\ \xd3\x26\xbb\xf7\xb0\xc0\x05\x9b\xcd\xc6\x18\x33\x18\xe3\x7b\x4d\ \xf6\x70\x91\x67\x2c\x4e\x1a\x31\x39\x16\x14\xb4\x2b\x82\x02\xcc\ \xc8\x25\x62\x41\xe9\x37\x35\xfb\x9e\x3c\x8e\x36\xb9\x67\x86\x92\ \xd4\x59\x2c\x96\xca\xca\x4a\x11\x00\xaa\xaa\xaa\x3c\x0f\x13\x67\ \xa8\xe0\xe8\x19\xf1\x93\x96\x8d\xb5\x36\xd9\x05\x7d\x3f\x43\x35\ \x0e\xde\xfd\x90\x01\x20\x4a\x16\x19\x18\x70\x0e\x16\x59\x5c\x31\ \xa6\x64\xce\xf0\x9a\x0d\xff\x9d\x56\x69\x09\xe7\xcd\xf6\xe4\x1b\ \x12\x8f\xbc\x53\x02\x92\x72\x8f\x00\x46\x80\x52\xb8\xec\xbc\x4e\ \xa7\xd3\x60\x30\xbc\xfe\xfa\xeb\xe4\x0d\x3c\xfd\xf4\xd3\x1b\x36\ \x6c\xf8\xf4\xd3\x4f\x0b\x0b\x0b\xa3\xa3\xa3\xdf\x7d\xf7\xdd\xdd\ \xbb\x77\xdf\x7e\xfb\xed\x76\xbb\x3d\x27\x27\x67\xef\xde\xbd\xa4\ \x99\x8a\xa2\xac\x5b\xb7\xce\x64\x32\x0d\x1f\x3e\x5c\x51\x94\xc9\ \x93\x27\x17\x16\x16\xae\x5c\xb9\x92\x1c\xd2\xd6\xd6\x56\xb3\xd9\ \x4c\xe7\x6f\x6b\x6b\x13\x45\x71\xed\xda\xb5\xbb\x77\xef\x16\x04\ \x61\xdb\xb6\x6d\xeb\xd7\xaf\x5f\xb3\x66\xcd\xf3\xcf\x3f\x0f\x00\ \xaf\xbe\xfa\xea\x9a\x35\x6b\x36\x6f\xde\xdc\xd2\xd2\x42\x5d\xe7\ \x30\xcb\x23\xe6\x44\xde\xfe\xcf\x39\x8a\x2c\x63\x17\x12\x23\x80\ \xe2\x44\xc5\x81\x92\x4d\x46\x05\x87\x4f\x8c\x19\x39\x35\x8e\x77\ \x0d\xd7\x18\x73\xc9\x47\x4f\x40\x00\x74\xa2\xd3\x22\xdb\x5a\x1c\ \x92\xc2\xd2\x23\x9a\x22\x44\xa9\xb0\x25\x0e\x38\x0a\x51\x7a\x8b\ \x55\x76\x02\x00\xb0\x11\x80\x97\x00\xec\xdd\x9f\x87\x4c\xed\xc5\ \x8b\x17\x45\x00\xa0\x27\xa3\x3b\x77\xc6\xd1\x26\x29\x4e\x45\x17\ \x2e\x42\xd7\xc6\xf9\x02\x1c\x40\x01\xe0\x00\xc8\x40\x16\xd0\x29\ \xcb\x28\x23\x20\x30\x84\x36\x49\x57\x6f\x0b\x73\x28\x82\x80\xe8\ \x60\x7c\xac\xa4\xfc\x19\x31\x1b\xc0\x0e\xf0\x2b\x80\x6b\x2f\x37\ \x25\x7a\xbd\x5e\x92\x24\xe2\x04\x00\xbc\xf0\xc2\x0b\x4f\x3c\xf1\ \xc4\xed\xb7\xdf\x5e\x58\x58\x98\x93\x93\x13\x1b\x1b\xfb\xc4\x13\ \x4f\xd8\xed\x76\x51\x14\xf3\xf2\xf2\x4a\x4b\x4b\x69\xf7\x5e\x70\ \xe5\x98\xc7\x8d\x1b\x77\xf6\xec\xd9\xa2\xa2\xa2\xb1\x63\xc7\x92\ \x5b\x4a\xa1\xbc\xfa\xf4\x47\x44\x44\xd4\xd5\xd5\xed\xde\xbd\x5b\ \xa7\xd3\x39\x9d\xce\xbc\xbc\xbc\x85\x0b\x17\xbe\xf2\xca\x2b\xf4\ \xed\xae\x5d\xbb\x1e\x7a\xe8\xa1\x84\x84\x84\xe6\xe6\xe6\x8e\x1b\ \x42\x1c\xb7\x68\x24\x2a\x68\xae\xb5\x31\xb1\x4b\xc7\x21\x28\x4e\ \x72\xc0\x19\x30\x70\x5a\x25\x87\x19\x98\x00\x4c\xf4\x64\x20\xbd\ \x80\x6c\x47\x2e\x30\xce\x19\x03\xfe\xbf\x52\x4a\x12\xc2\xdb\xfe\ \x50\x38\xcb\x29\x1b\xce\xff\xf5\xc7\x1f\xff\xcf\xf9\x04\x80\x45\ \x0c\xb2\x01\xfe\x04\xe0\xe8\xb8\xbe\x07\x10\x2b\x6b\x6a\x6a\x44\ \x49\x92\x9a\x9a\x9a\xc0\x13\x2d\x50\x41\x60\x70\xf1\x78\x53\xde\ \x33\x85\xe3\x17\x27\xc9\x4e\xc5\x1f\x61\x5a\x3b\x40\x24\x80\x15\ \x80\x33\xd0\x29\x10\x93\x64\x32\xc6\x19\x64\xbb\x1c\x01\xf8\xef\ \x96\x94\xaf\x2e\x5d\xa5\x30\x08\x33\x21\x37\xe9\x0e\xfd\xbb\xca\ \x22\xe3\x51\xce\x16\x03\xcc\x03\x68\xf4\xc4\xd2\xb0\xb0\x30\xab\ \xd5\xca\x18\xb3\xdb\xed\xf5\xf5\xf5\xe3\xc6\x8d\x03\x80\xd4\xd4\ \x54\x45\x51\x6a\x6b\x6b\xc9\xc9\xa0\xd2\x76\x6a\x2f\xe7\xfc\xa9\ \xa7\x9e\x4a\x49\x49\x39\x75\xea\xd4\x99\x33\x67\xf2\xf2\xf2\x36\ \x6f\xde\x7c\xee\xdc\x39\x7a\xbd\x77\x59\x6f\x20\x52\x3d\x18\xfd\ \x8f\x5e\xaf\xe7\x9c\x1b\x8d\x46\x8a\x11\xa8\xec\xe3\xa7\x9f\x30\ \x00\x80\xca\xbc\xfa\x19\xf7\x0a\xe1\x23\x0c\x1e\xc2\x01\x04\x7b\ \x8b\x24\xd9\xe5\x0e\xa7\x9a\x33\xc6\xc0\x38\x4c\xc7\x75\xdd\x38\ \x5e\xbd\x41\x76\xa0\xb3\x4d\x92\x1d\xb2\x82\xec\xe3\xca\xb1\xc3\ \x04\x7b\x2b\x37\x4c\xfa\x73\x61\xc6\xbf\x2b\x6f\x15\x58\x9c\x8c\ \x3f\x47\xb8\xcd\xd5\x69\x3d\x5f\xa1\xa6\xa6\x46\xb4\xd9\x6c\x14\ \x9d\x7a\x06\x02\x00\x14\x7d\x58\x56\xf4\x61\x59\x7f\x6e\xd6\x0b\ \xcc\x02\xf8\x1a\x20\x03\xc0\x02\x50\x01\x90\x90\x15\x3b\x75\x45\ \xba\x29\x31\x0c\x5c\x0d\x60\x0c\x1c\x66\xe9\xec\x8e\xaa\xe2\xed\ \xe5\xc0\xe0\x84\x82\x7b\x05\x48\x02\x18\xed\xa9\x79\xf4\x88\xd3\ \xf0\xab\xd5\x87\x4e\xa7\x13\xdc\xc6\xd5\x3d\xac\xe0\x9c\x57\x54\ \x54\xe4\xe4\xe4\xcc\x98\x31\xe3\x86\x1b\x6e\xb8\xfb\xee\xbb\x8b\ \x8b\x8b\x6f\xbd\xf5\xd6\x3d\x7b\xf6\x78\xbc\x5b\x62\x95\x9a\x0d\ \x53\xcf\xd3\x99\x43\x32\xea\x22\xc4\xaa\xaf\x5a\xbe\x5a\x79\x28\ \x26\x2d\x42\x76\x5e\xee\x96\x31\x00\x05\x92\x66\x8d\x18\x9e\x19\ \xe3\x30\x3b\x29\x38\x57\x24\xe5\xd8\x5b\x25\xb2\x43\xea\x9f\x26\ \x73\x81\xa5\xfd\x62\x74\x74\xb2\xc9\xa8\xe0\x0f\xca\x68\xd9\x09\ \xa6\x30\xe5\xe4\x43\x19\xdf\xb5\x39\x2b\x72\x6b\x19\x67\x33\x15\ \x7c\x10\xe0\x29\x80\xda\xde\x98\x51\x5f\x5f\x2f\x5a\xad\xd6\xf6\ \xf6\xf6\xae\x0d\x73\x07\x39\x19\xfd\xb8\xd7\x9e\x21\x00\xc8\x00\ \xb7\x00\x4c\x07\x98\x0a\xf0\x67\x00\x00\xa8\x2d\x6c\xde\xb5\xea\ \x3f\x6e\xd1\x1a\x00\x80\xea\xa5\x73\x04\x06\xd0\x0e\x50\x0c\x30\ \xf6\xf2\xb6\x91\xaf\x40\xcf\x2e\x00\x24\x24\x24\x24\x26\x26\x16\ \x16\x16\x02\x40\x61\x61\x21\xe7\xfc\xea\xab\xaf\xde\xb7\x6f\x1f\ \x1d\x46\xb5\x04\x00\xc0\x18\x7b\xee\xb9\xe7\xf6\xec\xd9\xb3\x6f\ \xdf\xbe\x82\x82\x82\x8d\x1b\x37\x96\x97\x97\xaf\x5a\xb5\x8a\x68\ \xe1\x9e\x0c\xed\x94\x18\xed\x9a\x27\xf5\xd0\x81\x0c\x4e\xef\xa8\ \xec\xae\xf9\xa7\x3f\xaf\xbc\xf5\xcd\x99\x61\xb1\x06\xd9\xa9\x88\ \x46\xe1\xf0\x96\xff\x16\xbe\x7f\xde\xeb\xce\xf3\x80\x93\x9f\x94\ \xa7\xde\x30\x52\x17\xa1\x63\xa8\x30\x60\x92\x02\x7a\x93\x4e\xd4\ \x73\x04\x44\x84\x03\x00\x07\x00\x3c\x06\x71\x9d\xd0\xd4\xd4\x24\ \xb6\xb7\xb7\x3b\x1c\x8e\x9e\x8f\xf3\x07\x27\x00\x40\x61\xc0\x39\ \x7b\x16\xe0\x04\xc0\x5f\x11\xce\x29\x28\x00\x28\x9c\x01\x00\xfe\ \x14\xad\x75\x80\x09\x0c\x65\xa4\xb4\x83\x08\x20\x77\x69\x9e\x2c\ \xcb\xa2\x28\xbe\xff\xfe\xfb\xeb\xd6\xad\x03\x80\x37\xde\x78\xc3\ \x6c\x36\x7f\xfa\xe9\xa7\x8c\xb1\x83\x07\x0f\x16\x16\x16\xbe\xf7\ \xde\x7b\xb7\xdd\x76\x5b\x7d\x7d\xfd\x8a\x15\x2b\xc6\x8d\x1b\xf7\ \xfd\xf7\xdf\x03\x80\x24\x49\xf3\xe6\xcd\x7b\xf0\xc1\x07\x17\x2c\ \x58\x50\x56\x56\x96\x99\x99\x39\x6a\xd4\xa8\x0f\x3f\xfc\x90\xce\ \xa9\xd3\xe9\xd4\xc4\xa2\x28\x8a\xee\x5b\x2d\x09\x82\xe0\xfe\x4f\ \xcf\x15\xf3\x08\xa2\x5e\x54\xba\x04\x11\x24\x81\xad\x55\x96\x96\ \x8a\xf6\xa8\xe4\x70\x6c\x47\x45\x51\xca\xbf\xaf\x65\x02\xe3\xfd\ \x7d\x02\x11\xa0\xfd\xa2\xb5\xf0\x83\xd2\xee\x0e\xa0\x87\xb0\xe7\ \x78\x86\x98\xdd\xd6\xd6\x26\x5a\xad\xd6\x60\x55\x95\x21\x00\xca\ \x68\x03\xd8\x01\x90\xef\x8a\xe8\x3c\x74\x0a\x39\xe3\x6e\x89\x1a\ \xf4\x44\x79\x7a\xb3\x83\x88\x87\x0e\x1d\x8a\x89\x89\x69\x6e\x6e\ \x5e\xbe\x7c\xf9\x85\x0b\x17\xc8\x94\xdc\x75\xd7\x5d\xdb\xb6\x6d\ \x3b\x7e\xfc\x78\x5b\x5b\xdb\xf7\xdf\x7f\x9f\x9f\x9f\x4f\x96\x85\ \x31\xb6\x70\xe1\xc2\xf7\xdf\x7f\x7f\xdf\xbe\x7d\x56\xab\x35\x22\ \x22\xe2\xb3\xcf\x3e\xdb\xb4\x69\x13\xd9\x9a\x86\x86\x86\x4b\x97\ \x2e\xd1\xf9\x2f\x5d\xba\x54\x5f\x5f\xaf\x5e\xce\x6c\x36\xd7\xd5\ \xd5\xa9\x0a\x61\xb7\xdb\x9b\x9a\x9a\xba\xf6\x24\xca\xa8\x78\x4a\ \x31\x21\x03\xc6\x80\xeb\x18\xe3\xc0\x04\x60\x1c\x74\x61\x22\xca\ \xa8\x78\xec\x01\xef\x40\xe9\xe3\xce\x17\x72\x9d\xd0\xfb\x6a\x06\ \xab\xd5\x2a\xda\xed\xf6\xe0\x14\x40\x30\x00\x84\xa4\xeb\xe2\x47\ \xcf\x1d\xd9\x6c\x97\xd9\x8e\x2a\x2c\x69\xf3\x6c\xf4\x7a\xeb\x25\ \x1a\x8c\x2d\x5b\xb6\xbc\xf5\xd6\x5b\xcd\xcd\xcd\x29\x29\x29\xc3\ \x87\x0f\x2f\x2e\x2e\x6e\x6f\x6f\x67\x8c\x49\x92\xc4\x18\x3b\x73\ \xe6\x4c\x56\x56\xd6\xcf\x7e\xf6\x33\x59\x96\x8b\x8a\x8a\xc2\xc3\ \xc3\x55\x27\xb1\xb1\xb1\xf1\xe6\x9b\x6f\x4e\x4d\x4d\x4d\x48\x48\ \xa8\xae\xae\xa6\x9c\x2f\x3d\xf7\x37\xdf\x7c\x33\xb1\x07\x00\x9e\ \x7e\xfa\xe9\x8d\x1b\x37\xaa\x97\x7b\xe7\x9d\x77\x3e\xf9\xe4\x13\ \xb2\xbf\x00\xf0\xcd\x37\xdf\x64\x66\x66\x36\x36\x36\x7a\x5f\xb3\ \x82\x97\xb3\x7b\xe0\x92\x8c\x97\x3f\x3c\xfd\xc6\x65\xb4\x08\x64\ \xd5\x21\x39\x2b\x13\x16\x27\xcd\x7b\x76\x2a\x63\x78\x81\xb1\x69\ \x77\x5e\x55\xff\x50\x41\xed\x89\xe6\x7e\xfb\x31\x66\xb3\xd9\x6c\ \x36\x73\xce\x4b\x4b\x4b\x4b\x4b\x4b\xc1\x15\x85\x83\xcb\x0f\x55\ \x14\xe5\xc4\x89\x13\x00\xc0\x18\xb3\x58\x2c\x94\xd5\x56\x5f\xe0\ \x9d\x3f\x7f\xfe\xfc\xf9\xf3\x70\x79\x89\x61\x63\x63\xa3\x7a\xfe\ \xb6\xb6\x36\xf7\x2a\x29\xf5\x0c\x04\xbb\xdd\x4e\x2f\x39\x87\xc0\ \x74\x0a\x59\x96\x83\x31\xfd\x86\x01\x2a\x28\x1a\x85\xa9\xbf\x4d\ \x57\x1c\x72\x7b\x93\x04\xf5\x76\x53\x94\xee\x67\xbf\x4a\x1b\xd0\ \x59\x19\x53\xe3\x4f\x7a\xa7\xe5\xfe\xd4\x52\x0e\x94\xf2\x10\x44\ \x05\xd5\x0f\xe8\x98\x8c\xcb\x39\x55\x57\xb8\xc7\x29\xee\x87\x75\ \x49\x6c\x77\xf6\x24\x86\x52\x85\x91\x18\xf8\xd2\x64\x32\x14\xc6\ \x18\xbd\x3e\x42\x74\x38\x20\xda\x20\x39\x14\x66\xb7\x2b\x61\xc3\ \xf4\x30\x00\x2d\x55\x1b\xd2\x9d\x86\x77\x0a\x2b\x3a\x7d\xeb\xf1\ \x57\x9d\x42\x8f\xee\xbe\xea\xee\x93\xc1\x0b\x4e\x19\x3d\x08\x38\ \xd9\x19\x03\x19\x59\xa4\xce\xf9\x5c\xe6\xd1\x55\x29\xc5\xc8\x18\ \xf8\xc2\x2e\x6a\xf0\x09\xb8\xc1\x60\x08\x8a\x39\x44\x04\x0e\xe0\ \x50\xf8\x79\x8b\xe9\x82\x2d\x1c\x29\x68\xd3\x10\x02\x30\x18\x0c\ \xa2\x5e\xaf\x0f\x96\x97\xc4\x18\x3a\x40\x7c\xb1\x22\x0b\x15\x34\ \x1a\xfa\xa9\xc1\xa1\x36\xc5\xde\xcb\x9b\x21\x4b\x2a\xdb\x15\x60\ \x0c\x10\x98\xc0\x04\x7d\xa8\xf8\xaa\x46\xa3\x51\x8c\x88\x88\xd0\ \xe9\x74\x56\xab\x35\xc0\xd7\xa6\x57\x44\x3a\x3d\xe8\x50\x41\x40\ \xa1\xef\xe5\x68\xe8\x5a\x7f\x21\xa4\x66\xf3\xd1\xcd\xd0\x8d\xf5\ \x7a\xb0\xfb\x21\xa1\x40\x6c\x22\x74\x78\x78\xb8\x18\x11\x11\x61\ \x30\x18\x02\x79\x6d\xea\x0a\x7b\x9b\x53\x71\x2a\x4c\x64\x8a\x03\ \x11\x81\xeb\x99\x6c\xa3\x92\x2a\x6f\xab\xc0\x04\x41\x88\x8e\x8e\ \xa6\x89\x65\xa1\x33\xf7\x84\xa6\x67\xc5\xc4\xc4\x50\x5e\xa4\x67\ \xb8\x53\x27\x14\x68\x41\x88\x8e\x8e\x16\x8d\x46\x63\x64\x64\x64\ \x7d\x7d\x7d\xe0\xe6\x56\x23\x30\xce\x1c\xed\xce\xd3\x5f\x54\x66\ \x3f\x34\x41\xb6\xc9\x8c\x33\xc9\x26\x9f\xfc\xbf\x65\x00\x5e\xf1\ \x82\xee\xb3\xaa\xaa\x6a\xd2\xa4\x49\x2f\xbe\xf8\xa2\xfa\x82\x23\ \x14\x40\x09\xb4\x84\x84\x84\xad\x5b\xb7\xf6\x7c\x1c\x20\xca\x0e\ \x85\x71\x40\x04\x2e\x30\xde\xdf\x72\x16\x9f\x23\x2e\x2e\x4e\x34\ \x1a\x8d\xb1\xb1\xb1\x41\xb9\xfc\xd9\x1d\x55\x53\x7e\x9d\x0a\x9c\ \x89\x61\x42\xcd\xd1\xa6\xd2\x7d\x17\xc1\xbb\x00\x95\x82\xc9\xc2\ \xc2\xc2\xdb\x6e\xbb\xcd\xfd\xc5\x44\xe8\x40\x9d\x49\x10\x3a\x32\ \xe6\x3d\x12\x12\x12\x44\xda\x21\x13\x82\x91\x8d\x61\x22\x53\x4b\ \x7b\xa8\xa0\xb7\x1f\xa5\x06\x6a\x72\x7a\x30\x42\xb6\xcb\x24\x1b\ \x4c\x60\x22\xb9\x9c\x21\x60\x4a\x12\x13\x13\x39\x00\x24\x24\x24\ \x40\x50\x92\x74\xd8\xe3\x3f\xaf\x04\xb8\x37\x39\x04\x6c\x08\x29\ \xdc\xe8\xd1\xa3\x39\x00\x8c\x19\x33\x26\xd8\xf7\x13\x64\xb8\x17\ \xe7\x05\x08\x0c\x00\x80\x0a\xde\x10\x81\x73\xd6\xef\x52\x59\x1f\ \x02\x11\x05\x41\x48\x48\x48\xe0\x00\x40\x93\x86\xaf\x4c\x90\x46\ \xd2\x14\x92\xc0\x4f\x37\xf5\x53\x21\x4b\xff\xa0\x4e\x5b\x4a\x4a\ \x4a\xe2\x00\x90\x94\x94\x04\x57\xea\x5a\x9c\x14\xc2\x64\x67\x67\ \x67\x64\x64\x5c\x99\x3d\xa0\x82\x68\x91\x98\x98\x38\x6c\xd8\x30\ \x0e\x00\xa9\xa9\xa9\xf4\xe6\x30\x70\xee\x05\x02\x00\xc8\x12\xa2\ \x8c\xa4\xa2\x82\x8e\x07\xd8\xb7\xa1\x74\x64\x62\x62\xe2\x47\x1f\ \x7d\x34\x75\xea\xd4\x0d\x1b\x36\x3c\xfe\xf8\xe3\x70\x05\xef\xa2\ \x42\xa3\x9f\x9c\x9c\xac\xd7\xeb\x39\x00\x8c\x1e\x3d\x7a\xc4\x88\ \x11\x81\xbf\x0f\x45\x42\xc5\xa5\xa2\x5c\xc7\x03\xec\x84\x53\x9e\ \x26\x21\x21\xa1\xbd\xbd\xfd\xcd\x37\xdf\x7c\xf4\xd1\x47\x7f\xff\ \xfb\xdf\x87\x85\x85\x05\xf2\xf1\xe0\xae\x92\x55\x04\xc0\x60\x4b\ \x15\xb5\x9a\x6a\xe5\x39\xad\x40\x95\x96\x96\x06\x01\x7d\x50\x10\ \x00\x24\xab\xa4\x48\xc8\x38\x43\x19\x75\xe1\x22\x17\x03\x1a\xa1\ \x91\xc9\x38\x71\xe2\xc4\xef\x7e\xf7\x3b\x92\x8d\x92\x92\x12\x87\ \xc3\x11\xa0\xb4\x1e\x02\x00\x08\x7a\x01\x69\xf6\x9c\x8c\xb2\x3d\ \x24\x32\x1c\x34\x43\x9f\x53\xbe\x25\x33\x33\x13\x02\x18\xa3\x52\ \xaf\x3b\x2d\x92\xd3\x22\x51\x39\x96\xde\x24\xea\x22\x04\x08\x78\ \xdc\x4e\xaf\x09\xc3\xc2\xc2\x1e\x7f\xfc\xf1\xf5\xeb\xd7\xcb\xb2\ \x1c\xa0\x4e\x40\x04\x00\x7d\x94\x0e\x65\x04\xce\x14\xa7\xe2\xb4\ \xca\x00\xc1\x8c\xd2\x89\x09\x93\x26\x4d\x02\x35\x58\xbe\xe6\x9a\ \x6b\x02\x7a\x0b\xe4\x5b\xd8\x15\x4b\xbd\x8d\xeb\x98\x22\x29\x86\ \x28\x5d\x64\x62\x18\x63\x01\x7d\x37\xc0\x18\x93\x65\x39\x3c\x3c\ \xfc\x4f\x7f\xfa\xd3\xe6\xcd\x9b\x0f\x1c\x38\x40\x46\x24\x00\x97\ \x46\x04\xc6\x98\x29\xc1\x88\x32\x72\xce\x64\x87\x62\x6d\xec\x61\ \x16\xa0\xdf\xc1\x5c\x2b\xe0\x66\x64\x64\x00\x40\xc7\xd6\x21\xb4\ \xd8\x43\x20\x33\xb5\x34\x13\xa4\xa5\xdc\xcc\x45\xae\x48\xa8\x37\ \x89\x23\xb2\x62\x10\x81\x8b\x7d\x5f\x45\xa3\x5f\x7f\x5c\xe0\x8c\ \xb3\x09\x19\x13\x76\x7c\xb9\xc3\x66\xb7\x01\x83\xbf\xff\xfd\x95\ \xe9\xd9\xd3\x19\x67\x82\x28\xf8\xf7\xd2\x22\x63\x9c\x45\x8c\x34\ \x46\x8d\x09\x97\xec\x32\xd7\x31\x6b\xb3\xdd\xd2\xe0\xc5\x0a\x16\ \xfe\x1b\x0e\xc6\x00\x60\xfc\xf8\xf1\xc3\x87\x0f\xef\x98\x65\x09\ \x00\x13\x27\x4e\x4c\x48\x48\xa0\x7f\x07\xf2\x6e\x6a\x0e\x37\x02\ \x4d\xf8\xb7\xc9\x93\xef\x4b\x0d\x8f\x37\xc8\x0e\x05\x15\x0c\xc0\ \x9f\x22\x2b\x8a\xac\x24\xc4\x8d\x3c\x53\x7c\x36\x32\x2c\xea\xd7\ \xf7\x3d\xa0\x63\x86\x92\x53\xe7\x50\x41\x59\x92\xfd\x7b\x69\x09\ \x51\xc1\x89\x77\x8d\x35\xc6\xea\x65\xa7\x22\x18\x84\xc6\x33\x6d\ \x4e\x8b\xcc\x78\xd0\x8c\x08\x8d\xfb\xb4\x69\xd3\x48\x41\x45\x2a\ \x85\x8d\x8a\x8a\x9a\x32\x65\xca\x9e\x3d\x7b\x02\xe7\x5e\x28\x08\ \x00\x15\xdf\xd7\x99\x2f\x5a\xf5\x51\x3a\xa7\x55\x8e\xb9\xca\xb4\ \x64\xeb\xec\xd3\xff\xaf\xb2\xb6\xa8\xc5\x5a\x6f\x93\x1c\x8a\x5f\ \xfb\x88\x0a\x7d\xff\x73\xee\x40\xde\x9a\xdc\x8e\xf9\xd1\x00\xa6\ \x28\x43\x74\x72\x84\x1f\xaf\x0a\x20\x86\x09\x51\xa3\xc3\x93\xaf\ \x4f\x98\xb0\x78\x8c\xa3\x5d\x62\x0c\xb8\xc0\x4a\xbf\xb9\x08\xe0\ \x9a\x9f\x1e\x0c\x90\x4a\xcd\x9e\x3d\xbb\xe3\x26\xc1\x35\xaf\x72\ \xf6\xec\xd9\x81\xa4\x05\x95\x24\x59\x9b\x1c\x27\xde\x3b\x97\xb3\ \x2e\xcb\x5c\x6b\x73\x5a\xa4\xc8\xd1\xe1\x33\x1f\x9d\x28\x3b\x14\ \xfa\x0b\x40\x1f\x51\x35\xb8\xfa\x4f\x45\x96\xbd\x58\x1a\x64\x40\ \x17\x14\x0d\x5c\x30\x08\x8c\x83\xa3\x5d\x52\x9c\x8a\x21\x4a\x5f\ \x7f\xb2\xe5\xdc\xae\x6a\x2a\x88\xf7\xe7\xa5\xbb\xbf\x27\x52\x08\ \x51\x9c\x31\x63\x06\xd0\x6a\x52\xe0\xb2\x2b\x39\x39\x39\x10\xd8\ \x5c\x27\x2a\xc8\x38\x3b\xf1\xbf\xcf\x45\x8f\x8d\x98\x72\x7f\xaa\ \xbd\xcd\x29\x59\x65\xa7\x59\x62\x1c\x18\x63\x5c\xe8\x6d\x29\x35\ \x5f\xdd\x06\xfc\xd4\x64\x26\xb0\x6e\x97\xe8\xf2\x11\x64\x87\x22\ \xd9\x64\xf2\xa2\x8c\x31\x7a\x5b\x8b\xe3\xbb\xa7\x7e\x90\x6c\x32\ \xe3\x41\x4b\x5d\x90\xbf\x39\x61\xc2\x84\x8e\xa4\x05\xd1\x82\x1e\ \x97\x6b\xae\xb9\x66\xc4\x88\x11\x75\x75\x75\x3e\x5f\xcb\xb7\x5b\ \x60\x87\x76\xe5\xae\x2f\x6c\x3e\xd7\x96\xf5\xab\x71\x51\x63\xc2\ \xb9\xc0\x50\x46\xcf\xb3\x0e\x87\x04\x18\x67\x8c\x03\xe3\x4c\xb6\ \xcb\x95\x07\xeb\xbf\xdf\xf8\x63\x53\x49\x9b\x9f\x26\x7f\x7b\x09\ \x9a\x3b\xf3\xf3\x9f\xff\x5c\x14\x45\x59\x96\x05\x41\xe8\x50\x0b\ \x59\x96\x23\x23\x23\x73\x72\x72\xb6\x6f\xdf\x1e\x38\x5a\xa8\x60\ \x50\xb8\xb5\xf4\xd4\xbf\x2a\xc7\xcc\x88\x1f\x31\x39\x26\x7a\xac\ \xc9\x18\xad\x13\xf4\x81\xce\x7b\x06\x02\x08\xb2\x5d\xb1\x34\xda\ \x9b\xcf\xb5\x55\x1f\x69\xac\x39\xda\x04\x7e\x5b\x10\xc0\x7b\xd0\ \x8c\xa9\x5f\xfc\xe2\x17\x3f\x7d\x44\x93\x6a\x68\xcf\x99\x77\xde\ \x79\x07\x82\x34\x5d\xce\xfb\x75\x02\x87\x18\x82\xde\x70\x72\x21\ \xe2\xe2\xe2\xea\xeb\xeb\xd1\xb5\x0f\xd5\x65\x9b\x52\x55\x54\x54\ \xd0\xd2\x41\xc1\xa9\xaf\x57\x97\x95\x1c\xd2\xfb\xb0\xd2\x42\x9c\ \xbc\x2f\x2b\x66\xfa\x15\x34\x31\x73\xf1\xe2\xc5\xe8\xb6\x71\xb2\ \xba\x2c\x1c\x53\x14\x25\x29\x29\x69\xd6\xac\x59\x2c\x58\x5b\xc3\ \x61\xc7\xca\x92\x38\x38\xf6\x7e\xeb\x27\x10\x29\x6b\x82\xa1\xe3\ \x3c\x21\xe2\x92\x25\x4b\xa0\xeb\x86\x97\xaa\x1d\xf9\xc7\x3f\xfe\ \x01\x43\x62\xda\xb5\x06\x6f\xa0\x96\xde\x54\x57\x57\xab\x46\xe3\ \x27\x23\xa2\x0a\x48\x55\x55\x55\x44\x44\x04\x04\xcb\x8e\x68\x08\ \x2c\x68\xad\x9f\x3b\xee\xb8\x83\x66\x61\xa9\x64\xf8\x89\x16\x2a\ \x33\x96\x2c\x59\xc2\x18\xd3\x04\xe3\x4a\x00\x8d\xf2\xb6\x6d\xdb\ \x54\x73\xe1\x81\x16\xf4\xc5\xf6\xed\xdb\xe1\x0a\x2e\x52\xba\x72\ \x40\x43\x3c\x66\xcc\x98\xf6\xf6\x76\x77\x0b\xd2\x99\x16\x54\xe9\ \x6a\x36\x9b\xc7\x8e\x1d\x0b\x1a\x33\x86\x3a\xc8\x82\xac\x5a\xb5\ \xaa\x93\x54\x74\xa6\x85\xfa\xf5\xda\xb5\x6b\xd5\x9f\x69\x18\xaa\ \x60\x8c\xe9\x74\xba\xa2\xa2\x22\xf7\xd0\xd4\x33\x2d\xe8\xeb\x92\ \x92\x12\xa3\xd1\x18\x52\x0b\x04\x68\xf0\x2d\xc8\xab\xb8\xf1\xc6\ \x1b\xbb\x72\xc2\x03\x2d\xd0\xe5\x91\xde\x7d\xf7\xdd\xa0\x45\xaa\ \x43\x17\xe4\x21\xec\xd8\xb1\x03\x2f\x8f\x41\x7a\xa1\x05\xad\x66\ \xaa\xb9\x17\x43\x12\x94\xd9\x9c\x3c\x79\xb2\xd3\xe9\x74\xf7\x34\ \x7b\xa2\x05\xa9\x8a\xa2\x28\xf3\xe7\xcf\x07\xcd\xc3\x18\x8a\x20\ \x23\xf0\xfe\xfb\xef\x7b\x94\x8a\x6e\x69\x41\x87\x7e\xf7\xdd\x77\ \xa0\x09\xc6\x90\x03\x49\x45\x7a\x7a\xba\xcd\x66\xa3\xd8\xd3\x5b\ \x5a\xa0\xcb\x0d\x21\xc1\xd0\x3c\x8c\xa1\x04\x1a\xcd\x37\xdf\x7c\ \xb3\x3b\xa9\xe8\x89\x16\xf4\x83\xdc\xdc\x5c\xd0\x04\x63\x08\x81\ \xa4\x22\x33\x33\xd3\x62\xb1\x90\xab\xd0\x37\x5a\xa8\x82\xb1\x60\ \xc1\x02\xd0\x04\x63\xa8\x80\xc6\xf1\xc3\x0f\x3f\xec\x41\x2a\x7a\ \xa1\x05\xfd\xec\xe8\xd1\xa3\xa2\x28\xd2\xde\x2d\xc1\x6e\x94\x86\ \x01\x81\x38\x91\x9d\x9d\x4d\x3b\x83\xf6\x30\xf4\x3d\xd1\x42\x65\ \xc6\x03\x0f\x3c\x00\x9a\x60\x0c\x7e\x90\x33\xb0\x6f\xdf\xbe\x9e\ \xa5\xa2\x77\x5a\x90\xf9\xa9\xac\xac\x8c\x89\x89\xd1\x04\x63\x50\ \x83\x9e\xea\xbb\xee\xba\xab\x57\x4e\xf4\x4e\x0b\xf5\x14\x2f\xbd\ \xf4\x12\x68\x82\x31\x68\x41\x15\x77\x91\x91\x91\x65\x65\x65\xb4\ \x7b\xfb\x40\x69\xa1\x28\x8a\x24\x49\x0e\x87\x63\xfa\xf4\xe9\xa0\ \x31\x63\x70\x82\x46\x6d\xf3\xe6\xcd\xde\x48\x85\x57\xb4\x50\x4f\ \x74\xe8\xd0\x21\xda\x74\x23\xd8\x6d\xd4\xd0\x37\xa8\x9e\xa6\x24\ \x49\x92\x24\x75\x17\x94\xf6\x99\x16\x2a\x33\x1e\x7d\xf4\x51\xd0\ \xd2\xe1\x83\x0a\x54\x68\x27\x8a\xe2\xe1\xc3\x87\xd1\x3b\xa9\xe8\ \x03\x2d\xc8\x20\x59\x2c\x96\x8e\x65\x31\xb4\x04\xd7\x20\x01\x3d\ \xc3\xeb\xd6\xad\xf3\x9e\x13\x7d\xa0\x05\xba\xb2\x5b\x47\x8e\x1c\ \xd1\xe9\x74\x94\x2c\x0b\x76\x93\x35\xf4\x02\x32\x1f\xb3\x67\xcf\ \xf6\xde\x7c\xf4\x99\x16\xe8\xaa\xdd\x7a\xe1\x85\x17\x40\x33\x25\ \x21\x0f\x5a\x83\x36\x32\x32\xf2\xf4\xe9\xd3\xe8\xa9\xd6\xc6\x67\ \xb4\x40\x97\x10\xdd\x72\xcb\x2d\xa0\x45\x25\x21\x0c\xc6\x18\x3d\ \xb7\x5b\xb7\x6e\xc5\xbe\x98\x8f\x7e\xd2\x82\x5e\xc5\xd6\xd7\xd7\ \x6b\x65\xc0\xa1\x0c\xe2\xc4\xca\x95\x2b\xb1\x4b\xf9\xae\x5f\x68\ \x81\x2e\xea\x1d\x38\x70\x80\x36\xb4\xd2\x9c\x8c\x50\x03\xa9\xf8\ \xcc\x99\x33\x1d\x0e\x47\x9f\x5c\x8a\x01\xd1\x02\x5d\x04\x7c\xeb\ \xad\xb7\x40\x73\x32\x42\x0c\xa4\xdf\xa3\x46\x8d\xaa\xa8\xa8\xc0\ \x3e\xba\x14\x03\xa5\x85\xca\x8c\x3f\xfe\xf1\x8f\xa0\x31\x23\x64\ \x40\x49\xee\xb0\xb0\xb0\xfd\xfb\xf7\x63\xdf\x5d\x0a\x1f\xd0\x82\ \x92\xe2\x88\xb8\x74\xe9\x52\xd0\x98\x11\x02\x50\xa7\x88\x7e\xf0\ \xc1\x07\xd8\x2f\x97\xc2\x07\xb4\x40\x57\x8e\xcb\x6c\x36\xd3\x0a\ \x6d\x5a\x60\x12\x5c\xd0\xfe\x5c\x9b\x36\x6d\x1a\x20\x27\x06\x4a\ \x0b\x74\x99\xae\x86\x86\x86\x89\x13\x27\x82\xa6\x19\xc1\x03\xf5\ \xfc\x9a\x35\x6b\x10\xd1\xe1\x70\x0c\x70\x58\x07\x4a\x0b\x74\x19\ \xb0\xf3\xe7\xcf\x5f\x75\xd5\x55\xa0\x69\x46\x30\x40\x3a\xb1\x62\ \xc5\x0a\x1a\x8e\x7e\x84\x1e\xbe\xa7\x05\xba\x98\x71\xe6\xcc\x19\ \x8d\x19\x81\x07\xe9\xc4\xbd\xf7\xde\x8b\x3e\xe2\x04\xfa\x8a\x16\ \xe8\x32\x66\x2a\x33\x34\x6b\x12\x18\x50\x3f\xdf\x77\xdf\x7d\xe8\ \x2a\xa5\xf3\xc9\x68\xfa\x8c\x16\xe8\xa6\x19\xb4\xf9\x99\xc6\x0c\ \x7f\x83\x7a\xf8\xd7\xbf\xfe\x35\xf9\xfe\xfd\x4b\x51\x78\x84\x2f\ \x69\x81\x6e\x7e\x46\x56\x56\x16\x68\xcc\xf0\x1b\xd4\x58\xf4\x91\ \x47\x1e\x41\x9f\xea\x04\xc1\xc7\xb4\x40\x17\x33\x9a\x9a\x9a\xe6\ \xcc\x99\x03\x1a\x33\xfc\x00\x75\x7b\xce\x0d\x1b\x36\xa0\xef\xfc\ \x09\x77\xf8\x9e\x16\xe8\x62\x46\x7b\x7b\xfb\x3d\xf7\xdc\x03\xae\ \x99\x4c\xc1\xee\xcc\x21\x02\x12\x09\x41\x10\xde\x7e\xfb\x6d\x44\ \xec\x6e\xca\xf9\x00\xe1\x17\x5a\xa0\x5b\x2a\x7e\xcd\x9a\x35\xd4\ \x1e\xed\x5d\xeb\xc0\x41\xd2\x1b\x1f\x1f\xbf\x7b\xf7\x6e\x1c\x70\ \xce\xaa\x07\xf8\x8b\x16\xe8\xca\x81\x22\xe2\x3b\xef\xbc\x13\x16\ \x16\x06\x9a\x41\x19\x00\x54\x67\x62\xda\xb4\x69\xa7\x4e\x9d\x42\ \x7f\x72\x02\xfd\x4a\x0b\x74\x7b\x6f\x92\x9f\x9f\x9f\x9e\x9e\x0e\ \x9a\x41\xe9\x17\x54\xa1\xfd\xe5\x2f\x7f\xd9\xda\xda\x8a\x7e\xe6\ \x04\xfa\x9b\x16\x04\x6a\x43\x43\x43\xc3\x1d\x77\xdc\x41\xcd\xd3\ \xf2\x5d\xde\x83\x24\xd6\x68\x34\xfe\xfd\xef\x7f\xa7\xfe\xf4\x61\ \x20\xda\x1d\x02\x41\x0b\x74\x7b\xc3\xfb\xf2\xcb\x2f\xd3\x6a\xf3\ \xa2\x28\x6a\xb2\xd1\x33\xd4\x88\x23\x2b\x2b\xab\xa0\xa0\x00\xfd\ \x13\x74\x78\x44\x80\x68\x81\x6e\xae\xc6\xe1\xc3\x87\xaf\xbd\xf6\ \x5a\x6a\xb9\x26\x1b\xdd\x41\xf5\xc3\x1e\x7a\xe8\xa1\xb6\xb6\x36\ \x1c\x40\xf1\x44\x3f\x10\x38\x5a\x10\xc8\xa0\xd8\xed\xf6\x75\xeb\ \xd6\xd1\x0b\x1e\x4d\x36\x3a\x41\x7d\x54\x32\x32\x32\x28\xe2\xc0\ \xc0\x72\x02\x03\x4f\x0b\x74\x33\x8d\x05\x05\x05\xd7\x5f\x7f\x3d\ \x75\x81\x46\x0e\x00\x50\xe7\x72\x0a\x82\xb0\x7a\xf5\xea\x4b\x97\ \x2e\x61\x00\x0d\x87\x3b\x82\x40\x0b\x44\x54\x14\x85\x64\x43\x51\ \x94\x2d\x5b\xb6\x8c\x1c\x39\x92\xfa\xe5\x8a\xb5\x29\xee\x6b\xef\ \x5f\x7f\xfd\xf5\xf9\xf9\xf9\xd4\x51\x01\x16\x09\x15\xc1\xa1\x05\ \x41\xcd\xe4\xd7\xd4\xd4\x3c\xfc\xf0\xc3\x06\x83\xa1\x53\x07\x5d\ \x09\x70\x6f\xef\xb8\x71\xe3\xde\x7d\xf7\x5d\xea\x9c\xa0\x88\x84\ \x8a\x60\xd2\x82\xa0\x3e\x10\xc7\x8e\x1d\x5b\xb6\x6c\x99\x7b\x67\ \x0d\x6d\xb3\xe2\x4e\x88\xb8\xb8\xb8\x8d\x1b\x37\x36\x37\x37\xa3\ \x9b\x6f\x1e\x44\x04\x9f\x16\xe8\x96\xf5\x42\xc4\xdc\xdc\x5c\x5a\ \xc4\x8d\x30\x24\x7d\x0e\xf7\xf5\x20\x62\x63\x63\x1f\x7b\xec\x31\ \x2a\xde\xc7\xe0\x59\x8d\x4e\x08\x09\x5a\x10\x64\x59\x56\x3b\x65\ \xef\xde\xbd\x4b\x97\x2e\x55\xb3\x7b\x82\x20\x0c\x81\x57\x2a\x24\ \x0f\x2a\x21\x12\x12\x12\x3a\x11\x22\x88\x56\xa3\x13\x42\x88\x16\ \x04\xf7\xde\xf9\xcf\x7f\xfe\xb3\x62\xc5\x8a\x98\x98\x18\xea\x47\ \x7a\xc8\x06\xa3\x78\x70\xce\xdd\xdf\x07\x65\x64\x64\x6c\xde\xbc\ \x99\x36\x98\xc4\x10\x23\x04\x21\xe4\x68\x41\x90\x24\x49\xb5\xaf\ \x15\x15\x15\x7f\xf9\xcb\x5f\xa8\xb2\x9c\x40\xcf\x5c\xe8\xf3\x83\ \xd8\xa0\xde\xa7\xc1\x60\x58\xb4\x68\xd1\xf6\xed\xdb\x6d\x36\x9b\ \xda\xcc\x50\x23\x04\x81\x61\x08\xef\x2d\x49\x5d\x46\xaa\xeb\x70\ \x38\xbe\xf9\xe6\x9b\x8f\x3f\xfe\x78\xe7\xce\x9d\xf5\xf5\xf5\x74\ \x00\x7d\x85\x88\x81\xde\xe6\xb9\x7b\xd0\xbc\x2e\xda\x88\x5a\xed\ \xdb\xcc\xcc\xcc\x3b\xee\xb8\xe3\x9e\x7b\xee\xa1\x45\x63\x00\x40\ \x96\xe5\x50\x5e\xb9\x30\xa4\x69\x41\x70\x27\x07\x00\xd4\xd7\xd7\ \xef\xdc\xb9\xf3\xb3\xcf\x3e\xcb\xcd\xcd\x6d\x6e\x6e\x56\x0f\x13\ \x45\xd1\x9d\xef\x81\xbc\x43\xda\x8f\x87\x76\x2a\x97\x65\x59\xfd\ \x3c\x2d\x2d\xed\xa6\x9b\x6e\x5a\xb2\x64\x49\x4e\x4e\x0e\xa5\x74\ \xa9\x2d\xa1\x4c\x08\xc2\x20\xa0\x05\x41\x95\x04\x95\x1f\xb5\xb5\ \xb5\x79\x79\x79\xbb\x76\xed\xca\xcb\xcb\x2b\x29\x29\x71\x3f\x98\ \x4c\x8c\x9f\x58\x42\x23\xca\x5c\xa0\x95\x1d\xd4\x6f\x0d\x06\x43\ \x56\x56\xd6\x82\x05\x0b\x6e\xba\xe9\xa6\xe9\xd3\xa7\x53\xa1\x09\ \x00\xc8\xb2\x1c\xb4\x7d\x87\xfb\x8e\x41\x43\x0b\x15\xf4\x44\xaa\ \x6f\x17\x01\xc0\x6a\xb5\x16\x15\x15\xed\xdf\xbf\xff\xc0\x81\x03\ \x27\x4e\x9c\x28\x2b\x2b\xeb\xd4\x28\xf7\x0c\x01\x7d\xe5\x25\x57\ \xdc\x19\xe0\x7e\xf5\x4e\x87\x85\x85\x85\x65\x64\x64\x64\x67\x67\ \xcf\x9d\x3b\x77\xc6\x8c\x19\x54\xf8\x4e\xa0\x83\x43\x5f\x1e\x3a\ \x61\xf0\xd1\x42\x45\x57\xfd\x00\x00\x8b\xc5\x72\xee\xdc\xb9\x13\ \x27\x4e\x1c\x3f\x7e\xbc\xa8\xa8\xa8\xa4\xa4\xa4\xba\xba\xda\xe1\ \x70\xf8\xf6\xd2\xd1\xd1\xd1\x29\x29\x29\x69\x69\x69\xd3\xa6\x4d\ \xcb\xca\xca\x9a\x3c\x79\x72\x72\x72\xb2\xfb\x01\x92\x24\xa9\x4e\ \x86\x6f\x2f\x1d\x18\x0c\x62\x5a\xa8\xa0\x47\x5f\x51\x94\xae\x89\ \x73\xab\xd5\x5a\x57\x57\x57\x5a\x5a\x5a\x55\x55\x55\x56\x56\x56\ \x5e\x5e\x5e\x5b\x5b\xdb\xd8\xd8\xd8\xd2\xd2\xd2\xda\xda\x6a\x36\ \x9b\x69\xad\x31\x77\x01\xa0\xe5\x85\x04\x41\xd0\xeb\xf5\x51\x51\ \x51\x51\x51\x51\x71\x71\x71\xf1\xf1\xf1\xa3\x46\x8d\xba\xea\xaa\ \xab\x92\x93\x93\xc7\x8e\x1d\x9b\x9c\x9c\x1c\x1f\x1f\xdf\xe9\x36\ \xc8\xc7\x74\x97\xb1\xc1\x8b\xa1\x40\x0b\x77\xa8\x14\x01\xb7\x32\ \x96\xae\x70\x38\x1c\x36\x9b\xcd\xe1\x70\x98\xcd\x66\x9b\xcd\x46\ \x11\x23\xb9\x23\xa2\x28\x1a\x8d\xc6\xb0\xb0\xb0\xb0\xb0\x30\xa3\ \xd1\x68\x30\x18\xba\x7b\x47\x43\x3c\x60\x6e\xf0\x63\xc3\x02\x8b\ \xff\x0f\x81\x99\x66\x42\x17\xac\xab\x60\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x06\x3d\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x64\x00\x00\x00\x64\x08\x06\x00\x00\x00\x70\xe2\x95\x54\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc1\x00\x00\x0e\xc1\x01\xb8\x91\ \x6b\xed\x00\x00\x05\xd2\x49\x44\x41\x54\x78\x5e\xed\x9c\xd7\xaa\ \x2c\x45\x14\x86\xb7\x39\x1c\x0c\x18\x41\x0c\x88\x01\x44\x14\x11\ \x11\xc1\x7c\x21\xde\x0b\x5e\x8b\x5e\xf9\x04\x3e\x84\x4f\xe1\x03\ \x28\x82\x07\x3c\xc7\x9c\x15\x45\xcc\x01\x73\x02\x73\xce\xf9\xf8\ \x7f\x38\x05\x65\x33\xf6\xee\xde\xd3\x55\xf3\x77\xcd\xfa\xe1\x63\ \xce\xde\x67\x76\xf7\xaa\xfa\x6b\xad\xaa\xee\xa9\x9e\xad\x01\xda\ \x37\x13\xfe\x10\x37\x88\xe6\xb5\xac\xf1\xae\xfc\x2d\x6e\x16\x4d\ \x6b\x59\xc3\x9d\x69\xde\x94\x65\x8d\x76\xa7\x69\x53\xba\x8d\x75\ \x53\x37\xbe\x44\xb3\xa6\x74\x1b\xea\xa6\x6e\x7c\x39\x4d\x9a\xd2\ \x6d\xa4\x9b\xba\xf1\xfd\xb5\xe4\xe7\x9b\x44\x33\xca\x1b\x07\x6e\ \xea\xc6\x77\xa3\xe8\x9a\xd2\x54\xa6\xe4\x0d\x03\x37\x2d\x8b\xaf\ \x69\x53\xf2\x46\x81\x9b\xfe\x2f\xbe\x66\x4d\xc9\x1b\x04\x6e\xea\ \x8b\xaf\x49\x53\xf2\xc6\x80\x9b\xb6\x8b\xaf\x39\x53\xf2\x86\x80\ \x9b\x86\xc4\xd7\x94\x29\x79\x23\xc0\x4d\x43\xe3\x6b\xc6\x94\xbc\ \x01\xe0\xa6\x31\xf1\x35\x61\x4a\x1e\x3c\xb8\x69\x6c\x7c\xb3\x37\ \x25\x0f\x1c\xdc\xb4\x93\xf8\x66\x6d\x4a\x1e\x34\xb8\x69\xa7\xf1\ \xcd\xd6\x94\x3c\x60\x70\xd3\x2a\xf1\xcd\xd2\x94\x3c\x58\x70\xd3\ \xaa\xf1\xcd\xce\x94\x3c\x50\x70\xd3\x14\xf1\xcd\xca\x94\x3c\x48\ \x70\xd3\x54\xf1\xcd\xc6\x94\x3c\x40\x70\xd3\x94\xf1\xcd\xc2\x94\ \x3c\x38\x70\xd3\xd4\xf1\xd9\x9b\x92\x07\x06\x6e\x2a\x11\x9f\xb5\ \x29\x79\x50\xe0\xa6\x52\xf1\xd9\x9a\x92\x07\x04\x6e\x2a\x19\x9f\ \xa5\x29\x79\x30\xe0\xa6\xd2\xf1\xd9\x99\x92\x07\x02\x6e\xea\xc6\ \x57\x0b\x4c\x9a\x7c\x2f\xf1\x7e\x8b\xd7\x3e\x71\xf2\x5c\x43\xfe\ \xa6\xa6\xba\xf1\xd5\xd4\x9f\xe2\xa0\x7f\xff\x39\x8d\xc2\x90\xd5\ \x35\x69\x7f\xec\xbf\x78\x0d\x99\xa8\x85\x0c\xa9\xad\xa2\xfd\x11\ \x19\x62\xa6\x30\xc4\x4c\x8e\x25\x8b\xe3\x1f\x2a\x76\x2d\x5e\xfb\ \xc4\x63\x6c\x3f\x8a\x5f\x04\xd7\x06\x35\x54\xb4\x3f\x1c\x0d\x61\ \x19\x79\xa2\x38\x4d\x9c\xc0\x2f\x7a\xf4\xbd\x78\x47\x7c\x22\x7e\ \x17\xdd\x58\x4b\x68\xe3\x0c\x21\x33\xce\x13\x97\x2d\x5e\xfb\xf4\ \x91\xb8\x5f\x3c\x2f\x7e\x12\x5c\x17\x94\xd6\xc6\x19\x72\xb4\xb8\ \x52\x5c\x2f\xae\xe0\x17\x3d\x7a\x43\xdc\x26\xee\x15\xdf\x09\xb2\ \xa4\xb4\x8a\xf6\x87\xe3\xa4\x4e\x4c\x87\x8b\xe3\xc4\x29\xdb\x40\ \x69\x23\xa3\x0e\x10\xa5\x07\x4a\x15\xc5\x2a\xcb\x4c\x61\x88\x99\ \xc2\x10\x33\x85\x21\x66\x0a\x43\xcc\x14\x86\x98\x29\x0c\x31\x53\ \x18\x62\xa6\x30\xc4\x4c\x61\x88\x99\xc2\x10\x33\x85\x21\x66\x0a\ \x43\xcc\x14\x86\x98\x29\x0c\x31\x53\x18\x62\xa6\x30\xc4\x4c\x61\ \x88\x99\xc2\x10\x33\x85\x21\x66\x0a\x43\xcc\x14\x86\x98\x29\x0c\ \x31\x53\x89\x8d\x72\xec\x91\x3a\x4c\x9c\x2a\xd8\x79\x78\xac\x18\ \x63\x3c\xfb\xac\xce\x11\x17\x2f\x5e\xfb\xf4\xb1\x78\x54\xbc\x22\ \xc6\xec\x5c\x64\x1f\x30\x8f\xa4\xb1\xe3\xf1\x7d\xf1\x8d\x60\x9f\ \xf0\x10\xcd\x6e\xe7\x22\x86\xd0\xa9\x67\x89\xcb\xc5\xb9\x82\x0d\ \x6d\x07\x8b\x21\xe2\x7d\x98\x78\x92\x60\xb3\x5c\x9f\x7e\x10\x6c\ \x27\xfd\x42\xd0\xa1\x74\xf2\x76\xe2\x7d\xec\x72\xfc\x40\x3c\x28\ \x5e\x17\xe9\xef\x87\x68\x76\x86\xf0\xff\x74\xea\xc9\xe2\x12\x71\ \x95\xb8\x48\xd0\xb9\xe9\x79\xbc\xbe\x63\xa4\xbf\x67\xe7\xfb\x21\ \xfc\xa2\x47\x64\x04\x3b\xdf\x7f\x13\x7d\xbb\xdf\x53\x1b\x78\xff\ \xb7\xe2\x2d\xc1\x9e\xe0\xa7\xc4\x7b\x02\x83\x86\xee\x9e\x9f\x9d\ \x21\x49\x47\x88\x33\x05\x86\x5c\x2b\xce\x17\x98\x42\x06\xd5\x9c\ \xbb\x52\xfc\xbc\x7e\x2d\x30\xe3\x09\x71\xbb\xc0\x8c\x31\xe5\x0a\ \x15\x35\xa4\x64\xc7\x50\xd3\xdf\x16\x0f\x88\xbb\xc4\x6b\x82\x12\ \x93\x3f\xf3\x5d\x5a\xb9\x19\xbf\x8a\x14\xcf\x6e\xc1\xbc\x83\x41\ \x63\xcc\x28\xae\x92\x86\x50\x02\x7e\x16\xd4\xf8\x67\xc4\xdd\x82\ \x91\xf9\xa9\xa8\xf5\x70\x0d\xe2\x5c\x94\xb4\xe7\x04\x66\x3c\x2e\ \xde\x14\x94\xba\x1a\x8f\x2f\x8c\x52\xe9\xd2\x41\x36\xf0\x50\xcd\ \xbb\x82\xd5\x10\x93\xe8\xcb\xe2\x33\xc1\xc8\xa4\xb3\x4a\x65\x0a\ \xc7\xe5\x1c\xcc\x19\x18\xc0\xf9\x1f\x13\x64\xea\x57\xa2\xe6\xa0\ \x18\xac\x1a\xb5\x1c\x53\x98\x34\xe9\x14\x46\xe7\x7d\x82\xd1\x4a\ \x09\x29\x59\xb6\x38\x36\x59\xc0\x13\x56\x94\x28\x06\x03\x65\xea\ \x73\x61\x55\xa6\x72\xd5\x9a\x5c\x53\xa6\x24\x53\x1e\x16\x2f\x89\ \x2f\x05\x9d\x33\x95\x31\x1c\x07\x18\xfd\x9c\x8f\x09\xfc\x49\xb1\ \x57\x90\x99\x98\x41\xf9\xb2\x55\x2d\x43\x92\x98\x53\xb8\x10\xc3\ \x94\x3b\x04\xa6\x50\x3e\xd2\xf3\x81\xab\x18\x93\xfe\x16\x33\x38\ \x0f\xe6\x33\x67\xf0\x74\x15\x4f\x5a\x51\xba\xec\xe6\x8c\xae\x6a\ \x1b\x42\x87\xb0\xcc\xa4\xb3\x1e\x11\x0f\x89\x94\x29\x53\xd4\x74\ \x8e\xc1\xea\x8e\x8b\x3d\x4c\x67\xce\x78\x41\xb0\x9a\xaa\xf5\x50\ \xe8\x4a\xaa\x6d\x08\x4a\xe5\x8b\x25\x28\xf3\x09\x17\x68\x4c\xb4\ \xe9\xe2\x6c\x27\x99\xc2\xfb\x39\x6e\x5a\xd5\x31\x5f\xdc\x23\x5e\ \x14\x5c\x85\x4f\x61\x76\x15\xad\xc3\x10\x44\xe7\x31\x92\x99\x70\ \xa9\xf1\x98\xc2\xd2\x38\xad\xbc\xc6\x0a\x43\x98\xc0\x29\x4d\x7b\ \x04\x99\x87\xc9\x64\x9e\x7d\x99\xca\xb5\x2e\x43\x10\x1d\x9f\x32\ \x05\x53\xe8\x44\xca\x0b\x23\x9a\x89\x77\x48\x96\xf0\x9e\x74\x1c\ \xcc\xe0\x3a\x87\x79\xe3\x55\x91\x8e\x33\x2b\x95\xbc\x75\x32\x54\ \xdc\xb7\x3a\x46\x9c\x21\x2e\x15\xd7\x08\xee\xf2\x72\x9b\x25\xbf\ \x21\x99\x9f\x37\xc5\x44\xa6\x91\x19\xac\xa6\x30\x94\x6b\x0d\xee\ \xe0\x8e\x31\x75\xac\x8a\xf6\xc7\x3a\x33\x24\x89\xc9\x96\xd2\x42\ \x89\xa1\xdc\x30\x11\x33\xda\x99\xfc\xfb\xca\x17\x1d\x43\xd9\xe3\ \xa2\xf3\x69\xc1\x52\xfa\x59\xc1\xb7\x3a\x94\xbe\xc6\x59\xab\x68\ \x58\x4e\x29\x31\xd2\x0e\x14\x17\x88\x5b\x04\xc6\x30\xca\x31\x05\ \xf2\x18\xf8\x99\xf9\x06\xe3\x6e\x15\xdc\xc0\xe4\x6b\x38\x6a\x0c\ \xb0\x3c\x0e\x98\x54\x0e\x19\x92\x44\xe3\x98\x80\x3f\x14\x8c\x78\ \x2e\xe6\x30\x85\x2f\x97\xa1\x34\xa5\x0e\xc0\x0c\xca\x14\x2b\x28\ \x32\x8a\xe5\x33\xcb\xe8\x31\xb7\xd0\x6d\xe5\x64\x48\x12\x77\x84\ \xd3\x2d\xf2\x74\xef\x2b\xcd\x09\x69\xc9\x9c\x5f\xf1\x33\x81\x73\ \x71\x69\x7b\x3b\x64\x8c\x1c\x26\xf5\x65\x4a\x9f\x1a\xf2\xa9\x23\ \xdf\x7b\x72\xb5\x38\x5b\xf0\xd1\x30\xd7\x19\x64\x05\x66\xb0\x2a\ \x4b\x73\x46\x2d\x15\xed\x0f\x57\x43\x10\x1f\x64\x1d\x25\x4e\x17\ \xd7\x89\x0b\x05\x1f\x0d\x93\x1d\x77\x0a\x4a\x16\xf7\xa6\x6a\x7c\ \xe1\x4c\xae\x8d\x35\x04\x51\x52\xc9\x0a\x3e\x6d\xe4\xcb\x66\xc8\ \x1c\x6e\xdd\x53\xc6\xd2\x87\x4b\xdd\xf8\x4a\x6b\xa3\x0d\x41\x98\ \x72\xbc\x38\x52\x90\x35\x2c\x75\x53\x66\xd4\x36\x03\x6d\xbc\x21\ \x88\xcc\xc0\x0c\xce\xcd\xc4\xbe\xce\x1b\x85\x61\x88\x99\x8a\xf6\ \x87\xe3\xb2\x77\xa3\x15\x86\x98\x29\x0c\x31\x53\x18\x62\xa6\x9d\ \x4c\xea\xa1\xff\x2a\x26\xf5\x96\x15\x86\x98\x29\x0c\x09\x85\x42\ \xa1\x50\xa8\x09\x6d\x6d\xfd\x03\x55\x05\x7f\xb7\x68\xfd\x50\x93\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x10\x3b\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x64\x00\x00\x00\x64\x08\x06\x00\x00\x00\x70\xe2\x95\x54\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ \xa8\x64\x00\x00\x0f\xd0\x49\x44\x41\x54\x78\x5e\xed\xdd\x05\xac\ \x2c\x49\x19\x05\xe0\xc5\xdd\xdd\xdd\xdd\x25\xb8\x6b\xf0\xe0\x41\ \x82\xbb\x26\x10\x02\xc1\x02\x81\xc5\xdd\x5d\x17\x97\x45\x82\x2c\ \xb0\xb8\x5b\x70\x77\x77\xf7\xf3\x91\x5b\x64\x18\xba\x7b\xaa\x7b\ \xba\x7b\xe6\xb2\x73\x92\x93\xf7\xee\xbd\x33\xdd\x55\xf5\xd7\xaf\ \x55\xd5\x7d\xc0\x0e\x3b\xec\xb0\xc3\x0e\x3b\xec\xb0\xc3\x0e\x13\ \xe0\x70\x7b\xff\xee\x07\x9c\x24\x3c\xdd\x1e\x4f\x14\x1e\x73\x81\ \x47\x0a\xff\x1a\xfe\x2e\xfc\xed\xde\xbf\x3f\x0d\xbf\xb9\xc7\x9f\ \x84\xfb\x02\xdb\x28\x90\xc3\x87\xc7\x0d\xcf\x1f\x5e\x28\xbc\x60\ \x78\x86\xf0\xd8\xe1\x91\x43\x83\x7f\x84\xd0\xe7\x50\x1f\xf0\x9f\ \x7b\xfc\xc7\x1e\xff\x1e\x12\xd2\x9f\xc3\x5f\x87\x5f\x0d\x3f\x1e\ \x7e\x2c\xfc\x54\xf8\x9b\xd0\xe7\xb7\x0a\xdb\x22\x90\x23\x86\xa7\ \x0a\x2f\x17\x5e\x36\x3c\x7b\x48\x00\x45\x03\x8e\x1a\x96\xc1\xef\ \x8b\x22\xa4\x3f\x86\xbf\x0f\x69\xcf\xaf\xc2\xcf\x87\xef\x0a\x0f\ \x09\x7f\x18\x12\xe0\xc6\xb1\x69\x81\x9c\x38\xbc\x78\x78\xf9\x90\ \x46\x9c\x2c\x3c\x41\x48\x08\x04\x30\x15\x08\x88\x69\xfb\x59\x48\ \x18\x9f\x08\xdf\x11\x7e\x38\xfc\x79\xb8\x31\x6c\x4a\x20\x27\x0d\ \x69\xc2\x55\xc3\xf3\x86\x27\x0f\x8f\x17\x4e\x29\x84\x36\xd0\x8c\ \x5f\x86\xdf\x0b\x3f\x17\x12\xcc\x7b\xc2\xef\x86\xb3\x63\x6e\x81\ \x9c\x22\xbc\x72\x78\x99\xf0\x3c\x21\x07\x7d\xac\x70\x5b\x40\x6b\ \xbe\x1d\x7e\x3a\x7c\x7b\xf8\xce\xf0\x47\xe1\x6c\x98\x4b\x20\x7c\ \xc0\x25\xc3\x6b\x85\x84\x71\xfa\xf0\x68\xe1\xb6\x82\xaf\xf9\x5a\ \xf8\xde\xf0\xe0\xf0\xd0\x90\x0f\x9a\x1c\xa2\x95\xa9\x71\x8e\xf0\ \xe6\xe1\x2d\xc2\xab\x84\xa7\x0c\x45\x4a\xdb\x0c\xd1\x9c\x30\xfb\ \xac\xa1\xc9\xc3\x9c\xfe\x22\x9c\xdc\xbf\x4c\xa9\x21\x06\x9d\x9f\ \xb8\x61\x78\xb5\x50\xee\x30\xc6\x04\x28\x0e\xf9\x4f\xa1\x90\xf6\ \x2f\x7b\xbf\xe3\x7f\x0c\x24\x6d\xa4\x7d\xc7\xd8\xfb\xdd\xba\xe0\ \x63\x38\xfe\x37\x87\x07\x85\xb4\xe5\x6f\xe1\x24\x98\x42\x20\xae\ \x69\x46\x5d\x3d\xbc\x75\x78\xd1\xd0\x20\xf5\x45\x09\x57\x45\x42\ \x3f\x08\x7f\x1c\x72\xbe\x42\x56\x33\x95\x59\x61\x46\x08\xc5\xa0\ \x11\xb6\xfb\xa0\x28\x4d\xb4\x26\x9f\xd1\x16\xb3\x5d\xe0\x70\xc2\ \x50\xfb\x86\xf4\xdb\xfd\x3e\x10\x3e\x3f\x7c\x5b\xa8\x1d\xa3\x63\ \x6c\x81\x98\x91\x1c\x37\xad\xb8\x4b\xc8\x3c\xf5\xd1\x0a\x42\x30\ \xe3\x39\xd2\x6f\x85\x22\x1d\xb6\xfc\xcb\xe1\xd7\x43\x33\x55\x06\ \x4e\x08\xab\x40\x30\xb4\x92\x20\x24\x96\x67\x09\xcf\x18\xca\x77\ \x4e\x1b\x8a\xf4\xe4\x3f\x7d\xc6\x80\x66\x7c\x23\x7c\x6a\xf8\xca\ \x50\x05\x60\xd4\xe4\x72\x4c\x81\xe8\x9c\x8e\xde\x2c\xbc\x4f\x78\ \xf4\xb0\xf6\xfa\x34\x41\xe6\x4c\x08\x32\x6a\xd9\xf4\xfb\x43\xd1\ \xce\x98\xce\x54\x9b\x2e\x10\x5e\x22\x54\x05\x20\x28\x6d\xee\x93\ \xf7\x10\x80\xcc\xff\xd1\xe1\xcb\x42\xe1\xf2\x56\x24\x95\x8b\xe0\ \x2f\xce\x16\x1e\x18\x9a\xbd\x1a\x5d\x43\x1d\xe1\x2c\x3f\x19\x3e\ \x2b\xbc\x66\x78\x9c\x70\x0e\x30\x65\xd7\x0d\x5f\x10\x12\x3c\x73\ \xa8\x3d\x4d\xed\x5c\xa6\x09\xa4\x9f\x0f\x0f\x69\x9d\xc9\xb8\x35\ \xd0\x18\xd1\x08\x35\xa6\xd2\x4d\x1d\x68\x22\x9b\xcc\x14\x3d\x33\ \xe4\xfc\x87\xf8\x99\x31\x20\x00\xb8\x52\xc8\x37\xd0\x4e\xed\x32\ \xe0\x4d\x6d\x5e\xa6\xfe\x3e\x2e\xa4\x69\x73\x44\xac\x2b\xa1\x11\ \x1a\xf3\xc8\xb0\x56\x18\x3e\xc7\x29\x73\x8c\x37\x08\x45\x43\xdb\ \x00\x66\x8b\xef\xd3\x2e\x5a\xdb\xa7\x3f\x0f\x0b\x4f\x1d\x6e\xa2\ \xd2\xf0\x1f\xf0\x0f\x9c\xf6\x83\xc2\x5a\x33\xc5\x61\x2b\x87\xdf\ \x2f\xe4\xfc\xb7\x11\xa7\x09\x1f\x12\xca\xd8\x55\x8b\x9b\xfa\xb1\ \x4c\x21\x38\xbf\x29\x50\x18\x3b\x50\xaa\xc6\xf1\xc3\x7b\x84\xaa\ \xa7\x4d\x8d\x5c\xa6\x99\xf4\xc1\x50\x38\x7c\x94\x70\x9b\xc1\x8c\ \x5d\x3f\x14\x5c\xd4\x6a\x8a\xdc\xe8\xb6\xe1\x5c\x3e\xf0\xbf\xc0\ \x6f\xdc\x28\x34\x8b\x6a\xed\xed\x8b\x42\x65\xf5\x8d\xcd\xa0\x9e\ \x60\x7e\x54\xa0\x25\x83\x4d\xfd\x59\xa6\x71\x10\x12\x5f\x23\x9c\ \xdd\x9f\x70\xc2\xd6\x12\x6a\xa2\x12\x0d\xe5\xf0\xcf\x14\x6e\xd4\ \xc6\x0e\x80\x81\x15\x3d\x3e\x2d\xac\xe9\x2b\x6d\x52\xfb\xba\x58\ \x38\x1b\x44\x54\x22\x23\xf9\x41\x53\xa3\x16\xa9\x13\x1c\x9e\xaa\ \xee\x56\x44\x21\x03\xa0\xdd\x26\x93\xbc\xa3\x46\x28\x7f\x08\x1f\ \x1f\x0a\x76\x26\x87\xd0\xf4\xee\xe1\xf7\xc3\xa6\xc6\x2c\xf3\xc9\ \xe1\x7e\x16\x46\x81\xf6\x9f\x39\x7c\x4a\xd8\xd4\xcf\x65\x4a\x16\ \x6f\x1f\x4e\x1e\xca\x5f\x31\xb4\x80\xb3\xca\xd1\x99\x49\x7c\x86\ \x4e\xec\x77\x61\x14\xf0\x9b\x2a\xd7\x4a\x26\x4d\x7d\x5e\xa4\xf1\ \x51\x8c\xb4\xe4\x30\x19\x2c\xaf\x52\x45\x45\xb5\xa6\x46\x14\x0a\ \x15\x3f\x12\x9e\x33\xfc\x7f\x11\x46\x81\x8a\x84\x92\xcb\x47\xc3\ \x55\xc1\x8c\x5c\x46\x26\x6f\x99\x7a\x12\xa8\x51\x7d\x26\xec\x6a\ \x88\x99\xf1\x9d\x50\x68\xdb\xc7\x81\x13\x9c\x8a\x2c\x8d\x9a\x13\ \x1c\xb6\x02\x64\x9f\xb6\x2a\xf1\x5f\x3b\x54\x58\xec\xf2\x29\xc6\ \x89\xe0\x94\x67\xaa\x51\x3b\x83\x25\x80\xb7\x0a\x2f\x15\x6a\x50\ \x13\x34\x42\x81\xf0\x49\xe1\x4b\x43\xc2\xa9\x81\x36\x58\x57\xbf\ \x73\xa8\xe8\xc7\xfe\x2a\xb5\x4f\x0d\x21\xad\x3c\x4a\xb1\x51\x15\ \x59\x99\x5f\x1f\x56\x81\x10\xb4\xd1\x38\x5c\x24\x64\xca\x9a\x42\ \x79\xbf\x93\x93\xb0\x28\xb6\x1d\xc9\xd7\x46\xc3\x4d\x42\x05\xb8\ \xc5\x19\xb0\x4c\xd1\xc5\x5b\xc3\x3e\x19\x38\x61\x30\x01\xcf\x0d\ \x95\x53\x94\xd6\xfd\x9f\x80\xa6\x84\xf5\x7c\x3e\x4e\x41\x91\x20\ \x44\x8d\xe7\x0b\x6b\x8b\x84\x06\x5b\xa9\xc4\x12\xaf\x2c\xbd\x69\ \x3c\x0a\x99\xef\xeb\x84\xa3\x81\x4a\x1b\xa4\xb2\xb1\xac\x89\x66\ \x8d\xf5\x8a\xeb\x85\xb5\x28\xc2\x78\x4e\x48\x18\xae\x43\xcd\xd9\ \x5e\xf7\xe3\x83\xa6\xc0\xb9\x43\x15\x5e\x33\xb7\x98\x5f\xf7\x7f\ \x46\x48\x28\xb5\x56\x03\x4c\x54\x11\x67\x97\xe9\x72\x9f\x27\x86\ \x16\xcc\x46\x81\xcc\x53\x09\xa1\xe9\x66\x85\x66\xda\xf3\xc2\xda\ \x42\xe1\xa2\x30\x8a\xa9\x28\x34\x48\x3a\xa1\x1c\xcf\xc6\x8f\x05\ \xb3\x5a\x94\x44\x18\xcc\xc7\xb2\x2f\xa4\x9d\x12\x58\xda\x59\x2b\ \x14\x3b\x66\x5e\x11\x2a\x9b\x2c\x5e\x6b\x99\x96\x7d\xaf\x10\xae\ \x0d\x2a\xfc\x98\xb0\xac\x8c\x35\xd1\xec\xb0\x9f\xc9\x66\xb7\x1a\ \xe8\x2c\xbb\x4d\x0b\x96\x85\x51\x68\xb0\x0c\x9a\x59\x2b\x11\x5d\ \xb7\xdc\xe2\x9e\xca\x36\x26\x0d\x13\xb3\x2c\x8c\x42\x42\x79\x7a\ \xd8\x47\x53\x6c\xdc\xb0\xaa\xd9\xa5\x25\x96\xa0\x1f\x1c\xd6\x5e\ \xb3\x15\x2a\x9f\xab\xf2\x0e\xab\x67\x2f\x0c\x6b\xb7\xf5\x08\x03\ \xad\x21\x14\x33\xd5\x46\x83\xa6\x8a\xcc\xbe\xcb\x94\x87\x76\xa6\ \xe4\x0f\xae\x63\xd0\xda\x84\x51\x68\x92\x58\x4e\xa8\x0d\x57\x25\ \x7f\xaf\x0a\xbb\xb4\xc4\xf8\xbd\x3e\x5c\xe9\x5f\x57\x85\x7b\x12\ \x1b\x6b\xd0\x6d\x83\xe1\x66\xd6\xbd\x5f\x17\xd6\x2e\xb5\xda\x78\ \xa0\x13\x65\x60\xda\x40\x2b\xc4\xfd\x2a\xa8\x77\x0d\x6d\xc7\xe9\ \x2b\x14\xdf\xb7\x96\xee\xfb\xae\xe3\x9a\x5d\xda\xa6\x3d\x84\xa6\ \x7d\xb5\x1b\xf8\x68\x1c\x81\x88\x0c\xdb\xfa\xa3\xdd\x96\x8a\x6d\ \x9b\x1d\x0c\x0d\x67\xc7\xf9\x07\x37\x6a\xa2\x19\xfc\x86\xd0\x20\ \xf7\x81\x28\xe7\xd9\x21\x2d\x59\x35\x63\xd1\x67\x9e\x10\x1a\xdc\ \xda\x48\x88\x30\x68\x86\xc2\x60\xed\x3d\x68\x07\x3f\xc2\xbc\xf5\ \x81\xa5\x08\xc5\x56\xeb\x3d\x4d\xd7\x46\xd7\x96\x58\x0f\x36\xbf\ \x62\xe8\xf7\x85\x5d\xb6\x91\x76\x3c\x20\x1c\x02\x51\x14\x33\xb2\ \x18\xed\x74\xd1\x67\xd4\xc6\x6a\x84\xe2\xef\x06\xd5\xe0\x76\xb5\ \xbf\xd0\xb5\xb5\x83\xff\x20\xc4\x21\x78\x44\x28\x9f\x69\xba\x3e\ \x6a\x87\xb4\x60\xf0\x7a\x89\x2d\x9f\xb6\xec\x37\x5d\x1c\x75\xc2\ \x82\x93\x64\x6e\x28\x0c\x1a\xc7\xdd\x14\xf5\x34\xd1\x67\x14\xf8\ \xba\x6a\x64\x7e\xef\xba\xb5\x25\x73\xd7\x74\x7f\xc2\x5b\x27\xaa\ \x13\xd4\x48\x00\xbb\xfa\x21\x5a\x1d\x5c\x9a\xbf\x67\x48\x03\x9a\ \x2e\x8c\x3a\xcb\x76\xda\x5a\xb3\x0e\x0c\x02\x4d\x61\xfe\x6a\x85\ \x22\xae\xb7\xdb\x63\x59\xfd\xfd\x5c\xae\x57\x7b\x2d\xf7\x55\x5d\ \x70\xbd\x75\x60\x4d\x9e\x06\x74\x4d\x02\xb9\xda\x1d\xc3\x41\x78\ \x71\xd8\x95\x0c\x0a\x85\xa9\xe9\x18\x30\xe3\xfb\xd8\x7a\x9d\xa6\ \x29\xcb\x83\x48\x33\xe4\x36\xfe\x5e\x7b\x2d\x76\xdd\x12\xc1\x18\ \x20\xd8\xae\xe8\x51\xd2\xcb\x2c\x0e\x82\x64\xa6\x6b\x91\xff\xb3\ \xa1\x4d\xd4\x63\x40\xb4\x47\x28\x3a\x54\x6b\x66\x44\x37\x34\xa1\ \x38\x60\x3e\x89\x30\x44\x7b\xb5\xc2\xa0\x69\x16\x92\x06\x3b\xda\ \x25\xdc\x2e\xb4\xb5\xa9\xe9\x7e\xc8\xe9\xd3\xa2\xde\x10\x83\x4b\ \xf6\xba\x3a\x26\xaa\xb0\x6f\x77\x2c\xb0\xfd\x1c\x36\xc7\x5d\x3b\ \xa0\x62\x7f\x33\xee\xc6\xa1\xa4\xcf\xcf\xb5\xdf\x95\xf0\x8e\xbd\ \x9f\x4a\xf1\x95\x5f\x6d\xba\x27\xba\xef\x87\xc2\xde\x65\x14\x65\ \x0d\x9b\xc6\x9a\x2e\x5a\xf8\xda\x50\x81\x6d\x4c\x88\x8e\x64\xe6\ \x7d\x84\x22\x2c\x57\x7d\xed\x13\xad\x3d\x36\x9c\x62\x73\x9b\x5c\ \xc9\x41\x9f\xa6\xfb\x16\x2a\xd2\xb6\x16\x4f\xdb\x12\x43\x19\x7a\ \x5b\x99\x1d\x74\x4a\x86\x3e\xf6\x71\x63\x19\xad\x32\x84\x88\x87\ \x4f\xd1\x81\x2e\x30\x35\xc2\x48\x1b\xaa\x1d\x12\xed\x32\x3d\x65\ \x40\xf8\x0c\xd7\xb6\x8f\x98\x79\x1c\x13\xc2\xde\xa2\xa5\x6d\x50\ \xd1\x68\x9d\xc8\x6d\x02\xb1\x6d\x5f\x62\xd5\x06\xdb\x2d\x25\x3a\ \xec\xf8\xd8\x28\x42\xe1\xb4\x99\xa3\x32\x90\x6d\x20\x84\xc2\x36\ \xf8\x3e\x7f\xc8\x47\x11\xf6\x14\xc2\x00\xe3\x61\x5c\xba\xaa\x16\ \xc6\x55\x22\xd9\x88\x36\x81\xac\xda\x0d\xee\x86\x66\xc2\x54\x20\ \x14\x26\xd3\x00\xca\x53\x4a\x06\x3c\x04\xbe\x67\xad\x46\x31\xd3\ \xf5\xec\x9c\x9c\x42\x18\xe0\x5e\xc6\x45\x28\xdd\x06\x66\xd9\xf8\ \x36\xa2\x6d\xd0\x95\xd1\xbb\xec\xab\x01\x9a\x42\x3b\x16\x61\xd0\ \xbe\x12\x2a\x99\x28\x99\x1b\xd4\xbe\x42\xf1\x79\xa1\xfb\x4b\x42\ \x11\x95\x8d\x6c\x43\x05\x5b\x0b\x93\x95\x36\xb6\xc1\xb8\xb6\xe6\ \x6e\x6d\x02\xf1\x85\x2e\x0d\x71\xc3\xae\x59\x30\x16\x0c\x1e\xa1\ \x18\x4c\x67\x31\x4a\x5e\x54\x03\x9f\xe3\xf0\xed\x12\xa1\x19\xc2\ \xd1\x39\xb0\x4a\x20\xc6\xb5\xb7\x40\xd8\xb9\x2e\x9b\xcc\x69\x4d\ \xa5\xf6\x4d\xf8\x62\xf8\xee\x90\xef\xea\x03\x02\xb4\xcc\xfa\x85\ \x7f\xff\x34\x0f\x98\xdb\xae\xb1\x31\xe6\xad\xfe\xb9\x4d\x20\x25\ \x21\x6c\x83\xef\x8d\x1d\x32\x76\x41\x39\xc4\x63\x37\xfa\x1e\x5d\ \x10\x79\xc9\x0d\x4a\xf2\x38\x07\xf8\x88\xae\xb1\x31\x99\x5b\x35\ \xa8\x4d\x20\xec\x75\x57\xe8\x46\xc2\x73\xec\x60\xa7\xa5\x32\x78\ \xbb\x25\xad\x5f\xaf\x0a\x6d\x17\xe1\x73\x4e\x49\xd9\x14\x6e\x3d\ \x44\xd2\x39\x07\x84\xb5\x5d\x11\xaa\x71\x35\xbe\x8d\x68\x13\x08\ \xd3\xd0\xa5\x76\x72\x94\xda\x15\xc2\xa1\x30\xcb\x08\xc3\x56\x9d\ \x5b\x86\xec\x6e\xad\x30\x0a\x7c\x9e\x10\x95\x78\xee\x16\xaa\x59\ \xf5\xbd\x46\x5f\xac\x12\x88\x71\xed\x2d\x90\x52\x0e\x6f\x83\x9b\ \x4e\xf9\x48\x0c\x6a\x6f\xd9\xd6\x20\xde\x21\x34\x01\x86\x0e\xa4\ \xef\x11\xe6\x6d\xc2\x22\x94\xa9\xcc\x6d\x99\x00\x5d\xd6\x83\x8f\ \x69\xf5\x85\x6d\x02\x91\xdc\x74\x45\x0a\x6c\xb9\x7a\xcc\x14\x9b\ \x89\x09\x43\x15\xd7\xc6\x39\xa5\x6a\x9d\xec\x12\x06\x5f\x57\xd8\ \x06\xdf\x37\x6b\x99\x3e\xd7\x55\x89\x98\x42\x28\xc6\xc3\xb8\x74\ \x59\x0f\xe3\xaa\x22\xdc\x88\x36\x81\xc8\x64\xbb\xc2\x5a\xdf\x53\ \xb2\x18\x7b\xdf\x6a\x11\xc6\x9d\x42\x03\xd7\x25\x08\x20\x04\x25\ \x1c\xbb\x3a\x24\x64\xab\x84\x82\xd6\x79\x5c\x7f\x0a\xa1\xd8\xff\ \xcc\x72\xb4\x8d\x2b\x08\x8b\x1d\x74\x6a\x44\xdb\x17\x7d\x81\x40\ \xba\x3a\x48\x20\x63\x9e\x13\x34\x38\x0a\x7e\x06\xcb\x43\x07\x6a\ \x84\x41\xf5\xe5\x19\xf7\x0d\x15\x3b\x6b\x92\x47\xd7\xbd\x57\x58\ \x84\xd2\x35\x78\x7d\x61\xcb\x6d\x97\x29\xd7\x36\x02\x51\x0c\xed\ \x0d\x07\xf7\x4b\xf8\xdb\xc4\x31\xd7\x43\x0c\xd2\xba\xeb\x21\xd6\ \xc2\xad\x87\xf8\xbd\xbf\x37\x7d\x6f\x91\x3e\xa3\x0a\xb0\x2f\xd6\ \x43\xc0\xde\x57\xe6\xa0\xe9\xc2\xa8\xd2\x6b\xff\xd2\x18\xe0\xc0\ \x6b\x57\x0c\xd1\xe7\x14\x09\xdb\x56\x0c\xfb\x5c\xc7\x1e\xb1\x31\ \x57\x0c\xad\x0a\x36\xdd\x0b\xd7\x5a\x31\x5c\xb5\xa6\x2e\x5a\x78\ \x75\xd8\x5a\x06\xa8\x84\xa4\x4f\x01\x91\x89\xac\x19\x48\x9f\xd1\ \x71\x42\x5c\x9e\xd9\x7e\x26\x94\x3e\x6b\xea\x34\xaa\xac\xd1\xaf\ \x03\xa6\xca\x19\xf7\x2e\x0d\x57\xc5\x16\x35\x0e\xc2\xa5\xc3\x9a\ \x5d\x27\xeb\x9c\x12\x32\x78\x7d\x77\x9d\xd0\x0c\x49\x5e\x9b\x43\ \xf6\x7b\xd7\xf5\xb9\x5a\xf3\x27\x20\xf0\xf9\x75\x76\x9d\xd8\xbb\ \x3b\xe9\xae\x13\x9b\xdf\xd4\x81\xba\x3a\x35\xc6\xbe\x2c\x05\xc0\ \x5a\x61\x58\x23\xb1\xa2\x28\x1a\xeb\x82\xbf\xf7\x15\x8a\x76\x30\ \x9b\x43\xf7\x65\x31\xdf\x9e\x62\xd4\x74\x7d\x64\x51\xde\x12\xca\ \x53\x06\x81\xfa\x97\x01\x6b\xba\x01\x32\x33\x6f\x0c\x95\x28\xfa\ \xc0\x91\x80\xbe\x3b\x17\x99\x95\x21\x3b\x17\xd9\xec\xda\x7b\xc8\ \xbf\x08\x9d\x30\xfb\xc0\x82\x93\xe2\x67\xd7\xce\x45\x1b\xb9\xf9\ \xab\xb5\x02\x08\xc7\xd8\xbe\x14\x36\xdd\x00\x75\x82\x59\xeb\x73\ \x20\xa5\x6c\xf9\xd1\xc0\x55\x03\xe5\xef\x68\x8d\xdd\xf7\xfa\xe6\ \x0d\x84\x42\x13\x99\xc5\x72\xad\xa6\xfb\x14\xfa\xbb\x3d\xba\xe5\ \x81\x32\xb5\x50\x2f\xe3\x1f\xba\xae\x6f\x2d\xdd\xd3\x21\x3a\xb1\ \x2a\x06\x67\xb2\x9c\x19\xa4\xf6\x4d\x20\x6d\x9b\xb1\x09\xa4\xb6\ \xb6\x25\x72\xe3\x48\xdd\xbb\x6b\xb6\xe8\x84\xb0\x9b\x26\x11\x88\ \x4d\x66\x6d\xed\x68\x83\xef\x9b\x50\xbe\xef\x3a\x65\x70\xda\xa0\ \x3d\x84\x6e\xa6\xd7\xae\x88\xca\xce\x1d\x54\x72\x46\xb2\xad\x3f\ \xda\x6d\xa5\xd2\x8e\x93\xb5\xa0\x71\x9e\x81\x55\x73\x3e\xa4\xf6\ \x40\x8a\x6b\xd6\x9e\x0f\x61\x32\xb7\xfd\x7c\x88\x67\x0f\xcf\x76\ \x3e\x04\x9c\xa8\x5d\x75\x82\xca\x16\x1c\xcf\x9b\x6a\x5d\x2b\x5e\ \x82\x86\x6d\xe2\x04\x15\xf3\xe5\x2c\x4b\x53\x54\x47\x18\x4c\x69\ \x1f\x61\x08\x75\x55\x0a\x5c\x6f\xf1\x5a\xcb\xb4\x69\x7d\x94\x13\ \x54\xe0\x8c\xa1\x81\xab\x39\x63\xb8\xd2\x46\x2e\x40\xa7\x2f\x1c\ \xd2\x94\xb2\xfd\xd2\x20\x49\x9e\xcc\x64\x83\x37\x05\xca\x19\x43\ \xa6\xb3\x08\xc5\xfd\x69\xe3\x90\x33\x86\x66\x7f\x97\x76\x98\x5c\ \x02\x92\xd1\xce\x18\x42\xed\x29\x5c\x89\x91\x7a\x4e\x2d\x8a\x50\ \x08\xc0\xa0\x98\xa5\x04\x34\xf7\x29\x5c\xda\xe8\x98\x74\x6d\x04\ \x47\xdb\xec\xad\x3a\x34\x2c\x35\xbf\x36\x7a\x9e\x7c\x75\xd0\x53\ \x3b\x1b\xcc\x5a\xb6\x5c\xd8\xd9\xb6\xf8\xa2\x33\xf6\x73\x11\x4c\ \x79\xce\xd4\x2a\x68\xb0\xd8\xbd\x14\xdb\x6c\x68\x90\x3b\x78\x40\ \xc1\x94\x10\x49\x29\xa0\xca\x09\x0c\x18\x87\xcf\x0f\x9a\xe9\x35\ \x60\x9a\xad\x42\xb2\x08\x5d\x4b\x10\x4c\xd9\xc1\xe1\xcb\xf7\xfe\ \x3f\x2a\x6e\x1a\xd2\x92\x36\x87\x88\x84\x50\x9e\xe4\xd0\x47\xf5\ \x7d\x76\x93\x4f\x72\xe8\xd3\x56\x8b\x4f\x9e\xce\x40\x9b\xbb\x4c\ \x95\x71\x72\x46\xbd\xd7\x93\x1c\xfa\x40\xad\x5f\x7c\xce\x26\x36\ \x35\xa0\x50\xa8\xa9\x21\xe7\x0a\xfb\x74\x74\x3f\x80\x75\x60\x62\ \x3d\x45\xb5\x6b\x62\x22\x13\xec\xd1\x54\x93\x3d\xeb\x04\x44\x0a\ \xb5\x4f\x03\xb2\x39\x6d\x48\x32\xb7\xad\x60\x92\x65\xfe\x07\x85\ \x4d\x7d\x5e\xa4\xf1\x79\x53\x38\xe9\xd3\x80\x80\xbd\x3c\x2c\x3f\ \x2f\x8b\x7f\x6b\xea\xe7\x32\xd5\xf8\xac\x8d\x4c\xb1\xc4\xfd\x3f\ \xe0\xdc\x95\x22\xac\x7c\x35\x35\x66\x91\x34\xe5\xb0\xf8\x44\xb9\ \xbe\xa5\x97\xb5\x61\xd3\x9a\x97\x9d\xac\x32\x5d\xc8\xd6\x4a\xb8\ \x74\x6a\xcc\xe5\xd2\x39\x40\x18\x1c\xbf\xf6\xd7\x08\xc3\x78\x88\ \xaa\xc6\x3c\xc8\x54\x05\xf6\xd4\x43\x87\x6d\x86\x58\xe5\xdc\x0a\ \x9d\x59\x54\xbe\x58\xb7\x0c\x32\x17\xb4\x53\x89\xc7\xc1\xd6\xa6\ \xfe\x2c\xd3\x38\x48\x8e\xfb\x46\x98\xa3\x41\xc9\xd9\x3e\xa7\xb2\ \xdb\x63\x15\x45\x5f\x8a\x6b\x1e\x66\x33\x8b\x6d\x5d\x03\x0a\xa5\ \x72\x0c\xaf\xd9\xab\xb1\x02\xa8\x92\x61\xef\xd7\xe0\x73\xe8\xeb\ \xc2\x0c\xb2\xeb\xe4\x81\xe1\xaa\x6c\xb5\x50\x15\x55\xd5\xf3\xfe\ \x61\x9f\x8c\x7e\x4e\x78\x04\xc6\x43\x43\x89\x63\xd7\xfa\xc6\x22\ \x15\x2c\xef\x1d\x76\x55\x7c\x67\x01\x9f\xe0\x5c\xdd\x90\x67\xbf\ \x0b\x9f\x25\x9b\x83\x57\xd0\x46\x86\x42\xe1\xbe\x7e\xf6\x7b\x01\ \x7f\x22\xf2\xb2\xd2\x56\xdb\x09\x14\x8d\x28\x95\xa8\x23\x09\x12\ \x36\x65\xc6\x98\x27\x6f\x8e\x53\x70\xdc\xf7\x6f\x47\x28\x90\xc1\ \x8a\x46\xc6\x78\x7f\x48\xdf\x07\xd9\x0c\xc5\xe2\xfb\x43\xd4\xce\ \x54\x20\x6a\x22\x29\x24\x30\x66\x6a\xf4\xf7\x87\x8c\x69\xef\x34\ \x6a\xac\x37\xec\x70\xa6\xde\xf7\xa4\x76\xd6\xf7\x90\x4e\x17\xec\ \x49\x2e\x6f\xd8\xf1\xaf\xc1\xec\xfb\x86\x9d\xd2\xd6\x47\x85\x8a\ \x86\x0a\xa3\x04\x39\x0a\xc6\x76\x40\x3a\xe5\x88\x72\x79\x07\x55\ \xd7\xb3\xb6\x9a\x60\xf6\x89\xc6\x1c\x2f\x26\x1c\x19\xaf\x50\x72\ \xf9\x1d\x54\x66\x67\x17\xf4\x8b\x09\x54\x47\x52\x83\x63\x52\x54\ \xaa\xfd\xbb\xee\x3b\xa8\xb4\x43\x5e\xb2\xf5\xef\xa0\x2a\x70\x4d\ \xe6\x40\x3c\xee\x5c\x87\x7d\x48\xb5\xeb\xed\x8b\x28\xa6\x41\x00\ \xa0\x54\xb3\xf8\x96\x36\x66\x4e\x39\x5b\xb5\x80\x89\x34\x43\x09\ \x5e\x25\xd6\xbd\x38\x68\x61\x79\x79\x4b\x9b\xc1\x37\x51\x2c\x0f\ \xc0\x90\x7e\xd3\x54\xeb\x1f\x4c\x9c\x87\x03\x68\xc7\xe8\x98\x42\ \x20\x05\x66\xdf\xe2\x7b\x0c\xcd\xd6\x31\x1c\x1f\x21\x19\x1c\xc2\ \xa0\x29\x42\x53\xbf\xa3\x9d\x04\x82\xcc\x25\x8e\x11\xf5\x10\xb6\ \x55\x41\x19\xb8\xc2\x62\x79\x86\xd8\x24\x98\x52\x20\x05\xb2\x73\ \xda\xe2\xb9\xf1\xde\x8d\x6e\xd6\xce\x71\xdf\x75\x41\x43\x2d\xf3\ \x0a\x38\x08\xc3\xb3\xdc\x45\x85\x93\x62\x8e\x50\x8d\xcd\xd7\xa9\ \x92\x68\x71\xac\x9c\x68\xdb\xca\xe3\x36\x80\x06\x3a\xb9\x6b\x13\ \xa0\xcd\x1b\xaf\x09\xe7\x78\xda\xf6\x6c\xb1\x33\x67\x28\x43\x17\ \x5e\x72\x84\xc5\x29\xb3\xf7\xdb\x54\x46\xe1\x97\x44\x79\x76\x21\ \x5a\x73\x47\xc7\x2e\xb4\x7f\x16\x6c\xca\x74\x70\xb2\x8e\x2b\xdb\ \xd3\x24\xfc\xe4\x70\x99\xb2\xb9\x26\xc8\x22\xf8\x03\xc1\x82\xc0\ \xc1\xe0\xab\x62\x1f\x12\x8a\xf0\x66\xc7\xa6\x6d\xb9\xf5\x6c\x51\ \x18\xff\x62\x0b\x8e\x10\x55\x24\xd4\x27\x2f\x18\x02\x41\x80\xa2\ \xa8\x1d\x27\x42\x69\x79\x8f\x52\x8e\xa5\x67\x51\xdd\xc6\xb0\x2d\ \xce\x95\x66\xc8\x0f\x3c\x78\x53\x19\xc5\x52\x29\x8d\x29\xfe\x86\ \x59\x23\xa0\x21\xed\xe5\x9c\x69\x01\x33\xc9\x37\x30\x4b\x42\x56\ \xbb\x4c\x3c\x84\x8d\x36\xd8\xf9\x32\x59\xe4\xd4\x07\xdb\x18\xed\ \x68\x13\x61\xd0\x18\xbb\x1b\x51\x42\xe7\x77\x02\x01\x24\x40\x02\ \x2a\x2c\x30\xf3\x0b\x0d\xb0\x24\x53\x20\xc1\x24\x95\x0a\x80\x77\ \x7a\x94\xd7\x47\x8c\x9a\xd4\x8d\x81\x6d\x14\x48\x1b\xe4\x31\x96\ \x82\x55\x97\x8b\x59\x93\x00\x96\x88\xcd\xc0\x1b\x64\xa6\x08\x45\ \x77\x02\x09\x19\xbf\xff\xef\xb0\xc3\x0e\x3b\xec\xb0\xc3\x0e\x3b\ \xcc\x85\x03\x0e\xf8\x17\x13\x38\xc8\x5d\x25\x6a\x17\x2b\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x03\x0d\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\x6e\ \x6f\x22\x3f\x3e\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x73\ \x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\x57\x33\ \x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x32\x30\x30\x31\x30\ \x39\x30\x34\x2f\x2f\x45\x4e\x22\x0a\x20\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x54\x52\x2f\ \x32\x30\x30\x31\x2f\x52\x45\x43\x2d\x53\x56\x47\x2d\x32\x30\x30\ \x31\x30\x39\x30\x34\x2f\x44\x54\x44\x2f\x73\x76\x67\x31\x30\x2e\ \x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\x67\x20\x76\x65\x72\x73\x69\ \x6f\x6e\x3d\x22\x31\x2e\x30\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\ \x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\ \x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x77\x69\x64\ \x74\x68\x3d\x22\x32\x35\x30\x2e\x30\x30\x30\x30\x30\x30\x70\x74\ \x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x35\x30\x2e\x30\x30\ \x30\x30\x30\x30\x70\x74\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\ \x22\x30\x20\x30\x20\x32\x35\x30\x2e\x30\x30\x30\x30\x30\x30\x20\ \x32\x35\x30\x2e\x30\x30\x30\x30\x30\x30\x22\x0a\x20\x70\x72\x65\ \x73\x65\x72\x76\x65\x41\x73\x70\x65\x63\x74\x52\x61\x74\x69\x6f\ \x3d\x22\x78\x4d\x69\x64\x59\x4d\x69\x64\x20\x6d\x65\x65\x74\x22\ \x3e\x0a\x0a\x3c\x67\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\ \x22\x74\x72\x61\x6e\x73\x6c\x61\x74\x65\x28\x30\x2e\x30\x30\x30\ \x30\x30\x30\x2c\x32\x35\x30\x2e\x30\x30\x30\x30\x30\x30\x29\x20\ \x73\x63\x61\x6c\x65\x28\x30\x2e\x31\x30\x30\x30\x30\x30\x2c\x2d\ \x30\x2e\x31\x30\x30\x30\x30\x30\x29\x22\x0a\x66\x69\x6c\x6c\x3d\ \x22\x23\x30\x30\x30\x30\x30\x30\x22\x20\x73\x74\x72\x6f\x6b\x65\ \x3d\x22\x6e\x6f\x6e\x65\x22\x3e\x0a\x3c\x70\x61\x74\x68\x20\x64\ \x3d\x22\x4d\x36\x38\x32\x20\x32\x32\x35\x33\x20\x6c\x33\x20\x2d\ \x32\x34\x37\x20\x36\x30\x20\x2d\x32\x31\x20\x63\x32\x34\x38\x20\ \x2d\x38\x36\x20\x34\x32\x34\x20\x2d\x32\x36\x30\x20\x35\x30\x37\ \x20\x2d\x35\x30\x31\x20\x33\x36\x20\x2d\x31\x30\x35\x20\x34\x38\ \x0a\x2d\x32\x35\x30\x20\x32\x39\x20\x2d\x33\x35\x39\x20\x2d\x33\ \x37\x20\x2d\x32\x30\x39\x20\x2d\x31\x37\x31\x20\x2d\x34\x31\x31\ \x20\x2d\x33\x34\x36\x20\x2d\x35\x32\x30\x20\x2d\x36\x30\x20\x2d\ \x33\x38\x20\x2d\x39\x37\x20\x2d\x35\x34\x20\x2d\x32\x33\x32\x20\ \x2d\x31\x30\x36\x20\x2d\x32\x33\x20\x2d\x39\x0a\x2d\x32\x33\x20\ \x2d\x39\x20\x2d\x32\x33\x20\x2d\x32\x35\x34\x20\x6c\x30\x20\x2d\ \x32\x34\x35\x20\x33\x37\x39\x20\x30\x20\x33\x38\x30\x20\x30\x20\ \x35\x38\x20\x34\x38\x20\x63\x31\x30\x32\x20\x38\x36\x20\x32\x34\ \x39\x20\x32\x34\x37\x20\x33\x31\x37\x20\x33\x34\x39\x20\x31\x30\ \x33\x20\x31\x35\x36\x0a\x31\x37\x37\x20\x33\x33\x30\x20\x32\x32\ \x32\x20\x35\x32\x34\x20\x32\x32\x20\x31\x30\x30\x20\x33\x31\x20\ \x34\x35\x31\x20\x31\x34\x20\x35\x36\x39\x20\x2d\x33\x30\x20\x32\ \x30\x36\x20\x2d\x31\x31\x38\x20\x34\x33\x36\x20\x2d\x32\x33\x34\ \x20\x36\x30\x39\x20\x2d\x37\x30\x20\x31\x30\x36\x20\x2d\x32\x33\ \x39\x0a\x32\x39\x30\x20\x2d\x33\x32\x39\x20\x33\x36\x30\x20\x6c\ \x2d\x35\x34\x20\x34\x31\x20\x2d\x33\x37\x37\x20\x30\x20\x2d\x33\ \x37\x36\x20\x30\x20\x32\x20\x2d\x32\x34\x37\x7a\x22\x2f\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\ \x00\x00\x00\xc9\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x7e\x49\x44\x41\x54\x38\x8d\xcd\xd1\x31\x0e\ \x82\x50\x10\x84\xe1\x4f\x0b\x12\x0e\x03\xa1\x90\x8a\xd6\x86\x44\ \xce\x44\x38\x8a\x67\xe0\x0c\x9e\xc0\x52\x7b\x3c\x84\x09\x16\x58\ \x18\x83\x8f\xe4\x51\xe0\x24\x5b\xec\x6e\xe6\xcf\x66\x96\x7f\x54\ \x85\x73\xac\xb9\xc0\x03\x75\x8c\x39\xc3\x80\x26\xc6\xbc\xc3\x15\ \xe3\x4c\x3d\x71\x47\x87\x34\x04\xc9\xdf\x17\x9c\xbe\xe6\x09\x4a\ \xf4\xb8\x2c\x41\x0e\xa6\x0c\x8e\x3f\xf6\x3d\xda\x10\x80\xf0\x17\ \x4a\xdc\x96\x00\x21\x25\xa6\x4c\x56\x69\xfc\x6c\xf6\x6b\x69\xdb\ \x03\xb6\xd7\x0b\x26\xa7\x15\x72\xfb\xba\xe1\x95\x00\x00\x00\x00\ \x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\xf4\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ \xa8\x64\x00\x00\x01\x89\x49\x44\x41\x54\x58\x47\xe5\x95\xbd\x4a\ \xc5\x40\x10\x85\x57\x5b\x41\xc1\x42\x51\x41\xb1\x12\x5b\x3b\x5f\ \xc0\x4e\x9f\x42\xf0\x31\xc4\x77\xf0\x07\xdf\xe1\x22\xd6\x3e\x80\ \x8d\xad\x95\x88\xa0\xa0\x9d\x88\x62\x61\xa7\xe7\x64\x66\x2f\x43\ \x88\x9b\x9d\xbd\x89\x08\xf9\xe0\x30\x93\x6c\x7e\x76\xe7\x64\x36\ \x61\xf0\x4c\x69\xf4\xb2\x0a\xed\x48\x3a\xe6\x0a\x7a\x92\xb4\x7f\ \xf6\xa1\xef\x9a\x78\xee\x4f\xe0\xea\x2f\xa1\xfa\x04\x58\x01\x8e\ \xf5\x4e\xd3\xea\xa3\xdc\x55\x98\xd6\x58\xca\x1d\x74\x2b\x69\x19\ \x25\x13\xd8\xd4\x48\xce\xa0\x73\x49\x2b\x76\x21\x97\x0d\xde\x09\ \xf0\xe1\x07\x92\x56\x7c\x42\x5f\x92\x56\xec\x41\xf5\xee\x48\xe2\ \x9d\x00\x1f\x3e\x23\x69\x78\x84\xf8\xe1\x51\xf7\x3c\x51\x82\x77\ \x02\x6b\x1a\xc9\x31\xc4\xbe\xa7\x4e\x78\x42\xd9\xd0\xd8\x39\x2c\ \xff\x1b\xd4\xf4\xc5\xdb\xce\xf8\x80\x7a\x69\x47\xfb\x92\x67\xc8\ \xbe\x84\x39\x2b\x11\xc7\xb3\xdb\xd1\x63\xc1\x92\x46\x72\x0a\xd9\ \x6d\x97\x39\x2d\x89\x58\xab\x92\xe4\xfe\x0b\xb8\xc2\x1b\x68\xa1\ \x3a\x0a\xe1\x10\x7a\x91\x74\xcc\x32\x74\x24\x69\x78\x85\xb6\x20\ \x3b\xc9\x89\xb0\xe5\xcf\x55\x96\x0d\xb9\x16\xcc\x6b\xf4\xb0\xa8\ \x31\x49\x8e\x05\x2c\xff\x35\xb4\x52\x1d\x35\x97\x3f\x62\x6d\xe0\ \x35\xdb\xd0\xc4\x36\xd8\xf2\xbf\x43\xa9\x16\xe3\x18\xaf\xc9\xb6\ \x21\xc7\x82\x59\x8d\x84\xfb\x7e\x6a\x45\x1c\x63\x87\x44\xe6\x34\ \x16\xc3\x15\x3d\x40\xd9\x2b\x02\xb6\x62\xbc\x37\x55\xb1\xd6\x0a\ \x70\xef\x5f\x97\xb4\x08\xde\x9b\xfc\x39\x79\x36\xa2\x0b\x88\x3f\ \x9e\x36\x78\xcd\x48\xd2\x76\xda\xba\x80\xe5\x8b\x2b\xe0\x83\x73\ \xbf\xe8\xd2\xfb\x06\xc8\x6f\x16\xd8\x12\x76\x85\xcb\x0a\xdb\x4a\ \x5d\xa9\xb1\x85\x3d\x5d\xd0\x0b\xff\xd6\x82\xa1\x10\xc2\x0f\x6f\ \x35\x79\x22\x70\x77\xe8\xe3\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ \x00\x00\x00\xaf\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x64\x49\x44\x41\x54\x38\x8d\xed\x92\x31\x0a\ \x80\x30\x0c\x45\x1f\x1e\xa1\xe7\x71\x10\xaf\xa6\x78\x57\x77\x97\ \x74\x89\x83\x16\x44\xd3\x92\x16\x04\x07\x1f\x64\x09\xc9\x23\x81\ \x0f\x5f\x63\x01\x04\xd0\x5b\x6d\xc0\xe0\x11\x08\x10\x8c\xbe\x02\ \xab\x47\xa2\x85\x7e\x7f\x4a\xc6\x56\xc1\xf5\x9d\x6a\x41\x76\xa6\ \x73\x2c\x14\xf9\x05\x4f\x41\xc4\x0e\x52\x22\x70\x84\x2d\xcb\x8c\ \x1d\xe5\x54\x02\x4c\x8d\xc7\xbe\xc4\x0e\x62\x40\x1f\x4c\x3d\x90\ \xe3\x21\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x1c\x03\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x01\xd5\x00\x00\x00\x9a\x08\x06\x00\x00\x00\xff\xc0\xd2\xfa\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ \xa8\x64\x00\x00\x1b\x98\x49\x44\x41\x54\x78\x5e\xed\xdd\x6d\x6c\ \x1c\x67\x81\x07\xf0\xe7\x99\xb1\x9d\x36\x29\xd4\xb9\x96\x70\xa0\ \x56\x75\x10\x25\x95\x40\xba\xa4\xe9\x11\xb5\xa1\x57\x9b\xa3\x92\ \x1d\x54\x70\x3e\x51\xa9\x1f\xba\x4e\x48\xae\xa7\x4a\x4d\x2d\x71\ \x2f\x48\x85\x24\x27\xa4\x7e\x38\x74\x49\xf8\x84\x92\x4b\xbc\xfd\ \x80\xc4\xdd\xe9\x52\x97\x4a\x89\x05\x3d\xec\xa3\x6f\x0a\xd0\xd8\ \x48\x45\x70\x07\x22\xae\x82\xca\xd1\x2b\xad\x8b\x2e\x6f\xb6\x77\ \x9e\x7b\xfe\xeb\x79\x9c\xf1\x74\x76\x77\x9e\xdd\xd9\xd9\x99\xd9\ \xff\x4f\x79\xb2\x6f\xde\x9d\xdd\x9d\xdd\xf9\xef\xf3\x32\xcf\x08\ \x22\x22\x22\x22\x22\x22\x22\x22\x22\x22\x2a\x22\xe9\x9f\x12\xa5\ \x66\x74\x74\x74\xc0\x3f\x4b\x94\xba\xc9\xc9\xc9\x79\xff\x2c\x51\ \xe2\x18\xaa\x94\x2a\x04\xaa\x52\xea\x82\x7f\x91\x28\x75\xae\xeb\ \x0e\x9c\x3e\x7d\xfa\x0d\xff\x22\x51\xa2\x18\xaa\x94\xaa\x60\xa8\ \x6e\xda\xb4\xa9\x7a\x1d\x51\x1a\xde\x7a\xeb\xad\xea\x29\x43\x95\ \xda\x89\xa1\x4a\xa9\x32\xa1\x8a\x40\x3d\x7e\xfc\xb8\x7f\x2d\x51\ \xfb\xed\xdf\xbf\xbf\x1a\xac\x0c\x55\x6a\x27\xc7\x3f\x25\x22\x22\ \xa2\x16\x31\x54\x89\x88\x88\x12\xc2\x50\x25\x22\x22\x4a\x08\x43\ \x95\x88\x88\x28\x21\x0c\x55\x22\x22\xa2\x84\x30\x54\x89\x88\x88\ \x12\xc2\x50\x25\x22\x22\x4a\x08\x43\x95\x88\x88\x28\x21\x0c\x55\ \x22\x22\xa2\x84\x30\x54\x89\x88\x88\x12\xc2\x50\xad\x6d\x50\x97\ \x23\xba\x60\x9e\xda\x09\x5c\x41\x44\x54\x40\xfd\xba\x1c\xd4\x65\ \x5a\x17\x6c\xef\x94\x7f\x8a\xcb\x25\x5d\xc8\x02\x43\xf5\x3a\x7c\ \xb0\xf0\x01\x42\x80\xbe\xab\x0b\x3e\x50\x4f\xea\xc2\xc3\x94\x11\ \x51\x51\x1d\xd0\x05\x01\x7a\xa8\x7a\x49\x88\x19\x5d\x26\x57\xce\ \x56\x2b\x16\xd8\x1e\xe2\x76\x6e\x07\x63\x62\xa8\x5e\x77\x54\x17\ \x7c\x80\xf0\x41\xc2\x07\xeb\x98\x2e\x44\x44\x45\x85\x4a\x04\xb6\ \x7b\xa8\x50\x0c\xf9\x65\x4c\x97\xdd\xba\x6c\xd6\xe5\xb0\x2e\x80\ \x40\x65\x6b\x5d\x4c\x0c\xd5\xeb\xf0\x4b\x6d\xa3\x2e\xf8\x30\xe1\ \x43\x85\x5a\x2a\x11\x51\x51\x3d\xea\x9f\x02\x2a\x12\x61\xd8\x26\ \x9a\xeb\x51\xd9\xd8\xba\x72\x96\xea\x61\xa8\x5e\x37\xaf\xcb\xc2\ \xca\x59\x22\x22\xd2\x9e\xf3\x4f\x81\xa1\x1a\x03\x43\x95\x88\xa8\ \x3b\xa1\xa9\x17\xdd\x5c\xa6\x99\x37\x0a\x2a\x1b\x64\x81\xa1\x4a\ \x44\xd4\x9d\x10\x98\xe8\xe6\x32\x83\x94\xa2\x04\x07\x28\xcd\xf9\ \xa7\x54\x07\x43\x95\x88\x88\x6a\xc1\xe8\x60\x28\xeb\xc2\x50\x8d\ \x81\xa1\x4a\x44\x44\x61\x18\x11\xfc\xac\x2e\xa8\xa9\x62\xb0\xd2\ \xb8\x2e\x14\x03\x43\x95\x88\x88\xc0\xec\xa7\x3f\xab\x0b\xf6\xd5\ \x47\xb0\xa2\xdf\x15\xbb\xda\x70\x10\x67\x4c\x0c\x55\x22\x22\x02\ \xec\x36\x83\x60\x35\xa3\x7c\x71\x19\xcd\xbf\x9c\x55\xc9\x02\x43\ \x95\x88\x88\x00\xe1\x29\xfd\x82\xfd\xf5\xd1\xe4\x6b\x26\x7e\xc0\ \xac\x4a\xa8\xb9\x52\x03\x0c\x55\xca\x8d\x73\xe7\xce\x89\x1f\xfe\ \xf0\x87\xab\xe5\xf5\xd7\x5f\xf7\x6f\xc9\xa6\x4b\x97\x2e\xad\x79\ \xbe\x28\xb8\x8e\x28\x07\x30\x32\x18\xb3\x2d\x61\x22\x1c\xe0\xac\ \x4a\x31\x31\x54\x29\x37\x9e\x7f\xfe\x79\xf1\xad\x6f\x7d\x6b\xb5\ \x20\xa4\xb2\x0c\x01\x1a\x7c\xbe\x28\x17\x2e\xe0\x07\x3f\x51\x6e\ \x60\x90\x92\x99\x55\x69\x54\x17\xd6\x56\x1b\x60\xa8\x12\x75\xa9\ \xb7\xde\x7a\x4b\x3c\xf5\xd4\x53\xe2\x91\x47\x1e\x11\xe3\xe3\xe3\ \xd5\x96\x00\xa2\x08\x6f\xf8\xa7\x10\xdc\x6f\x95\x22\x30\x54\x89\ \xba\xd4\x77\xbf\xfb\xdd\x6a\x13\x3a\x6a\xd4\xa8\x41\xa3\x26\xcd\ \xe6\x69\x8a\x70\xb3\x7f\x4a\x31\x30\x54\x89\xba\x54\xb8\x4f\xda\ \x84\x2b\x75\x05\x34\xe3\xe2\xf0\x96\xd8\x75\x06\xcd\xba\xf5\x98\ \xd1\xc0\xe8\x67\xe5\x04\x10\x0d\x30\x54\x89\xba\xd4\xa6\x4d\x9b\ \xfc\x73\xd7\x45\x5d\x47\x85\x84\xdd\x65\x50\x10\xae\x66\xd6\xa4\ \x28\xb8\xcd\x34\xf9\x3e\xe3\x9f\x52\x1d\x0c\xd5\xda\x82\x7d\x07\ \xec\x9c\xa7\xc2\x79\xe2\x89\x27\xc4\xe6\xcd\xd8\x73\x42\x88\x0d\ \x1b\x36\x88\xbd\x7b\xf7\x32\x54\xbb\x07\x06\x1f\x99\x09\x1d\x10\ \xae\x51\x7d\xa5\x08\x54\x8c\x00\x06\xfc\x7d\xbd\x39\x82\xc9\xc7\ \x50\x5d\xeb\x88\x2e\x98\x4d\x04\x6d\x60\x38\x35\xd0\x3c\x82\xeb\ \xd0\x5c\x82\xc2\x43\x20\x51\xee\x21\x40\x8f\x1c\x39\x22\x26\x27\ \x27\xc5\x77\xbe\xf3\x1d\xf1\xd0\x43\x0f\xf9\xb7\x50\x17\x40\xa0\ \x62\xa6\x24\x73\x14\x1a\x6c\xdf\x30\x2d\x21\x82\xd3\xec\x97\x6a\ \x02\x15\x47\xb2\xc1\xdf\x52\x0c\x0c\xd5\xb5\x7e\xa6\x0b\x8e\x1f\ \x88\x66\x0e\x73\x48\x24\x53\x70\xdd\x7f\xfa\x85\x53\x76\x11\x51\ \xde\xa1\x7f\x14\x4d\x15\x98\x8a\x70\x52\x17\xd4\x56\x71\xe0\x72\ \xd4\x5c\x71\x1b\xb6\x7b\x1b\x75\xc1\x91\x6c\x28\x26\xcc\x9c\x41\ \x05\x53\x7a\xf9\xc1\xad\xa2\xd2\xa3\xbf\x20\xaa\xc9\x66\x6b\xb9\ \x20\xa4\x5a\x28\xdf\x3f\x65\xf6\x4f\x4b\xcc\xe8\xe8\xe8\x80\x52\ \xea\x02\x6a\x49\xc7\x8f\x1f\xf7\xaf\x8d\x07\xbb\x7f\x04\x07\xd7\ \x7c\xf6\xb3\x9f\xad\x36\x61\x66\x15\x76\x59\xd9\xbf\x7f\xbf\x7f\ \x69\xc5\x37\xbe\xf1\x0d\xf1\xa9\x4f\x7d\xca\xbf\x44\x69\xc2\xba\ \xc0\x3a\x71\x5d\x77\xe0\xf4\xe9\xd3\xc1\xdd\x44\x88\x12\xc3\x9a\ \x6a\x41\x94\xa6\x87\x07\xf6\xbc\x32\x72\x64\xcf\x4b\x23\xef\x3a\ \xa2\x67\xd6\x71\xc5\xb3\x8e\x2b\x27\x9a\x2b\xfa\xbe\x8e\x9c\xc6\ \x63\xed\x79\x79\xd7\x04\x1e\xdb\x5f\x4c\x2e\x60\xc3\x69\x4a\x3b\ \x77\x11\x49\x6b\x39\x41\x9d\x58\x66\x33\xf0\xdc\x82\xcf\xd5\x46\ \xf8\xbe\x59\x7e\x9d\x44\x61\xac\xa9\x16\xc0\xde\x57\x76\x1d\x50\ \x9e\x3a\xa4\xd7\x66\xfb\x06\x54\x39\xf2\xd0\xa9\x7b\xcf\xa0\x39\ \xa8\x25\xed\xaa\xa9\xe2\x7a\xcc\xb0\x84\x09\x0c\xc2\x1b\x61\x0c\ \xc6\x41\xed\x10\x7d\x86\xad\x0e\xc4\xc1\x46\x1e\x33\x3b\x45\x4d\ \x39\x88\xc7\xc6\x72\x1e\x7e\xf8\xe1\xea\x79\xfc\x6d\xab\x35\x55\ \x2c\xc3\xbc\x2e\xec\xee\x12\x5e\x26\x06\x18\xe1\xf5\xe1\xbd\x40\ \xb1\x81\xc7\x0d\xce\x4a\x85\xc7\xfa\xea\x57\xbf\xea\x5f\x8a\x16\ \xbe\xcf\x8e\x1d\x3b\x56\xfb\x62\xcd\x7b\x83\xe7\x8a\xf3\x41\x78\ \x6c\xb3\x0e\xa2\x5e\x7f\xf0\x75\x86\x77\xf5\x01\xbc\x46\x2c\x0b\ \xaf\xb1\xd9\x75\x98\x87\x9a\x6a\x69\x7a\xb0\x5f\xf4\xf5\x0e\x88\ \x8a\xbb\x32\x6e\x43\x8a\x79\xb1\x7c\x75\xae\x3c\x34\xc3\x2e\xa7\ \x06\xde\xf7\xde\x35\x45\x2e\x08\x77\x79\xbe\xbc\xf3\x07\x4d\xef\ \x3a\x14\x19\xaa\x5f\xfe\xc9\xf0\xa0\xbe\x49\x3f\x31\xc9\x51\xaf\ \x19\xe7\x2d\x8b\x3b\x84\xa7\xd2\x39\x8a\x44\x02\xc1\x9a\x74\xa8\ \x62\xc4\x2a\x26\x2d\x88\x33\x1b\x10\x96\x89\xc0\xb3\x0d\x1f\x03\ \x81\x71\xf2\xe4\x49\xff\x52\x7d\x66\x39\xad\x84\x2a\x5e\x93\xcd\ \x84\x0c\x78\x7d\xf8\x91\x11\xf7\xf1\x31\xf9\x03\x8a\x11\x67\x9d\ \x84\xef\x83\xd7\x88\x65\xe2\x7d\xc1\xfb\x13\x07\x82\x15\xeb\xcd\ \xc0\x3a\xc5\xeb\x0c\x07\x71\x14\x3c\x47\xdc\x17\x01\x6b\x2b\xcb\ \xa1\x5a\x7a\x71\x78\xd0\x75\xe5\x41\xa5\xaa\xfd\x99\xef\x23\xa5\ \x98\xa9\x2c\x8b\x63\xe5\xbf\x38\x8b\xbe\x4f\x0a\x68\xf4\xde\x35\ \x69\x5e\xbf\xeb\x33\xde\xa2\x77\xb8\x3c\x34\x65\x06\x73\xc5\xb2\ \x26\x54\x11\xa6\xd2\x75\x8e\x08\xc5\xd1\xad\x79\xa0\x2a\x4a\x78\ \x4b\xfe\x85\xb4\x48\x31\x7e\xea\xbe\xb3\x66\x54\xa0\xb5\x24\x43\ \x15\x1b\x56\x6c\x24\x6d\x27\x2c\x40\xe0\xa1\xd8\x08\x87\x49\x1c\ \x08\x9c\x60\xad\x0e\xe2\x86\x6a\x33\xcb\x33\x10\x72\x58\x76\x23\ \xe1\x65\x34\x1b\xaa\x58\x07\x51\xb5\xcb\x7a\x4c\xb0\xda\xfc\x50\ \x09\x6a\xa6\x6f\x3a\x8b\xa1\x8a\xae\x15\x77\x9d\x9c\x88\x1f\x08\ \xb2\xdc\xcc\x86\xbe\x88\x50\x33\x75\xd6\xdd\x78\x50\xe7\x55\x3b\ \x07\x52\xcd\x7b\x15\x31\x6e\xf3\x63\x66\x35\x54\xf7\xff\x74\x64\ \x54\x49\x89\x21\xd5\x94\x13\x95\x6b\x4a\x27\xab\x7f\x21\x3d\x0b\ \xde\xe2\x95\xcd\xcd\x36\x47\x25\x19\xaa\x61\x78\x4c\x6c\x68\x71\ \x0a\x08\xdb\x5a\x35\x58\x9b\x8d\x72\x38\x48\x0c\x34\x69\x22\xd8\ \x83\xcb\x43\xa9\x57\xe3\x8a\xb3\x5c\x04\x31\x6a\x6e\x61\xb8\x9f\ \x79\x7d\xa8\xbd\x62\x59\x78\x3f\xa2\x96\x17\x67\x39\xe1\xd7\x15\ \x67\x9d\x84\xef\x83\xf7\x20\x58\x93\xc6\x32\xd1\x54\x8b\xeb\xa1\ \xde\x3a\x40\xb0\x86\x6b\xb7\xc1\xfb\x07\x5f\x63\x58\x9c\xe7\x1a\ \x16\x27\x54\xf7\xfd\xf8\xc1\xad\x15\xb7\x95\xe6\x43\x0b\x8b\xba\ \x78\xf2\xa0\xfe\xdf\x76\xcc\xc2\xbc\xe8\x55\x87\x85\xeb\x5f\xea\ \x52\xf2\xaa\x3c\xa0\x37\x7f\xe9\xac\x2b\x8b\xca\x44\x35\x54\x4b\ \xb3\xc3\x03\xbd\x9e\x83\xfd\x2f\x73\x35\x20\xa5\x9b\xb5\x5a\x4b\ \x5d\xef\x2e\x8b\x4f\xdc\xf4\x47\x71\xb9\xd2\x23\xfe\xfb\xff\x3e\ \xe8\x5f\x1b\x93\xd2\x1f\xb0\xcf\x34\x57\x5b\x6d\x47\xa8\xe2\xb1\ \x6a\x35\x7b\x62\x23\x8a\x10\x08\xd7\x18\xe3\x2e\x1f\xf7\x0f\x37\ \xe1\x02\x6a\xba\x08\x05\x13\x1e\x41\x08\x0a\x2c\x33\xaa\xd9\x36\ \x4e\xd8\x99\x8d\xbf\x51\xef\xf5\x41\x38\xe8\x00\xc1\x84\x7d\x50\ \xeb\x09\xdf\x2f\xce\x7b\x12\xb5\x2c\xc0\xf2\xd0\x1f\x8b\xc7\x08\ \xc3\x6b\x41\x6d\xb4\x5e\x13\x3d\x5e\x1b\x5e\x63\xad\xfb\x63\xdd\ \x07\xdf\x13\xb0\xad\xad\xd6\x0b\xd5\x7d\xb3\x18\x97\x20\x0f\xc9\ \xa6\x47\xcc\x5b\xd2\x69\x50\x59\x6c\xe1\x47\xb1\xde\x72\xbb\x7d\ \xfa\xbf\x35\x6d\x8d\xdd\x43\x2d\xeb\xed\xdf\xb2\x7f\x21\x25\x9e\ \xa3\x36\x97\xef\x6d\xdc\x42\x50\x1d\xfd\xdb\xb3\x2c\x31\xb9\x01\ \x03\x35\x47\x54\xc5\x3f\xd3\x84\x2d\x1b\xde\x13\x4f\xdf\x75\x5e\ \x3c\x7e\xc7\x2f\xc5\xdf\x7c\xec\x75\xf1\xf5\x3b\xe7\xaa\x21\x1b\ \x97\x72\xc4\x17\xfd\xb3\x0d\xe9\x6d\x46\x5b\xfb\x7b\xb1\x11\xae\ \xb7\x71\x35\x81\x84\x00\x0c\xc2\xc6\x35\x4e\x1f\x60\x54\x80\xa0\ \xd9\x12\xa1\x1a\x15\xa8\x80\x65\xe1\x39\xd5\xba\xbd\x9e\xa8\x9a\ \x67\xbd\x40\x05\x13\xf0\x41\xb5\x6a\x78\xed\x60\x02\x3c\x2a\x10\ \x01\xd7\x23\x70\xf1\x77\x51\x50\xdb\xc7\xfb\x55\xef\xfe\x51\xef\ \x67\xf8\x87\x52\xb3\xf6\xcf\x7e\xfe\x88\xf0\xc4\xd1\xd4\x02\x55\ \xf3\x74\x28\xb4\xd4\xca\xa4\xef\xeb\xe9\x1f\xd6\x5d\x09\xaf\x3d\ \xcd\x40\x55\x98\x97\x40\x8d\xc5\x09\x54\xa8\x86\xaa\x74\xe3\x6f\ \x24\x29\x1b\x54\x0b\xdf\xa7\xd2\xed\xbf\x5e\x13\xa2\xb7\xdf\x78\ \x59\xfc\xe5\xad\xbf\xf3\x2f\x35\x26\x63\xf4\xb9\xeb\xa7\x37\xa8\ \x0b\x5a\x3f\x26\xf4\x69\xdb\xa6\x37\xab\xb7\x31\x0e\x42\xf0\x84\ \xff\xae\xd1\xe0\x26\x84\x5b\x78\xc3\x8d\x3e\xc4\x70\x80\x45\x41\ \x80\xd8\xf6\xdb\x02\xc2\x30\x08\x41\x12\xa7\x36\x16\x15\xf2\xe1\ \xc7\x6a\x97\x46\x23\x86\x8d\x5a\xef\x5b\x70\xd0\x52\x2d\x58\x77\ \xe1\xc1\x49\x49\xfc\x68\xd8\xf7\xda\xae\x92\xf2\x54\xba\x93\x1b\ \xe8\x2f\x44\x2b\x3f\x8a\x0d\x85\xaf\x70\x0b\xdb\x81\xbc\xaa\xfe\ \x20\xb1\x64\x5a\xe6\xb6\xde\xfc\x8e\x55\x05\x42\x9b\xf7\xa4\x1c\ \x3a\xb5\x73\xaa\xec\x5f\x6e\x68\x65\x3f\x55\x25\x59\x4b\xcd\x9b\ \x26\xbf\x4c\xb7\xf4\x5d\x13\xb7\xea\x12\xb6\x65\xc3\x1f\xfd\x73\ \xb1\xd4\xfc\x45\xaf\x9f\x56\xbf\x2e\x68\x77\x44\xa0\x62\xf0\x05\ \xfa\x5e\xdb\x32\xa8\x22\xd8\x9f\xd9\x08\x02\x27\x3c\x78\x07\x1b\ \xe5\x7a\x23\x6b\xa3\x36\xda\x36\x41\x19\x0e\x81\x38\xc2\xcf\x27\ \x6e\x6d\x17\x7f\x87\xe5\x21\x80\x4d\x89\x7b\xdf\x56\x60\x39\x71\ \xd7\x41\xd4\xfb\x61\xb3\x0e\xc3\x35\xdd\x7a\xeb\xce\x02\xfa\x34\ \x53\x95\x64\x0d\xb3\x1b\x6b\xab\xca\xf3\xcf\xc4\x84\x6d\xde\xd7\ \xee\xfc\x59\xb5\x55\x0e\xad\x73\x5f\xd7\xe7\x6f\xbf\xa1\xf1\x67\ \x47\x57\x1e\x66\xbc\xf5\x57\xb6\x95\x77\x9e\x69\xb8\x7b\x8d\xbf\ \xdd\xab\xb6\xca\xad\x84\xaa\x54\x5d\x3f\x92\x2c\x77\x9a\xec\x4b\ \xb9\x52\x89\x1e\xdd\x70\xd9\x6b\x7d\xd4\x83\xfe\x50\x61\x02\x6e\ \x54\x8f\xcc\x2f\x7f\xec\x7e\xb3\x59\x3f\xd5\xd8\xbf\xf2\x6c\xd8\ \x86\x46\x54\x4d\xa9\x5e\x6d\x35\x5c\x4b\xb5\x09\x90\x66\x85\x1f\ \x1f\xb5\xe5\xb8\x35\x32\x34\x13\xa3\xe6\x6e\x4a\xf8\x47\x44\xa7\ \x45\xad\x2f\xbc\xa7\x71\x85\xef\xdf\x6a\xa8\xae\xec\x3a\xd8\x81\ \x6e\x2f\xcb\x50\xa8\x2b\xc9\xc7\xca\x03\xfc\x86\xb0\xfc\x1d\xf1\ \x85\x4d\x17\xd7\x54\x24\x10\xb2\x5f\xfa\x68\xfd\x56\x1c\xbd\x88\ \x63\x27\x3f\x73\x76\xa8\xbc\xad\xf1\x80\x4c\xfd\xb7\xe8\x3e\xc5\ \x5c\xf1\x68\x95\x1b\xac\x86\xaa\xaa\x48\xcc\x67\x4b\x79\xd2\x64\ \xa8\x62\x60\xd2\xf3\xbf\xbf\xdd\xbf\xb4\x02\xd7\xfd\xeb\x9b\xf1\ \xb7\x2d\xb7\xbe\x79\x79\x41\x7f\x78\xa6\x43\x05\xc7\x65\xc4\xe0\ \x25\xd4\x62\x31\xbd\x21\xc2\xf4\x90\x2e\x99\xd9\x69\x1d\x1b\xe5\ \x70\x68\xd5\x6b\x22\x0d\xdf\x56\xab\x4f\x30\x49\x51\xb5\x39\x8c\ \x04\x4e\xaa\xff\x30\x6b\xd2\xa8\x4d\x67\x4d\x2b\x5d\x37\x61\x49\ \x3e\x56\x1e\x34\xf3\x7a\x3f\x71\xd3\x7b\xfe\xb9\xeb\xb6\xdc\x54\ \xa3\x65\xce\xef\x3f\x9d\xd8\x79\xb6\x61\x97\x80\x7e\x2a\x5b\x75\ \x41\x8b\x1c\xf6\x9a\xc1\x06\xb4\x7a\xe4\x9f\x6a\xa8\x2e\xf7\xf4\ \xe9\x8d\xa1\xcc\xcc\xc6\x8f\x1a\x93\x2b\x6d\x0c\x4d\xf9\x9e\x0e\ \xd5\x89\x8b\x1f\x17\xaf\xbc\xb3\x49\xbc\xf0\xf6\x47\xc4\x37\x7f\ \xf3\x49\xf1\xf6\xe2\x0d\xfe\xad\x8d\x6d\x99\x7b\x07\xc1\x89\x5f\ \xf9\xc1\x62\x9a\x84\xf1\x39\x1a\xd3\x61\x9a\xc9\xd6\x0f\x9b\x26\ \xc4\xf0\x6d\x69\x84\x2a\x42\x26\x5c\xa3\x46\x6d\x15\xc1\x8a\xd1\ \xab\x18\x45\x9b\xd6\x00\xa4\xa2\x93\xca\xe9\xcc\x36\x2f\xc9\x20\ \xec\xb2\x50\x95\x4d\x54\x26\xfe\xb0\xb8\xce\x3f\x77\x5d\xd4\x75\ \x5a\xac\xfe\x53\xfd\x96\x9b\x2e\x2e\xd4\x4e\xb1\xed\xc3\xe7\x68\ \x5c\x3f\xb5\x21\x5d\xe6\xaa\x9b\xe6\xf2\xb6\x49\x7d\x65\x65\x1c\ \xe7\x29\x1f\x1c\xb7\x89\x4f\x57\xc0\x2b\xef\x6e\x12\x13\xbf\xfd\ \xb8\xf8\x97\x37\x37\x8b\x8b\x57\xec\x6a\x0b\x1f\xfb\xf9\xc2\x6e\ \x7d\x82\x43\x41\x05\x0b\x8e\x74\x81\x20\x45\xb8\x5e\xd0\x1f\x3a\ \x34\x85\x64\xae\xaf\x3e\x5c\x33\x0a\x8f\xb4\x35\xa2\xae\x6f\x77\ \xd3\xaf\x81\x81\x3b\x51\x4d\xd5\x78\x4e\x18\xb1\x8c\xdd\x4b\x46\ \x47\x47\xab\xa7\x08\xd9\xb4\x06\x24\x15\xcd\x52\x6f\x9f\xfe\xbc\ \xe6\xbc\x32\xd1\xda\x66\x20\x7f\xf0\x7a\x2d\x5f\x73\xb8\x65\x0e\ \x5e\x78\xfb\xa3\xfe\xb9\x15\x5b\xce\xbf\x23\x8e\x7d\xfe\x85\x85\ \x89\x9d\x67\x8e\xe8\xed\x56\xb8\x15\x6e\x4d\xd1\x7f\x1e\xec\xe2\ \xc2\xd1\xcc\xd0\x2a\xb7\xba\x8b\xe1\x6a\x7d\xe7\xc4\xf6\xa9\xb2\ \xa7\xd4\x6e\x29\xd8\xbf\x9a\x0b\x7a\x2d\xca\x1e\xff\x7c\xaa\x54\ \x79\x68\xf2\xe2\xa4\x5e\xfc\x4c\xa8\xe0\xd7\xdd\x36\x5d\xcc\x34\ \x86\xe8\xb4\xc7\x87\x30\xf5\x81\x20\x45\x80\x60\xad\xb5\xdf\xa6\ \x81\x1a\x2b\x42\x76\x7c\x7c\xbc\x5a\x8b\x2d\x6a\x13\x71\xbb\xa0\ \x32\xa1\x3c\x85\x8d\x62\xba\xf4\x97\x25\x31\x49\x3e\x56\x4e\xd8\ \xd6\x56\xff\xeb\xd2\xcd\xe2\xef\x7f\xb9\xbd\xda\x2a\x87\xd6\x39\ \xb4\xcc\xe1\x7c\xd0\x8e\x17\xde\x5c\xb8\x69\x61\x09\x7b\x35\x84\ \x5b\xe0\xa2\x8a\x69\x95\xc3\x2c\x4b\x47\xf5\xd3\x59\xf3\xc3\x2c\ \xf2\xe9\xed\xfd\xe9\xc8\xa8\x54\x98\xfb\x97\xb2\xae\xb2\xa8\xbe\ \xa8\x57\x62\x5a\xeb\x6a\xbe\x3a\x1a\xae\x41\xe7\xbd\x0e\x52\xd4\ \x50\xb1\x1b\x0d\x8e\xcd\x08\xcf\xe9\xe7\x88\xce\xfc\xc4\xe7\xfe\ \x45\xf0\xd8\x08\xf7\x4f\x62\xa0\x0c\x06\xf5\x84\xa1\x56\x18\x9e\ \xf4\x01\x7f\x67\x33\xb0\x26\x89\xc7\x00\x3c\x5f\x94\x38\xcd\xbe\ \x78\x5f\xeb\xed\x13\x1a\x94\xc4\xe4\x0f\xb5\xde\xbf\x5a\x50\xc3\ \x0e\xc2\xfa\x8b\x3b\xa0\x0a\xef\x41\x78\xa6\x29\x1c\x60\x3d\xae\ \xa8\xc9\x1f\x4a\xb3\xa3\xfd\x7d\xde\xb5\x59\x25\xd2\xdb\x03\x02\ \xbb\x84\x54\x77\x87\x49\x80\x74\x75\xcd\xa8\xd7\x32\x65\x72\x0e\ \xa3\x7f\x3d\x4c\x9c\x91\xb0\x7b\xa6\xff\x67\xe6\xaf\x9f\x9a\x8d\ \x33\xb7\x39\xb6\xb7\x18\x94\x69\x3e\x33\xd8\xd6\x3d\xa3\xd7\x42\ \xb5\x42\xda\x5d\x6b\xa3\x80\x4a\xaf\x0e\x0f\x48\x4f\x3e\xdb\xee\ \x60\xd5\x8f\x3f\x57\x71\xd4\xee\xb8\x3b\x40\x83\xfe\xd8\x63\x0b\ \x8a\xbe\x07\xf4\x35\x54\xef\xc7\x50\xb5\x0f\x55\x03\x7d\xbc\x68\ \xea\xc5\x7b\x60\x4a\x2d\x08\xd6\x46\xbb\xf4\x30\x54\x57\x3c\x36\ \x3b\x3c\x50\xf1\x1c\x7c\x4e\xd7\x3e\xb9\x36\x49\x32\x14\xaa\xb3\ \x2a\xb5\x30\xbe\x22\xaf\xf0\xfe\xd9\xee\x5a\x13\x8b\x14\x93\xde\ \x35\x35\xde\x68\x6e\xe5\x88\x8a\x03\xfe\xfe\xb0\x5e\x1b\x65\x5d\ \x28\x0b\xf4\x4a\xea\xd7\x2b\x23\xb2\x06\x88\x15\x68\x42\xa9\x96\ \x3d\x2f\x0e\x1f\x12\x8e\xc4\x0a\x4e\xf6\x17\x37\x46\xc3\x29\x75\ \xec\xd4\xfd\x53\x89\x4c\xe0\xd0\xe9\x50\xb5\x79\x8c\x56\x02\x00\ \x92\x0e\xd5\x30\x84\x2c\x76\x09\x42\xc8\x61\x59\x41\xe8\x3b\xc6\ \xfb\x5b\x6f\x74\x2d\x43\x75\xad\x95\x5d\x6c\x9c\x41\xe9\x88\x3b\ \xfc\xab\xda\xa6\x72\x4d\x0d\xea\xef\x56\x4b\xdf\x55\x29\xc5\x9c\ \xb3\x4e\x36\x7d\x88\xb2\x5c\xab\xe8\x7f\x4b\xfa\x3d\x4c\x7a\x7b\ \xb7\x62\x5e\x87\xf6\x50\x9c\x83\x16\xf8\xe1\x8a\xd1\xbf\xa6\x52\ \x83\x01\x4b\xd4\x69\x7a\xc5\xa0\x29\x01\xa1\xb5\x5b\xaf\x10\x0c\ \xcb\x5e\xa5\x6f\x43\x9f\x24\xfa\x27\xb1\xb2\x1a\x6e\x41\x4a\x3f\ \x1a\x19\x95\x2e\xf6\x95\x92\x37\xfb\x57\x35\xc5\x13\xea\x3d\x55\ \xd1\xcf\xe5\x03\x57\x66\xe2\xec\xab\x15\x57\xa7\x43\xf5\x91\x47\ \x1e\x59\x33\xaa\x17\x93\x39\xd4\x9a\xd0\xc1\x6c\x84\x0d\x0c\x1e\ \x8a\x33\xfb\x8f\xd1\xee\x50\x0d\x0a\x87\x1d\xd4\x7b\x6d\x10\xbe\ \x4f\xb7\x87\x6a\x9a\xd0\xc2\xe4\x78\x12\xa3\x47\x4d\xff\x9c\x1d\ \xfd\x63\xd7\x73\xd5\x36\x9b\x96\xa3\xa2\xf1\xdf\xc3\x76\xcd\x59\ \xbf\xe0\x55\xc4\x58\xdc\xa3\xd3\xe8\xed\x34\xb6\xd1\xa8\xd4\xec\ \xee\xc2\x86\x83\x4c\xda\xa8\x0b\xbe\x5c\x6b\x66\x40\xf7\x7f\x05\ \x21\x6c\x71\x1a\xeb\x17\x29\x3e\x04\xd8\xc7\xea\xe4\xce\x33\x63\ \xad\x14\x3c\x06\x1e\x2b\xc9\x40\xed\x34\x6c\x50\x6d\x76\x93\x09\ \x87\x5f\xbb\x47\xd9\xe2\xb9\xe1\x39\x06\x4b\x5c\x08\xcf\xb4\x9f\ \x2f\x35\x0f\x61\xa8\x7f\xb8\x36\xbd\xc7\x85\xae\x4d\xc7\x9e\x8b\ \xb6\xa8\xaa\xef\xe1\xe2\xba\x6d\xca\xab\x8e\xc0\x4d\x5a\xbf\xe3\ \x8a\x23\x38\x34\x9f\x7f\xb9\x2e\x5d\xe1\x41\xb3\x2f\xba\xb9\x56\ \xf6\x53\xa5\x8e\xc3\x70\x6c\x7c\x41\xb0\x33\x71\x70\xa7\x63\x33\ \x72\x16\x2b\xac\xab\xbf\x40\x51\xc2\x01\xd9\x48\xb0\x2f\xd5\xa8\ \x17\xaa\xe1\xdb\x50\x4b\xb6\x09\x3a\x5b\x68\xca\x45\x6d\x2a\x58\ \x6c\x5e\x63\xf8\xf9\xda\xbe\x3f\x94\xae\xf2\xce\xa9\xb2\xe7\xa9\ \x21\xab\x91\x2d\xfa\x6f\x3d\x21\xb7\x9d\xbc\x8f\x07\x2b\x87\xf2\ \xd0\xe4\xc2\xc4\xfd\x67\x9f\xf4\xd6\x5f\xd9\xa8\x6b\x96\xbb\xf5\ \xf6\xf3\x98\x14\x52\x6f\x2f\x9b\x2f\xba\x36\x33\x8e\xf5\x72\x6a\ \xe7\xd9\xcd\xcd\x1c\xb7\x96\xa1\x9a\x01\xf8\x75\xa3\x4f\xb0\x9f\ \x27\x1c\xd4\x1f\x0c\xec\x5c\x8c\x76\x32\x34\x29\xe0\xb6\x38\x23\ \xd2\xba\x0e\x42\xa8\xde\x60\x9d\x20\x84\x61\x38\x54\x51\xb3\x43\ \x93\x67\x2d\x68\x96\x0c\xf7\x49\xda\x1c\x50\x3b\x2a\xc4\xeb\x89\ \x0a\x78\x9b\xda\x26\x43\x34\x7f\xca\xf7\x4f\xcd\x60\xb0\x11\x46\ \xf1\xd6\xa5\x37\x12\xb2\x47\x56\x07\x26\xc5\x99\x8b\xb6\xdb\xa0\ \x45\x2d\xa9\x56\x3a\x1c\xd6\x12\xeb\xc5\x7f\x68\x6b\x0c\xd5\x8c\ \xd0\xdf\x19\xac\x44\x14\x34\x03\xa3\x86\x6a\x9a\x82\xf5\x2f\x2f\ \xd6\x52\x6b\x41\x1f\x5b\x9c\xda\x23\xc2\x30\xfc\x77\x8d\xfa\xf2\ \x10\xa8\xe1\x11\xb4\x66\x60\x50\x23\x08\xd4\x38\x7f\x17\x84\x50\ \x0d\x07\x2b\x5e\x5f\x9c\xb0\xc4\x6b\x0b\xff\xc0\x88\x0a\x69\xca\ \x20\xfd\x05\xc7\x6e\x31\xee\x3a\x29\x1c\x04\x2c\xf6\x3f\x47\xc8\ \xea\x52\xdd\x65\x46\x5f\x87\x30\x75\x70\xbd\xfe\x5b\xca\x36\x86\ \x6a\xb6\x98\xda\x2a\x9a\x80\xd1\x96\x3f\xaf\xbf\x43\x89\x8c\xba\ \x2d\x2a\x84\x09\x06\x30\xd5\xaa\x15\x9a\xdb\xc3\x13\xe7\xa3\x86\ \x1a\x67\x80\x0c\xfa\x2a\xc3\xb5\x55\x33\x58\x27\x2a\xcc\x11\x80\ \xb8\x2d\x3c\xa0\x26\xae\xa8\x29\x0a\x31\xb9\x43\xd4\xb2\x0c\xdc\ \xf6\xf4\xd3\x4f\xbf\xef\x6f\xa2\x66\x65\xa2\x0c\xd3\x5f\x76\x4c\ \x3f\xea\xa0\x46\x8a\x90\xd5\x05\x61\x5b\x9d\x92\x94\x61\x9a\x1b\ \x0c\xd5\x0c\xd1\xdf\x1b\xd4\x48\x83\x9d\xee\x6c\xf6\x8d\x01\x61\ \x82\x10\x43\x1f\x24\xc2\x05\xe7\x51\x10\xa6\xb8\x2e\xaa\x89\x38\ \xee\x88\x55\x84\x6f\xd4\x08\x5a\x04\x27\xc2\x2e\x6a\x79\xb6\x35\ \xd4\x20\x04\x7d\xb8\x76\x8c\xd7\x87\xc7\xc5\xe3\xa3\xc6\x8d\x59\ \x94\x50\x70\xde\x2c\x33\xdc\x4c\x8c\x40\xad\xd7\xb4\x4d\x44\xed\ \xc1\x50\xcd\x1e\x33\x87\x24\x6a\xa9\x6d\x39\x64\x5a\x51\x20\xec\ \x82\x4d\x9c\x08\x1f\xd4\x48\x51\x6b\x45\xa9\xd5\xdf\x8a\xdd\x38\ \x6c\x02\x07\x01\x15\x15\xac\xa8\x95\x86\x97\x17\x6c\xaa\xc5\x7d\ \x9a\x09\x36\x3c\xbf\xa8\x89\x1b\xf0\xf8\x26\x4c\x4d\xb8\x46\xbd\ \x46\xf4\x15\xdb\xec\xfa\x43\x44\xc9\x61\xa8\x52\x6e\x21\xb0\x50\ \xe3\x8c\xdb\xcc\x69\xfe\x1e\xb5\x41\x5b\x08\x48\x04\x55\x9c\x90\ \x44\x73\x31\xfe\x16\xf7\xa9\x37\xf9\x42\x2d\xb8\x0f\x66\x44\x8a\ \xbb\x3c\xc3\x2c\xd7\x66\xbf\x51\x22\x4a\x56\xa3\x31\x67\x94\xb2\ \x43\x2b\x03\x95\xd0\xa7\xba\x70\x78\x6d\x53\x70\x21\xdc\x75\xd7\ \x5d\xd5\xd7\x87\x00\xb0\xed\xf3\x43\x2d\x70\xe3\xc6\x8d\xab\x03\ \x7a\x50\x9b\x43\xe8\xdc\x7d\xf7\xdd\xd5\xa0\x34\xb5\xc4\x85\x85\ \xb5\xbb\xd6\xa2\xe6\x86\xdb\x51\x03\xbc\xed\xb6\xdb\xfc\x6b\xed\ \x6d\xd9\xb2\x65\xb5\x06\xb9\xb4\xb4\xf4\xbe\xe5\xe0\x39\x0d\x0f\ \x0f\x8b\xaf\x7c\xe5\x2b\xab\xfb\x8c\xe2\xef\x82\xcf\x19\xd7\xe3\ \x72\x1c\x58\x1e\x9e\xb7\x79\xce\x58\x1e\x1e\x2f\x08\xef\x23\xfe\ \x0e\xef\x25\x5e\x9f\x59\x6e\x1c\x78\xbf\xfa\xfa\xfa\x56\x9f\x1b\ \x0a\xde\xcb\x7a\xa2\xee\x63\xb3\x4c\xb4\x26\x84\xef\x1b\xf7\x87\ \x83\x59\xbf\xc1\xfb\x47\xd5\xe8\x6b\x41\xcd\x1e\x8f\xe1\x38\xce\ \xd1\x5f\xfc\xe2\x17\xef\x3f\xc8\x66\x87\x6c\xff\xab\x3b\xad\xc6\ \x4d\x9c\x3f\xfe\x2b\x76\x0b\x65\x18\xbb\xbf\x33\xc6\x9f\xf0\x01\ \x1d\x64\x68\xfe\x2d\xdc\xf0\xcd\x56\x66\x54\xb2\x61\x06\xed\xd8\ \xd4\xf4\x9a\x91\xd6\x72\x82\xcc\x32\x11\xa8\x28\x14\x0f\xfa\x9e\ \xf1\xde\x65\x61\x46\xa5\xa0\x7d\xaf\xed\xd2\x5f\xfb\xf8\x4e\x6c\ \x3f\xc3\xed\x76\x86\xb1\xf9\x37\x03\x4a\x2f\x3f\xb8\xb5\xf4\xa3\ \xe1\x12\xca\x3f\xfd\xe3\x3d\xa3\x7f\xf8\xd3\x1b\xfd\x5b\xa8\x59\ \x08\xb9\x34\x82\x2e\xad\xe5\x04\x99\x65\x32\x50\x89\xb2\x87\xa1\ \xda\x21\xa5\xe9\xc1\xfe\x3d\xaf\xee\x3a\xb8\xe7\xa5\x91\x77\x1d\ \xd1\x33\xeb\xb8\x72\x02\xe5\xe7\xf7\x7d\xe8\xc8\xdf\xfe\xfb\xa0\ \xf8\xbb\x7f\x7b\xa0\x7f\xcf\xcb\xc3\x98\xfc\x81\x88\x88\x72\x82\ \xa1\xda\x01\x7b\x5f\x19\x19\x75\x7a\x6f\xbc\x20\x3c\x75\x48\xc8\ \xe8\x09\xb5\xdf\xfe\xe8\x7a\x7d\xbd\x9c\x18\x7b\x65\x64\x3a\xee\ \xfc\x93\x44\x44\xd4\x59\x0c\xd5\x94\xed\x7d\xf5\xf3\x8f\x2a\x25\ \x9e\xad\x15\xa6\x61\x52\x89\x41\xa7\x4f\xea\x60\x1d\x8c\xf5\xf7\ \x44\x44\xd4\x39\x0c\xd5\x14\xa1\xc6\xa9\x2a\x9e\xd9\x0f\xd5\xc6\ \x80\x5c\x77\x23\x8e\xd9\x47\x44\x44\x19\xc6\x50\x4d\x91\xd3\xe7\ \x1c\x8c\x5b\x43\x0d\x43\x8d\xb5\xf4\x22\x0e\xa2\x4c\x44\x44\x59\ \xc5\x50\x4d\xc9\x4a\xbf\xa8\x6a\x69\xe0\x91\x74\xa5\x39\x14\x1c\ \x11\x11\x65\x10\x43\x35\x25\x4e\xaf\xc4\xa1\xdc\x5a\x52\xad\xad\ \xb2\x6f\x95\x88\x28\xb3\x18\xaa\x29\x51\x42\x6d\xf5\xcf\xb6\xa6\ \xaf\x97\x23\x81\x89\x88\x32\x8a\xa1\x9a\x12\x47\xc8\x3b\xfc\xb3\ \xad\xf1\x5c\xd6\x54\x89\x88\x32\x8a\xa1\x4a\x44\x44\x94\x10\x86\ \x6a\x06\xac\x77\x97\xc5\x43\x1f\xbe\x28\xc6\x6e\xfb\x95\xb8\x6f\ \x63\xed\x83\x51\x13\x11\x51\xb6\x31\x54\x3b\xec\x96\xbe\x6b\xe2\ \x6b\x77\xfe\x4c\x7c\x41\x87\xea\x7d\x7f\xf2\xbf\x62\xec\xf6\x5f\ \x8b\x2f\x7d\x64\xed\x01\xa7\x89\x88\x28\x1f\x18\xaa\x1d\xf6\xb9\ \x5b\xdf\x14\xb7\xea\x60\x0d\xfa\xdc\x87\x7e\xa7\xaf\xbb\xea\x5f\ \x22\x22\xa2\xbc\x60\xa8\x76\xd8\x2d\xbd\x6b\x03\xd5\x40\x0d\x96\ \x88\x88\xf2\x85\xa1\xda\x61\x17\xaf\x46\x1f\xbe\xeb\x0f\x8b\xeb\ \xfc\x73\x44\x54\x70\xf3\xfe\x69\x2c\x8f\x9f\xdf\x95\xcc\x9e\x04\ \xd4\x16\x0c\xd5\x0e\xfb\x8f\xb7\x3f\x22\x2e\x5e\x59\xef\x5f\x5a\ \xf1\xfc\xef\x6f\x17\x6f\x2f\xde\xe0\x5f\x22\x22\xa2\xbc\x60\xa8\ \x76\xd8\xe5\x4a\x8f\xf8\x87\x5f\x6d\x15\xdf\xfc\xcd\x27\xc5\xc4\ \x6f\x3f\x2e\x0e\xfc\xfc\xd3\xe2\x7b\x3a\x54\x89\x88\x28\x7f\x18\ \xaa\x1d\x26\x5d\x29\x9c\x3e\x29\x7e\xbd\xdc\x2f\xce\x5d\xfe\xb0\ \xb8\xd6\xd3\x8b\x29\x0d\xf5\x0d\xfe\x1f\x10\x11\x51\x6e\x30\x54\ \x3b\x45\x87\x26\xc2\xd4\xe9\xd5\x67\x83\x6b\x01\x79\xea\x0a\xe1\ \xea\xdb\xd6\x5c\x4f\x44\x44\x99\xc7\xcd\x76\x87\x38\x3d\x0d\x42\ \xd3\x0f\x5d\xd6\x58\x89\x88\xf2\x83\xa1\xda\x01\x08\x53\xd4\x46\ \xe3\xa8\x36\x05\x13\x11\x51\x2e\x30\x54\x3b\x00\xfd\xa8\x71\x55\ \x6b\xb3\xcc\x55\x22\xa2\x5c\x60\xa8\x76\x02\x43\x92\x88\xa8\x90\ \x18\xaa\x29\xf1\x84\x7a\xc3\x3f\x6b\x4d\x32\x84\x89\x8a\x4b\xc9\ \x05\xff\x5c\x2c\x8b\xcb\x4b\x1b\xfd\xb3\x94\x41\x0c\xd5\x94\x48\ \x65\x37\x6b\x4a\x4d\xcb\x57\xe7\xfc\x73\x44\x54\x04\x8e\x67\x15\ \xaa\x4a\xf2\x98\xca\x59\xc6\x3a\x50\x4a\x4a\x2f\x0e\x0f\x3a\x8e\ \x9c\xc6\x79\x8c\xea\xb5\xd9\x5d\xc6\x5b\x54\x42\x79\xd5\x95\x35\ \x77\x72\xe7\xd9\x6d\xfe\xd5\xb9\x34\x3a\x3a\x3a\xa0\x94\xba\xb0\ \x61\xc3\x06\xb1\x63\xc7\x0e\xff\x5a\xa2\xf6\x3b\x77\xee\x9c\xb8\ \x74\xe9\x92\x70\x5d\x77\xe0\xf4\xe9\xd3\x4d\xb7\x1c\x25\x6d\xdf\ \xf9\x91\x69\x9d\x94\x83\xfe\xc5\x86\x94\xe7\x0d\xfd\xf3\x9f\x4f\ \xcd\xf8\x17\x29\x63\x18\xaa\x29\x1a\x7b\x69\x64\x5a\x4a\x31\xe8\ \xae\xd3\x6f\xbb\xc5\x3b\x5f\xb9\xa6\xf4\x37\x09\xe7\xd4\xd8\xa9\ \x9d\x53\xe5\xea\x95\x39\x65\x42\xd5\xbf\x48\x94\x3a\x86\x2a\xb5\ \x13\x43\x35\x45\xa6\xb6\xda\x64\xa8\xce\x9f\xda\x79\x76\xb3\x7f\ \x55\x6e\xe9\x50\xed\xaf\x54\x2a\xa3\xfe\x45\xa2\xd4\xe9\x50\x9d\ \xd4\xac\x9a\x5c\xdb\x89\xa1\x5a\x2c\x0c\xd5\x94\xed\x79\x71\xf8\ \x90\x7b\xa3\x73\xd0\x32\x54\xe7\x3d\xa9\x86\xca\xf7\x4e\x25\xd3\ \x2f\x4b\x44\x99\xc1\x50\x2d\x16\x0e\x54\x4a\xd9\xa9\xfb\xa7\x0e\ \xe9\x40\xb5\x0a\x47\xb7\x67\x79\x37\x03\x95\x88\x28\xfb\x18\xaa\ \x39\xd0\xd7\xd3\xfb\xae\x7f\x96\x88\x0a\x46\x79\xc2\xaa\x7f\xd7\ \x53\xce\x80\x7f\x96\x32\x88\xa1\x4a\x44\x44\x94\x10\x86\x2a\x11\ \x11\x51\x42\x18\xaa\x44\x44\x44\x09\x61\xa8\x12\x11\x11\x25\x84\ \xa1\x4a\x44\x44\x94\x10\x86\x2a\x11\x51\x07\x29\x61\x37\xa1\xbe\ \x23\x15\xe7\xfe\xcd\x30\x86\x2a\x11\x51\x07\x49\x25\x2c\x67\x77\ \x92\x0c\xd5\x0c\x63\xa8\x12\x11\x11\x25\x84\xa1\x4a\x44\x44\x94\ \x10\x86\x2a\x11\x11\x51\x42\x18\xaa\x44\x44\x44\x09\x61\xa8\x12\ \x11\x11\x25\x84\xa1\x4a\x44\x44\x94\x10\x86\x2a\x11\x11\x51\x42\ \x18\xaa\x44\x44\x44\x09\x61\xa8\x76\x82\xb2\x9b\x41\x65\x71\x79\ \x69\xa3\x7f\x96\xba\xc3\xa0\x2e\x13\xba\x5c\xd0\x45\xf9\x05\xc7\ \xd4\x9d\xd6\xa5\xa4\x0b\x11\x65\x14\x43\xb5\x13\x1c\xcf\x2a\x54\ \x95\x74\x39\x83\x4a\xf7\x38\xa2\x0b\xc2\x73\x54\x97\x19\x5d\x0e\ \xeb\x72\x4c\x17\x7c\x66\x4c\xd8\xe2\x76\x7e\x26\x88\x32\x88\xa1\ \x4a\x94\x1d\x07\x75\x79\x52\x17\x04\xe8\x66\x5d\xc6\x74\x39\xe4\ \x5f\x87\xcb\xcf\xe8\x02\x08\x57\xfc\x2d\x11\x65\x0c\x43\x95\x28\ \x1b\x50\xf3\x44\x80\x02\x42\x35\xaa\x35\xc3\x04\x2e\xe0\xfc\x1d\ \x2b\x67\x89\x28\x2b\x18\xaa\x9d\x60\xd9\xa7\xaa\xa4\xc7\xa6\x3e\ \x02\x7c\x6e\x4c\x6d\x15\x86\xfc\x53\x22\xca\x08\x86\x6a\x07\x28\ \xa5\xde\xf3\xcf\xc6\xa2\xbc\x1e\x86\x6a\xf1\x21\x30\xd1\x7f\x3a\ \xe7\x97\x5a\xe6\xfd\x53\x18\xf0\x4f\x89\x28\x23\x18\xaa\x44\xd9\ \x81\xe6\xdf\x6d\xba\xec\xae\x5e\x6a\xcc\xaa\xc5\x83\x32\x4a\x2a\ \xcb\x1f\x47\x8a\xeb\x3d\xc3\x18\xaa\x1d\x60\x7f\x50\x62\xd6\x48\ \x68\xd5\x9f\xf9\xa7\x80\xd1\xc1\xd4\x65\x3c\xcb\xee\x23\x4a\x17\ \x43\xb5\x03\x6c\x0f\x4a\x2c\x79\xa4\x7f\xba\x0e\x23\x7f\x01\x81\ \x5a\xaf\x99\x98\x72\x42\x4a\x79\xb3\x7f\x96\x0a\x80\xa1\xda\x01\ \x9e\xb3\xa6\x5f\xac\x31\x7e\xe9\x68\xc5\x01\x5d\x4c\xab\x05\x76\ \xb7\xa1\x22\xb0\xfc\xd1\xec\x48\xcf\x6e\xfb\x41\xa9\x62\xa8\xe6\ \x81\xf2\x38\xa3\x12\x21\x4c\xcd\x2e\x37\x18\xd0\xc4\x0d\x6b\x51\ \x78\x0e\x5b\xa2\x0a\x84\xa1\xda\x01\x6e\xc5\xb2\xd9\x4e\x3a\xc1\ \x7e\x34\xea\x3e\x08\x54\x33\x8b\x12\x66\x57\x32\xe1\x4a\x45\x60\ \x39\x66\x62\x9d\xeb\x60\xfa\x4a\xca\x28\x86\x6a\x07\xb8\xbd\x76\ \xd3\x14\x6a\xfc\x25\xdb\xbd\xb0\xee\x9f\xd5\x05\x1b\x5e\xec\xa3\ \x8a\x49\x1f\xa8\x50\xec\x9a\x7f\x2f\xc9\xcb\x56\xbb\xe4\x51\xba\ \x18\xaa\x1d\xf0\xed\x6d\x53\xb6\x4d\x77\xfd\x8f\x9f\xdf\xc5\xd9\ \x73\xba\x8f\xa9\xa1\x6e\xd5\x05\x35\x54\x4e\xa6\x5f\x30\xfb\x7e\ \xbc\x0b\xeb\xd6\xc6\x42\x79\xdb\x0c\x47\xff\x66\x18\x43\xb5\x73\ \xac\x82\xf5\xca\x32\x67\xcf\xe9\x32\xc1\x40\x45\x1f\x2a\x6b\xa8\ \x05\xe4\x39\x96\xfb\xa8\x4a\xc5\xbe\xf4\x8c\x63\xa8\x76\x8e\x55\ \xbf\xaa\xeb\x28\xdb\x5f\xb4\x94\x5f\x58\xd7\x08\x54\x6c\x70\xc7\ \x75\x89\xea\x43\xc5\x6d\xb8\x9e\x5d\x03\x39\x26\x95\xb4\xfb\x5e\ \x7b\xea\x0d\xff\x1c\x65\x14\x43\xb5\x43\x3c\x25\xec\xbe\x1c\x8e\ \x78\xc0\x3f\x47\xc5\x86\x43\xbe\x99\x41\x49\x68\x9d\x38\xaa\x4b\ \x14\x73\xa4\x1a\xee\x6e\x95\x63\xd2\x55\x56\xdf\x6b\xa5\x1c\xee\ \x9b\x9c\x71\x0c\xd5\x8e\x51\x76\xb3\xe1\xe8\x5f\xb4\xfb\x7e\xfc\ \x20\x6b\xab\xc5\x86\x90\xc4\xa0\x24\xf4\x99\x61\xba\xc2\x7a\x9f\ \x11\xfe\xc8\xca\xb9\xd2\xec\x68\xbf\xfe\x5e\x9b\xc9\x3c\x62\x51\ \x72\x99\xa1\x9a\x71\x0c\xd5\x0e\xa9\xb8\xeb\xac\xa7\x98\x93\xb2\ \x17\xb5\x18\x2a\x26\x04\xaa\x69\xe6\x45\x2d\x15\xb5\x55\xec\x3a\ \x51\xab\xf0\xb3\x90\x73\xbd\xde\xa2\xf5\x3a\xec\x5d\x62\x4d\x35\ \xeb\x18\xaa\x1d\x52\xde\x36\xb9\x20\xa4\x5d\x6d\x55\x39\xe2\x40\ \xe9\xd5\x61\xbb\x81\x0d\x94\x17\xc1\x1a\x0b\x42\x15\xeb\xb9\x5e\ \x61\x5f\x6a\xde\x49\xf5\xa8\x7f\x2e\x16\x29\xd4\xfc\xb7\xef\xb5\ \xde\x73\x80\x52\x26\xfd\x53\xea\x80\x2f\xff\x64\xd7\x21\xe9\x54\ \x6b\x28\xf1\xe9\x20\x3e\x71\xf7\x59\x8e\x04\x2e\x1e\xec\x2e\x83\ \xb0\xb4\x85\x3e\x57\xee\x62\x91\x33\xfb\x5e\xdb\x85\xf5\x3d\xb1\ \x72\x29\xb6\xf2\x89\xed\x67\x38\x3d\x65\xc6\x31\x54\x3b\xa8\xba\ \x8f\x9a\x2b\x66\xfd\x8b\xb1\xe9\x5f\xac\x93\x8b\xce\x95\x31\xee\ \xaf\x46\x94\x3f\x25\xfd\xbd\xef\x71\xe5\xb4\xfe\x1e\x5b\xb5\x36\ \x78\x6a\x79\xf7\xc9\x7b\xbe\x3f\xe9\x5f\xa4\x8c\x62\xa8\x76\xd8\ \xbe\xf3\x23\xd3\xb6\x83\x15\x7c\x68\x06\x3a\xac\x7f\xb9\x96\x57\ \x2e\x12\x51\x96\x95\x66\x07\xfb\xfb\xc4\xfa\x03\x9e\x27\x9f\xb4\ \x0d\x54\x34\xfd\x1e\xdf\x7e\x76\xb3\x7f\x91\x32\x8c\xa1\xda\x61\ \xfb\x5e\x1b\x2e\x09\xe1\xd8\x36\x03\xad\xc2\xb1\x59\x1d\xe1\xcd\ \xe8\xd3\xe7\x44\x65\x69\xee\xc4\xa7\x7f\xc0\x81\x0c\x44\x19\xf1\ \xd8\xab\xc3\x03\x4b\xbd\x72\xab\xeb\x8a\x07\x3c\xcf\x29\xd9\x86\ \x69\x00\x9b\x7e\x73\x82\xa1\x9a\x01\xfb\x5f\x1b\xb9\xa0\x43\xb1\ \x99\xfe\xb4\x1a\xd4\xbc\x5e\xb3\xd5\x01\x0d\x8e\x92\xab\x03\x1b\ \x2a\x9e\xe5\xbe\xb1\x44\x14\x8b\xeb\x88\xd5\x69\x44\x95\x0e\x4e\ \x25\xe5\x80\x52\x72\xa0\x85\x10\x5d\xc3\x5d\xf4\x36\x73\x90\x52\ \x3e\x30\x54\x33\x60\xef\x4f\x47\x46\x1d\x29\xb1\x7f\x22\x11\x51\ \x18\x6b\xa9\x39\xc2\x5d\x6a\x32\xe0\xe4\x3d\x67\x27\x6d\x77\xaf\ \x21\xa2\xae\xb0\xa0\x6b\xa9\x98\xfb\x99\x72\x82\xa1\x9a\x11\xae\ \x54\x63\x18\x8c\xe0\x5f\x24\x22\x12\xca\x53\x87\xd9\xec\x9b\x2f\ \x6c\xfe\xcd\x90\x66\x77\xb1\x21\xa2\x42\x62\xb3\x6f\x0e\xb1\xa6\ \x9a\x21\x27\x3e\x7d\x66\x4e\x08\x8f\x5f\x22\xa2\x2e\x27\xa5\x98\ \xdb\xe0\x5c\xc6\x11\x8a\x28\x67\x18\xaa\x19\x73\x62\xfb\x54\xd9\ \x53\x6a\xb7\xfe\x5a\x71\x62\x07\xa2\x2e\x84\x40\x5d\x2f\x2f\x0f\ \x1d\xe5\xe4\x2e\xb9\xc4\xe6\xdf\x8c\x7a\x6c\x76\x78\xc0\xf3\xe4\ \x74\xb2\xbb\xda\x10\x51\xa6\x29\x75\xec\xc4\x3d\x67\x79\x40\xfa\ \x1c\x63\xa8\x66\x1c\xe6\x07\x76\x1c\xf5\x28\xc3\x95\xa8\xb8\xaa\ \x83\x14\x95\x18\x3f\x8e\x3d\x01\x28\xd7\x18\xaa\x39\x80\x5a\xeb\ \xf2\xb2\x53\x62\xb8\x12\x15\xce\x82\xf2\xc4\xb1\x9b\x7a\x2e\x1f\ \x65\x73\x6f\x31\x30\x54\x73\xa6\x3a\xad\xa1\x72\xbe\xa8\xd7\x1c\ \xe6\x0b\xe6\xe1\xbf\x88\x72\x48\x29\x35\xe3\x49\xf5\xcc\x07\x9d\ \xab\x93\x0c\xd3\x62\x61\xa8\xe6\xd8\x97\x7f\x32\xac\x83\x55\x6e\ \x95\x52\x3e\xa0\x84\x18\x90\x92\xc7\xd9\x24\xca\x1e\x34\xed\xca\ \x39\x21\xd5\x1b\x9e\x12\x33\x1f\x70\xaf\xcc\x30\x48\x8b\x8b\xa1\ \x5a\x30\x4f\xce\x0e\xf6\x5f\x5a\x5a\x3f\xa0\xa4\x87\xf9\x47\x75\ \xb9\x3e\xf7\xa8\xe3\x39\x6c\x3a\x26\x6a\x0b\xb5\xe0\x39\x6a\x35\ \x28\x1d\x4f\xcc\xf7\x2c\x8b\xf9\x1b\x6e\xb8\xba\xc0\x00\x25\x22\ \x22\x22\x22\x22\x22\x22\x22\x22\x22\xca\x39\x21\xfe\x1f\xf5\x3f\ \xad\x40\x47\xc3\xfd\x1e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x00\xd1\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x86\x49\x44\x41\x54\x38\x8d\xd5\xd1\x3d\x0a\ \xc2\x50\x10\x04\xe0\xcf\x84\x74\x5e\x44\x09\x08\x82\x57\xb0\xb0\ \x92\x1c\xca\xc2\x83\xa4\x91\x5c\x44\x3c\x83\x95\x7f\x67\x10\x82\ \x29\xb4\x90\xe0\x7b\x42\x52\x84\x0c\x2c\x2c\xcc\xec\xec\xb0\xcb\ \x98\x50\x62\xd5\xc7\x60\x8d\x07\x96\x7d\x4c\xb6\xb8\x61\x1e\x13\ \x4d\xb1\xc3\x19\x35\x5e\x3f\xea\x88\x49\x68\xf8\x84\xea\x13\x35\ \x6b\xf1\x05\xae\x98\x85\xb6\xef\x71\x08\x70\x1b\xdc\xb1\x88\xc5\ \xbf\x20\x0f\x70\x7f\xbf\x90\xe1\x89\x34\x26\x6a\x23\xf9\xea\x53\ \xef\xa3\xd5\x5d\x0d\x3a\x61\x78\x83\xe1\xd1\x00\x11\x26\x16\x72\ \x44\x98\x03\xe0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ \x00\x00\x01\x1a\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\xcf\x49\x44\x41\x54\x58\x85\xed\x96\x41\x0a\ \xc2\x30\x10\x45\x9f\x42\x77\xc5\x73\x68\xdd\xa8\x3b\xcf\xa2\x47\ \x52\x51\xd0\xc3\xe8\x35\xbc\x41\xd1\x0b\xd8\x8d\x52\x21\x2e\x66\ \x0a\x6e\xda\x26\x25\x45\x90\xf9\x10\x7e\x20\x93\xfc\x97\xb4\x25\ \x05\x93\xa9\x5d\x4e\x5b\x2f\xf3\x86\x1d\x16\x8e\x2a\x03\x30\x00\ \x03\x30\x00\x03\xf0\x01\x28\xd4\xd3\x80\x75\x47\xea\x8f\x18\x00\ \xb9\xfa\x34\x00\xa0\xaa\xcd\x1b\xab\x3c\x01\x2e\xea\xab\x00\x80\ \xb5\xfa\x39\x60\x4e\xad\x32\xe0\x0d\x3c\x81\x99\x47\xfd\x02\x78\ \x01\x25\x30\x8e\x01\x00\x70\x40\xee\xf6\x1b\x30\x6f\x09\xbf\x6b\ \xed\x2e\x56\x38\x40\x82\x3c\x0a\x87\xec\xee\x08\x2c\x91\x17\x33\ \xd5\xfe\x49\xc7\x1c\x72\xf4\x49\x4c\x80\x0a\x62\x8f\x1c\xad\xab\ \x69\x25\xb2\xf3\xe8\xe1\xdf\xca\x80\x2d\x70\x45\x3e\xd1\x42\xfb\ \x1b\x60\xd2\x67\xf0\x7f\x6a\xd0\x30\xd6\xe5\x4f\x38\x38\xeb\xe7\ \x77\x81\xc9\xf4\x01\x03\x59\x2a\x74\x94\x02\x88\x2a\x00\x00\x00\ \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x85\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x3a\x49\x44\x41\x54\x58\x85\xed\xd4\x21\x0e\ \x00\x20\x0c\x03\xc0\xc1\xff\xff\x0c\x9e\x2c\x20\x08\x20\xb8\x93\ \x35\x35\x4d\x23\xe0\x77\x25\xc9\xda\xcd\xce\x7a\xb8\x8c\xa5\x6c\ \x03\xa3\xdd\x4d\x4c\x3b\x6c\xe0\x39\x3f\x80\x1f\xc0\x0f\x00\x74\ \x6a\x27\x05\x10\x5a\x70\x8c\x42\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ \x00\x00\x0d\x3b\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x64\x00\x00\x00\x64\x08\x06\x00\x00\x00\x70\xe2\x95\x54\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ \xa8\x64\x00\x00\x0c\xd0\x49\x44\x41\x54\x78\x5e\xed\xdd\x65\x8c\ \x34\x49\x1d\x06\xf0\xc3\xdd\xdd\xdd\xdd\xdd\xdd\x3d\x10\x24\x10\ \x82\x7c\x21\x40\x80\x0f\x90\x00\xc1\x12\x2c\x81\x0f\x38\x81\xe0\ \x10\x20\x84\x04\x09\x10\x9c\xc3\x1d\x0e\x77\x77\x77\x87\xe7\xf7\ \xee\xf6\xbd\xc3\xa4\xab\xba\x67\xae\xbb\x67\x76\xa7\x9f\xe4\xc9\ \xde\xee\xed\xbb\x53\x5d\xff\xaa\xbf\x57\xf5\x31\x33\x66\xcc\x98\ \x31\x63\xc6\x8c\x19\x33\x46\xc0\x89\xf6\xbf\x1e\x04\x9c\x23\xbc\ \xd0\x3e\xcf\x16\x9e\x76\x81\x27\x0b\xff\x19\xfe\x29\xfc\xe3\xfe\ \xd7\x5f\x86\xdf\xdd\xe7\x2f\xc2\x03\x81\x6d\x14\xc8\x89\xc3\x33\ \x86\x57\x0e\xaf\x16\x5e\x35\xbc\x48\x78\xfa\xf0\xe4\xa1\xc9\x3f\ \x49\xe8\xf7\xd0\x33\xe0\x7f\xf7\xf9\x9f\x7d\xfe\x3b\x24\xa4\xbf\ \x87\xbf\x0f\xbf\x19\x7e\x3a\xfc\x54\xf8\xb9\xf0\x0f\xa1\xdf\xdf\ \x2a\x6c\x8b\x40\x4e\x1a\x9e\x2f\xbc\x71\x78\xa3\xf0\xd2\x21\x01\ \x34\x3b\xe0\x94\x61\x33\xf9\xab\xa2\x11\xd2\x5f\xc3\x3f\x87\x76\ \xcf\xef\xc2\x2f\x85\xef\x0d\xdf\x1f\xfe\x34\x24\xc0\x8d\x63\xd3\ \x02\x39\x7b\x78\xed\xf0\x26\xa1\x1d\x71\xae\xf0\x2c\x21\x21\x10\ \xc0\x58\x20\x20\xaa\xed\x57\x21\x61\x7c\x26\x7c\x77\xf8\xf1\xf0\ \xd7\xe1\xc6\xb0\x29\x81\x9c\x33\xb4\x13\x6e\x15\x5e\x31\x3c\x77\ \x78\xa6\x70\x4c\x21\x94\x60\x67\xfc\x36\xfc\x51\x78\x5c\x48\x30\ \x1f\x08\x7f\x18\x4e\x8e\xa9\x05\x72\x9e\xf0\x16\xe1\x0d\xc3\x2b\ \x84\x0c\xf4\xe9\xc2\x6d\x81\x5d\xf3\xfd\xf0\xf3\xe1\xbb\xc2\xf7\ \x84\x3f\x0b\x27\xc3\x54\x02\x61\x03\xae\x17\xde\x21\x24\x8c\x0b\ \x87\xa7\x0a\xb7\x15\x6c\xcd\xb7\xc2\x0f\x86\x6f\x0f\x8f\x0d\xd9\ \xa0\xd1\xc1\x5b\x19\x1b\x97\x09\xef\x13\xde\x37\xbc\x65\x78\xde\ \x90\xa7\xb4\xcd\xe0\xcd\x71\xb3\x2f\x19\x5a\x3c\xd4\xe9\x6f\xc2\ \xd1\xed\xcb\x98\x3b\xc4\xa4\xb3\x13\x77\x0f\x6f\x1d\x8a\x1d\x86\ \x58\x00\x8d\x41\xfe\x5b\xc8\xa5\xfd\xc7\xfe\xcf\xd8\x1f\x13\x69\ \x37\xda\x7d\xa7\xd9\xff\xd9\x09\x05\x1b\xc3\xf0\xbf\x2d\x7c\x43\ \x68\xb7\xfc\x2b\x1c\x05\x63\x08\xc4\xdf\xb4\xa2\x6e\x13\xde\x3f\ \xbc\x66\x68\x92\x56\x45\xe3\xae\xf2\x84\x7e\x12\xfe\x3c\x64\x7c\ \xb9\xac\x56\x2a\xb5\x42\x8d\x10\x8a\x49\x23\x6c\x9f\x83\xbc\x34\ \xde\x9a\x78\xc6\x58\xac\x76\x8e\xc3\x59\x43\xe3\x5b\xe7\xb9\x7d\ \xde\x47\xc2\x97\x85\xef\x0c\x8d\x63\x70\x0c\x2d\x10\x2b\x92\xe1\ \xb6\x2b\x1e\x12\x52\x4f\xab\xec\x0a\x42\xb0\xe2\x19\xd2\xef\x85\ \x3c\x1d\xba\xfc\xeb\xe1\xb7\x43\x2b\x55\x04\x4e\x08\x5d\x20\x18\ \xbb\x92\x20\x04\x96\x97\x08\x2f\x1a\x8a\x77\x2e\x18\xf2\xf4\xc4\ \x3f\xab\xcc\x81\x9d\xf1\x9d\xf0\x79\xe1\xeb\x43\x19\x80\x41\x83\ \xcb\x21\x05\xe2\xe1\x3c\xe8\xbd\xc3\x47\x85\xa7\x0e\xfb\xfe\x7d\ \x3b\x41\xe4\x4c\x08\x22\x6a\xd1\xf4\x87\x43\xde\xce\x90\xc6\xd4\ \x98\xae\x12\x5e\x37\x94\x05\x20\x28\x63\x5e\x25\xee\x21\x00\x91\ \xff\x33\xc2\xd7\x86\xdc\xe5\xad\x08\x2a\x17\xc1\x5e\x5c\x2a\x7c\ \x66\x68\xf5\x1a\x74\x1f\x7a\x10\xc6\xf2\xb3\xe1\x8b\xc3\xdb\x85\ \x67\x08\xa7\x00\x55\x76\xe7\xf0\xe5\x21\xc1\x53\x87\xc6\xd3\x36\ \xce\x65\x5a\x40\x9e\xf3\x29\xa1\x5d\x67\x31\x6e\x0d\x0c\x86\x37\ \x62\x1b\xdb\xd2\x6d\x0f\xd0\x46\x3a\x99\x2a\x7a\x51\xc8\xf8\xaf\ \x63\x67\x86\x00\x07\xe0\xe6\x21\xdb\x60\x77\x1a\x97\x09\x6f\x1b\ \xf3\x32\x3d\xef\xb3\x42\x3b\x6d\x0a\x8f\xb5\x13\x06\x61\x30\x4f\ \x0d\xfb\x0a\xc3\xef\x31\xca\x0c\xe3\xdd\x42\xde\xd0\x36\x80\xda\ \x62\xfb\x8c\xcb\xae\x5d\xe5\x79\x9e\x1c\x9e\x3f\xdc\x44\xa6\xe1\ \x78\xb0\x0f\x8c\xf6\xe3\xc3\xbe\x6a\x8a\xc1\x96\x0e\x7f\x74\xc8\ \xf8\x6f\x23\x2e\x10\x3e\x31\x14\xb1\xcb\x16\xb7\x3d\xc7\x32\xb9\ \xe0\xec\x26\x47\x61\x68\x47\xa9\x37\xce\x1c\x3e\x3c\x94\x3d\x6d\ \x1b\xe4\x32\xad\xa4\x8f\x86\xdc\xe1\x53\x84\xdb\x0c\x6a\xec\xae\ \x21\xe7\xa2\xef\x4e\x11\x1b\x3d\x30\x9c\xca\x06\xfe\x1f\xd8\x8d\ \x7b\x84\x56\x51\x5f\x7d\xfb\xca\x50\x5a\x7d\xe8\x15\xc4\x8d\xc5\ \xa1\x41\xfd\xc8\x40\x0b\x06\xdb\x9e\x67\x99\xe6\x81\x4b\x7c\xdb\ \x70\x72\x7b\xc2\x08\xab\x25\xf4\xf1\x4a\x0c\x94\xc1\xbf\x58\x38\ \xa4\x8e\xbd\x63\x28\x36\x69\x3e\x47\xbc\x72\xfb\x70\x48\x98\x58\ \xde\xe3\xf3\xc3\x3e\xcf\x6a\x37\xc9\x7d\x5d\x2b\x9c\x0c\x3c\x2a\ \x9e\x91\xf8\xa0\x6d\x50\x8b\xf4\x10\x0c\x9e\xac\xee\x90\xab\x86\ \x30\xda\x76\xa6\xcf\x1b\x5a\x28\x0d\xc4\x1d\x7d\x84\xf2\x97\xf0\ \xd9\x21\x67\x67\x74\x70\x4d\x1f\x16\xfe\x38\x6c\x1b\xcc\x32\x9f\ \x13\x0e\x2d\x0c\x2a\x6f\x71\x67\x2c\x93\xeb\x3a\x06\x2e\x1e\x3e\ \x37\x6c\xfb\xcc\x65\x0a\x16\x1f\x1c\x8e\xee\xca\xdf\x2c\x54\xc0\ \xe9\x32\x74\x56\x12\x9b\xe1\x21\x86\xd6\xa7\xdc\xcb\xb6\xcf\x5c\ \xe4\x18\x36\x05\x64\xae\xa5\x4c\xda\x3e\x73\x91\xe6\x47\x32\x52\ \xc9\x61\x34\x28\xaf\xda\x8a\x92\x6a\x6d\x83\x68\xc8\x55\xfc\x44\ \x78\xd9\x70\x0c\xe3\x66\x52\xda\x3e\x77\x91\x7e\x67\x0c\xc8\x48\ \x48\xb9\x7c\x32\xec\x72\x66\xc4\x32\x22\x79\x65\xea\x51\x20\x47\ \xf5\x85\xb0\x36\x10\x2b\xe3\x07\x21\xd7\x76\xac\x20\x69\x93\x02\ \x01\x29\x7e\x36\x4c\x62\xb1\x66\x53\xcc\x13\xc1\x49\xcf\xf4\x46\ \xdf\x49\x13\x00\xf2\xac\xe4\x6d\x4a\x6e\xab\x41\xf0\xc5\x5f\x18\ \x2a\x7d\x1a\xd0\x61\x84\xe0\xd6\xf3\xbd\x20\x6c\x02\xe2\x36\x98\ \x27\x1e\x9a\x0a\xa9\x80\xb1\x17\xfa\x0a\xe4\xfa\xa1\x2c\xa9\x6c\ \x69\x09\xa2\x55\x5d\x1b\xaf\x08\xfb\xa4\xc7\x0f\x32\xe4\xbb\x5e\ \x1a\x0a\x1c\x09\xa8\x04\xe9\x98\x6b\x84\xbd\xdd\xe0\x3e\x02\x51\ \x53\xd0\xa6\xa3\x94\x59\x82\xdd\xa0\x56\xf1\x92\x90\x07\x76\xd8\ \x61\x57\x50\xcd\xdc\x7f\x79\xb9\x9a\x36\x50\x87\xb1\x4b\x14\xcc\ \x3a\xd1\x47\x20\x24\x7c\xf9\xb0\xd6\x1d\xa2\x96\xa1\x21\x40\x62\ \x6e\x97\xf0\xd6\x50\x49\x57\xec\x51\x82\x54\x8a\x88\xff\x4a\x47\ \xbe\xeb\x40\x97\x40\xa4\x48\x48\x57\xc2\xad\x04\xab\x83\xdf\xfd\ \x9a\xd0\x56\xde\x25\xb0\x99\xea\x29\xca\xcb\xb5\x5d\x22\x48\x54\ \x14\xeb\xf4\x3a\xbb\x04\x22\x23\xab\x7f\x4a\x22\xb1\x04\xc9\x45\ \x05\x26\x89\xc3\x5d\x84\xa6\x3a\xbd\xc2\xb5\x5d\xc2\xf5\xd5\x10\ \xd8\x69\xdc\xbb\x04\x22\xb0\x11\x64\x95\x24\x4b\x97\xaa\x7b\xbf\ \x39\x9c\xa4\x6f\x69\x0b\xc1\x99\x79\x63\x68\x97\x94\x3c\x2e\xf3\ \xa7\x54\xac\x6d\xb6\x8a\x9a\x40\xb8\x6d\xbc\x2b\x1d\x1b\x25\x08\ \x02\xa5\x31\xac\x92\x5d\x06\x37\x58\xe6\x5b\x1c\x56\x82\xd0\x81\ \x40\xaa\xd9\xee\x9a\x40\x74\x9f\x4b\x24\xfa\x5a\x82\xe0\x48\xf0\ \x33\x4a\x4b\xcc\x01\x82\xa8\x5c\x76\xa2\xd6\x48\xa7\x86\xdf\x35\ \x9f\x55\x81\xf0\x0a\xd8\x8e\xd2\xef\x34\xea\x8a\x77\x35\x63\xaf\ \x1c\xa1\x7d\xa9\xa4\xb6\xcc\xa3\xbe\x30\x35\xa1\x22\xba\x04\x52\ \xab\x7e\xf9\x60\x31\x07\x83\x3e\x63\x6f\x87\xd4\x04\x02\x16\x38\ \xe3\x5e\x44\x4d\x20\x7c\xe7\x9a\x40\x6c\x4f\xa9\xee\x9a\x77\xb1\ \x4b\xe0\x6d\x9a\x8f\x9a\xfa\xa6\xb6\xc4\x74\x45\xd4\x04\xc2\x2b\ \xa8\x75\xa8\x5b\x0d\x5f\xdd\xfb\xcf\x19\xfb\x70\x2a\x4b\xeb\x6b\ \x09\x52\x29\xe6\xb5\x88\x92\x40\xf8\xcd\xfa\x62\x6b\x81\x8c\x96\ \xce\xb1\x8a\x41\x07\x15\x5f\x0b\x6b\x86\x5d\xa0\x6d\x5e\x8b\x69\ \x94\x92\x40\x44\xe6\xaa\x5d\x35\x17\x4d\x3b\xa5\x26\xe8\x19\x47\ \x21\x63\x21\x7a\x2f\xc1\x7c\xd2\x3a\xc5\x02\x5a\x4d\x20\xf2\xfe\ \x25\x48\x13\x10\xc8\x81\x39\x6e\x3c\x11\x24\x58\x09\xa4\x96\x46\ \x21\x10\x55\xcf\x56\x94\x04\xc2\x3d\xab\x1d\xaa\x91\xb3\xa2\x2b\ \x45\xa9\x33\x8e\xc2\x7c\x98\x97\x5a\xd6\xc2\xbc\x16\x53\x51\x25\ \x81\x74\x75\x83\xfb\xc0\xda\xd6\xdc\x55\x70\x79\xcd\x4b\xad\x1e\ \xc4\x8e\x98\xdf\x56\x94\x26\x5d\xbf\x6d\xcd\xa0\x2b\xca\x6c\xf3\ \xee\x10\x7c\x29\xa8\xad\x4b\xb1\x42\x2d\x65\x54\x83\xc5\x2a\xa5\ \x54\x82\x79\xad\x15\xfa\x5a\xf1\xf4\x90\xb7\x40\xe2\x6d\xd4\xa1\ \xa7\x1d\x68\x13\xe8\x53\x53\x1f\x8a\xd2\x42\x9a\x1a\x56\xc1\x63\ \x42\x19\x8c\xb6\xbf\x87\xec\xcc\x13\xc2\x56\x94\x76\x08\x3d\x57\ \xf3\xb0\x18\x2d\x05\xfe\xc3\x0e\xc2\x70\xd3\xc3\xe5\x8e\x7c\xd7\ \x0f\x12\x8c\xb5\xb9\x31\xe7\x45\xfb\x5c\x12\x48\xd3\xf5\x5d\x82\ \x7f\x57\x53\x69\x87\x09\xd4\xb7\xee\xcb\xbe\x60\x23\x6a\x73\x63\ \x31\x17\x55\x5a\x49\x20\xd2\x21\x35\xd7\x8d\x84\xb7\xbd\x83\x7d\ \x48\xe8\xb8\xe9\x0b\x6e\x6d\xcd\x43\x35\xaf\xc5\x74\x53\x49\x20\ \xdc\xda\xda\xb6\x13\xa3\xd4\xd2\x2a\x63\x62\xdb\xbd\xbb\x2e\x81\ \x98\xd7\x95\x05\x22\x51\xd6\x15\xdc\x6c\xea\x4a\x0c\xdd\x1e\x8a\ \x62\x53\xe2\x7d\xfb\x5f\xbb\xc0\xee\xaa\x77\xd4\xb4\x07\x1b\x53\ \xec\x3d\x28\x09\x44\x70\x53\x73\xdd\xe8\x55\xf9\x98\x4d\x9d\x0b\ \x7c\x44\x58\x5b\x30\x43\xc2\xe4\x3d\x76\xef\x3f\x3b\x61\x3e\xcc\ \x4b\x4d\x7b\x98\xd7\x62\xbe\xab\x24\x10\xc7\x93\x6b\xc1\x8d\x7f\ \x27\x35\x3f\x5a\xdf\x6a\x07\xde\x12\xde\x29\x74\x26\x64\x4c\xa8\ \x71\xdc\x20\xfc\xf2\x91\xef\xba\xa1\xff\x99\xe6\x28\xcd\x2b\x88\ \x53\x94\x7b\x5b\x51\x72\x6d\x35\xc7\xd9\xa6\x7c\xfe\xd2\xef\xf8\ \xff\x56\xce\xc7\x8e\x7c\xb7\x39\xa8\x55\xaf\x1b\xc4\x95\x40\xcf\ \x4b\x9c\xae\x9a\xab\xd3\x83\xf0\xb4\xb0\xd4\xa9\xc8\x73\x25\x64\ \xa7\xac\x6a\x59\xe1\x56\x38\xb8\xdf\xb8\xbf\x6d\xfc\x62\xe8\x52\ \x99\x19\x47\xf1\xa0\xd0\x51\xef\xb6\xf9\x42\x19\x8e\x77\x84\x45\ \xd4\xb6\x96\x68\xbc\x56\x0d\xd4\x63\x54\xad\x0f\xef\x20\x1c\xc1\ \xa0\x5d\x4a\xe0\x2c\x31\x07\x45\xd4\x04\xd2\x5c\x14\x59\x82\x8c\ \xa5\x73\x83\x2b\xe7\x65\x0e\x29\xd8\x0e\x07\x94\x6a\x65\x6f\xdd\ \x29\x8e\x74\x14\x51\x13\x88\xe6\x05\x35\x8f\x12\xfc\x5b\x17\xbb\ \x48\xc6\xcd\xd8\xeb\x81\x66\xcb\x6a\x29\x27\xd7\x77\xac\x2d\x10\ \xff\x90\xe1\x29\xb9\x97\x3e\x58\xe5\x8b\x21\x9b\xb1\x77\x42\x80\ \x97\x55\x12\x08\x47\x41\xd9\xbb\xea\xb1\xd5\x04\x62\x77\xa8\x11\ \xd7\xd4\x16\xb7\xd7\xca\xd0\x4d\xb1\xcb\xa0\xbe\xcd\x43\xb1\xf0\ \x14\xd8\x1d\xe6\xb3\x9a\x69\xa8\x09\x84\x57\x70\x6c\xa8\x67\xb5\ \x04\x29\x02\xe7\x46\x74\xc8\xef\x32\x5c\x5e\xa3\x2c\x2b\xb1\x58\ \x82\x1e\x36\x0d\xe9\xe6\xb5\x88\x9a\x40\x40\x57\xa2\x54\x45\x29\ \xaf\xd5\xa8\x2d\x41\xda\xa6\x72\x5b\x9b\x86\xe8\xfc\x2e\x61\xcd\ \x7e\x98\x3f\x77\xbc\x74\xc6\x6c\x5d\x02\x11\x1c\xb1\x25\xbc\x83\ \x12\x94\x23\x75\x39\x5e\xe7\xc8\x77\xbb\x07\x99\x60\xcf\x5f\xf3\ \x36\x05\x98\xe6\xb1\xf3\xca\xd9\x2e\x81\x90\xac\xce\xf6\x62\xa8\ \x1f\xf8\x1b\x76\xc9\xbd\xc2\x62\xad\xf8\x90\x82\xab\x7b\xbf\x50\ \x4c\x56\x9b\x4b\x29\x1e\x81\x76\x49\xd3\x1c\x8f\x2e\x81\x80\x32\ \x26\xe9\xd6\x8c\x91\x81\xf1\xb6\x5c\x03\xbb\x4b\x70\x03\x9e\x33\ \x34\x35\x75\xcd\x39\x12\xd3\xe1\x60\xb8\x67\xe8\x1a\xbc\xc5\x34\ \xc0\x32\x45\xf5\xce\x18\xca\x2d\x1d\x76\xb0\x15\x8c\x38\xa7\xa7\ \xeb\xae\x30\x27\x93\xd9\xd8\x5e\xe8\xb3\x43\x80\x71\x77\x61\x7d\ \x2d\x95\xc2\xb8\x71\xfd\x5c\x98\xbc\xa9\xb4\xfc\x54\x50\x7e\x78\ \x40\x28\x28\xae\x35\x14\x4a\x95\xd0\x30\xa3\x24\x60\xd9\x08\xbb\ \x44\xa0\xd8\xb6\x12\x70\xf1\x26\x87\xc3\x5a\x73\x57\x7c\x72\x3b\ \x83\x20\x8f\x4d\x68\x9b\x07\x34\x4f\x32\xbb\x2b\xdd\xe4\xb0\x0a\ \x44\xa1\x2e\x7c\xec\x7b\xd7\x89\x4e\x8d\xc3\x24\x14\x69\x22\x71\ \xd7\xd5\x43\x69\xa5\xda\xc2\x44\x59\x0e\xcd\x11\xa3\xd6\x8c\x6e\ \x1a\xf6\xbd\x0d\xe8\xd5\xe1\x18\xb7\x01\x6d\x0a\x82\x3e\xf5\xa1\ \x3e\x37\xcc\x99\x1f\x67\xd8\x47\xbd\x0d\x08\xd8\x86\x4d\xdf\x97\ \xb5\x09\x18\xbf\xc5\xe5\x66\xbc\xb6\xe7\x5c\xa6\x46\x39\xb5\x91\ \x49\x6c\xa9\x83\x8b\x2e\x98\x51\x8a\x6c\x1b\xcc\x22\xed\x94\x31\ \x6e\x94\x9b\x12\xc6\xad\xcc\xb0\xca\x8d\x72\xcd\x5d\xbe\x93\xc1\ \xbb\xa2\x1c\x05\xee\x52\x5d\x48\xd7\xba\xb3\x70\xe8\x3b\x17\xa7\ \x00\x61\xac\x73\xe7\xa2\x17\x10\x4c\x0a\xfa\xd4\xa5\xc3\xaa\x5f\ \x5d\xc6\xad\xe1\xab\xc2\x31\x6e\x25\x1d\x0b\xc6\xc9\xad\x75\x29\ \x40\xdb\xf3\x2c\xd3\x3c\x68\x4f\xda\x98\x87\x29\xd5\xfc\xd0\x50\ \x04\xdf\x36\xc0\x65\xf2\xbe\xf8\xe3\x0a\xfc\xdb\x1e\xa7\x88\xbc\ \xdd\xdb\xeb\x35\x7b\x7d\xb4\x00\x2a\x53\x88\x4d\x6a\x15\xc3\x51\ \x61\x05\xb9\x0b\xe5\x71\x61\x57\xb4\xda\xb0\xb9\xd9\x5a\x87\xf8\ \xb6\x46\xf4\x0e\x65\x3e\x29\x94\xbf\x33\xde\xb6\xe7\x58\xa6\xa3\ \x19\x8f\x0c\xbb\x2a\x86\xa3\x83\x4d\x50\x0f\x59\xe7\xee\x77\xee\ \xb3\x60\xb3\x7a\xb3\xc1\x84\x90\x8f\x3b\xd0\x77\xbf\x37\x60\x4f\ \x78\x5e\xae\x50\xed\xfb\x10\xc8\x1b\xf9\x46\xe8\x35\x15\x9c\x84\ \x4d\xa9\x31\xea\xc9\x9b\xe3\x5c\xb3\x74\xe0\xdf\x8e\xd0\x40\x04\ \x3b\xd4\xfb\x43\x1c\x1b\x9e\x02\x8b\xef\x0f\x91\xcd\x96\x81\xe8\ \xe3\x49\x21\x81\x51\x53\x83\xbf\x3f\x64\x48\x7d\x67\x50\xf4\xef\ \x10\x6f\xd8\x61\x4c\xbd\xef\x49\xee\xac\xd8\x98\xbc\x06\x24\x05\ \x79\x4e\x2e\x13\xf3\xd5\x64\x1a\x73\xd7\x99\xca\x45\x34\x63\x75\ \xca\xec\x75\xe1\xa0\x6f\xd8\x19\xda\x00\x79\x28\x39\x9f\xe6\x1d\ \x54\xb5\xbb\xb6\xda\x60\xf5\xf1\xc6\x1c\xfb\x22\x1c\x11\x2f\x57\ \x72\xf9\x1d\x54\x5d\xe7\x1b\x3d\x17\x15\x28\x8f\x24\x07\x47\xa5\ \xb8\xfb\xd0\x57\x63\x22\x04\x45\x25\x8b\x68\x95\x39\xa0\xa2\x8c\ \x43\x5c\xb2\xf5\xef\xa0\x6a\xe0\x6f\x52\x07\xfc\x71\xd5\x34\x7d\ \xae\xeb\xd4\xdb\x1b\xd5\xc0\x01\x90\xaa\x59\x7c\x4b\x1b\x35\x27\ \xb5\x2d\x5b\x40\x45\x5a\xa1\x04\x2f\x13\xeb\xb3\x18\x68\x6e\x79\ \xf3\x96\x36\x93\x6f\xa1\x38\xee\x0d\xeb\x3c\xb7\x9d\xaa\xfe\x41\ \xc5\x79\x0b\xe8\x28\x57\x52\x8d\x21\x90\x06\x56\x9f\x7a\xb3\xdd\ \xe2\x3d\x86\x56\xeb\x10\x86\x8f\x90\x4c\x0e\x61\xd8\x29\x5c\x53\ \x3f\xb3\x3b\x09\x04\xa9\x4b\x1c\xc2\xeb\x21\x6c\xbd\x05\x22\x70\ \x89\xc5\x0f\xed\xff\x6c\x14\x8c\x29\x90\x06\xa2\x73\xbb\xc5\xbd\ \xf1\xde\x8d\x6e\xd5\x4e\xf1\xb9\x27\x14\x76\xa8\xf2\x2b\x87\x83\ \x30\xdc\xe5\xce\x2b\x1c\x15\x53\xb8\x6a\x74\xbe\x87\x6a\x02\x2d\ \x86\x95\x11\xad\x1d\xfb\xda\x34\xec\xc0\xaf\x84\xce\xa1\x78\x59\ \xd8\x9b\xc2\x5a\x7f\xda\x60\x98\xca\x77\x66\x0c\x45\xe8\xdc\x4b\ \x86\xb0\x31\xca\xf4\xfd\x36\xa5\x51\x9a\x3b\xaf\x9c\x7d\xf1\x76\ \x07\x74\xec\xc2\xf8\x27\xc1\xa6\x54\x07\x23\xab\x4b\xc5\xfb\xd4\ \xb9\x9f\x0c\x6e\xd7\x75\x50\x63\x81\x3d\xe0\x2c\x70\x1c\x4c\xbe\ \x2c\xb6\xb3\xe9\x3c\xbc\xc9\xb1\x69\x5d\xee\x2c\x05\x2f\x8c\x7d\ \xd1\x6c\xc6\x45\xe5\x09\xad\x12\x17\xac\x03\x4e\x80\xa4\xa8\xb3\ \x94\x5c\x69\x71\x8f\x54\x8e\xd2\xf3\xca\x27\x9b\x86\xc4\xb6\x18\ \x57\x3b\x43\x7c\xa0\x47\x58\x1a\x45\xa9\xd4\x8e\x69\xec\x0d\xb5\ \x46\x40\xeb\x8c\x97\x71\xb6\x0b\xa8\x49\xb6\x81\x5a\xe2\xb2\x1e\ \x17\xba\xb8\xd2\x6e\xd0\x51\x38\x9a\xe7\xb4\x0a\xb6\xd1\xdb\x31\ \x26\xc2\xb0\x63\x5c\x6d\xd1\xbc\xb3\xd6\xcf\x38\x02\x48\x80\x04\ \xd4\xb0\x81\x95\xdf\xd0\x04\x0b\x32\x39\x12\x54\x52\x93\x01\xd0\ \x96\xa3\x69\x8d\x60\x06\x0d\xea\x86\xc0\x36\x0a\xa4\x04\x71\x8c\ \x52\xb0\xec\x72\xa3\xd6\x04\x80\x8d\xc7\x66\xe2\x4d\x32\x55\x84\ \xbc\x3b\x8e\x84\x88\xdf\x7f\xcf\x98\x31\x63\xc6\x8c\x19\x33\x66\ \x4c\x85\x63\x8e\xf9\x1f\x14\xbe\xad\xda\x96\xb0\xa6\xcd\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x84\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x39\x49\x44\x41\x54\x58\x85\xed\xd4\x31\x0a\ \x00\x20\x0c\x03\xc0\xea\xff\xff\xac\x1f\x28\x74\x28\x8a\xe0\xdd\ \x98\x25\x4b\x48\x04\xfc\x6e\x24\xd9\xba\xd9\x39\x0f\x97\x51\xca\ \x36\x50\xe9\x6e\xc4\x06\xde\xe2\x07\xf0\x03\xf8\x01\x80\x0d\x4a\ \x27\x05\x10\x7b\xf4\xd9\x97\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ \x00\x00\x0e\xae\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\xfa\x00\x00\x00\xfa\x08\x06\x00\x00\x00\x88\xec\x5a\x3d\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ \xa8\x64\x00\x00\x0e\x43\x49\x44\x41\x54\x78\x5e\xed\xdd\x05\x90\ \x23\x69\x19\x87\xf1\xc3\xdd\xdd\xdd\x39\xdc\xdd\x5d\x0e\x77\x77\ \x28\x5c\x0e\x87\x82\xc3\xe5\x28\xdc\xdd\x0b\x28\xdc\xfd\x70\x77\ \x2d\xec\x90\xc2\xdd\xfd\x7d\x36\xe9\xb9\x9e\xec\xdb\x92\xb4\xce\ \xec\xf3\xab\xfa\xd7\x64\x67\x93\x74\xa6\xd3\x5f\xcb\x67\xbd\x9f\ \x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\ \x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\ \x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\ \x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\ \x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\ \x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\ \x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\ \x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\ \x92\x24\x49\x92\x24\x0d\xe3\x28\xcb\x9f\x43\x39\xfe\xf2\xe7\x46\ \x0e\xb7\xfc\xa9\xf9\x3b\x68\xf9\xb3\xca\xdf\x22\x8f\x59\x3c\xd4\ \x48\x4e\x18\x61\x9d\xdf\x3c\x72\xf4\xc8\x37\x22\x77\x8b\x7c\x38\ \xd2\x97\x87\x46\xee\x1d\xa1\xa0\xff\x28\xf2\xa4\xc8\xb3\x22\xda\ \x85\x8e\x11\xf9\x5f\x43\xfa\xdc\xb8\xd4\x8c\x82\xfd\xa5\x48\xf6\ \x5d\x5c\x3a\xd2\x87\xa7\x45\xb2\xf7\xbf\x6f\x44\xbb\x90\x05\x7d\ \x7e\x9e\x13\xc9\xbe\x07\xf2\x9e\x48\x57\xa7\x8b\x64\xef\x5d\xe4\ \x22\x11\xed\x32\x16\xf4\x79\x39\x20\x92\x7d\x07\x45\x0e\x8d\x74\ \x75\xb5\x48\xf6\xde\x45\x3e\x12\xd1\x2e\x63\x41\x9f\x97\x6f\x45\ \xb2\xef\xa0\xc8\x9b\x23\x5d\x9d\x22\x92\xbd\x77\x39\xf7\x88\x68\ \x17\xb1\xa0\xcf\xc7\x81\x91\x6c\xfd\x97\x73\xcd\x48\x1f\x5e\x1f\ \xc9\xde\xbf\xc8\x5f\x22\xa7\x8a\x68\x97\xb0\xa0\xcf\xc3\xb1\x23\ \x7f\x88\x64\xeb\xbf\xc8\x07\x23\x7d\xb9\x58\x24\x5b\x46\x39\xaf\ \x88\x68\x97\xb0\xa0\xcf\xc3\xa3\x22\xd9\xba\x2f\xe7\xb2\x91\x3e\ \xbd\x2c\x92\x2d\xa7\x9c\xfd\x23\xb5\x6c\x47\xdf\x39\x68\x4b\xad\ \xf3\x9f\xc8\xd3\x17\x0f\x35\x80\x13\x44\x7e\x1c\x39\xda\x9e\x7f\ \xe5\x5e\x19\xb9\xc5\xe2\x61\x6f\xce\x1a\xf9\xe6\xe2\x61\xa5\x97\ \x44\x6e\xbb\x78\x28\xa9\x8b\xc7\x45\xb2\xa3\x69\x39\x67\x8e\x0c\ \xe1\xe0\x48\xb6\xbc\x72\xd8\x21\x48\xea\xe0\xa4\x91\x7f\x45\xb2\ \x02\x56\xe4\xf1\x91\xa1\x9c\x38\xf2\xd7\x48\xb6\xdc\x22\xcf\x8b\ \x48\xea\xe0\xc9\x91\xac\x70\x15\xf9\x65\x84\x7a\x94\x21\x3d\x2c\ \x92\x2d\xbb\x9c\x33\x46\x24\x6d\x88\x82\x9c\x15\xac\x22\x0f\x89\ \x0c\x8d\xba\x01\x9a\xd3\xb2\xe5\x17\x61\x87\x94\xea\xa3\x32\xee\ \x88\x91\x73\x2f\xc3\x29\xce\xb7\x23\x87\x44\x58\x39\x75\x8e\x1a\ \xb9\x5c\xe4\xd4\x11\x5e\xc7\x80\x80\x22\xff\x8d\x0c\x8d\xbf\x9d\ \xe5\x16\x39\xd9\xf2\xe7\x6f\x23\xbf\x59\xe6\x7b\x11\x06\x12\xa8\ \x3b\xd6\xf7\xc5\x23\x27\x59\x86\x23\xe0\xaf\x23\xac\xe7\x5f\x44\ \x3e\x1d\x99\xa3\x9b\x44\x5e\xbd\x78\x58\x89\xee\xaa\x3f\x5c\x3c\ \x1c\x14\x83\x59\xee\xba\x78\x98\xfa\x55\x84\xd3\xfc\xbd\xac\x16\ \xf4\x6b\x44\x4e\xb9\x0c\x9d\xf6\x8f\x1c\x39\xd2\xf2\xe7\xe1\x23\ \xa0\x20\x50\x03\xcc\x97\x45\x05\xc5\x6d\x22\x19\x7a\x07\xd1\x73\ \x87\x9a\xca\x32\x3a\xfc\xdf\x2b\x42\xa7\x82\x23\xf0\x8b\xc4\xeb\ \x22\x8f\x8d\x7c\x65\xcf\xbf\xba\xe3\xf3\x9f\x6f\x25\xe7\x8d\xb4\ \x41\xbb\xe9\xd7\x22\xaf\x8a\xd0\xd4\xc1\xb5\xd2\x50\x58\xd7\x74\ \x7d\x3c\x79\x84\x1d\x0f\xeb\xa7\x08\xeb\x7f\xf5\xdf\xab\x28\x38\ \x8c\x76\x1a\xca\x15\x23\x6c\x1b\x74\xd2\x60\x58\x66\x79\xfb\x28\ \xb0\xc1\x3f\x71\xf1\x70\x8f\x3b\x47\x6e\x18\xb9\x44\x84\xe7\x56\ \xe1\xb3\x7f\x20\xc2\x7a\x7e\x1b\xbf\x98\x89\x77\x46\xae\xba\x78\ \x98\x7a\x7b\xa4\xaf\x0e\x32\x4d\x2e\x14\x69\xda\x21\xde\x2c\x52\ \xb9\x63\x3a\x5b\x84\x86\xfe\xec\x74\x60\x35\x5f\x88\xf0\x85\xf3\ \xc5\x64\xff\x5f\xce\xef\x22\xd7\x8d\xe0\xb4\x11\x0a\x7f\xf6\xbc\ \xaa\x3c\x3b\x52\xb7\x71\x34\xb9\x63\xe4\xfd\x91\xec\xbd\x37\x49\ \x31\x14\x94\x82\xd6\x37\x86\x37\x72\x74\xcb\x96\xdb\x36\x7d\x0c\ \xa6\xc8\xf0\x7d\x7f\x35\x92\x2d\x73\x35\x6f\x89\xe0\xd6\x91\xef\ \x44\xb2\xe7\x34\x85\xf7\x60\xa3\x9e\xda\x99\x22\xd9\xe7\x2b\xe7\ \xc6\x91\x31\x1d\x12\xc9\x3e\x47\x91\x77\x47\x2a\x7d\x2a\x92\xbd\ \x28\xcb\xef\x93\xdf\x35\xe5\x35\xc9\xef\xda\xe6\xad\x91\x75\x5d\ \x30\xf2\xd3\x48\xf6\x7e\x7d\x84\xa3\xd6\xb5\x22\x7d\x39\x7b\x84\ \xcb\x95\x6c\x59\xeb\x64\xa8\x82\xce\xe5\x58\xb6\xbc\x2c\x1f\x8f\ \xbc\x77\xe5\x77\x9b\xe6\x76\x91\x29\x35\x75\x90\xe1\x54\x79\x6c\ \xac\x93\xec\xb3\x94\x93\x36\xb5\x71\x8d\x9c\x3d\x79\x4e\x59\xb7\ \xe9\xa0\x69\xd0\x41\x5f\xe1\x34\xa9\x0f\xb7\x8c\x64\xef\xbf\x6e\ \x86\x28\xe8\x5c\xe2\x64\xcb\x1a\x2b\x5c\xc2\x4d\x85\x11\x62\xd9\ \x67\x2a\xc2\x78\xf1\xb1\x71\x36\xd9\xd4\x0d\x77\xaf\xeb\x78\xae\ \xf3\x86\x6e\x16\xe8\x03\xa7\xe0\x17\x5d\x3c\x6c\x74\xbc\xc8\x59\ \x16\x0f\x07\x47\x4f\xa8\xc6\xee\x87\x2d\x1c\x73\xf9\x73\x8e\xe8\ \xdf\x3d\xa5\x07\x45\xaa\xea\x81\x86\x44\x1d\xc4\xa5\x16\x0f\x2b\ \xbd\x6b\xf9\x73\x4c\xf4\x80\xe4\x8c\xa9\x0e\x7d\xe4\xb7\xa1\xa0\ \xd3\xbd\x8e\x6a\xfb\xb9\xbb\xcb\xf2\x67\x13\x3a\x36\x8c\x89\xf6\ \xcd\xae\x68\x69\x98\xab\x39\x7c\xb6\x17\x47\x98\xb6\x69\x4c\x4d\ \x85\x1c\x5c\xa6\x4c\xa1\x69\xb9\xb4\x6e\xa4\x6e\x15\xc9\x4e\x01\ \xda\x86\x1d\xc5\xcf\x56\x7e\xb7\x4e\xbe\x9e\xfc\x6e\x35\x54\x84\ \x95\x6b\x77\xeb\x7c\x22\x92\xbd\x47\x5d\xfe\x19\xa1\x29\xed\xe7\ \xcb\xc7\xd9\x73\xaa\x72\x8e\x48\x57\x5c\x9e\x64\xef\xbd\x4e\x86\ \xba\x46\xa7\xe6\x3c\x5b\xde\x26\xa1\x2e\xe2\x27\x11\x5a\x6f\xb2\ \xff\xaf\xca\xfd\x23\x63\x7a\x74\x24\xfb\x1c\x45\xa6\x6c\x0e\xbc\ \x40\x24\xfb\x4c\xe5\xd0\xe4\x97\x62\x63\x65\x50\x04\xa7\x05\x6d\ \x6a\xd4\xc9\x47\x23\x57\x8f\x14\x6e\x10\x59\xa7\xc0\x3f\x32\x42\ \xfb\x3b\xd8\x0b\xd1\x6e\x9d\x3d\xaf\xc8\x85\x23\x6d\x30\x82\xe8\ \x4f\x91\xec\x3d\xf8\x3d\x23\xbd\xe8\x5c\x40\x1b\x29\xcb\x3f\x51\ \x64\xd5\xb1\x22\xbc\xcf\x6b\x23\xd9\xfb\x94\xc3\xdf\xd1\x87\x6b\ \x47\x5e\x1a\xe1\x3b\xa0\x69\x91\x50\x11\x56\xec\x80\x68\xc5\xa8\ \xeb\x0a\x39\x54\x41\xc7\x79\x22\x1c\x59\xa9\xb8\x5d\x77\x47\xf8\ \xd9\xc8\x03\x22\xab\x1b\xdf\x71\x22\x5c\xfa\x3c\x21\x92\xbd\xae\ \x1c\x0e\x06\x63\x6a\xba\x3e\x7f\x4a\x64\x4a\x4d\xd7\xe9\xad\xea\ \x8f\xf8\x62\xb2\x17\x97\x43\x13\x48\xd6\xf4\xc5\x75\x6b\xf6\xfc\ \x72\x38\x03\xc8\x6a\xad\xe9\x40\x93\x3d\xbf\xc8\x95\x22\x6d\xd1\ \xde\x4b\x93\xd5\x53\x23\xf7\x8b\x5c\x27\x72\xce\xc8\x26\x18\x91\ \x94\x7d\x9e\x22\x1f\x8a\x8c\xa5\xee\xec\x67\xc8\x82\x5e\x46\xdf\ \x88\x6c\xf9\x59\x38\x32\xb6\x41\xa5\x30\x3b\xb6\xec\x3d\x8a\x9c\ \x21\x32\x06\xae\xcf\xb3\xe5\x97\x53\x34\x1b\x4f\x85\xf6\xfd\xec\ \x73\x15\xa1\x69\xba\x51\x53\x41\xaf\x1b\xfb\x7c\xfd\x48\xf6\x9a\ \x72\xaa\x9a\xa6\x38\x8a\x66\xcf\x2f\x42\x61\x9d\x4a\x5d\x13\x21\ \xb5\xfc\x63\xd9\x29\x05\x9d\x33\x8f\xba\x8e\x26\x99\xa6\x89\x16\ \xee\x14\x19\x03\xfd\x4a\xb2\xe5\x97\x93\xf6\x40\x1b\x51\x53\x13\ \xe6\xb6\x83\x4f\xd6\xbb\xaa\x09\xa7\xd7\xf4\x74\xaa\xf2\xf7\xe5\ \xcf\x2a\x7c\x59\x55\x6d\xe3\x4d\xaf\xdd\xe4\xf3\xf6\xa5\xae\x02\ \x84\x5e\x6c\x3a\xcc\x1f\x23\x57\x89\xac\x5b\x2b\x4d\xdd\x0a\x97\ \x56\x55\x38\xd5\x1f\x43\x53\xc5\x1f\x35\xdf\x4d\x5d\xbc\x87\xc6\ \x34\x53\x75\xb6\xfd\x0d\x9b\x14\x1c\x7a\xc6\x75\xf9\x23\xe9\xe2\ \xb8\xa9\x3e\x0b\x3a\x5d\x7c\xe9\xa8\xc2\x51\x84\x6e\xa7\x37\x8d\ \x30\x11\xff\xf5\x96\xff\xe6\xfa\xfc\xf4\x91\x42\x5d\xd7\xd7\x21\ \x7a\xca\xed\x54\xf4\xee\xbb\x42\x84\xfa\x9b\x4d\xd4\xf5\x19\x1f\ \xab\x19\xb2\xa9\xa0\x4f\x5d\xc8\xd1\xf4\x19\xb6\xd5\x3b\x6d\x52\ \x70\xa6\x9c\x95\xa6\x4b\x41\xa7\x60\xd3\x44\xf7\xa2\xc8\x97\x23\ \xd4\x13\x70\x1a\xcc\x91\xfa\x1d\x11\x76\x40\xcc\xbf\xf5\x86\xe5\ \xbf\xe9\x12\x5c\x54\x0e\x52\x39\x49\x2f\xa9\x2a\x16\xf4\xc3\x30\ \x4e\x81\x4b\xbf\x4d\xfd\x63\xf9\x33\xc3\x40\xa8\x31\xec\x86\x82\ \xde\xf9\x88\xde\xe7\x51\x75\x5d\x9b\x2c\x9b\x9e\x5d\xcf\x8c\x50\ \x58\xa9\xa0\x60\xca\x9d\x73\x45\xd6\xc1\x34\x42\x4c\xbf\x5b\x65\ \xca\x75\x32\x37\x43\xae\x8b\xb1\xd6\xf3\x6e\x28\xe8\x1c\x7c\xb6\ \xee\xd7\xb6\xc9\x8a\xdb\x49\x47\xf4\x47\x44\xb8\xd4\xa0\xf6\xbd\ \x6e\xae\xaf\xae\x2c\xe8\x87\xb1\xa0\x8f\xa3\xcd\x67\xd8\x3a\x7d\ \xdf\x64\xc5\x4d\xb9\x51\xb7\xdd\xc9\xd0\x05\x96\x8a\x9d\xbe\xda\ \xb7\x9b\xec\x6b\xa7\xee\x75\xdf\x43\xd7\xed\xa3\x6e\x5d\x8e\xb5\ \xed\x35\x8d\x98\xa4\x1f\xc1\xd4\xda\x7c\x86\xad\xbf\x63\xa7\x15\ \xf4\x36\x28\xe4\x5c\x63\xb7\xed\x1b\xdf\x87\x29\xcf\x72\xe6\xa6\ \xeb\xf6\x51\xf7\xfa\xb1\x76\xa8\x4d\xa3\xd2\xa6\x6e\x5a\x43\x9b\ \xcf\x40\xc5\xe8\x1e\x9b\x7c\x29\x73\xde\xa8\x19\xf3\x4e\x21\x1f\ \xab\x63\x85\xf6\xd6\x75\xfb\x98\xc3\x11\x9d\xfa\x9c\x3a\x59\x4f\ \xca\xb1\xb5\xf9\x0c\x5b\x7f\xc7\x26\x2b\x6e\xac\x95\xbd\x89\x67\ \x44\x2c\xe4\xd3\xea\xba\x7d\xd4\xbd\x7e\x2e\x05\x7d\x27\x1c\xd1\ \xe9\x22\xbb\x35\xc0\x6b\x93\x15\x37\xd7\x23\x3a\x7d\xa9\x99\x0a\ \xab\x0d\x06\x55\xd0\xbf\xfa\x0e\x11\xda\xcb\x99\x1a\x89\xbf\x8b\ \x6b\x1a\xf6\x94\xcc\xcf\x4d\xfb\x3a\xe3\xc4\xe9\xc2\xc9\xd4\x46\ \xab\x53\x62\x29\xd7\xb5\x30\xd6\x1d\xd1\xe7\x72\xea\xbe\x13\x8e\ \xe8\xdb\x76\x56\x9b\x7c\x29\x63\xed\x55\xd7\x75\x9f\xe5\xcf\x3a\ \x4c\x87\x44\xcf\x3c\x0a\xf6\x03\x23\x2f\x8c\xd0\x9d\x97\x82\x8f\ \x7f\x47\x58\x41\xdf\x8d\x7c\x32\x42\xbb\xfa\xc3\x23\x74\xd9\xa5\ \xcf\xbc\x16\xea\x76\xf6\x5d\xb7\x8f\xba\xd7\x8f\xb5\xed\x35\x1d\ \xd1\x99\xc3\x61\xea\x39\x04\x9a\x8e\xe8\x5b\xd7\xe7\xd8\x2d\x05\ \x9d\x89\x29\x98\xac\xb2\x0e\xe3\xc6\x69\x3f\x7f\xfe\x9e\x7f\xad\ \x8f\x8e\x33\x6a\x36\xe4\x35\xfa\x58\x8a\x1d\x7f\x1d\x46\xf3\x4d\ \xa9\xa9\x2f\xc8\xb6\xe6\xb7\x21\x0a\xed\x14\x05\xa2\x72\xa0\xfd\ \xd2\x3d\x23\x07\x2d\x1e\x6e\x8c\xa3\xbd\x86\x37\x87\x03\x09\xf3\ \x22\x7e\x71\xf1\xb0\x12\xb3\xda\x4e\xa9\x69\x9b\x67\x98\xed\x96\ \x39\x1e\x9d\x37\xc1\xf4\xc8\x55\x18\xc3\xdd\xf5\xe6\x83\x77\x8f\ \x4c\x39\x77\xd9\xbe\x64\x0e\x47\x74\x6c\x3c\x8b\xcb\x08\x18\xd2\ \xdb\x74\x57\x16\x66\x8b\xdd\xb2\x5b\x0a\x7a\x5d\xaf\xb7\x2e\x53\ \x4b\x31\xe6\x98\x99\x44\xd8\x51\x4c\x3d\x77\xda\xbe\x62\x2e\xdb\ \x24\x1d\xae\xea\x4c\x59\xd0\x9b\x96\x4d\x8d\xfb\xb6\x19\x70\x56\ \xf7\x9e\xd4\x36\xd3\xd1\xe4\x32\x91\xaa\xa3\xe4\xf7\x23\xcc\x22\ \xc3\x1d\x5a\x98\x0e\x68\x15\xef\xc1\x48\xb0\x2a\xdc\x0c\x81\xda\ \x6d\x66\x4c\x59\xc5\xe7\xa9\x9b\x83\x8d\x4a\x12\x2a\x19\x28\xbc\ \xe5\x79\xee\xf8\xbc\xc5\x4c\x35\xab\xa8\x38\x61\x78\x23\x5f\x5c\ \x9b\xde\x44\x5c\xfb\x70\x83\x0a\x66\x7a\xb9\x7d\xa4\xae\x8f\x7b\ \x19\x7f\x17\x33\xa8\xb0\x3c\x86\x31\xae\x73\xa3\x07\x26\xc9\x60\ \x86\x1f\xd6\x3d\x7f\x07\xfd\xf3\xcf\xbf\xcc\xea\x8d\x27\x78\x4e\ \x55\x8d\x2b\xc3\x7c\xff\x1c\xe1\xbd\x98\xf2\x97\x9f\xab\x61\xac\ \x35\xeb\x83\x6b\x69\x86\x93\xb6\xc5\x7a\x60\x86\x1f\x3e\x13\x43\ \x50\x99\x3b\x20\xc3\xf7\x42\x0b\x05\x95\x45\x6c\x70\x6d\x7b\x91\ \xb1\xee\x98\xa6\x9b\xd1\x83\x55\x15\x5d\xfc\x6d\xcc\x71\xc8\x74\ \x54\x3c\x1e\x12\xeb\xa6\xee\x56\xd5\x1c\x5c\x18\x6e\x9d\x6d\xc7\ \x43\x63\x70\x16\xeb\xaa\xca\xfb\x22\xcc\x9f\xb0\x17\xbe\xb4\x17\ \x44\x8a\x41\xeb\x6d\x43\xc5\xd6\x6a\xe5\x0b\x4d\x5c\xd9\x73\x57\ \xc3\x6c\x22\x57\x8e\x94\xb1\x03\xc8\x9e\xbb\x1a\x36\x68\xee\x02\ \x53\xe0\x66\x01\xd9\xf3\xca\x61\x83\x7b\x6e\x84\x41\x2d\x34\xa9\ \x9d\x26\x42\xa1\x66\x48\x25\x33\xc8\x50\xb0\x0f\x8d\x64\xaf\xdd\ \x24\xb4\xe9\x37\xe1\xae\x35\x74\xf0\xc9\x5e\x3f\x46\x98\x25\xa8\ \xcd\x00\x1f\x36\xe8\xec\xf5\x6d\xd2\x34\xe5\x12\x3b\x36\x3e\x47\ \xf6\xda\xba\xbc\x3c\x72\xdc\xc8\x90\x9a\xa6\x36\x2b\x6f\x83\x63\ \x62\xf4\x65\xf6\x79\x8a\x54\xce\xaf\xc7\xd0\xcc\xec\x05\x6d\x42\ \x01\x29\xa3\x29\x2a\x7b\x5e\x16\xf6\x9a\xe5\x7b\x4a\xb7\x2d\xe8\ \x45\x98\xd4\xb2\xd0\x65\x72\xca\xa1\x52\x79\xd3\xbb\xc0\x29\x2a\ \x33\xac\x66\xaf\x1b\x33\x4d\xe3\xc6\xb9\xc9\x7e\xf6\xba\x75\x52\ \xb7\x1e\x3e\x16\xc9\x5e\xd3\x26\x8c\x4a\x1c\x12\xdb\x76\xb6\xdc\ \x22\x53\x4c\x10\xc9\xd9\x6b\xf6\x59\xca\x49\xef\x72\x43\xb3\x54\ \xf6\xe4\x75\x52\x1e\x27\x7c\x40\x24\x7b\x4e\x55\xa8\xe8\x2a\xac\ \x5b\xd0\xb9\xef\x55\xa1\xeb\x4c\xb6\x43\xa4\xae\xe3\x05\xb7\x39\ \xca\x5e\x33\x45\xea\xe6\xd2\xe3\x72\x29\x7b\xcd\x3a\xa9\x9a\xd8\ \x91\x26\xaa\xec\xf9\x6d\x33\xe4\x7d\xf0\xc0\xd9\x5e\xb6\xdc\x72\ \xc6\xbe\x75\x54\xd3\xce\x27\x9d\x9e\x9b\xa3\x4a\x5d\x8d\x75\x5b\ \xe5\xf7\x58\xb7\x32\xa5\xed\x35\x70\xa6\xbc\x5c\x6e\x80\xc8\xc4\ \x94\x73\xc2\x70\xc7\xaa\x1b\x64\x70\x5d\x3e\x17\x74\x20\xca\x30\ \xc3\x0e\x63\xf1\xbb\x62\x26\x9f\xac\xc2\xb4\xeb\x3a\xe0\x3d\x87\ \xbc\x59\x07\xf7\xed\xa3\x4e\xa0\x4e\x5f\x77\xeb\x69\x83\xef\xa2\ \x7c\x16\x9b\xa1\x93\x57\x8a\xb9\xd2\xb3\x3d\x43\xdb\x30\xdf\x7a\ \x59\x9b\xc9\x21\xcb\xa1\x9b\x69\x61\xdd\x23\x7a\x56\xb0\xf9\x43\ \xb3\xe7\x4e\x11\x2a\x2e\xab\x50\xb1\x95\xbd\x66\x8a\xd0\x5c\x53\ \xa5\x6e\x7a\xe9\xb6\x39\x24\x92\x61\x47\x92\x3d\xbf\x6d\xa8\x53\ \x19\x1a\xf7\x3e\xcf\x96\x5d\xa4\xa9\x17\x5d\x9f\xe8\xfd\x99\x7d\ \x86\x72\x6a\x77\x9e\x0f\x8e\x64\x2f\x6a\x93\xd5\x0a\x09\x26\x8e\ \xcc\x9e\x97\x65\x75\x03\x58\xa7\xa0\x53\xb3\x4d\x0d\x72\x86\x6e\ \xab\xd9\x6b\x36\x0d\xa7\x43\x4d\xd3\xeb\x66\x29\xef\xc4\x32\x4c\ \x5f\x95\xbd\x6e\xcc\x34\xf5\x31\xa0\xe6\x39\x7b\xdd\x3a\xe1\x14\ \xb8\x0a\xf7\xfc\xce\x5e\xd3\x26\x4d\xeb\xb7\x0f\x54\xda\x66\xcb\ \x2e\x87\xfb\x03\x8c\xa1\x69\x3a\xec\x37\x46\x1a\x51\x89\xc6\x74\ \xc1\x9c\xaa\x30\x13\x27\x35\xdb\x59\x68\x32\x61\x7a\x63\xe6\x54\ \xcb\xee\x0b\xcd\x6d\x64\xb3\x0f\x41\x98\x0f\x8c\xbd\x30\x37\x01\ \x60\x4f\xb9\x3a\x07\x58\x5d\x41\xa7\xd6\x9c\x1b\xe6\xd3\x63\x89\ \x16\x82\xf4\x8e\x91\x25\x97\x8c\x74\xa9\x64\xe4\x68\x4c\x21\xe0\ \xae\x18\x38\x30\x92\x3d\xaf\x1c\xd6\x0f\xaf\xe3\x3e\xdf\x6d\xa7\ \x3a\xa6\xa9\x84\xf5\x41\xb7\xcb\xec\x3d\xfb\x0e\x3b\x48\x9a\xbf\ \xa8\x48\x62\x50\x4f\x1b\xd4\xbb\x30\xbd\xf0\x0f\x22\x34\x6d\x66\ \xef\x5b\x0e\x4d\x6c\x8c\x17\x78\x53\xa4\x4d\x7b\x33\x2d\x21\x8c\ \x2d\x68\x9a\x46\xba\x58\xbf\x9c\x52\xaf\x3b\x95\x74\x17\xcc\xb8\ \x9a\x7d\x9e\x22\x34\x67\x0d\xad\x4d\xdd\x17\xcf\xd9\x67\x31\x6c\ \x95\x42\x4a\x13\x51\x55\xc5\x12\x6d\xb2\x9f\x8f\x70\x03\x79\xa6\ \x9f\xaa\x6a\xa3\xa4\xfe\xa1\x2e\x5d\xd1\x54\x39\x74\xfa\x90\xfd\ \xed\xe5\x74\x91\xbd\x5f\x91\xa9\xd0\x77\x21\xdb\x6e\xca\xa9\xeb\ \x3b\xd2\x07\x76\xcc\xd9\x72\x8b\x74\x99\x90\x73\x57\xa2\xb3\x08\ \x37\xb8\xe7\x48\x43\xd3\xde\x58\x73\x85\x6b\x67\xe3\xec\x24\x2b\ \x60\x45\x86\xbc\x19\x25\x67\x3c\xd9\x32\xcb\xb9\x51\x44\x52\x47\ \xdc\x5d\x35\x2b\x60\xe5\x70\xeb\xaf\x21\x70\x19\x94\x2d\xaf\x48\ \x53\x77\x5d\x49\x6b\x60\x02\x92\xac\xa0\x15\xa1\xfe\xaa\x8f\xe6\ \xc8\x32\x76\x1e\xd9\xb2\xca\x99\xfa\x3e\x70\xd2\xae\x72\xf9\x48\ \x56\xd0\xca\x39\x38\xd2\x17\xba\xa6\x33\xb6\x23\x5b\x4e\x91\x6d\ \xc3\x51\x25\xf5\xa3\xe9\x5a\x9d\x94\xbb\x75\x77\xd1\x74\x8f\x76\ \xd2\x76\xfa\x34\x49\x6b\x60\x64\x61\x56\xe0\xca\xe9\xe3\xa8\xce\ \xe8\xd0\xa6\xa3\x79\xab\x76\x73\x49\x9b\xe1\x3e\x7c\x59\xc1\x2b\ \xc2\x90\xe5\xae\xda\x54\xfe\x55\x0d\xcd\x96\xd4\x13\x46\xfd\x65\ \x85\x8f\xf4\x31\xaa\xad\x69\xc0\x0f\xb3\x1e\x4b\x1a\x58\x5d\x41\ \xa4\x4b\x79\x1f\x3e\x17\xc9\xde\x9f\x9e\x97\x92\x46\x42\x1f\x77\ \x26\x92\x2c\x17\xc2\x3e\xa7\x05\xdf\x3f\xf2\x99\x48\xf9\xfd\x99\ \xa8\x64\xed\x1b\x48\xf4\xd5\x25\x52\xda\x57\x31\xcb\x0d\x33\x25\ \x31\xa7\x20\xbd\xe3\x9a\x26\x95\xdc\x04\xe3\x50\x18\xe9\xc7\xa0\ \x16\xc6\x98\x48\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\ \x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\ \x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\ \x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\ \x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\ \x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\ \x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\ \x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\ \x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\x49\x92\x24\ \x49\x92\x24\xf5\x67\xbf\xfd\xfe\x0f\x9b\xbc\x15\xda\x67\xbe\x80\ \x39\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\xe7\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x9c\x49\x44\x41\x54\x38\x8d\xcd\xd2\x3d\x0a\ \xc2\x40\x10\x86\xe1\x87\xa0\x76\x2a\xa4\xf5\x50\x76\x36\x7a\x1b\ \x83\x07\xf1\x08\x82\xad\x78\x24\x2d\x04\xb7\xf0\xa7\x48\xfc\x4b\ \xd4\x6c\xd4\xc2\x0f\x3e\xd8\x19\x76\x5f\x66\x76\x86\x7f\xd1\x0c\ \x01\x27\x1c\xb1\x46\xb7\x09\x20\x20\x2d\xce\x09\xe6\x05\x34\x5a\ \xa7\x52\x3c\xc0\xae\xc8\x3f\x73\x40\xf6\x0e\x50\xa7\x14\xfb\x6f\ \x00\xd7\x37\x49\x29\xd9\xc1\x12\x07\xaf\xcb\xbf\x18\xb4\x4a\x80\ \x51\x01\x6d\xcb\xa7\x51\x5b\x41\x39\x58\x60\x1c\xd7\x41\x15\xd0\ \xc3\x06\xfd\x26\x80\xfb\x3f\x18\xca\x17\x68\x1b\x09\x78\x50\xc0\ \x0a\x93\xc8\xfb\x95\x31\x66\x6e\xab\x1c\xe3\x80\xe9\x27\x95\xfe\ \x5e\x67\xca\x4f\x38\x40\x1e\xdb\x25\xdb\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x03\x0e\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x19\x00\x00\x00\x19\x08\x06\x00\x00\x00\xc4\xe9\x85\x63\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc2\x00\x00\x0e\xc2\x01\x15\x28\ \x4a\x80\x00\x00\x02\xa3\x49\x44\x41\x54\x48\x4b\xad\x96\x4d\x4f\ \x13\x51\x14\x86\xcf\x4c\xb1\x45\x34\x8d\x48\x5b\xc4\x44\xe2\x17\ \xea\xc6\x85\x74\x67\x4c\x8c\x0b\xd1\x0a\x3f\x80\x98\xd4\x85\xd1\ \xea\xca\x98\x74\x83\x0b\x83\x1f\x0b\xdd\x90\xb8\xe9\xa6\x76\x25\ \xd1\xf8\x03\xd4\xfa\xf1\x1b\x8a\x1a\x0d\x12\x15\xdd\x89\xa0\xc4\ \x68\x43\xcc\x14\xe8\xf8\x9e\x7b\xef\x4c\xa7\xcc\x6d\xe9\x50\x1f\ \x52\x72\xce\x99\xce\xbc\x3d\xf7\x9e\x7b\xce\x18\xd4\x84\x4c\xe9\ \x54\x9f\x4d\xe6\x59\x98\x23\xf8\x0c\x12\x19\x51\x71\x41\x60\xff\ \xc1\xbf\x29\x7c\x9e\x18\x54\x7d\x94\x4f\xbe\x98\x13\x61\x0d\x5a\ \x91\x4c\xe9\x74\x8f\x4d\xc6\x4d\x5c\xbe\x00\x37\x22\xa3\x4d\xb1\ \x20\x5a\x30\xc8\x1e\xcf\x27\x9f\x2f\xaa\x98\x8b\x4f\x24\x53\x4a\ \x9d\x84\xc0\x03\x98\x3b\x64\x24\x10\xdf\x21\x74\x2e\x9f\x2c\xbe\ \x52\xbe\xa0\x4e\xe4\x62\x29\x35\x8a\xd0\x24\xcc\x0e\x19\xd9\x10\ \x2b\xc8\x2a\x7d\x3f\x59\x7c\xac\xfc\x9a\x88\xca\xe0\x19\xcc\x76\ \x04\x1c\x56\x90\xd1\x19\x27\x23\x21\x22\xf7\xc0\x7c\x0f\x73\x23\ \x4b\xd4\x88\x79\x83\x56\x0f\xa3\x20\x7e\x98\xec\xc9\x4d\x6e\x4f\ \xe0\x78\x2c\x45\x7b\xb7\x1c\x52\x9e\xa0\x17\x3f\x7c\x9c\x0d\x43\ \x96\x69\xe8\x2b\xec\x56\xaa\x48\xcb\x89\xf8\x08\x8d\xee\xba\x4c\ \xd6\xea\x5f\xba\xf7\xf9\x3a\x7d\x59\x9a\x51\x57\xc8\x42\x36\x7b\ \x4c\x75\x0e\xda\x16\x30\xf0\xd7\x19\xea\xa2\xab\xfb\x6f\x7b\x33\ \xc2\x73\xcd\x34\x2f\x17\x1f\x34\x1f\x5b\x3b\xa2\xe2\xc6\x66\x78\ \x05\x1c\x4c\x23\x44\x11\xb3\x53\x79\xbc\x15\x94\x62\x11\x9c\xe4\ \x7a\xba\x37\xc5\x68\xec\xe0\x04\xa5\xfb\xaf\x34\x14\xd2\x09\x54\ \xaa\x16\xe5\x66\x6f\xd1\x87\xf2\x1b\x15\x11\x0c\x42\xc4\xdb\x2a\ \xa4\x40\xf6\xc0\x1d\x4a\x44\x76\xd2\xb1\xd8\x90\x56\x28\x80\x00\ \x30\xa2\xa2\xba\xbc\x9c\xdf\x9d\x15\x02\x0e\x6b\x85\x82\x09\x48\ \x42\xc9\x4b\x03\x37\x94\x2d\x98\x5d\x9a\xa6\x23\xdb\x8e\xd2\x66\ \x6c\xa2\x43\x7f\xd7\x3e\x91\x61\x4f\x38\x11\x58\x80\x31\xd0\x4a\ \x7e\xaf\x5d\xb2\x44\xa4\x8f\xb2\x03\x77\xa9\x3b\x1c\x53\x11\x3d\ \xad\x08\x70\xb7\xe6\xe5\xe2\x76\x5d\xc7\x82\x35\x47\x13\x9f\xc6\ \xe8\x57\xe5\xa7\x8a\xf8\x69\x4d\x40\x30\xc5\x22\x4f\xa5\x5d\x8f\ \x2b\xb4\xec\x17\x5a\xae\x56\x5a\x15\xe0\x85\x2d\x9a\x18\x38\x0f\ \x61\x63\x1e\xf8\x11\x42\x1f\xaf\x41\xa8\x36\x22\x82\x08\x00\x3c\ \xb7\x3a\x69\xca\x89\x66\x17\x54\xd0\xc7\x82\xf5\x0d\x42\x9c\xd1\ \xa2\x2b\x30\x5d\x7e\xad\xae\xae\x87\x5d\xe0\xe7\x8b\x32\x41\xff\ \x8a\xa3\x7f\xbd\x83\xd9\xcb\xbe\x0e\x2e\xeb\xed\xe1\x38\xcd\x94\ \xdf\xaa\xc8\xba\xb8\x5d\xd8\xad\x45\x54\xd9\x10\x56\x90\xf7\xe7\ \xbf\xcc\x13\x64\x31\x8c\xc1\xf5\x92\x1d\xf7\x30\xca\x80\x9d\x86\ \x89\x2f\xb4\x85\x33\x19\x85\x00\x53\x77\xe2\xe5\xc8\xb4\x87\x61\ \xce\xcb\x48\x60\x70\x9f\xc8\xc0\x1d\xbd\x8c\xaf\xad\xf0\x2f\xe0\ \xb5\xc4\x97\x73\x70\xb5\x55\xa7\x81\xdf\x56\x72\x7c\x9f\x37\x03\ \x87\x5a\x7f\xd0\xc0\x03\x8d\xe7\x01\xb7\x6b\xb8\xda\xf7\x2e\x3e\ \x07\x5c\xa6\x8d\xdf\xbb\x88\xfe\x01\x2d\xca\x14\x85\x20\xa6\xbe\ \xe2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x09\x1b\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x64\x00\x00\x00\x64\x08\x06\x00\x00\x00\x70\xe2\x95\x54\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc2\x00\x00\x0e\xc2\x01\x15\x28\ \x4a\x80\x00\x00\x08\xb0\x49\x44\x41\x54\x78\x5e\xed\x9c\x57\xc8\ \x24\x45\x14\x85\xd7\x9c\xc5\x9c\xd7\x2c\x98\x33\x6b\x0e\xb8\x66\ \x45\x14\x0c\x0f\x0a\xa2\xbe\x88\x22\x98\x31\xe7\x9c\xb3\x20\x62\ \x06\xc5\x27\x31\xa0\xb8\x2a\x06\x54\x74\x4d\x88\x98\xf3\x9a\xb3\ \xae\x39\x9f\xef\x9f\xe9\xb5\xf6\xda\xdd\xd3\xa9\x7a\x6a\x7a\xeb\ \xc0\xc7\xf4\x1f\xa6\xfa\x76\x9f\xae\x74\xab\x66\xc6\x15\xd0\x3f\ \x23\xc2\x1f\x62\x7f\xd1\x79\xa5\x5d\x7c\xa8\xfc\x2d\x0e\x16\x9d\ \x56\xda\x85\x87\x4c\xe7\x4d\x49\xbb\xe8\xd0\xe9\xb4\x29\xf6\x62\ \x43\x93\x8d\x2f\xa1\xb3\xa6\xd8\x0b\x0d\x4d\x36\x3e\x97\x4e\x9a\ \x62\x2f\x32\x34\xd9\xf8\xfe\x4a\xf9\xf9\x40\xd1\x19\xb9\x17\x07\ \xa1\xc9\xc6\x77\x80\xb0\xa6\x74\xaa\xa6\xb8\x17\x06\xa1\x29\x2d\ \xbe\x4e\x9b\xe2\x5e\x14\x84\xa6\xac\xf8\x3a\x6b\x8a\x7b\x41\x10\ \x9a\xf2\xe2\xeb\xa4\x29\xee\xc5\x40\x68\x1a\x14\x5f\xe7\x4c\x71\ \x2f\x04\x42\x53\x91\xf8\x3a\x65\x8a\x7b\x11\x10\x9a\x8a\xc6\xd7\ \x19\x53\xdc\x0b\x80\xd0\x54\x26\xbe\x4e\x98\xe2\x06\x0f\xa1\xa9\ \x6c\x7c\x23\x6f\x8a\x1b\x38\x84\xa6\x2a\xf1\x8d\xb4\x29\x6e\xd0\ \x10\x9a\xaa\xc6\x37\xb2\xa6\xb8\x01\x43\x68\xaa\x13\xdf\x48\x9a\ \xe2\x06\x0b\xa1\xa9\x6e\x7c\x23\x67\x8a\x1b\x28\x84\xa6\x26\xe2\ \x1b\x29\x53\xdc\x20\x21\x34\x35\x15\xdf\xc8\x98\xe2\x06\x08\xa1\ \xa9\xc9\xf8\x46\xc2\x14\x37\x38\x08\x4d\x4d\xc7\x17\xbc\x29\x6e\ \x60\x10\x9a\x7c\xc4\x17\xb4\x29\x6e\x50\x10\x9a\x7c\xc5\x17\xac\ \x29\x6e\x40\x10\x9a\x7c\xc6\x17\xa4\x29\x6e\x30\x10\x9a\x7c\xc7\ \x17\x9c\x29\x6e\x20\x10\x9a\x6c\x7c\x6d\x81\x49\x8d\xef\x25\x9e\ \xa9\xff\x9a\x27\x4e\xee\xaa\xc8\x7b\xda\x94\x8d\xaf\x4d\xfd\x29\ \x66\xeb\x1d\x36\xa3\x68\x48\x7d\x35\x7a\x3f\x66\xee\xbf\x46\x05\ \xa2\x2e\xd4\x90\xb6\xe5\xf5\x7e\xc4\x1a\x12\x98\xa2\x21\x81\x29\ \xc4\x26\x8b\xf2\xe7\x14\xf3\xf4\x5f\xf3\xc4\xc7\xd8\x7e\x14\xbf\ \x08\xe6\x06\x6d\xc8\xeb\xfd\x08\xd1\x10\x86\x91\x8b\x8b\xe5\xc4\ \x62\xfc\x22\x47\x3f\x88\x77\xc4\xa7\xe2\x77\x61\x63\xf5\xa1\x19\ \xce\x10\x6a\xc6\x5a\x62\xf3\xfe\x6b\x9e\xa6\x88\x87\xc4\x8b\xe2\ \x27\xc1\xbc\xc0\xb7\x66\x38\x43\x16\x10\x5b\x89\xbd\xc4\x96\xfc\ \x22\x47\x6f\x88\x9b\xc4\x83\xe2\x7b\x41\x2d\xf1\x2d\xaf\xf7\x23\ \xc4\x4e\x9d\x98\xe6\x16\x8b\x88\xf1\x03\xa0\x69\xa3\x46\xcd\x22\ \x7c\x3f\x28\xad\x28\x8e\xb2\x02\x53\x34\x24\x30\x45\x43\x02\x53\ \x34\x24\x30\x45\x43\x02\x53\x34\x24\x30\x45\x43\x02\x53\x34\x24\ \x30\x45\x43\x02\x53\x34\xa4\x3d\x2d\x28\x56\x13\x24\x4d\xe7\xe0\ \x17\x69\x8a\x86\xf8\xd5\x7c\xe2\x44\xf1\x8a\xf8\x46\xbc\x2a\xde\ \x17\xdf\x89\xfb\xc5\x2e\x62\x3a\x45\x43\xfc\x89\xc4\xe8\x5b\xe2\ \x2c\xb1\x06\xbf\x70\xc4\x3a\xcf\x8e\xe2\x5e\x71\x9f\x20\xa1\x3a\ \xa6\x68\x88\x1f\xed\x2e\x58\x16\x20\xf9\x39\x48\x3b\x8b\xc7\x05\ \xb5\x29\x1a\xe2\x41\x5b\x88\x3b\x44\x99\xfd\x5a\xac\xfb\x5c\xc7\ \x41\x34\xa4\xbc\xde\xeb\xbf\x22\xf7\x18\xad\x2f\xee\x12\x99\x9d\ \x76\x8e\xf6\x11\x13\xa2\x21\xe5\x75\xa8\xf8\xb8\x0f\xc7\x89\x30\ \x63\x92\x58\x68\xec\xa7\xf2\x62\x3d\xe7\xa0\x68\x48\x79\x31\x3a\ \x5a\xa6\x0f\xc7\x28\xcb\x8c\xc7\xfa\xaf\x45\x35\x31\x1a\x52\x5f\ \x59\x66\x9c\x2e\xb6\xee\xbf\x16\xd5\xf8\x68\x48\x3d\xe5\x99\x71\ \x5a\xef\x70\xec\xb5\xb0\x29\xd1\x90\xea\xca\x32\xe3\x0c\x91\x98\ \x91\xa8\xa8\x29\x53\xa2\x21\xd5\xb4\xae\x60\xa7\x8b\x35\xe3\x7c\ \x71\x6a\xef\xf0\x7f\xc2\x94\xe3\x7a\x87\x99\x9a\x14\x0d\x29\x2f\ \x6a\xc6\xc3\x62\xe1\xb1\x9f\xfe\xd3\x99\x62\xd0\x0d\xc7\xb0\xac\ \x9a\xc2\xf6\xa2\x1b\xa2\x21\xe5\x94\xd5\x4c\x61\xc6\x29\xbd\xc3\ \x81\xa2\xa6\x3c\xda\x3b\x9c\x4e\xb7\x8b\xc9\x3e\x36\xca\xb1\x47\ \x6a\x2e\xb1\xac\x60\x06\xca\x93\x54\xc6\x78\xf6\x59\x91\x15\x9d\ \xd0\x7f\xcd\xd3\x27\x82\xb4\x03\xc9\xbb\x32\x3b\x17\xd9\x07\xcc\ \x47\xd2\xd8\xf1\x48\xb2\xef\x5b\xc1\x3e\xe1\x3c\x65\x99\x41\xae\ \xea\xe4\xde\x61\x21\x51\x43\xac\x79\x2f\x0b\x76\x6a\x4e\xf5\x65\ \x08\x37\x75\x15\x41\x1a\x81\xc4\x1a\x39\x9d\xd9\x45\x11\xf1\x7f\ \x98\xb8\x94\x60\xb3\x5c\x9e\xa6\x0a\xb6\x93\x7e\x29\xb8\xa1\xdc\ \xe4\x41\xe2\xff\xd8\xe5\xf8\x81\x78\x44\xbc\x26\x92\xf7\x67\xa9\ \x29\x33\xa8\x49\x27\xf5\x0e\xa7\xe9\x2b\xb1\x8e\xe0\xe1\x2a\xb4\ \xdb\xaf\xac\x21\xfc\x9d\x9b\xca\xc4\x69\x63\xc1\x58\x7c\x43\xc1\ \xcd\x4d\xf2\x3b\x79\x65\x24\xef\x27\x23\x3a\x28\x05\x41\x8d\x60\ \xe7\xfb\x6f\x22\x6f\xf7\x7b\x72\x0d\xfc\x3f\xa9\x6f\xb2\xb0\x24\ \xff\x9e\x16\xa4\x3f\x30\x28\xeb\xfd\x74\xe0\xfc\xaf\xed\x33\x2e\ \x14\xc7\xf6\x0e\x0b\x89\xd1\x97\x35\x8f\x58\xb6\x17\x93\xc7\x7e\ \x2a\x28\x2e\xc6\xa5\xa8\xc8\x5e\xae\x27\x8e\x10\x0f\x08\x9e\x00\ \xf6\xde\xda\x8f\x18\xfb\x86\x1b\x9d\x34\x51\xd4\x84\xa7\x04\x37\ \x73\x23\xc1\xee\xfa\xbc\x24\x20\x66\xf0\x04\xdb\x32\x2f\x10\x65\ \x44\xcd\xb0\x65\xb0\x3e\xc2\x83\x5a\x5a\xb6\xa0\xa2\xa2\xdf\xc0\ \x94\xb5\xc5\x21\x82\xa7\xec\x6b\x81\x29\xc9\x4d\xb2\x65\x37\x8d\ \x6b\x06\x7d\x0c\x35\x82\x9b\xc3\x5a\x05\xcd\xea\xac\x22\x4b\xbe\ \xcd\xd8\x40\x54\x92\x2d\xac\x8c\xe8\x4f\x58\xba\xe4\x49\x38\x52\ \xdc\x2d\x3e\x14\x34\x1d\x6d\x19\xc2\xb9\x7e\x16\x4f\x08\xda\xfc\ \x1d\xc4\x12\x22\x6f\xa0\x91\x65\x06\x35\xab\x8c\x38\x9f\x2d\xe3\ \x0b\x31\xe8\x63\x16\xb9\xb2\x05\x96\x15\xa6\xd0\x19\x62\xca\xe1\ \x82\x15\x32\x32\xa5\xb4\xfb\x3c\xb9\xbe\x8c\xa1\x5c\xce\xc1\x8d\ \x7d\x49\x9c\x2d\x30\x83\x5d\xf3\x55\x9a\xa9\xb2\x66\x70\x3e\x5b\ \xc6\xe7\xa2\x96\x19\xc8\x16\x5a\x45\x98\x32\xbf\x48\x4c\xb9\x47\ \xd0\x91\xfa\xac\x29\x98\x4d\xa7\xf9\x8c\xa0\xd9\x98\x28\x96\x16\ \x79\x03\x85\xa6\xcc\x38\x47\xd8\x32\x30\x63\x4d\x51\x5b\xb6\xe0\ \x3a\x4a\x4c\x39\x4a\xd0\x84\x7c\x26\x92\x11\x92\x3d\x4f\x15\x28\ \x07\x30\x83\xb9\x05\xa3\x97\x4b\xc5\x66\xa2\x6a\x07\x1e\x94\x19\ \xc8\x16\x5e\x47\x74\xa2\x0c\x7f\x19\xe1\x50\x53\xc8\x07\x31\xfa\ \xfa\x55\x24\x37\xd3\x9e\xaf\x28\xc9\xfb\xa9\x75\xcc\x4f\xa8\x19\ \xe7\x89\x9d\x04\xe7\x64\x28\x9d\x35\xdc\xce\x32\xe3\x22\x51\x46\ \xe7\x0a\x5b\x06\x0f\x9d\xdd\xe4\x50\x4b\xf6\x04\x75\x95\x74\xf4\ \x0c\x89\x8f\x17\x0c\x89\x3f\x12\x75\x9b\xaf\xc4\x0c\x9a\xc2\x67\ \xc5\xc5\x82\x6d\x36\x4b\x8a\x2a\x1d\x78\x19\x33\x30\xfa\x32\x61\ \xcb\xc0\x8c\xd5\x45\xa3\xb2\x27\x69\x42\x98\xc2\x90\x98\xe6\xeb\ \x68\x41\x4d\x61\x48\x9c\x98\x52\xd6\x18\xfe\x9f\x99\x36\x9f\xca\ \x25\x8d\x42\xcd\x60\xc2\xc5\xe4\xb4\xca\xd0\xb6\xac\x19\x97\x0b\ \x5b\x06\x9f\x0c\x6e\xdc\x0c\x64\x4f\xd4\x94\x78\x6a\xa9\x29\x9b\ \x0a\x66\xbc\x2c\x87\x32\xeb\xae\x52\x53\xe8\x33\x30\x83\x3e\x83\ \x36\x9f\xd1\x14\x66\xe4\x7d\xce\xbd\x29\x33\xae\x10\xb6\x0c\xcc\ \x18\x94\x87\xab\x2c\x7b\xb2\x26\x45\x4d\xa1\xb3\x25\xb1\x86\x29\ \x4c\xdc\xb8\x98\xe4\x8b\x00\xec\xb9\x2d\xfc\x8f\xdb\x81\xd3\x6c\ \xb0\x01\x0d\x33\x18\x4d\x95\xed\x33\x68\xe6\x8a\x2a\xcb\x0c\x72\ \x6b\x2b\x0b\x6f\xb2\x27\x6c\x5a\x74\xb6\x4c\xd4\x18\x09\x61\x0a\ \x49\x3c\xfa\x14\xb7\xa3\xb7\xe6\x24\xbf\x4b\x9a\xa9\xe7\x05\x4f\ \xf6\x6e\x82\x79\x06\x35\xc3\xb7\x19\x57\x0a\x5b\x06\x93\x5e\xaf\ \x66\x20\x7b\x52\x1f\xa2\x9d\xa7\xf9\x62\xd2\x44\x6a\x9a\x05\x20\ \x6a\x4a\x56\x9f\xc2\xcf\xc9\x3c\x83\x49\xdf\x35\x62\x57\x41\x86\ \xb8\x4a\x9f\x51\xd6\x8c\xab\x84\x2d\x03\x33\x56\x12\xde\x65\x4f\ \xec\x4b\x5c\x28\x37\x93\x9b\x46\x4d\x61\x9e\x92\xcc\x51\xd2\x0c\ \xa1\x76\xf0\xc5\x01\xe4\x96\xc8\x28\xd3\xf4\x55\x19\x4d\x95\x35\ \xe3\x6a\x61\xcb\x20\x95\xdf\x8a\x19\xc8\x9e\xdc\xb7\x48\xb3\xf0\ \x4d\x0e\xec\x1a\x27\x21\x49\x93\xc4\xcd\x4f\x8c\xa1\x66\x90\x28\ \x7c\x4e\xd0\x67\x30\xb4\xa5\x66\x0c\x4a\xd5\xa7\x99\x71\x89\x28\ \xaa\x3c\x33\x56\x14\xad\xc9\x06\xe0\x5b\xcc\xa6\xb9\xc1\x3c\xf5\ \x27\x88\x27\x05\x93\x47\x3a\x7a\x8c\xa1\x03\x67\xa5\x0f\x33\xd8\ \xd4\xbc\xbc\xc0\x8c\xac\xda\x91\x55\x33\xca\x9a\x41\xb3\x68\xcb\ \x60\xb5\x71\x05\xd1\xaa\x6c\x10\x6d\x88\x8e\x9e\x09\x1d\x69\x72\ \x16\x75\x58\xd9\xa3\xa3\x67\xae\x42\x9f\xc1\xb8\x7f\x0f\xc1\xcd\ \xa8\x32\xb4\x2d\x6b\xc6\xb5\xc2\x96\x31\x14\x33\x90\x0d\xa4\x2d\ \x25\x59\x62\xd6\x0d\xc8\x9c\x32\x4f\x61\xfd\xfc\x7a\x41\x33\xc5\ \xd0\x16\xe3\xb2\xe4\xd3\x0c\x56\x19\xa9\x99\x43\x91\x0d\xa6\x4d\ \xd1\x0c\xb1\x90\xb4\x89\xd8\x5b\xec\x27\xb6\x13\x0c\x93\xf3\x72\ \x53\x4c\xca\xa8\x4d\x36\xf6\x32\x66\x70\x6e\xbe\x69\xc8\x96\xc1\ \xf2\x2f\x0f\xc3\xd0\x64\x03\x6a\x5b\xdc\x18\x36\x49\xb0\x69\x62\ \x55\xc1\x3c\x23\x6f\xd2\x87\x98\xcb\xd8\xb8\xcb\x98\x41\xed\xbc\ \x59\xd8\x32\xde\x14\xa4\xf0\x87\x2a\x1b\xd4\x30\x44\x6d\x60\x6b\ \x11\x5f\xdb\x34\xc8\x0c\x3e\x1e\x46\xe7\xef\xc6\x4c\x0a\xbe\xa8\ \xb2\xcc\x60\x88\x3d\x74\x33\x90\x0d\x2c\x74\xed\x29\xdc\x78\x19\ \x26\x93\xc4\x2c\x22\xcc\xb8\x4d\xb8\xef\x07\xb6\x0a\x31\xc8\x08\ \x42\x36\xb8\xd0\x45\xa7\x6f\x63\x66\x46\x4f\x12\x33\x4f\x98\x71\ \x8b\xb0\xef\x7d\x5d\x30\x0c\x0f\x46\x36\xc0\xd0\x45\x0a\xc3\xc6\ \x0c\x7c\x7b\x29\xcb\xb8\x69\xc2\x8c\x5b\x85\x7d\x4f\x70\x66\x20\ \x1b\x64\xc8\x22\x17\x66\xe3\x75\x61\x86\xcf\x3a\x89\xab\x2c\x33\ \x82\x6a\xa6\x5c\xd9\x40\x43\xd6\x31\xc2\xc6\x6b\x21\x3f\xc6\x0c\ \x1f\x8d\x44\x9f\x61\x65\x83\x1d\x16\xef\x0a\xd6\xc7\xf3\x44\xee\ \x2b\xed\xbd\x16\x4c\x61\x5e\x73\xa7\xf3\xbb\x04\x36\x3e\x2f\x2a\ \x82\x95\x0d\x78\x98\x90\x3e\xc9\x12\x13\x48\xd6\x50\xd2\xde\x57\ \x14\xd2\x32\x83\x36\x78\x7b\x55\x5e\xba\x7a\xd4\xb4\x8d\x18\x94\ \xf1\xcd\x13\x66\x6c\x2b\x48\xb7\x04\xad\xb4\x27\x69\x18\x0c\x6a\ \xb2\xd2\x16\x8d\x8a\xf2\x82\xb0\xbb\xdb\xa3\x6a\xea\x6d\x91\x76\ \xb3\xf3\x60\xad\x85\x79\xcb\xb4\x2f\x7f\x89\x6a\x46\xcc\x15\xd2\ \x6e\xb8\x85\x9d\xf7\xac\x44\xf2\xc1\x4c\xd6\xf0\xf3\x96\x7b\xa3\ \x6a\x88\xe4\xa3\xcd\x5f\x01\x2b\x8c\x8c\x9a\xc8\x65\x91\xb2\x9f\ \x57\x44\xb5\xa4\xc3\x04\x7b\x68\x59\xab\xb8\x51\xec\x2b\x48\xd3\ \x8f\x90\xc6\x8d\xfb\x17\xa6\x87\x61\x1e\x5b\xff\x9d\x6e\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\x83\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x38\x49\x44\x41\x54\x58\x85\xed\xd4\xb1\x11\ \x00\x20\x08\x03\x40\x74\xff\x9d\x75\x01\x3c\x2a\xb4\xf0\xbf\xa4\ \x20\x4d\x2e\x11\xf0\xbb\x91\xdc\xd6\xcd\xcc\xd9\x1c\x46\xa9\xa3\ \x03\xd9\xcf\x23\x1d\x78\xce\x0e\x60\x07\xb0\x03\x00\x1b\x8a\x27\ \x05\x10\xf6\xe3\x4e\x43\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x1a\xb9\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\xb2\x00\x00\x00\xb2\x08\x02\x00\x00\x00\x69\xe8\x02\x3f\ \x00\x00\x01\x35\x69\x43\x43\x50\x49\x43\x43\x20\x50\x72\x6f\x66\ \x69\x6c\x65\x00\x00\x78\x9c\x63\x60\x60\x32\x61\x80\x82\xdc\xbc\ \x92\xa2\x20\x77\x27\x85\x88\xc8\x28\x05\xf6\x7b\x0c\x6c\x0c\x2c\ \x0c\x82\x0c\xda\x0c\x16\x89\xc9\xc5\x05\x0c\x98\x80\x11\xc1\xfc\ \x76\x0d\xc2\xbb\xac\x8b\x45\x1d\x21\xc0\x95\x92\x5a\x9c\x0c\xa4\ \xff\x00\x71\x65\x52\x76\x41\x09\xd0\xe8\x0a\x20\x5b\xa4\xbc\x04\ \xcc\xee\x01\xb1\x93\x0b\x8a\x40\xec\x05\x40\xb6\x68\x11\xd0\x81\ \x40\xf6\x0e\x90\x78\x3a\x84\x7d\x06\xc4\x4e\x82\xb0\x1f\x80\xd8\ \x45\x21\x41\xce\x40\xf6\x17\x20\xdb\x21\x1d\x89\x9d\x84\xc4\x86\ \xda\x0b\x02\xf2\xc5\x20\x8f\x7b\xba\x3a\x9b\x19\x5a\x9a\x99\xe9\ \x1a\xe9\x1a\x2a\x24\xe5\x24\x26\x67\x2b\x14\x27\x27\xe6\xa4\xa6\ \x90\xe1\x2b\x02\x00\x14\xc6\x10\x16\xb3\x18\x10\x1b\x33\x30\x30\ \x2d\x41\x88\x21\xc2\xb3\x24\xb5\xa2\x04\xc4\x72\x29\xca\x2f\x48\ \xca\xaf\xd0\x51\xf0\xcc\x4b\xd6\x43\xd1\x9f\xbf\x88\x81\xc1\xe2\ \x2b\xd0\x8c\x09\x08\xb1\xa4\x99\x0c\x0c\xdb\x5b\x19\x18\x24\x6e\ \x21\xc4\x54\x80\x61\xc7\xdf\xc2\xc0\xb0\xed\x7c\x72\x69\x51\x19\ \xd4\x19\x52\x40\x7c\x94\xf1\x0c\x73\x12\xeb\x64\x8e\x6c\xee\x6f\ \x02\x0e\xa2\x81\xd2\x26\x8a\x1f\x35\x27\x18\x49\x58\x4f\x72\x63\ \x0d\x2c\x8b\x7d\x9b\x5d\x50\xc5\xda\xb9\x69\x56\xcd\x9a\xcc\xfd\ \x75\x97\x0f\xbe\x34\xff\xff\x1f\x00\x81\x49\x5c\xa9\xc6\xc2\x24\ \x8e\x00\x00\x19\x3f\x49\x44\x41\x54\x78\x9c\xed\x9d\x7b\x74\x94\ \xc5\xdd\xc7\x7f\xf3\x3c\xcf\xee\x3e\x7b\xcb\x6d\xb3\xe6\x46\x08\ \x9b\x40\x48\xc8\x05\xc1\x80\x16\x4d\x41\x0e\x58\x24\x3d\xa5\xa0\ \x20\xc7\xa2\xb6\x88\x58\x7b\xb4\xbe\x5a\xad\x9e\xf2\x9e\x1e\xa8\ \x6f\x5b\x34\x54\x4f\x2d\xb6\xc8\x91\x42\x78\x9b\x16\xb1\x05\x4f\ \xad\x1c\x0d\xbc\x90\x22\xe5\x7e\x4f\x20\xb7\x4d\x08\x21\x09\x24\ \x61\x73\x21\xc9\xee\xe6\xb9\xcd\xfb\xc7\x98\x75\x4d\x26\xc9\x86\ \x5c\x71\xe7\x73\xf2\x47\xb2\x99\x9d\x99\xe7\x99\xef\x33\xf3\x9b\ \xdf\xfc\xe6\x19\x84\x31\x06\x06\xe3\xeb\x70\x63\x5d\x01\xc6\x78\ \x84\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\ \x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\ \x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\ \x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\ \x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\x41\ \x81\xc9\x82\x41\x41\x18\x7a\x16\x6c\x4b\xc1\x9d\x02\x42\x28\xd0\ \x94\xac\x51\x19\xbd\x19\x6a\x6f\xa1\x28\xca\x17\x5f\x7c\x21\x49\ \x12\xcf\xf3\x30\x18\x3d\x32\x46\x07\x8c\x31\x42\x48\x96\x65\xa3\ \xd1\x38\x77\xee\x5c\xf2\xe7\x80\xdf\x1a\xaa\x2c\xdc\x6e\xf7\xdb\ \x6f\xbf\xed\x72\xb9\x38\x8e\x63\x1d\xcf\xf8\x04\x21\x84\x31\x8e\ \x89\x89\x19\x3d\x59\x70\x1c\x17\x1a\x1a\x3a\x61\xc2\x84\xc7\x1f\ \x7f\x5c\xd3\x34\xd6\x5b\x8c\x37\x30\xc6\x1c\xc7\xfd\xe9\x4f\x7f\ \x12\x45\x31\xf0\x6f\x0d\x83\xc9\x29\x49\x52\x58\x58\x58\x76\x76\ \xf6\xd0\xb3\x62\x8c\x10\x5b\xb7\x6e\x95\x24\x29\xf0\xf4\xc3\x20\ \x0b\x00\x90\x65\x59\x55\x55\x4d\xd3\x38\x8e\xcd\x78\xc7\x17\xa4\ \x51\x06\x3b\xbe\x0f\x8f\x2c\x10\x42\x3c\xcf\x23\x84\x98\x2c\xc6\ \x1b\xb7\xd7\x28\xac\x15\x19\x14\x98\x2c\x18\x14\x98\x2c\x18\x14\ \x98\x2c\x18\x14\x98\x2c\x18\x14\x98\x2c\x18\x14\x98\x2c\x18\x14\ \x98\x2c\x18\x14\x98\x2c\x18\x14\x98\x2c\x18\x14\x98\x2c\x18\x14\ \x98\x2c\x18\x14\x98\x2c\x18\x14\x98\x2c\x18\x14\x98\x2c\x18\x14\ \x98\x2c\x18\x14\x86\x27\x0c\x67\x40\x34\x4d\xf3\x05\x08\x71\x1c\ \x37\x6a\x21\x9f\xfe\xe5\x92\xd8\xf4\x11\x02\x63\xac\x69\x1a\xdc\ \x6e\xd8\xcb\x78\x63\xc4\x65\x41\x82\xc6\x9c\x4e\xe7\xe1\xc3\x87\ \x31\xc6\x18\xe3\x59\xb3\x66\xcd\x98\x31\x63\xa4\xcb\x25\x21\xce\ \xfb\xf7\xef\x77\x3a\x9d\x7a\xbd\x5e\xa7\xd3\x2d\x5d\xba\x34\x34\ \x34\x74\x84\x8a\x23\xf1\x69\x23\x94\xf9\xe8\x33\xe2\xb2\x20\x0f\ \x6b\x5d\x5d\xdd\x9e\x3d\x7b\x54\x55\x55\x55\xd5\x6c\x36\x8f\xb4\ \x2c\x88\xfe\x10\x42\xc7\x8e\x1d\x2b\x28\x28\xb0\x5a\xad\x06\x83\ \x21\x3b\x3b\x3b\x34\x34\x34\xc0\x88\xf8\xc1\x22\xcb\xf2\xd9\xb3\ \x67\xdd\x6e\x77\x62\x62\x62\x42\x42\xc2\xb0\xe7\x3f\xca\x8c\xd2\ \x20\x22\x08\x82\xc5\x62\x21\x61\xc0\x7a\xbd\x7e\x74\x0a\x05\x00\ \x51\x14\xad\x56\xab\xc5\x62\x31\x18\x0c\x23\xf7\x34\xab\xaa\xba\ \x6b\xd7\xae\x0f\x3f\xfc\x50\x92\xa4\x84\x84\x84\x57\x5e\x79\x65\ \xea\xd4\xa9\x77\x74\xc0\xf3\x28\xd5\x1b\x63\xac\x76\x33\x9a\xbb\ \x8c\x34\x4d\x1b\x85\x72\x65\x59\xfe\xfc\xf3\xcf\x05\x41\x88\x88\ \x88\x28\x2f\x2f\xbf\x70\xe1\x02\xdc\xe1\x1b\xec\xee\x54\x39\x8f\ \x2b\x10\x42\x56\xab\x55\x51\x14\xd2\x1d\x0e\x6a\xa3\xce\xf8\x84\ \xc9\x62\x18\xd0\xeb\xf5\xcf\x3e\xfb\xac\xcd\x66\xf3\x7a\xbd\x0b\ \x16\x2c\xb8\xff\xfe\xfb\xc7\xba\x46\x43\xe5\x4b\xdb\x02\xc3\x97\ \x36\x1a\x82\x3b\xb8\xeb\x1b\x2b\x10\x42\x77\xdf\x7d\xf7\xb6\x6d\ \xdb\x30\xc6\x82\x20\x90\xe1\xe3\x8e\x1e\x44\x04\x15\xab\x3c\xe2\ \x11\x20\x72\x19\x1a\xd6\x38\x34\x4a\x5d\x08\x71\x2a\x20\x84\xc8\ \xde\x59\x32\xf6\x0f\xd6\xab\xe1\x73\x18\x90\xbd\x53\x3e\x03\xe2\ \x36\x5a\xc5\xdf\xf7\xe0\xfb\x04\x02\x73\x45\x68\x9a\x46\x4c\x5a\ \xdf\x57\xfa\xca\x1c\xfc\xae\x91\x5a\x22\xb5\x38\xdf\xbd\xf2\xfd\ \x49\x92\x8d\x90\xf8\x84\x5d\xd5\x5b\xad\xba\xb0\x70\xbd\x2d\xc6\ \x38\xc1\x61\x99\xca\x23\x7e\x14\x94\x41\x6e\x87\xff\xd4\xc0\xff\ \xf2\x02\xb7\xe1\x55\x55\xe5\x79\xde\x97\x8f\x7f\x26\xb2\x2c\x0f\ \xea\x96\xf5\xc8\xaa\xf7\x7f\xfb\x6f\x03\x5f\x85\xfb\x4a\xd3\xdb\ \xb1\x41\x2e\x93\x5a\xa2\x7f\x71\x44\xeb\x3d\x6e\x88\xef\xcf\x11\ \x9a\xef\x08\x9f\xd6\x7e\xa8\x61\x55\x03\x2d\x42\x1f\x79\x8f\xed\ \x81\x05\x31\x4b\x1c\x96\x64\x0c\x78\xe4\x46\x13\xf2\x7c\xf0\x3c\ \x7f\xfd\xfa\xf5\xd2\xd2\xd2\xe6\xe6\x66\x8f\xc7\xc3\x71\x9c\x28\ \x8a\xf1\xf1\xf1\xa9\xa9\xa9\x16\x8b\xa5\xaf\x67\xae\x47\x3e\x3c\ \xcf\xbb\xdd\xee\xa2\xa2\xa2\x9a\x9a\x1a\x49\x92\x10\x42\xa2\x28\ \x46\x47\x47\xc7\xc7\xc7\xc7\xc7\xc7\xfb\x3f\xa0\x03\x56\x89\xe7\ \x79\x49\x92\xca\xca\xca\x1a\x1a\x1a\x9a\x9a\x9a\xc8\xed\x36\x99\ \x4c\x36\x9b\x6d\xe2\xc4\x89\x13\x27\x4e\x84\xbe\xdb\x00\x63\x7c\ \xe5\xca\x15\x97\xcb\x45\xda\x7e\xf2\xe4\xc9\x56\xab\xd5\xf7\x70\ \x93\x5f\x6e\xdd\xba\x75\xe5\xca\x15\x52\xc9\xd8\xd8\xd8\xe8\xe8\ \x68\x8e\xe3\x5a\x5b\x5b\x4b\x4a\x4a\x6e\xdc\xb8\xe1\xf5\x7a\x49\ \x1d\xec\x76\x7b\x46\x46\xc6\x5d\x77\xdd\x05\x7e\x82\x40\x08\x55\ \x56\x56\x5e\xbb\x76\xed\xfa\xf5\xeb\xe4\x43\x8b\xc5\x62\xb7\xdb\ \x33\x33\x33\x2d\x16\x0b\xe9\x39\x86\xb7\xdb\x10\x2c\x82\x15\x00\ \x21\x00\x15\xab\x05\x75\x7b\x4b\xda\xce\xaf\x9e\xfc\x72\x7a\xd8\ \x3d\xc3\x58\x86\x3f\x18\x63\xbd\x5e\x8f\x10\xda\xbd\x7b\xf7\xd1\ \xa3\x47\x2b\x2a\x2a\xbc\x5e\x2f\x74\xcf\x60\xef\xba\xeb\xae\xd4\ \xd4\xd4\x85\x0b\x17\x66\x67\x67\xfb\x5c\x52\xd4\x7c\x48\x0b\x1d\ \x3a\x74\xe8\xe0\xc1\x83\xa5\xa5\xa5\x8d\x8d\x8d\x82\x20\x90\x7c\ \xc2\xc2\xc2\x62\x63\x63\x1f\x7a\xe8\xa1\x9c\x9c\x1c\x83\xc1\xd0\ \xbf\x32\x7c\x23\xd7\xe1\xc3\x87\x0f\x1f\x3e\x5c\x5e\x5e\xde\xd0\ \xd0\xa0\x28\x0a\x74\x77\x69\x21\x21\x21\x13\x27\x4e\xcc\xc8\xc8\ \x78\xe2\x89\x27\x4c\x26\x13\xb5\x4a\x9a\xa6\xed\xd9\xb3\xe7\xe8\ \xd1\xa3\x82\x20\x18\x8d\xc6\xd7\x5f\x7f\x3d\x35\x35\xd5\x97\x92\ \xb4\x77\x65\x65\xe5\x1f\xfe\xf0\x87\xd6\xd6\x56\x9e\xe7\x7f\xf8\ \xc3\x1f\xe6\xe4\xe4\xec\xdb\xb7\xaf\xb0\xb0\xb0\xaa\xaa\xea\xe6\ \xcd\x9b\x3c\xcf\x93\xe2\x44\x51\x9c\x3a\x75\xea\xfc\xf9\xf3\x17\ \x2f\x5e\xcc\xf3\x3c\xc7\x71\x2e\x97\x6b\xf7\xee\xdd\xe7\xcf\x9f\ \xaf\xaf\xaf\x6f\x6f\x6f\x27\x29\x65\x59\x8e\x8c\x8c\x9c\x36\x6d\ \xda\xdc\xb9\x73\x1f\x7a\xe8\xa1\x1e\x23\xd1\x20\x9a\x03\xe8\x93\ \x76\x41\xc5\x2a\xf9\x0d\x01\x8a\x30\xd8\x9b\xbc\x37\x3e\xa8\xd8\ \xf4\xdf\x19\xef\x44\x8a\xd1\x83\x2d\x63\xe0\x4a\x60\xcc\xf3\x7c\ \x4b\x4b\x4b\x6e\x6e\xee\xc1\x83\x07\x01\x40\xaf\xd7\x9b\xcd\x66\ \x62\xa9\xe9\x74\x3a\x49\x92\x4e\x9e\x3c\x59\x54\x54\xd4\xd8\xd8\ \xb8\x74\xe9\x52\x62\x73\x50\xaf\x96\xe3\xb8\x8f\x3f\xfe\x78\xe7\ \xce\x9d\xb7\x6e\xdd\xb2\x5a\xad\x51\x51\x51\xb2\x2c\x2b\x8a\x82\ \x10\x52\x14\xa5\xaa\xaa\x6a\xcb\x96\x2d\x4d\x4d\x4d\x2d\x2d\x2d\ \x82\x20\xf4\xe5\xb1\xf0\x7d\xfe\xd1\x47\x1f\xe5\xe7\xe7\xbb\xdd\ \x6e\xa3\xd1\x68\x32\x99\x14\x45\x21\xb2\x23\xf6\xe3\x95\x2b\x57\ \x4a\x4b\x4b\xcb\xcb\xcb\xd7\xac\x59\x93\x9a\x9a\x4a\xcd\xaa\xab\ \xab\x4b\x92\x24\xd2\xf9\x53\x85\x28\xcb\xb2\xd7\xeb\x95\x24\x49\ \x10\x84\x6b\xd7\xae\x6d\xde\xbc\xf9\xe0\xc1\x83\x1d\x1d\x1d\x06\ \x83\x21\x24\x24\x04\x00\x78\x9e\xd7\xe9\x74\x9a\xa6\x95\x95\x95\ \x95\x96\x96\xba\x5c\xae\xd5\xab\x57\x97\x95\x95\xbd\xff\xfe\xfb\ \x45\x45\x45\x7a\xbd\x9e\xe7\x79\xff\x94\xb2\x2c\x9f\x3a\x75\xea\ \xf2\xe5\xcb\x5e\xaf\xf7\x7b\xdf\xfb\x1e\x74\x77\x4b\x83\x68\x0d\ \x80\xbe\xc6\x84\xaf\xbc\x9c\x18\xb0\x82\x65\x91\x37\xba\xba\x1a\ \xff\xb7\xea\xbd\x97\x52\xdf\x18\xf6\x61\x84\xf4\x75\x07\x0e\x1c\ \xb8\x75\xeb\x16\xcf\xf3\x06\x83\x21\x25\x25\x25\x2a\x2a\x8a\x3c\ \x4c\x25\x25\x25\x3a\x9d\xce\x62\xb1\x48\x92\xb4\x75\xeb\x56\x9b\ \xcd\x36\x6f\xde\x3c\xda\xd5\x60\x84\x50\x61\x61\x61\x5e\x5e\x9e\ \xaa\xaa\x61\x61\x61\x92\x24\x49\x92\x94\x9a\x9a\x3a\x69\xd2\x24\ \x8c\xf1\xd5\xab\x57\x6b\x6b\x6b\xdb\xda\xda\xfe\xf5\xaf\x7f\xf1\ \x3c\x6f\x34\x1a\xfb\xea\x30\xc8\x4d\xfc\xec\xb3\xcf\xf2\xf2\xf2\ \xc8\x4d\xef\xec\xec\x9c\x30\x61\xc2\xec\xd9\xb3\x2d\x16\x4b\x5b\ \x5b\x9b\xd3\xe9\xac\xac\xac\x24\x7d\xc0\x85\x0b\x17\x36\x6f\xde\ \xfc\xc6\x1b\x6f\x44\x44\x44\xf4\xce\x8a\xf3\xa3\xaf\xb2\x7c\x09\ \x0a\x0b\x0b\x3b\x3a\x3a\x64\x59\xb6\xd9\x6c\x29\x29\x29\xe1\xe1\ \xe1\xb2\x2c\xd7\xd4\xd4\x94\x97\x97\x93\x3b\xa0\x28\xca\x47\x1f\ \x7d\xa4\xd3\xe9\xce\x9c\x39\x53\x5c\x5c\x6c\x32\x99\xcc\x66\xb3\ \xc3\xe1\x20\x83\x4b\x7d\x7d\xfd\xe5\xcb\x97\xc9\x0b\x67\xdc\x6e\ \xf7\x5f\xff\xfa\x57\x87\xc3\x91\x91\x91\xd1\xc3\x5c\x1b\xb8\x35\ \x00\xea\xbd\x35\x2d\x72\x13\xf4\x6a\xe9\x9e\xce\x6f\x0d\x6b\x3a\ \xa4\x2f\x69\xbb\x50\xd2\x76\x3e\x35\xec\xee\x9e\x46\x86\xa6\xc1\ \x90\x0d\x9c\x8e\x8e\x0e\x84\x50\x56\x56\xd6\xd3\x4f\x3f\x6d\xb7\ \xdb\xc9\x13\x29\x49\xd2\xd5\xab\x57\xb7\x6f\xdf\x5e\x5c\x5c\x6c\ \x34\x1a\x55\x55\xcd\xcf\xcf\x4f\x4f\x4f\x8f\x8c\x8c\xec\xfd\x10\ \xb4\xb7\xb7\x6f\xdb\xb6\x4d\x92\x24\x51\x14\xdb\xdb\xdb\xa7\x4f\ \x9f\xfe\xdc\x73\xcf\x45\x47\x47\xeb\x74\x3a\x00\x90\x65\xb9\xb9\ \xb9\x79\xcb\x96\x2d\xa7\x4e\x9d\x12\x45\xb1\xaf\xc7\xd7\x57\x99\ \xfc\xfc\x7c\xd2\x31\xa8\xaa\xfa\xe2\x8b\x2f\x66\x67\x67\x8b\xa2\ \xc8\xf3\xbc\xa2\x28\xb2\x2c\x97\x96\x96\x6e\xde\xbc\xb9\xa1\xa1\ \x21\x34\x34\xb4\xba\xba\x3a\x3f\x3f\xff\x85\x17\x5e\xe8\x6d\x64\ \x60\x3f\xfa\x2a\xcb\x97\xa0\xa3\xa3\x43\xa7\xd3\x2d\x59\xb2\x64\ \xf9\xf2\xe5\x46\xa3\x91\xb4\xa5\x24\x49\x4e\xa7\xf3\xf7\xbf\xff\ \x7d\x63\x63\xa3\x4e\xa7\xd3\xe9\x74\xff\xf8\xc7\x3f\x54\x55\x35\ \x99\x4c\x0b\x16\x2c\x78\xea\xa9\xa7\xc8\x82\x1f\x00\x28\x8a\xd2\ \xd0\xd0\xb0\x71\xe3\xc6\x6b\xd7\xae\x99\x4c\xa6\x9b\x37\x6f\x16\ \x16\x16\xa6\xa5\xa5\x91\xf1\x65\xc0\x0e\x83\xa4\xb9\xd8\x76\xf2\ \xe3\xfa\xbc\x9b\x5d\x37\x34\xda\x38\x42\x69\x63\x84\x40\xc1\xd2\ \xb9\xd6\xe3\x00\x80\x71\xf7\x0d\xd5\xb4\xaf\x34\x71\xf6\x2c\x1c\ \x38\x00\x8a\x02\xb7\xe5\x4e\x56\x55\xf5\x81\x07\x1e\x58\xbf\x7e\ \x7d\x7c\x7c\xbc\x28\x8a\x82\x20\x90\x67\x3a\x25\x25\x65\xdd\xba\ \x75\x33\x66\xcc\xe8\xec\xec\x34\x9b\xcd\x57\xae\x5c\x39\x7a\xf4\ \x68\x8f\xeb\x54\x55\x15\x21\xf4\xe7\x3f\xff\xb9\xb9\xb9\x59\x14\ \x45\x8f\xc7\x93\x99\x99\xf9\xf3\x9f\xff\xdc\xe1\x70\x18\x8d\x46\ \x41\x10\xc8\x93\x1d\x17\x17\xf7\xda\x6b\xaf\xcd\x9d\x3b\xb7\xab\ \xab\xab\xff\xdb\x74\xe2\xc4\x89\x9b\x37\x6f\x1a\x0c\x86\x96\x96\ \x96\x15\x2b\x56\x2c\x5a\xb4\xc8\x6c\x36\x93\x76\x22\x59\xcd\x98\ \x31\xe3\xc5\x17\x5f\xb4\xd9\x6c\x5d\x5d\x5d\x00\x70\xe6\xcc\x19\ \xb7\xdb\x3d\x94\x17\x85\x11\xeb\x6a\xe5\xca\x95\xcf\x3c\xf3\x4c\ \x58\x58\x98\xc1\x60\x20\xd5\x36\x99\x4c\x99\x99\x99\xaf\xbd\xf6\ \x1a\xe9\xde\x7c\x63\x5f\x4e\x4e\xce\x4f\x7f\xfa\xd3\xd0\xd0\x50\ \xdf\x05\x8a\xa2\x98\x90\x90\xf0\xf2\xcb\x2f\x73\x1c\xa7\xaa\xaa\ \xc1\x60\x28\x2b\x2b\xab\xaf\xaf\x87\x00\x5e\x86\xa9\x61\x0d\x03\ \x3e\xde\xfc\x7f\x5b\xaf\x6c\xbc\xd2\x59\xe6\x56\x3b\xbd\xaa\xbb\ \xf7\x0f\x47\xcb\x86\x53\x34\xf9\x7a\x7b\x35\xc8\x0a\x56\x14\x50\ \x14\x90\x65\xe0\x38\xe0\x38\xa8\xab\x83\xff\xf9\x1f\xb8\xe7\x1e\ \xf8\xc9\x4f\xa0\xb6\x16\x10\x02\x55\x1d\xd4\x4d\x51\x55\x35\x2e\ \x2e\xee\x67\x3f\xfb\x19\xa5\xc6\x9a\x16\x12\x12\xb2\x62\xc5\x0a\ \xbd\x5e\xaf\x28\x8a\xc5\x62\xd9\xbb\x77\x6f\x67\x67\xa7\xff\x0d\ \x25\xd6\xfb\xc5\x8b\x17\xa1\xfb\xe5\x4c\xab\x56\xad\x8a\x8c\x8c\ \x54\xbf\x5e\x0d\x4d\xd3\x2c\x16\xcb\xea\xd5\xab\x93\x93\x93\xc9\ \x34\xa7\xaf\xfa\x54\x57\x57\xf3\x3c\x2f\xcb\x72\x58\x58\x98\xc3\ \xe1\xd0\x34\x8d\xd8\x9b\xfe\x15\xce\xcc\xcc\x4c\x4a\x4a\xf2\x7a\ \xbd\x3c\xcf\x7b\xbd\xde\xb2\xb2\x32\xb8\xdd\xb7\x91\x22\x84\x34\ \x4d\x4b\x4e\x4e\x5e\xb6\x6c\x59\x8f\x7f\x91\x0c\x1d\x0e\xc7\xdc\ \xb9\x73\xdd\x6e\x37\x51\x73\x74\x74\xf4\xa3\x8f\x3e\x4a\xcd\x2a\ \x31\x31\x31\x23\x23\x43\x96\x65\xbd\x5e\xdf\xd0\xd0\xd0\xd2\xd2\ \x12\x48\x05\x04\x5e\xb8\xea\xae\xf8\x7b\xdd\xf6\x4e\xa5\xdd\xc0\ \x1b\x39\xc4\x53\x7f\x04\x0c\x08\x7d\xbd\x1b\x41\x64\x5e\xa0\xe7\ \x41\x27\x7c\x35\xca\xb8\x5c\xf0\xcf\x7f\xc2\x9b\x6f\x42\x59\x19\ \xcc\x98\x01\x6b\xd7\x82\xcd\x06\x00\xb7\x31\xa6\x64\x67\x67\x93\ \xce\xb0\x07\xe4\x46\x4c\x99\x32\xe5\xbe\xfb\xee\x3b\x74\xe8\x50\ \x68\x68\x68\x7d\x7d\x7d\x43\x43\x83\xc5\x62\x21\x09\xc8\xd8\x79\ \xe4\xc8\x91\xd6\xd6\x56\x41\x10\x24\x49\x9a\x32\x65\xca\xf4\xe9\ \xd3\x7b\x8f\xa9\xe4\xee\x47\x45\x45\xc5\xc4\xc4\x14\x17\x17\x1b\ \x0c\x86\x7e\xea\x43\x46\x7d\x8f\xc7\x23\x49\x12\xc7\x71\xc4\xe1\ \xe1\x73\x1b\x10\xb3\x77\xd1\xa2\x45\x53\xa7\x4e\x25\x63\x8d\xcd\ \x66\x83\xdb\x75\x62\x92\xce\x2f\x22\x22\x42\xa7\xd3\xf5\xe8\x08\ \x49\x41\x3a\x9d\x2e\x29\x29\x89\xbc\xd0\xd2\xe3\xf1\x4c\x9f\x3e\ \x9d\x6a\xca\x00\x00\xc7\x71\xc9\xc9\xc9\x67\xcf\x9e\x35\x18\x0c\ \x6e\xb7\x9b\x74\x66\x81\x88\xb5\xac\xe3\xe2\x0d\x6f\xad\x55\x08\ \x55\xb1\xd2\x57\x1a\xa1\x77\xab\x62\xc0\x48\xd0\x59\x1a\x3a\xe0\ \xf2\x27\x20\xa8\xc0\xe9\xa0\xb1\x11\xfe\xfe\x77\xd8\xb7\x0f\xc2\ \xc3\x61\xfd\x7a\x58\xb3\x06\xe2\xe2\x7c\x57\x33\x60\x3d\x7a\x60\ \x34\x1a\xa9\x9f\x23\x84\x54\x55\x35\x1a\x8d\xc9\xc9\xc9\xfb\xf7\ \xef\x07\x00\x41\x10\x2a\x2b\x2b\x93\x92\x92\xbe\xac\x18\xc6\x00\ \x50\x5f\x5f\xef\xf5\x7a\x49\xaf\x9b\x95\x95\xe5\x1f\x7f\xd5\xf3\ \x42\x30\x26\x83\x4e\x3f\x95\x89\x8d\x8d\xed\xea\xea\x22\xfd\x76\ \x41\x41\x41\x52\x52\x52\x5c\xf7\xa5\x91\x79\x0d\xd1\xc7\xec\xd9\ \xb3\x67\xcf\x9e\xdd\xa3\xb6\x83\xbc\xee\xaf\x50\xfb\xe8\x62\x89\ \xbe\x45\x51\x24\xae\x08\x4d\xd3\x62\x62\x62\xfa\xf1\x8f\x99\xcd\ \x66\xdf\xef\x03\x9b\x14\x80\x01\xa0\x4b\xf5\xd6\xb8\x2b\x07\x1c\ \xfd\x05\xe8\x65\x71\x60\x84\x0c\x2a\x9f\xf8\xce\x87\xf0\xf1\xbb\ \x5f\x13\x4d\x78\x38\x14\x14\x40\x56\x16\x00\x80\x24\xc1\xed\x86\ \x4d\xf4\x63\x00\x12\x8b\xcc\x6e\xb7\x8b\xa2\x48\x5a\xb4\xae\xae\ \xae\x47\x9a\x86\x86\x06\xf2\x30\x19\x8d\xc6\xb4\xb4\xb4\x7e\x86\ \xf9\x40\x6e\xd6\xcc\x99\x33\x13\x13\x13\x6b\x6b\x6b\x43\x42\x42\ \xce\x9c\x39\xf3\xe6\x9b\x6f\x66\x65\x65\xa5\xa4\xa4\x64\x64\x64\ \xf8\xe4\xab\x69\x9a\x7f\x9d\xc9\x5b\xc2\x02\xba\xd4\x3e\xe8\xff\ \xeb\xfe\x5d\x69\x8f\x11\xad\x07\x01\x3a\xeb\xfc\x91\xd4\x2e\x49\ \xf5\x08\x88\xd2\xee\xfe\x50\xc2\x70\x34\x50\x75\x18\x65\x2d\xfa\ \x2f\xc8\x52\x11\xb9\xe3\xb2\x0c\x3b\x77\x42\x75\x35\x6c\xd8\x00\ \x3f\xf8\x01\xac\x5c\x09\x7a\x3d\x60\x7c\x1b\x5d\x05\xf4\x7b\x53\ \x48\x43\x5a\xad\x56\x32\xf5\x42\x08\xb5\xb5\xb5\x91\x7f\x11\x9f\ \x87\xaa\xaa\x9d\x9d\x9d\xa4\xbf\x15\x04\x61\x88\x41\x78\x18\xe3\ \xa8\xa8\xa8\xb5\x6b\xd7\xe6\xe6\xe6\xb6\xb6\xb6\x9a\x4c\xa6\x8a\ \x8a\x8a\xd2\xd2\x52\xbb\xdd\x1e\x15\x15\x65\xb7\xdb\x89\x3e\x26\ \x4f\x9e\x4c\xc4\x37\x3a\xab\x5f\xfe\x2a\xef\xbf\xc4\x41\xd5\x87\ \xcc\x28\x4d\x3a\xcb\x5d\xe2\x04\x8f\xe6\x31\x81\xa5\x9f\xc4\x34\ \x59\xa8\xea\x83\x13\x1e\x8d\xcc\x7e\x16\xfb\xcf\x67\x1f\x7d\x14\ \x7e\xf7\x3b\xc8\xcb\x83\x82\x02\x28\x28\x80\x0d\x1b\x20\x3e\x1e\ \x06\xaf\xd6\x01\xaa\x8e\x10\x00\x18\x8d\x46\xa3\xd1\xd8\xd1\xd1\ \x01\x5f\x7f\x20\x10\x42\x3e\x0b\x80\x98\x9f\x7d\x8d\x47\x81\x17\ \xa7\x69\xda\xcc\x99\x33\x7f\xf3\x9b\xdf\xe4\xe7\xe7\x9f\x38\x71\ \x42\xa7\xd3\xe9\xf5\xfa\xd6\xd6\xd6\xa6\xa6\x26\x8c\xf1\xe9\xd3\ \xa7\x2d\x16\x4b\x78\x78\xf8\x82\x05\x0b\xbe\xfb\xdd\xef\x0e\xf1\ \xea\xc6\x03\x3c\xe2\x93\xad\x99\x61\x7a\x9b\x8c\x65\x1e\x78\x0d\ \xe8\x2d\xc8\x21\x40\xe4\x87\x43\x1c\xc6\x58\xd2\xba\x1c\xd6\xe4\ \x47\x13\x56\x43\x0f\x1f\x47\x46\x06\x6c\xdb\x06\xa7\x4f\xc3\xac\ \x59\xb0\x7d\x3b\x2c\x5e\x0c\xc5\xc5\xc0\x71\x83\x9d\x89\x0c\x91\ \x1e\xe3\xc5\xd0\xe3\xf0\xc8\x24\xc5\xe1\x70\xbc\xfe\xfa\xeb\xb9\ \xb9\xb9\x0f\x3e\xf8\xa0\xc5\x62\x11\x45\xd1\x6c\x36\x1b\x8d\x46\ \xb7\xdb\xdd\xd4\xd4\x54\x5e\x5e\xfe\xc1\x07\x1f\xfc\xf8\xc7\x3f\ \xae\xae\xae\xee\x5d\x87\x3b\x0b\x55\x53\x33\x43\x66\x2d\xb0\x2f\ \x91\x34\x49\xd2\xba\xc8\x7c\xb5\xf7\xcf\x97\xce\x6f\x0d\x54\x45\ \x55\x44\x5e\xcc\x0c\x9b\xbd\x66\xca\xab\x02\xd7\x6b\xa6\x80\x31\ \xf0\x3c\xdc\x73\x0f\x1c\x39\x02\xaf\xbc\x02\x7a\x3d\xa4\xa5\x01\ \xc6\x43\xf7\x6e\x7d\xbd\x10\x0c\x00\x1d\x1d\x1d\x1d\x1d\x1d\x64\ \xc8\xe8\xb1\xb2\x6a\xb5\x5a\x89\x79\x48\xc6\x11\x45\x51\xfa\x9f\ \x65\x04\x5e\xa8\x20\x08\x29\x29\x29\x29\x29\x29\x8a\xa2\x94\x96\ \x96\x5e\xba\x74\xc9\xe9\x74\x3a\x9d\xce\x5b\xb7\x6e\x79\x3c\x1e\ \x8c\xf1\xb5\x6b\xd7\x7e\xf1\x8b\x5f\xfc\xf2\x97\xbf\x4c\x49\x49\ \x19\x62\x89\x63\x08\x79\xfe\x97\x4f\x58\xa3\x81\x76\xcc\x75\x50\ \xd6\xbc\xbe\xd5\x0f\x7f\x04\x91\x37\x02\x80\x89\x37\x87\x1a\xc2\ \x67\xd9\xb2\x1f\x8e\x5b\x01\x64\x32\xd2\xc3\x21\xea\x3f\x8c\x6d\ \xda\x44\xff\x7c\xc8\x10\x93\xd3\xe3\xf1\x74\x76\x76\x1a\x0c\x06\ \x45\x51\xac\x56\x6b\x77\x39\x88\x0c\x28\x7a\xbd\x9e\x0c\xf3\x92\ \x24\xd5\xd5\xd5\x25\x27\x27\x0f\xa5\xc4\x1e\x83\x14\x00\x08\x82\ \x90\x9e\x9e\x9e\x9e\x9e\x0e\x00\x1e\x8f\xa7\xa0\xa0\x60\xff\xfe\ \xfd\xb5\xb5\xb5\x7a\xbd\xbe\xad\xad\xed\xdd\x77\xdf\xcd\xcd\xcd\ \x35\x99\x4c\x77\x74\x94\x0d\x06\xfc\xd8\x84\xb5\x8b\xa2\x97\x17\ \xb5\x9d\x72\x2b\x1d\xbd\x57\x46\x84\xa7\x92\x5e\xc0\x00\x91\x86\ \xa8\x29\x21\x69\xd0\x2d\x88\x01\x56\xd5\xc9\xad\xbc\xdd\x7e\xa2\ \x9f\xa5\x51\x62\x72\x36\x34\x34\xb4\xb7\xb7\x9b\xcd\x66\x49\x92\ \x62\x62\x62\xfc\xbf\x08\x00\x91\x91\x91\xfe\xfe\xf2\xc9\x93\x27\ \x43\x1f\xc6\x57\x20\xbd\x7d\x5f\x0b\xe5\xe4\xbb\x06\x83\x61\xc9\ \x92\x25\x73\xe6\xcc\x59\xbf\x7e\x7d\x55\x55\x95\x5e\xaf\x6f\x6a\ \x6a\x3a\x79\xf2\xe4\x83\x0f\x3e\x48\xe2\x33\x02\xbe\xe8\xf1\x05\ \x02\xa4\x61\x2d\x54\x08\x7f\xc0\xf6\x10\x35\x81\x70\x9f\x7d\x3e\ \xf9\x0d\x83\x86\x31\xe6\x50\x00\x97\x3a\xb4\x81\xa3\xaf\x09\x1e\ \x99\xb5\x77\x74\x74\x94\x94\x94\x98\x4c\x26\x4d\xd3\x24\x49\xf2\ \x39\x2d\xa0\xbb\xed\x27\x4e\x9c\x48\x16\x39\x3b\x3b\x3b\x2f\x5e\ \xbc\xb8\x70\xe1\x42\x45\x51\xa8\xad\x3b\xe0\x03\x5d\x51\x51\x51\ \x58\x58\x88\x10\xf2\x7a\xbd\xdf\xfa\xd6\xb7\x66\xce\x9c\x49\xea\ \xe0\x3f\xb3\xed\xea\xea\xb2\xdb\xed\x73\xe6\xcc\xa9\xa9\xa9\x01\ \x00\x59\x96\x89\x9b\xf9\x4e\x87\x43\x1c\x89\xd4\xa4\xce\x54\x39\ \x15\xab\x2a\x56\x35\xac\x01\xa0\x80\x34\x31\x64\x8e\x1d\x3b\x26\ \xcb\x72\x5f\xab\x4a\x4e\xa7\xf3\xc4\x89\x13\xa4\xe1\x63\x63\x63\ \x7d\xce\x25\xe8\x0e\xcb\xbb\xff\xfe\xfb\xc3\xc3\xc3\x89\xaf\xa9\ \xa2\xa2\xa2\xb6\xb6\x96\x58\x21\xfe\x99\x90\xa5\xac\x1b\x37\x6e\ \xd4\xd7\xd7\x93\x41\xa7\x47\x29\xe4\x13\x97\xcb\xf5\xb7\xbf\xfd\ \x6d\xd7\xae\x5d\x3b\x76\xec\xf8\xf7\xbf\xff\xed\x8b\x9a\xf1\x4f\ \x49\x96\xd0\x22\x22\x22\x0c\x06\x03\x09\x89\x90\x65\x79\xb8\x6e\ \xc5\xd8\x42\xec\x0c\xba\xff\x9b\x47\x3c\x8f\x78\x0e\x71\xa3\x13\ \xdc\xcb\x71\xdc\xe5\xcb\x97\x3f\xf8\xe0\x03\x5f\x08\x27\xf9\x9c\ \x4c\x38\xdd\x6e\x77\x5e\x5e\x9e\xaf\xdb\x58\xb4\x68\x91\xcf\xf3\ \x0d\xdd\xe6\x45\x44\x44\x84\xc3\xe1\xc0\x18\x1b\x0c\x86\x9a\x9a\ \x9a\x1d\x3b\x76\x90\xd5\x0a\x9f\x95\x40\x3c\x1c\x5d\x5d\x5d\xf9\ \xf9\xf9\x55\x55\x55\xc4\x69\xd8\xf3\x8e\x20\x04\x00\x77\xdf\x7d\ \x77\x4c\x4c\x4c\x58\x58\xd8\xc4\x89\x13\xcf\x9d\x3b\x57\x54\x54\ \x44\x16\x21\x7d\xc9\xc8\x60\xc7\xf3\x7c\x49\x49\x89\xdb\xed\x06\ \x00\x51\x14\x1d\x0e\x07\x0c\xcd\xcb\x39\xfe\x19\xed\x0d\x01\x08\ \x21\x9d\x4e\xf7\xd9\x67\x9f\x6d\xda\xb4\xe9\xfa\xf5\xeb\xbe\x9b\ \x4b\xa2\x5d\x36\x6c\xd8\x50\x5e\x5e\x6e\x32\x99\x3c\x1e\xcf\xa4\ \x49\x93\xe6\xcd\x9b\xe7\x2f\x1d\x00\x20\xcd\x46\x56\xe4\x65\x59\ \x36\x18\x0c\xc7\x8e\x1d\x7b\xeb\xad\xb7\xc8\xf1\x47\xbe\xac\x1a\ \x1b\x1b\xdf\x7e\xfb\xed\x83\x07\x0f\x92\xa5\x87\xbe\x2a\x23\x8a\ \x62\x76\x76\x36\xf1\x8f\xb5\xb5\xb5\xbd\xf3\xce\x3b\xe7\xce\x9d\ \xf3\x1f\x8f\x10\x42\xb2\x2c\xe7\xe5\xe5\x1d\x39\x72\x44\xaf\xd7\ \x13\xdf\x7c\x56\x56\x16\x7c\xd3\x65\x31\x4a\x9b\x0d\xa1\xdb\x9c\ \x44\x08\xe9\xf5\x7a\x8f\xc7\x53\x58\x58\x78\xfe\xfc\x79\x87\xc3\ \x61\xb7\xdb\x79\x9e\xbf\x79\xf3\x66\x71\x71\xb1\xd7\xeb\x25\x13\ \x10\x4d\xd3\x1e\x7f\xfc\xf1\xe8\xe8\x68\xaa\x71\x1a\x1d\x1d\xbd\ \x72\xe5\xca\x2d\x5b\xb6\x90\xad\x8b\xa7\x4f\x9f\x7e\xfe\xf9\xe7\ \x93\x93\x93\x23\x23\x23\x01\xe0\xc6\x8d\x1b\x35\x35\x35\xcd\xcd\ \xcd\xbe\x28\x8a\xbe\xbc\xe0\x18\xe3\x27\x9f\x7c\xb2\xbc\xbc\xfc\ \xf2\xe5\xcb\xa2\x28\xba\x5c\xae\x5f\xfd\xea\x57\xc9\xc9\xc9\x99\ \x99\x99\x56\xab\x55\x96\xe5\x1b\x37\x6e\x5c\xba\x74\xa9\xa6\xa6\ \x46\xa7\xd3\xa9\xaa\x2a\x08\xc2\x63\x8f\x3d\x46\xc2\xc9\x7a\x64\ \x88\xfc\x18\xf0\x0e\x0c\x28\xa9\x00\x93\x0d\x2a\xe5\xa0\x18\x25\ \x59\x10\x1f\x03\x69\xa1\xb5\x6b\xd7\x1e\x3e\x7c\xb8\xa4\xa4\xa4\ \xa9\xa9\xa9\xad\xad\xcd\x3f\xa2\x9a\xe3\x38\xb2\x0c\xf6\xdc\x73\ \xcf\xcd\x9f\x3f\x1f\x68\x0f\x25\xf9\xe4\xe1\x87\x1f\xf6\x78\x3c\ \x3b\x76\xec\xf0\x78\x3c\x24\xf0\x82\x6c\xf1\x03\x00\x45\x51\x14\ \x45\x91\x24\x69\xc5\x8a\x15\x2d\x2d\x2d\x85\x85\x85\xa4\x51\xa9\ \x15\x33\x99\x4c\xaf\xbe\xfa\x6a\x6e\x6e\x6e\x55\x55\x95\x2c\xcb\ \x1c\xc7\x95\x96\x96\x96\x95\x95\xe1\xee\x18\x63\x32\x00\xb9\xdd\ \x6e\x8b\xc5\xb2\x6c\xd9\xb2\xc5\x8b\x17\x53\x6b\x45\xf6\x33\x42\ \xdf\xcb\x60\xb8\x7b\xbb\xa5\x2f\xcf\x01\xef\xd5\x80\x1b\x24\xf1\ \x88\x6d\xe1\x1c\x25\x59\x90\x58\x34\x62\x69\x3a\x1c\x8e\x79\xf3\ \xe6\xed\xde\xbd\xfb\x3f\xff\xf9\x8f\xcb\xe5\x6a\x6f\x6f\x27\x69\ \x48\x14\x5a\x5c\x5c\xdc\xd2\xa5\x4b\x17\x2e\x5c\x08\x03\x05\x27\ \x2e\x5b\xb6\x4c\x10\x84\x7d\xfb\xf6\x35\x36\x36\xb6\xb7\xb7\xfb\ \xee\x78\x78\x78\x78\x6c\x6c\xec\xac\x59\xb3\x9e\x7e\xfa\xe9\xbd\ \x7b\xf7\x9e\x3c\x79\x52\xaf\xd7\x53\x37\x00\xa2\xee\xb3\xdd\x36\ \x6e\xdc\xb8\x67\xcf\x9e\x33\x67\xce\xd4\xd4\xd4\xb4\xb4\xb4\x10\ \x7b\x96\x54\xc0\x68\x34\xda\x6c\xb6\xd8\xd8\xd8\x9c\x9c\x9c\x7e\ \x0e\xde\x32\x1a\x8d\x24\x7e\xc7\x60\x30\x50\xeb\x4c\x96\x7c\x89\ \x68\xa8\x71\x05\xfe\x29\x2d\x16\x0b\x31\xba\x49\xdc\x72\x3f\x29\ \xcd\x66\xb3\xc9\x64\xea\x6b\x63\xc1\x6d\x33\xd4\x73\x50\x3b\x3a\ \x3a\x56\xaf\x5e\x9d\x90\x90\x90\x9b\x9b\x4b\x8d\x97\x27\x4d\xdb\ \xdc\xdc\x5c\x5e\x5e\x4e\xd6\x03\xa7\x4e\x9d\x6a\xb7\xdb\x01\xa0\ \xba\xba\xfa\xe2\xc5\x8b\x75\x75\x75\x24\x8c\xcf\x66\xb3\xc5\xc5\ \xc5\xcd\x9c\x39\x33\x32\x32\x32\x90\xed\x0f\x24\x67\x97\xcb\x75\ \xfc\xf8\xf1\xc6\xc6\xc6\xe6\xe6\x66\x12\x7d\x33\x69\xd2\xa4\x8c\ \x8c\x8c\x09\x13\x26\x60\x8c\x6b\x6b\x6b\xaf\x5e\xbd\x0a\x00\x3c\ \xcf\xa7\xa5\xa5\x85\x84\x84\xf4\x96\x9a\xef\x93\xf6\xf6\xf6\x73\ \xe7\xce\x55\x55\x55\xdd\xba\x75\x8b\x84\x75\x91\xed\x05\x89\x89\ \x89\xd3\xa7\x4f\x27\x56\x6d\x5f\x7e\x8e\xb2\xb2\xb2\xa6\xa6\x26\ \xf2\xdf\xb4\xb4\xb4\xb0\xb0\x30\xfc\xf5\x0d\x01\xad\xad\xad\x65\ \x65\x65\xe4\xc0\xb0\xf0\xf0\xf0\xf4\xf4\xf4\xbe\x6a\xd2\xd4\xd4\ \x54\x5c\x5c\x2c\x8a\xa2\x2c\xcb\x09\x09\x09\xfd\xbc\x16\xa1\xa6\ \xa6\xa6\xba\xba\x5a\x10\x04\x45\x51\xd2\xd3\xd3\x23\x22\x22\x7a\ \xe7\x49\xea\xbc\x6a\xd5\x2a\x00\xf8\xcb\x5f\xfe\x12\xe0\xbe\x92\ \x11\x97\x05\x15\xdc\x6b\xfb\x90\x3f\x81\x7b\x8a\xfa\x49\x39\x28\ \x77\x13\x99\x2d\xf7\x53\xf9\x01\x13\x8c\x5b\x6e\x4f\x16\xa3\x67\ \x5b\xf4\xd8\x6a\x47\x1e\x3e\xdf\x3e\x41\xf0\xdb\x40\x17\x78\x73\ \xfa\xb6\x57\xe0\xee\x4d\x8b\x64\x28\xf1\x65\xd2\xbb\x5c\x6a\x3e\ \xbe\xd9\xb2\x2f\xa8\xc7\x57\x25\xf0\x0b\xda\xee\xa7\x26\x03\xbe\ \x05\xca\xbf\x26\xa8\xdf\xdd\x8b\x81\xa7\x1c\xb9\x57\x4f\x8d\x92\ \x2c\x10\xed\x1d\x42\xfe\xf7\x9a\xc4\x4f\x0c\x31\x67\x12\xf4\x36\ \x60\xb9\x01\x66\xd5\x7f\x93\xf4\x60\xc0\x94\x81\xd7\x24\xf0\x94\ \x23\xd7\x7b\x8d\x97\x5e\x71\x58\x94\x3e\x8c\x8f\xcb\x37\xdb\x2d\ \x31\x20\xe3\x45\x16\x8c\x71\x05\x93\x05\x83\x02\x93\x05\x83\x02\ \x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\ \x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\ \x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\ \x93\x05\x83\x02\x93\x05\x83\xc2\xf0\x04\xed\x91\x0d\x0b\xfd\xbc\ \xdd\x8c\x31\x56\xdc\x5e\xa3\x0c\x8f\x2c\xf8\x6e\x86\x25\x37\xc6\ \x30\x42\x1a\xa5\xc7\x9e\xcd\x01\x19\x06\x59\x08\x82\xe0\xf1\x78\ \x2e\x5d\xba\xe4\xdb\x75\xc3\x18\x3f\x90\xd8\x69\x55\x55\x07\x75\ \xa0\xe4\x50\x65\x81\x31\x26\x2f\xb6\xdd\xb0\x61\xc3\x6d\xbc\x0f\ \x90\x31\x0a\x90\x2d\xbe\x64\xcb\x56\x80\x0c\x55\x16\x82\x20\x3c\ \xf2\xc8\x23\x1e\x8f\x67\x88\xf9\x30\x46\x1a\xf2\xba\xa9\x00\xbb\ \xf3\xa1\xee\x2a\xf3\xbd\xbd\x84\x0d\x1f\xe3\x19\xff\x3d\x51\x81\ \x30\x54\x59\x30\xbe\x91\x30\xbf\x05\x83\x02\x93\x05\x83\x02\x93\ \x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\ \x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\ \x05\x83\x02\x93\x05\x83\xc2\xe8\xbd\xf3\x7b\xdc\x42\x16\x0b\x7d\ \x4b\x86\xa8\xfb\x54\xdc\xb1\xac\xd3\x58\xc3\x56\x50\x19\x14\x82\ \x7d\x10\x51\x55\x75\xcf\x9e\x3d\xdf\xff\xfe\xf7\x13\x13\x13\xed\ \x76\x7b\x4c\x4c\xcc\xfc\xf9\xf3\xf3\xf2\xf2\xc6\xba\x5e\x63\x4c\ \x50\xf7\x16\x1e\x8f\x67\xd5\xaa\x55\x9f\x7e\xfa\xe9\x9c\x39\x73\ \x16\x2d\x5a\xd4\xde\xde\x7e\xfc\xf8\xf1\x03\x07\x0e\x00\xc0\x4b\ \x2f\xbd\xf4\xdb\xdf\xfe\x56\x10\x84\x20\x8d\x5b\xc6\x41\x09\x39\ \x69\xe1\x99\x67\x9e\x01\x80\xa5\x4b\x97\xfa\xff\xeb\xad\xb7\xde\ \x22\x52\x58\xb7\x6e\x1d\xc6\xd8\x77\x7e\x56\x50\x11\xa4\xb2\xc0\ \x18\x77\x75\x75\x99\xcd\x66\x41\x10\xbe\xf3\x9d\xef\x28\x8a\xe2\ \xf5\x7a\x55\x55\x25\x22\xf8\xf6\xb7\xbf\x8d\x10\x4a\x4a\x4a\x6a\ \x6e\x6e\x26\x67\xde\x8c\x75\x65\x47\x9b\x60\xb4\x2d\xc8\x95\x6b\ \x9a\x36\x63\xc6\x0c\x45\x51\x1c\x0e\x07\xd9\xe4\x42\x5e\x20\xaf\ \xaa\xea\xfc\xf9\xf3\x31\xc6\x1e\x8f\xa7\xa2\xa2\xc2\xff\x14\xb4\ \xe0\x21\x18\x27\xa8\x64\x2f\x8d\x28\x8a\x5b\xb7\x6e\x2d\x2a\x2a\ \xba\xf7\xde\x7b\xc1\xef\x5c\x6e\x9e\xe7\x43\x42\x42\xa0\xfb\x60\ \x18\x08\xca\xc9\x6a\x30\xca\x02\xba\x5b\x3a\x35\x35\x35\x35\x35\ \xd5\xff\x13\x02\x39\x99\x46\xa7\xd3\x91\x23\x14\x83\x50\x16\xc1\ \x38\x88\xf8\x50\x55\x95\x98\x0e\xe4\x4f\x8c\x31\x00\x74\x76\x76\ \x7e\xfe\xf9\xe7\x00\xb0\x7c\xf9\x72\xb3\xd9\xdc\xe3\x9c\xf7\x20\ \x21\xa8\x27\xa8\x3d\x90\x65\x59\xa7\xd3\xed\xd8\xb1\xe3\x47\x3f\ \xfa\x51\x64\x64\xa4\xd3\xe9\x0c\x0d\x0d\x65\xbd\x45\x50\xa3\xaa\ \xaa\x4e\xa7\x73\xb9\x5c\xeb\xd6\xad\x13\x45\x71\xd3\xa6\x4d\x41\ \xab\x09\x60\xb2\x20\x28\x8a\xc2\xf3\x7c\x43\x43\x43\x4e\x4e\x4e\ \x5b\x5b\xdb\xfb\xef\xbf\xff\xd4\x53\x4f\x41\x50\x1a\x9b\x04\x26\ \x0b\x20\xe7\x4a\xd6\xd7\xd7\x3f\xf2\xc8\x23\x45\x45\x45\x7f\xfc\ \xe3\x1f\x9f\x7c\xf2\x49\x72\x8e\x5a\x5f\x87\x9a\x7e\xf3\x19\x13\ \x6f\xc9\xf8\x41\x51\x14\x8c\x71\x7d\x7d\xfd\xbd\xf7\xde\xab\xd7\ \xeb\x77\xee\xdc\x89\x31\x96\x65\x39\x08\x5d\x58\xfe\x04\x75\x6f\ \x41\x0e\xdd\x74\x3a\x9d\x0f\x3f\xfc\xb0\xd3\xe9\xdc\xb5\x6b\xd7\ \x13\x4f\x3c\x41\x06\x14\x84\x50\x4d\x4d\xcd\xc9\x93\x27\xc7\xba\ \x8e\x63\x43\x90\xfa\x2d\x08\x1c\xc7\x15\x14\x14\xac\x59\xb3\xc6\ \x6a\xb5\x1e\x3e\x7c\x78\xda\xb4\x69\x9a\xa6\x91\x03\x16\x31\xc6\ \xbf\xfe\xf5\xaf\xeb\xeb\xeb\x3f\xf9\xe4\x13\x55\x55\x87\xf7\x38\ \xc9\xf1\x4f\x90\xf6\x16\x18\x63\x00\xd8\xbe\x7d\xfb\xf2\xe5\xcb\ \x1d\x0e\xc7\x17\x5f\x7c\x31\x6d\xda\x34\xf0\xdb\xea\x2f\x49\xd2\ \xa1\x43\x87\x7c\x07\x7d\x07\x1b\xc1\xd8\x5b\x90\x03\x95\xdf\x7b\ \xef\xbd\xe7\x9f\x7f\x1e\x00\x6a\x6b\x6b\xe7\xcc\x99\xd3\xd5\xd5\ \xe5\xff\xfa\x07\x8e\xe3\x9c\x4e\x67\x3f\x07\x59\x7f\xb3\x09\x46\ \x59\x90\xae\xc2\xe9\x74\x9a\xcd\x66\x32\x2f\xed\xed\xca\x24\xa7\ \xda\x93\x37\xc8\x04\x21\xc1\xe8\xe5\xc4\x18\x23\x84\x2e\x5c\xb8\ \x50\x59\x59\xd9\x97\xc5\xc0\x71\x9c\xd7\xeb\x8d\x8f\x8f\x9f\x33\ \x67\x0e\x0e\x3e\xa7\x16\x52\xb5\x20\x9d\x9a\x73\x28\x20\xbb\x4a\ \xc3\x64\xc5\x04\x03\x04\x91\x32\x82\xb1\xb7\xf8\x8a\xfe\x2f\x1d\ \x07\xad\x45\x0e\x42\x51\xdb\xa9\xb1\xae\x03\x63\xdc\x81\x5e\x3c\ \xbf\x62\xac\xeb\xc0\x18\x77\x08\xed\x4a\xeb\x58\xd7\x81\x31\xee\ \x10\x04\xa4\x1b\xeb\x3a\x30\xc6\x1d\x02\x1e\xc0\xee\x62\x04\x23\ \xc1\x6a\x6a\x33\xfa\x85\xc9\x82\x41\x81\xc9\x82\x41\x81\xc9\x82\ \x41\x81\x99\x9c\x0c\x0a\x02\x0a\x26\x57\x3f\x23\x40\xd0\xfe\x86\ \xbd\x63\x5d\x07\xc6\xb8\x23\xb8\x97\xca\x18\x7d\xc0\x6c\x0b\x06\ \x05\x66\x5b\x30\x28\xb0\x09\x2a\x83\x02\x93\x05\x83\x02\x93\x05\ \x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\ \x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\ \x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\ \x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\x83\x02\x93\x05\ \x83\xc2\xff\x03\x78\xf8\xa2\x0e\x16\xe4\x5d\x96\x00\x00\x00\x00\ \x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\x93\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x01\x48\x49\x44\x41\x54\x58\x85\xed\xd6\x4f\x4a\ \x03\x31\x1c\xc5\xf1\xcf\x5a\x71\x67\x15\xa1\xe0\x42\xef\xa4\x6e\ \xb4\x8b\xe2\xba\x22\x88\xa2\x47\xb2\x47\x10\x14\x3c\x81\xd6\xba\ \x14\xea\x5e\x3d\x80\x65\x5c\x4c\x86\x46\xd1\xb6\x99\x3f\x15\xc4\ \x07\xd9\x84\xbc\x7c\x5f\x92\x5f\x32\xc3\xbf\xea\xd1\x0e\x86\x78\ \x41\x1f\xed\x45\xc2\x8f\x91\x7d\x69\xcf\xd8\x5e\x04\xfc\x24\x00\ \xc7\xe8\xa2\x85\xeb\xd0\x37\xc2\x56\x93\xf0\x62\xe5\x63\x1c\x44\ \xfd\x4b\xb8\xd2\xf0\x4e\xfc\x04\x5f\x48\x88\x59\xf0\x46\x43\xc4\ \x67\x3e\x0d\x5e\x68\xd9\xe7\x9a\xa8\x74\x3b\x62\x78\x27\xc1\x17\ \x87\xe8\x57\x85\x67\x38\x2a\xe1\x6f\x05\xef\x6b\x55\x78\x86\x7b\ \xac\x25\xce\xd1\x0d\xde\x61\x59\xf8\x58\xbe\xf2\xfb\x12\x21\xf6\ \x83\x3f\xc3\x5e\x0a\xfc\xbb\x6a\x6f\xe1\x2e\xf4\x3f\x62\x63\xc6\ \x1c\xbb\x78\x0f\xe3\x2f\xaa\xc2\x0b\xcd\x1b\xa2\x11\xf8\xbc\x21\ \x1a\x85\xcf\x0a\x51\x1a\xbe\x13\xc1\xf7\xe7\xf4\xac\x63\x10\x7c\ \x03\xf4\x4c\x0a\xee\x2c\x05\x0e\x0f\xc1\xd8\x4d\xf4\xc5\x21\x8a\ \x96\x0c\x27\xff\x99\xc8\xb0\x5a\xc2\xdb\xab\x0a\x27\x7f\x26\x33\ \xf9\xb3\xb9\x9c\xe0\x8b\xef\xf9\x69\x59\x38\xf9\x87\x62\x94\x18\ \xe2\xa0\x2e\x78\xa1\x4d\x3c\x85\x09\x6f\xb1\x32\x65\x6c\xe9\x6a\ \xaf\x23\x44\x0c\x3f\xaf\x13\x3e\x4f\x88\x8e\xc9\xb6\x37\x02\x2f\ \xb4\x65\x52\x13\x37\xf2\x8f\xcf\xa1\x9a\xcf\x7c\x96\xe2\x9d\xa8\ \xab\x25\xab\x8d\x4b\xbc\xfd\x56\x80\x7f\xfd\x6d\x7d\x00\xd1\x84\ \xbb\x35\x03\xbe\x08\xdc\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x01\x63\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x01\x18\x49\x44\x41\x54\x38\x8d\xa5\xd3\xbf\x2e\ \x44\x41\x14\x06\xf0\x9f\x3f\x8d\x15\xbb\x15\xd1\xe8\x90\xb5\x05\ \x7a\xd9\xc2\x13\x68\x14\x1e\x44\x21\xda\x6d\xfd\xcd\xe5\x01\x88\ \x6c\xa5\x10\x85\x42\x34\xe2\x05\x58\xc5\x8a\x55\xe2\x01\x88\x56\ \x14\x77\x6e\x4c\x26\xf7\x36\x7c\xc9\x49\x66\xce\xf9\x66\xce\x77\ \xce\x9c\xe1\x9f\x18\xaa\xf0\xb7\xb0\x86\xf9\xb0\x7f\xc6\x05\xfa\ \x29\x71\x38\xd9\x37\xd0\xc5\x35\x26\x71\x1b\x6c\x0a\x37\x38\x43\ \xbd\x4a\x4d\x03\x3d\x1c\x62\xac\x24\x5e\x43\x86\x87\xaa\x4b\xba\ \xe1\x70\x81\x69\xec\x63\x2f\xac\x0b\x64\x38\x4d\x0f\xb7\xf0\x9a\ \x64\xbe\xc3\x0e\x76\x43\x19\xb1\x92\x37\x34\xe3\x0b\xb6\x43\xa6\ \x18\xf7\x18\x09\x72\xbf\x92\xd8\x01\xb6\xf8\x6d\xe2\x2c\x1e\x13\ \xd2\x32\xbe\xd1\xc1\x55\x12\xeb\x61\x0e\x46\xd3\x5a\x12\x6c\x60\ \x05\xab\x55\x84\x42\xc1\x0b\x16\x4b\xe2\xef\xd8\xc4\x67\xe2\x5f\ \xc2\x20\x76\x2c\xc8\x9b\x58\x4b\x88\x33\xc1\x62\x8c\x2b\x69\x22\ \xf9\x90\x64\x89\xaf\x13\x2c\xc6\x11\x4e\x4a\xd4\xaa\xcb\x87\x24\ \x2b\x51\x52\x64\x3e\x96\xbf\xce\x44\xe1\x8c\x9b\xf8\x81\x76\xc8\ \x30\xc0\x79\x20\x93\xbf\xc8\xba\x7c\x9c\xdb\xa2\x9e\x54\x7d\xa6\ \xa6\xfc\x33\x15\x75\xf6\x71\x89\xa7\x0a\xfe\xdf\xf1\x03\x27\xfd\ \x32\xe4\xdd\x25\x00\xc7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x00\xae\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x63\x49\x44\x41\x54\x38\x8d\xcd\x92\x41\x0a\ \xc0\x20\x0c\x04\x87\xbe\xab\x60\xe8\x33\x7d\x61\xed\x2b\xbc\x44\ \x28\xd4\x98\x88\x07\xbb\xd7\x9d\x5d\xd8\x10\xf8\x93\x32\x90\x02\ \x9c\x28\xfb\x51\x02\x8a\x53\x22\xca\x88\x05\x9c\xc0\x0d\x5c\x86\ \x57\x0c\xcf\x05\xc3\xe1\x5e\x60\x3a\xdc\xd4\xf6\x0e\x37\x1f\xb3\ \xad\x51\x2d\x4d\x58\x3a\xe2\x08\x74\x4b\xdc\x27\x51\xef\xb1\x98\ \xec\x84\xdf\x25\xdd\x57\xde\xa3\x0a\x64\xe5\x1c\x32\x38\xcd\xe4\ \x26\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x01\xd7\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x01\x8c\x49\x44\x41\x54\x58\x85\xed\xd6\xcf\x4b\ \x15\x51\x18\xc6\xf1\x4f\xb7\x16\xc1\x05\x85\x28\x25\x84\x10\xdb\ \x28\x44\x24\xa8\x18\x41\x11\xad\xec\xef\x10\x5a\xb4\x6e\xa1\x0b\ \x71\xd5\xa2\x16\x41\x2b\xfd\x33\x5c\x0a\x6d\xfd\xb1\x11\xac\x96\ \xd1\xc6\x54\x68\x53\x54\x46\x68\xa5\x2d\xce\xbd\x70\x19\xce\x19\ \xee\xdc\x3b\x77\xee\x22\x1f\x38\x30\xbc\xef\xbc\xe7\xfd\x32\xf3\ \xce\x33\x87\xff\x5d\x17\x12\xf1\x3a\x66\x73\xf2\x31\x7d\xc7\x11\ \x3e\xe2\xb8\x4b\x2e\x0b\x38\xeb\x70\xfd\xc1\x0e\x16\x31\xd0\x29\ \xc0\x72\x17\x00\xad\xeb\x33\xee\xe5\x35\xaa\x75\x4a\xd8\xa6\x86\ \xb0\x8e\x9b\xfd\x02\x20\xcc\xd3\x8b\x54\xf2\x52\xc1\xcd\xde\x63\ \x2b\x12\xbf\x8a\x29\xdc\x48\xd4\x3d\x6e\x80\xfc\x6c\xb7\x51\x6a\ \x06\x5e\xe5\xd4\xd4\xf0\x24\x51\x77\x86\x5b\xa9\xa2\xb2\x74\x8a\ \x55\x6c\x26\xf2\x57\x7a\x0d\xd0\xd4\xa7\x44\xfc\xb0\x2a\x80\x89\ \x48\xec\x2b\xf6\x62\x37\x17\x1d\xc2\x3c\x5d\xc4\x53\xdc\x8e\xe4\ \x56\x71\x52\x06\xc0\xa3\xc6\x66\x59\x5d\xc3\x24\x46\x23\xb9\xb7\ \x78\x5e\xb0\x4f\x69\x4e\x78\x24\xc7\x84\xe8\xbd\x11\xd5\x05\xdf\ \x78\xd0\x2f\x00\xc2\xeb\x59\xc3\x78\x19\x00\x2b\xc2\xf7\x9c\x5d\ \xc3\x98\xc6\x92\x30\xf1\x59\x0d\xe2\x65\x91\x46\x9d\x38\x61\x53\ \x77\xf0\x3b\x52\xfb\x17\xd7\xb3\x37\xf7\xe2\x15\xec\x8a\xbb\x61\ \x4d\x38\xe4\xf4\x1c\x80\xb4\x1b\x56\xf2\x04\xa2\x8d\x1a\xfa\x55\ \x05\xc0\x14\xee\x27\x72\xfb\xd9\x40\x51\x27\x1c\xc4\x58\x24\x7e\ \x19\x23\x98\xc3\x7c\x62\xdf\x1f\xd8\x68\xb7\x51\x59\x4e\xd8\xba\ \x5e\xc7\x1a\x55\x61\x44\xf0\x41\x38\x25\xf7\x05\xe0\x1d\x1e\x0a\ \xff\x85\x4a\x01\x0e\xf0\x0c\x33\x8d\xeb\xa8\x52\x43\xf8\x06\x77\ \x15\x03\xfc\x86\x2f\xc2\xe3\xde\x16\x06\xee\xb4\x40\xfd\xb9\xfa\ \xa3\x7f\xde\xd4\x90\xf2\xea\x8a\xc9\x88\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\xa7\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x5c\x49\x44\x41\x54\x38\x8d\xed\x93\x41\x0a\ \x80\x30\x0c\x04\x47\x9f\xd0\x2f\x5b\x7c\xb3\x39\xd5\x4b\xc5\xba\ \x52\xdb\x4a\xbc\x39\x10\xb2\x90\x65\xc9\x21\x81\x8f\x58\x01\x03\ \x52\xee\x71\x34\xc0\x80\x90\x75\x00\xb6\x9a\x71\x2a\x74\x7a\x39\ \xbb\xa0\xc6\xe6\x7c\x6e\x18\xb5\xdf\xa8\x05\x74\xf3\x07\x38\x04\ \x28\xe5\x05\x2a\x8f\x17\x79\x10\x39\x7f\x40\xcb\x80\xc5\x65\x4d\ \x57\x76\x10\x96\x17\x24\xc4\x1a\x99\x16\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ \x00\x00\x03\x43\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x19\x00\x00\x00\x19\x08\x06\x00\x00\x00\xc4\xe9\x85\x63\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc2\x00\x00\x0e\xc2\x01\x15\x28\ \x4a\x80\x00\x00\x02\xd8\x49\x44\x41\x54\x48\x4b\xad\x96\x3f\x4c\ \x54\x41\x10\xc6\xbf\x7d\xf7\x10\x4a\x8d\x05\x58\x91\x50\x90\x78\ \x9c\x12\xb5\xa2\x34\x1a\x51\x08\xd4\x72\x91\x02\x0c\xa1\xa2\x27\ \x34\xc4\x86\x02\x48\x28\x08\xe5\x41\x21\x01\x0b\x28\x48\x20\x67\ \x34\x6a\x49\xa5\xe6\x34\x9e\x1d\x96\x1e\x09\x9c\x16\x14\x87\xf7\ \xee\xad\x33\xfb\xf6\xdd\xfb\xff\x0e\x89\xbf\xe2\xb2\x33\x7b\xb7\ \xdf\xee\xce\xcc\xce\x09\xa4\x70\x7e\xf7\x66\x57\x5b\xdd\xc8\x43\ \xe2\x09\x04\xee\x09\x1b\xd7\xf4\x14\xa4\x81\x5f\xe4\xff\x48\xfe\ \x62\xbd\xcd\xde\x6a\xff\xf4\xbd\xa2\xa7\x22\xc4\x8a\xd4\xfb\xb3\ \xd7\x33\xb6\x78\x41\x8b\x3e\x27\xb3\xc3\xf1\xa6\x52\x23\xd1\x42\ \xc3\x90\xf3\x6d\xa5\xf2\xa9\xf6\x35\x89\x88\x58\xb7\xb3\xf7\x49\ \x60\x8b\x76\xd9\xa5\x5d\x17\x47\xa0\x42\x42\x79\xf3\x4b\xf9\x83\ \xf6\x28\x02\x22\xf6\xad\xbe\xa7\xb4\xfb\x97\x34\x34\x1d\xcf\xa5\ \xb0\xe8\x54\xe3\xc6\xd7\x6f\xaf\xb4\xed\x89\xa8\x13\x34\xc4\x1b\ \x1a\x7a\x02\x1d\x74\x53\xb5\x9a\x36\x52\x88\x7e\xcf\x6a\x64\xe4\ \x23\xf7\x44\x06\x7f\xe8\x18\x6c\xd1\xd0\x13\xe8\xec\x04\x76\x77\ \x81\xb1\x31\xed\x48\x60\x70\x10\x38\x38\x00\x7a\x7a\xb4\x43\x61\ \xf2\x7a\xbc\x2e\x1b\x4a\x84\x83\x1c\x88\x01\x0b\x6c\x6c\x00\xdd\ \xdd\xc0\xdc\x5c\xb2\x10\x0b\x2c\x2e\x3a\xdf\x5f\x5f\x0f\x0a\xd1\ \x7a\x6a\x5d\x42\x70\x9a\x5e\x39\x37\x7e\xd0\xd8\xc9\x22\x83\x74\ \x77\x76\x80\xde\x5e\x65\x2a\xa4\x04\x16\x16\x80\xed\x6d\xed\x20\ \x5c\x81\x4c\x46\x3b\x88\xe3\x63\x60\x78\xd8\x7f\x75\xb5\x3f\xed\ \x76\x8f\xa1\xea\xc0\x9f\xa6\xb6\x0d\x2c\x2d\x05\xef\x58\x50\xe8\ \xfc\x27\x8a\x13\x68\x34\x80\xe5\xe5\x70\x6c\x3a\xcc\xba\xc8\x0b\ \x3b\xd7\xf7\x56\x48\x3c\xd4\x4e\x8f\x81\x01\x60\x75\xd5\x09\xaa\ \x0b\x9f\x68\x6f\x0f\x18\x19\x89\x0a\xcc\xce\x02\xc5\xa2\x76\x78\ \x48\x81\xf7\x82\xd2\xb6\xea\xaf\xe4\x00\x71\x42\x61\x52\x04\x18\ \x4a\xe7\xdf\x46\xa2\x00\x73\x78\x08\xcc\xcc\x84\xaf\xc0\xa3\x85\ \x00\x43\xeb\x5f\x55\xd9\x95\x0a\x0b\x6d\x6e\x6a\x23\xc4\xfe\x7e\ \xaa\x80\x4b\x6b\x11\x0e\xf2\xc4\x84\x36\x42\x8c\x8e\xb6\xae\x23\ \xc2\x50\xaf\x69\x12\x71\x59\xe4\x27\x9c\x75\x31\xa8\x98\xa8\xe7\ \x3a\x8e\xa4\x34\x2d\x14\xd2\xd3\x3b\x8c\xc4\x67\x43\x0a\xf9\x5a\ \x9b\x1e\x49\x02\x1c\xe4\x95\x95\x68\x32\xa4\x08\x49\x43\x16\xa3\ \x15\xcf\xe9\xca\x6f\x11\x3f\x15\x2e\x71\x59\x14\x97\xde\x67\x67\ \xc0\xd0\x10\x50\xad\x6a\x87\xae\x78\xee\x68\xdc\x70\xb4\xd3\xd9\ \xe1\xd4\x14\x70\x72\xe2\xd8\x49\x69\x1a\x4e\x6f\x16\x98\x9e\xf6\ \x0b\x70\x3c\x0a\xb4\xfe\x4f\x95\x5d\xdc\xd1\xb8\xe1\xa8\x19\xe6\ \xe8\x08\x98\x9c\x74\xde\xa2\xb4\x3a\x70\x85\x4e\xa9\x19\xb2\x40\ \xa9\xa4\x27\x14\x15\x6a\xcb\xf3\x3c\xf0\xfa\x49\x7f\xf6\x41\xc6\ \x12\x1c\x9f\xff\xd3\x4f\x4c\xf9\xd8\x2c\x95\xdf\xb1\xd1\xac\x13\ \x76\xd0\xf1\xc6\x69\x68\x39\x1e\xe2\x22\x02\x4c\x48\x80\xd7\x71\ \x05\x98\x40\x31\x72\xcb\xe4\x1d\x04\xae\xee\xdf\xa8\xf0\xef\xfd\ \xad\x97\x89\x54\x3c\xef\xc0\xca\xc8\x1c\xed\x66\x8d\xcc\x0b\x1e\ \x45\xfd\x5b\x59\xa3\x4c\xca\xf9\x4f\xe0\xd2\x8c\x49\x1c\x94\xde\ \x37\x4c\x4b\x3c\x13\xb6\xe0\xff\x5d\x77\xf8\xb1\xd3\x53\xaa\x92\ \xb9\xd0\xb8\x0e\x2c\x53\x6e\x72\x16\xe9\xa9\x10\xc0\x5f\xd7\x77\ \x48\xe8\xe8\xc2\xdf\x4d\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x03\x4d\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x64\x00\x00\x00\x64\x08\x06\x00\x00\x00\x70\xe2\x95\x54\ \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ \xa8\x64\x00\x00\x02\xe2\x49\x44\x41\x54\x78\x5e\xed\xdd\xbd\x8a\ \x15\x31\x1c\x05\xf0\xeb\x07\x8a\x2f\xa0\x8d\x95\xa2\x2c\x8a\xa0\ \x20\x82\xbd\xa8\xcd\x62\xa3\x0b\xea\x53\x58\x69\xa7\x76\x5a\xf9\ \x1e\x8a\xa8\xfb\x12\x36\x56\xa2\xd8\xe8\x0a\xb2\x8d\x36\x2a\xf8\ \x81\x36\x7a\xfe\x24\x45\x18\x32\x33\x77\x32\xc9\xdc\x33\xd9\xf3\ \x83\x43\xc2\x2d\x96\xc9\x3d\xbb\xe1\x86\x3b\xec\x2c\x44\x44\x44\ \x44\x44\x44\x26\xb6\xcb\x8f\x39\x1c\x43\xd6\x91\x23\xc8\x41\x7b\ \x81\xc0\x17\x64\x0b\xd9\x44\xde\xdb\x0b\x3b\xc1\x71\xc4\x16\xfc\ \x8f\x3c\x2f\x10\xfb\xa5\xa9\xda\x65\xe4\x3b\x12\x7b\x03\x18\xf3\ \x0d\xb9\x84\x54\xe9\x0c\xf2\x13\x89\x2d\x9c\x39\xbf\x90\x73\x48\ \x55\xf6\x20\x6f\x91\xd8\x82\xe7\x90\x77\xc8\x5e\xa4\x1a\x37\x90\ \xd8\x42\xe7\x94\xeb\x08\x9d\xdd\x7e\x1c\xea\x9a\x1f\xe7\xec\xaa\ \x1f\xa9\xa4\x16\x72\xd6\x8f\x73\x46\xb9\x86\xd4\x73\xc8\x1f\x64\ \x9f\x9b\x46\xbd\x44\xb6\xdd\x74\x65\x0e\x23\xe7\xdd\x34\xea\x2f\ \xb2\xdf\x4d\xe7\x2f\xb6\x27\x87\x61\xd8\xd2\xec\x1a\x62\xd7\x16\ \x86\x4e\xea\x96\x25\x85\xa8\x10\x32\x2a\x84\x8c\x0a\x21\xa3\x42\ \xc8\xa8\x10\x32\x2a\x84\x8c\x0a\x21\xa3\x42\xc8\xa8\x10\x32\x2a\ \x84\x8c\x0a\x21\xa3\x42\xc8\xa8\x10\x32\x2a\x84\x8c\x0a\xe9\xd6\ \xf5\x05\x57\x11\x2a\xa4\xdb\x2d\xe4\x81\x9b\x4e\x43\x85\xf4\xbb\ \x8d\x4c\x56\x8a\x0a\x59\xce\x64\xa5\xa4\xde\xe4\xd0\xf7\x7d\xf4\ \x06\xf2\xc4\x4d\x57\xe6\x14\x72\xd3\x4d\x5b\xdd\xf1\x63\x9b\xc7\ \x48\x78\x7f\xc0\x7d\xe4\x9e\x9b\x72\x69\xde\x2c\xd0\x4c\x0d\xf7\ \x6d\x19\x2b\xa4\xb9\xb6\xa2\x7f\x29\xda\xb2\x86\x2b\xba\x7d\xa9\ \x90\x34\xc5\x4a\x51\x21\xe9\x8a\x94\xa2\x42\xc6\xc9\x5e\x8a\x0a\ \x19\x2f\x6b\x29\x2a\x24\x8f\x6c\xa5\xb0\x9c\x43\x0e\x20\x27\xdc\ \x94\x8a\xbd\xc9\x17\xdc\x74\x29\x2b\x3b\xa7\x34\x3f\x9b\x37\x33\ \xf4\x1c\x72\x12\x89\xfd\x9c\x39\x66\xd4\x5f\x8a\xb6\xac\xfc\x46\ \x6d\x5f\x2a\xa4\x8c\xe4\x52\x54\x48\x39\x49\xa5\xa8\x90\xb2\x7e\ \xfb\x71\x69\x2a\xa4\x1c\xfb\xb4\x65\x9f\xba\x06\x51\x21\x65\x24\ \x95\x61\x54\x48\x7e\xc9\x65\x18\x1d\x0c\xbb\xed\xd8\x83\x21\xab\ \xd8\x17\x54\x6d\xb9\x8b\x8c\xa6\x2d\x2b\x8f\x51\xdb\x54\x48\x85\ \x8c\x97\xad\x0c\xa3\x42\xc6\xc9\x5a\x86\x51\x21\xe9\xb2\x97\x61\ \x54\x48\x9a\x22\x65\x18\x15\x32\x5c\xb1\x32\x4c\xcd\x37\xca\xad\ \x21\x57\xdc\xb4\xd5\x43\x3f\xb6\xd1\x8d\x72\x19\xd9\x35\xc4\xae\ \x2d\x4c\x9f\xf0\x1c\x92\xe5\x9c\xd1\x47\x5b\xd6\x72\x8a\x6e\x53\ \x21\x15\xd2\x6f\xb2\x32\x8c\x0a\xe9\xf6\x08\x99\xac\x0c\xa3\x42\ \xba\xd9\xbf\x2a\x9c\x94\x0a\x21\xa3\x42\xc8\xa8\x10\x32\x2a\x84\ \x8c\x0a\x21\xa3\x42\xc8\xa8\x10\x32\x2a\x84\x8c\x0a\x21\xa3\x42\ \xc8\xa8\x10\x32\x2a\x84\x8c\x0a\x21\xa3\x42\xc8\xa8\x10\x32\x7a\ \xe4\x51\x25\x3e\x21\xe1\xcd\x02\x73\xcc\x47\x84\x4e\xea\x96\xf5\ \xca\x8f\x73\x46\xb9\x86\xd4\x42\x56\x7d\xcf\x55\x0e\x4f\xfd\x58\ \x05\x7b\xf4\xea\x1b\x24\xb6\x15\xcc\x21\xaf\x11\x5b\x43\x55\x4e\ \x23\x3f\x90\xd8\x82\x99\x63\x0f\x27\xae\xe1\xc1\x98\x51\x17\x11\ \x7b\x24\x76\x6c\xe1\x8c\xf9\x8a\xd8\x35\x57\xcd\x1e\x1a\xff\x1c\ \x89\xbd\x01\x4c\x79\x86\x1c\x45\xa8\xa5\x9e\x43\x62\x6c\xb1\xeb\ \x7e\x3c\x64\x2f\x10\xf8\x8c\x7c\x40\x36\x91\x2d\x7b\x41\x44\x44\ \x44\x44\x44\x44\x42\x8b\xc5\x7f\xe5\x3f\x89\x9d\x4e\x70\xa6\x97\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x00\xd4\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x89\x49\x44\x41\x54\x58\x85\x63\x60\x18\x05\ \xa3\x80\x74\x30\x9f\x81\x81\xe1\x3f\x01\x7c\x9f\x58\xc3\x98\xc8\ \x70\x80\x03\x11\x6a\x0e\x90\x61\x2e\x59\x40\x8a\x01\xe2\xe3\xa7\ \xe4\x1a\x40\x4e\x08\x20\x03\x63\x28\x7d\x76\xc4\x3a\xc0\x04\x4a\ \x9f\xa1\xd0\x1c\xb2\xc1\x33\x06\x48\x1a\x90\x1c\x08\xcb\x29\x4e\ \x80\x0c\x0c\x94\x45\x01\xc5\xf1\x3f\xe4\x1d\x30\x9a\x00\x29\x4e\ \x80\x0c\x0c\xe4\x47\x01\x55\xe2\x7f\x48\x3b\x60\x34\x01\x52\x25\ \x01\x32\x30\x90\x17\x05\x54\x8b\xff\x21\xeb\x80\x91\x97\x00\xa9\ \xda\x0a\x46\x07\xc4\x44\xc1\xa0\x6a\x05\x8f\x82\xe1\x07\x00\x83\ \x09\x30\xf9\xc1\x98\x1d\x5e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ " qt_resource_name = b"\ \x00\x0f\ \x00\x7b\x2b\x75\ \x00\x49\ \x00\x63\x00\x6f\x00\x6e\x00\x73\x00\x5f\x00\x52\x00\x65\x00\x66\x00\x65\x00\x72\x00\x65\x00\x6e\x00\x63\x00\x65\ \x00\x05\ \x00\x4f\xa6\x53\ \x00\x49\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ \x00\x07\ \x09\xc1\x57\xa7\ \x00\x72\ \x00\x75\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0e\ \x01\x21\x26\xa7\ \x00\x62\ \x00\x64\x00\x73\x00\x69\x00\x6d\x00\x5f\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x08\xc8\x58\x67\ \x00\x73\ \x00\x61\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x09\ \x06\x97\x98\x67\ \x00\x61\ \x00\x62\x00\x6f\x00\x72\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0b\ \x02\x6e\xc3\xe7\ \x00\x6f\ \x00\x76\x00\x65\x00\x72\x00\x6c\x00\x61\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x04\xb2\x58\xc7\ \x00\x75\ \x00\x6e\x00\x64\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x10\ \x0c\xd3\x1f\x47\ \x00\x63\ \x00\x6f\x00\x6c\x00\x6f\x00\x72\x00\x5f\x00\x70\x00\x69\x00\x63\x00\x6b\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0c\ \x02\xc1\xfc\xc7\ \x00\x6e\ \x00\x65\x00\x77\x00\x5f\x00\x66\x00\x69\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x0a\x22\x33\x07\ \x00\x62\ \x00\x64\x00\x73\x00\x69\x00\x6d\x00\x5f\x00\x6c\x00\x6f\x00\x67\x00\x6f\x00\x32\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x0b\xb2\x58\x47\ \x00\x72\ \x00\x65\x00\x64\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0d\ \x08\xd5\xc4\xe7\ \x00\x75\ \x00\x6e\x00\x64\x00\x65\x00\x72\x00\x6c\x00\x69\x00\x6e\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x10\ \x0a\x7b\xb6\x67\ \x00\x63\ \x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x5f\x00\x61\x00\x6c\x00\x69\x00\x67\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0b\ \x00\xc4\x52\x27\ \x00\x73\ \x00\x69\x00\x6d\x00\x54\x00\x69\x00\x6d\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x04\x54\x04\xe7\ \x00\x72\ \x00\x69\x00\x67\x00\x68\x00\x74\x00\x5f\x00\x61\x00\x6c\x00\x69\x00\x67\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x08\x01\x59\x27\ \x00\x6d\ \x00\x61\x00\x69\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x02\x4f\x22\xa7\ \x00\x6f\ \x00\x70\x00\x65\x00\x6e\x00\x5f\x00\x66\x00\x6f\x00\x6c\x00\x64\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0b\ \x0c\x53\x24\x67\ \x00\x73\ \x00\x75\x00\x63\x00\x63\x00\x65\x00\x73\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0b\ \x05\x79\x4e\x27\ \x00\x73\ \x00\x61\x00\x76\x00\x65\x00\x5f\x00\x61\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0e\ \x08\x95\xe3\xa7\ \x00\x6c\ \x00\x65\x00\x66\x00\x74\x00\x5f\x00\x61\x00\x6c\x00\x69\x00\x67\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x02\x12\x49\x67\ \x00\x62\ \x00\x64\x00\x73\x00\x69\x00\x6d\x00\x5f\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x32\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x10\ \x02\x96\x70\x87\ \x00\x63\ \x00\x6c\x00\x65\x00\x61\x00\x72\x00\x5f\x00\x66\x00\x6f\x00\x72\x00\x6d\x00\x61\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x0c\x33\x5a\x87\ \x00\x68\ \x00\x65\x00\x6c\x00\x70\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x0c\x07\x58\x47\ \x00\x71\ \x00\x75\x00\x69\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x06\x27\x5a\x67\ \x00\x62\ \x00\x6f\x00\x6c\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0a\ \x06\xcb\x4f\xc7\ \x00\x72\ \x00\x65\x00\x6d\x00\x6f\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x09\ \x09\x65\x8e\x67\ \x00\x65\ \x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0d\ \x0c\x6c\x85\xa7\ \x00\x65\ \x00\x78\x00\x70\x00\x6f\x00\x72\x00\x74\x00\x5f\x00\x61\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0a\ \x02\xfc\x42\x47\ \x00\x69\ \x00\x74\x00\x61\x00\x6c\x00\x69\x00\x63\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct_v1 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x24\x00\x02\x00\x00\x00\x1c\x00\x00\x00\x03\ \x00\x00\x01\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x6f\x6f\ \x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xc8\ \x00\x00\x02\x62\x00\x00\x00\x00\x00\x01\x00\x00\x99\x8b\ \x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x8b\xe8\ \x00\x00\x00\x98\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x63\ \x00\x00\x02\x86\x00\x00\x00\x00\x00\x01\x00\x00\xb4\x48\ \x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x50\x39\ \x00\x00\x03\x40\x00\x00\x00\x00\x00\x01\x00\x00\xc1\x16\ \x00\x00\x01\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x7c\xae\ \x00\x00\x00\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x4d\x74\ \x00\x00\x02\x24\x00\x00\x00\x00\x00\x01\x00\x00\x8f\xe5\ \x00\x00\x02\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xb7\xf8\ \x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x24\ \x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00\xb9\xd3\ \x00\x00\x01\xce\x00\x00\x00\x00\x00\x01\x00\x00\x7d\x36\ \x00\x00\x02\x40\x00\x00\x00\x00\x00\x01\x00\x00\x99\x04\ \x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x33\xe3\ \x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x6d\xc8\ \x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00\xba\x7e\ \x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x50\xec\ \x00\x00\x01\x68\x00\x00\x00\x00\x00\x01\x00\x00\x6e\xe6\ \x00\x00\x01\x32\x00\x00\x00\x00\x00\x01\x00\x00\x6c\xf3\ \x00\x00\x02\xc2\x00\x00\x00\x00\x00\x01\x00\x00\xb7\x46\ \x00\x00\x02\xac\x00\x00\x00\x00\x00\x01\x00\x00\xb5\xdf\ \x00\x00\x02\x08\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xd3\ \x00\x00\x03\x20\x00\x00\x00\x00\x00\x01\x00\x00\xbd\xc5\ \x00\x00\x00\xca\x00\x00\x00\x00\x00\x01\x00\x00\x4e\x41\ " qt_resource_struct_v2 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x24\x00\x02\x00\x00\x00\x1c\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x6f\x6f\ \x00\x00\x01\x7f\x26\x32\x4b\x81\ \x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x0e\xc8\ \x00\x00\x01\x7a\x7a\xcc\x36\x6e\ \x00\x00\x02\x62\x00\x00\x00\x00\x00\x01\x00\x00\x99\x8b\ \x00\x00\x01\x7a\x7a\xcc\x36\x6e\ \x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x8b\xe8\ \x00\x00\x01\x7d\xb2\xf7\x5e\xe7\ \x00\x00\x00\x98\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x63\ \x00\x00\x01\x7d\xb6\xca\xd9\x30\ \x00\x00\x02\x86\x00\x00\x00\x00\x00\x01\x00\x00\xb4\x48\ \x00\x00\x01\x7d\xb3\x00\x7b\x5f\ \x00\x00\x00\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x50\x39\ \x00\x00\x01\x7d\xb2\xf7\xf5\xc3\ \x00\x00\x03\x40\x00\x00\x00\x00\x00\x01\x00\x00\xc1\x16\ \x00\x00\x01\x7d\xb3\x02\x62\xed\ \x00\x00\x01\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x7c\xae\ \x00\x00\x01\x7d\xb3\x01\x4d\x71\ \x00\x00\x00\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x4d\x74\ \x00\x00\x01\x7d\xb2\xf2\xf3\x99\ \x00\x00\x02\x24\x00\x00\x00\x00\x00\x01\x00\x00\x8f\xe5\ \x00\x00\x01\x7d\xb3\x18\xc2\xa3\ \x00\x00\x02\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xb7\xf8\ \x00\x00\x01\x7d\xb3\x01\xfe\x34\ \x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x24\ \x00\x00\x01\x7e\xdd\xf3\x69\xe1\ \x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00\xb9\xd3\ \x00\x00\x01\x7d\xb2\xf5\xe9\xc9\ \x00\x00\x01\xce\x00\x00\x00\x00\x00\x01\x00\x00\x7d\x36\ \x00\x00\x01\x7b\xdd\xcc\x05\xd7\ \x00\x00\x02\x40\x00\x00\x00\x00\x00\x01\x00\x00\x99\x04\ \x00\x00\x01\x7d\xb3\x01\x1f\xc7\ \x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x33\xe3\ \x00\x00\x01\x7d\xb3\x18\xd0\x59\ \x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x6d\xc8\ \x00\x00\x01\x7d\xb3\x02\x9b\xc6\ \x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00\xba\x7e\ \x00\x00\x01\x7b\xdd\x98\xbe\xba\ \x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x01\x7e\xdd\xf7\x66\xb4\ \x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x50\xec\ \x00\x00\x01\x7b\x71\x75\xd1\xa2\ \x00\x00\x01\x68\x00\x00\x00\x00\x00\x01\x00\x00\x6e\xe6\ \x00\x00\x01\x7d\xb3\x01\x83\x0f\ \x00\x00\x01\x32\x00\x00\x00\x00\x00\x01\x00\x00\x6c\xf3\ \x00\x00\x01\x7d\xb2\xf2\x00\x41\ \x00\x00\x02\xc2\x00\x00\x00\x00\x00\x01\x00\x00\xb7\x46\ \x00\x00\x01\x7d\xb2\xfd\x02\xf6\ \x00\x00\x02\xac\x00\x00\x00\x00\x00\x01\x00\x00\xb5\xdf\ \x00\x00\x01\x7d\xb3\x04\x21\xa1\ \x00\x00\x02\x08\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xd3\ \x00\x00\x01\x7b\xec\x12\x34\x16\ \x00\x00\x03\x20\x00\x00\x00\x00\x00\x01\x00\x00\xbd\xc5\ \x00\x00\x01\x7e\xca\x15\xa4\x64\ \x00\x00\x00\xca\x00\x00\x00\x00\x00\x01\x00\x00\x4e\x41\ \x00\x00\x01\x7d\xb3\x1b\x2a\x81\ " qt_version = [int(v) for v in QtCore.qVersion().split(".")] if qt_version < [5, 8, 0]: rcc_version = 1 qt_resource_struct = qt_resource_struct_v1 else: rcc_version = 2 qt_resource_struct = qt_resource_struct_v2 def qInitResources(): QtCore.qRegisterResourceData( rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data ) def qCleanupResources(): QtCore.qUnregisterResourceData( rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data ) qInitResources() ================================================ FILE: bdsim/bdedit/Icons.qrc ================================================ Icons/bdsim_icon.png Icons/bdsim_icon2.png Icons/bdsim_logo2.png Icons/run.png Icons/abort.png Icons/simTime.png Icons/error.png Icons/success.png Icons/main.png Icons/overlap.svg Icons/bold.png Icons/italic.png Icons/underline.png Icons/left_align.png Icons/center_align.png Icons/right_align.png Icons/color_picker.png Icons/clear_format.png Icons/new_file.png Icons/open_folder.png Icons/save.png Icons/save_as.png Icons/export_as.png Icons/quit.png Icons/undo.png Icons/redo.png Icons/remove.png Icons/help.png ================================================ FILE: bdsim/bdedit/LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: bdsim/bdedit/README.md ================================================ ![block diagram](https://github.com/petercorke/bdsim/raw/master/figs/eg1-bdedit.png) `bdedit` is a multi-platform PyQt5-based graphical tool to create, edit, render and execute block diagram models. Key features include: * allows graphical creation of block diagrams * the diagram is stored in a human readable/editable JSON file with extension `.bd` * creates good-quality graphics for inclusion in publications * can launch `bdsim` to import and execute the model * automatically discovers all bsdim and toolbbox blocks and adds them to the block library menu * icons can be easily created using any image creation tool or a LaTeX expression **Note that `PyQt5` is licenced under GPL3.** # Getting started From the examples folder ``` % bdedit eg1.bd ``` will create a display like that shown above. To run an existing `.bd` file ``` % bdrun eg1.bd ``` which will: * parse the JSON file * instantiate all blocks and wires * compile and run the diagram It takes standard options like ``` % bdrun +g -a eg1.bd ``` to enable graphics but disable animation. Pushing the run button, top left (triangle in circle) will save the file then spawn `bdrun` as a subprocess. ## Adding a block Click a block from the library panel on the left-hand side. Each category is initially closed, click it, to open it and reveal the blocks within. All blocks, when added are instantiated in the middle of the canvas. The key elements of a block are: * input ports, small blue boxes * output ports, small red triangles or arrow heads * icon, which occupies the middle of the box (a 250x250 PNG image) * block label, beneath the block, must be unique in the diagram * block class, this appears when you hover the cursor over a block * port labels, small text inside the box, against input and/or output ports. Most ports don't have port labels. * parameters, these are set by right clicking the block, see [details here](block-parameters). Blocks can be flipped by typing "f" while the block is selected. ## Wiring blocks together * Click one port then click another port, or * Click and drag from one port to another. An output port can connect to an arbitrary number of input ports. ## Selecting and moving elements ![highlighted elements](figs/Figure_2.5-Block_and_Wire_Selected.png) All selected elements, wires or blocks have an orange highlight when selected. You can drag a region to select all blocks and wires within. Selected items can be moved: click and drag them. Blocks cannot be resized. Selected wires can be adjusted: * horizontal segments can be dragged up and down, * vertical segments can be dragged left or right. ## Connectors ![connectors](figs/Figure_1.2-Example_of_Block_Diagram_as_represented_in_bdedit.png) The wire dragging is a little limited, and sometimes it is hard to get the layout you want. In this case try adding a "Connector" from the "Canvas Items" category. Run a wire to the input of the connector and one or more wires from the output of the connector. You can drag the connector by dragging it, click close to, but not on, the connector to highlight it. A wire can have an arbitrary number of connectors in it. ## Block parameters ![parameter panel](figs/Figure_2.9-Parameter_Window.png) Right clicking a block opens a panel on the right. The parameters are initialized to values taken from the docstring for the block. Make changes and then click "Update parameters". This will check validity of the types and values. Click "View documentation" will open a browser window at the GitHub documentation page for the block class. Block parameters can be: * constants * a native Python expression, prefix it with an equal sign, for example `=[x**2 for x in range(10)]`. * an expression which relies on global variables or other run-time context, add a Main block to the diagram. It contain the name of a Python script (by definition, for a block diagram `model` it would be `model-main.py` which will be spawned when you hit the Run button. This can create the runtime environment, load the `.bd` file, for example: ``` from bdsim import bdload, BDSim sim = BDSim() #debug='i') bd = sim.blockdiagram() lmbda = 0.08 bd = bdload(bd, "IBVS.bd", globals=globals()) bd.compile() bd.report() out = sim.run(bd, 100) print(out) ```* ![parameter panel](figs/Main.png) # Grouping boxes ![parameter panel](figs/GroupingBoxes.png) These are transparent colored boxes that are drawn below all other graphical items. They can be used to add structure to the diagrams. # Free text Click `Canvas Items/Text Item` to drop a default string `Text` onto the canvas. Select the text to edit it. Formatting options are in the toolbar. Multiline text with left/right/centred alignment is supported. # Further details More detail about how the tool works and key datastructures are in the [technical report](TechReport.md). This is a little dated now, and the visual appearance of the GUI has evolved. ================================================ FILE: bdsim/bdedit/TechReport.md ================================================ bdedit was developed by a QUT computer science student project team. The following is the final report of that student project. Significant subsequent work has been on this tool since the project completed. # BdEdit Technical Report Contact Details Developers Samara - Email: samara.barwick@connect.qut.edu.au Rory - Email: rory.higgins@connect.qut.edu.au John - Email: john.wishart@connect.qut.edu.au Daniel - Email: daniel.petkov@connect.qut.edu.au Supervisor Professor Peter Corke - GitHub: https://github.com/petercorke # Table of Contents 1. [Context](#h1) 2. [Feature Exploration of Bdedit](#h2) [Interface](#h2-sh1) [Adding Blocks](#h2-sh2) [Sockets](#h2-sh3) [Socket Flipping](#h2-sh4) [Further Block Manipulation](#h2-sh5) [Wires](#h2-sh6) [Connector Block](#h2-sh7) [Intersection Management](#h2-sh8) [Editing Block Parameters](#h2-sh9) [Screenshot](#h2-sh10) [Grid Mode](#h2-sh11) [Grid Snapping](#h2-sh12) [Saving and Loading](#h2-sh13) 3. [Class Architecture (High Level)](#h3) [1) The Interface Class](#h3-sh1) [2) The Scene Class](#h3-sh2) [3) The GraphicsView Class](#h3-sh3) [4) The Block Class](#h3-sh4) [5) The Socket Class](#h3-sh5) [6) The Wire Class](#h3-sh6) 4. [Making changes to code](#h4) [Adding more blocks types to application](#h4-sh1) [Block parameters explained](#h4-sh1) [JSON file structure outline](#h4-sh2) [How icons were created](#h4-sh3) [Procedure for updating changes to existing icons, or adding new ones](#h4-sh4) 5. [Appendices](#h5) [APPENDIX A – High Level Class Architecture Diagram](#h5-sh1) [APPENDIX B – Stepped Wire Drawing Logic](#h5-sh2) [Embedded Links](#h5-sh3) # 1. Context In engineering, complex systems are often represented with block diagrams (refer to Figure 1.1), where blocks represent functions with inputs and outputs, and wires represent the flow of values between the ports of these functions.

Example of System as a Block Diagram

These block diagrams can be modelled and simulated as code through the bdsim [[1]](#h5-sh3-item1) Python package developed by Professor Peter Corke, where the blocks and wires are represented in terms of Python class and method calls. To aid with the conceptualization of the developed block diagram model and its modelling process, the bdedit package was developed as an addition to bdsim, allowing for block diagrams to be created graphically with items that visually represent the blocks, in/out ports and the wires (refer to Figure 1.2). Bdedit supports the saving and loading of these diagrams to and from a JSON file, which stores all the necessary data for the diagram to later be simulated through bdsim.

Example of Block Diagram as represented in bdedit

# 2. Feature Exploration of Bdedit ## Interface Installing the bdsim package and its necessary files, then running the bdedit.py [[2]](#h5-sh3-item2) file, launches a new window containing a graphical user interface (refer to Figure 2.1). This interface contains three areas of focus, the canvas (grey grid space), the library browser panel, and the toolbar.

Bdedit Graphical User Interface

## Adding Blocks Through this interface, the user can create a block diagram by choosing from a list of available blocks found within the Library Browser panel. These will call on the classes related to those blocks, to create a block which both stores its values internally within the program and graphically represents that block within the diagram. The graphical information of these blocks and wires is then stored within the canvas area (refer to Figure 2.2).

Adding Blocks to Canvas and Connecting them

## Sockets These blocks have sockets, representing its inputs and outputs. These are determined through the block type, with some blocks only having input sockets (sink blocks or INPORT blocks), some only having output sockets (source blocks or OUTPORT blocks), and others having both input and output sockets (function, transfer, discrete or SUBSYSTEM blocks) (refer to Figure 2.3).

Examples of Blocks

These sockets can be used to connect the output of one block to one or more other blocks, creating a flow of data. The following logic is applied to these sockets based on their types: * Sockets cannot connect to the same socket type (Output cannot connect to Output, Input cannot connect to Input) * Input sockets can only have one wire connecting into them (any further wires that are connected will be disconnected until the existing wire is removed) * Output sockets can have any number of wires connected to them ## Socket Flipping Blocks can also be flipped, reversing the sides on which the input and output sockets are located. This can be achieved through pressing the 'F' key (F for flip). These are only graphically updated and do not impact the flow of logic to those sockets (refer to Figure 2.4).

Example of Flipping Socket Orientation

## Further Block Manipulation Blocks can also be selected/ moved and deleted as desired. All items are restriced to being moved around within the borders of the canvas. Wires can also be selected and deleted, but not moved. Sockets cannot be selected, moved or deleted through mouse interaction. Selecting, or rather clicking on, a socket creates a draggable wire. As the block is moved, so too are its sockets. The only instance where sockets move relative to the block, is when the number of input or output sockets changes (which is controlled through the [parameter window](#h2-sh9)). The selection of an item is indicated by a colour change. When a block, connector block or wire is selected, its outline changes from a thin black line, to a thicker bright orange outline (refer to Figure 2.5).

Block and Wire Selected

As mentioned above, blocks and wires can be deleted when desired. This is done through first selecting the item, then pressing either the 'Backspace' or the 'Del' keys on the keyboard. If a wire is deleted, only the wire will be removed. If a block is deleted, any wires that were connected to it will also be deleted with the block. ## Wires Blocks can be connected to one another by either clicking on a socket of one block, then clicking on a socket of another block, or by click-and-dragging from one socket and releasing over the socket of another block (refer to Figure 2.6). When a wire is connected between two points, wire logic will be applied to it, in order to determine the path it should take to connect those two blocks. Moving the block around once the wire is connected, will update the position of the wire end points, and as such, will cycle through the wire logic to determine what path the wire should follow (refer to [APPENDIX B](#h5-sh2) for examples and a walkthrough of this logic).

Example of Dragging Wire

## Connector Block The sections of the wires cannot be pulled out and positioned to the user's liking, as their path is solely dependent on the position of the two points it connects. Hence, to assist with routing the wires when the diagrams become more complex with wires travelling in multiple directions, a connector block can be used as an intermediary point through which the wire must travel. These provide the user with more control over how the wire travels between any two points, by creating more points in between the start and end point, which the wire must first connect. The comparison between using a connector block and not, can be seen in Figure 2.7. The connect block appears as a single Input and Output socket joined together on one edge; and similarly to other blocks, it is also flip-able.

Usage of Connector Blocks

## Intersection Management As seen in the bottom part of Figure 2.7, wires can overlap at times, and although they are fairly easy to follow in this figure, it can become difficult to follow the flow of logic when the diagram becomes more complex. To address this problem area, parts of wires that cross over each other can be separated to indicate they do not cross, but instead pass over each other (refer to Figure 2.8). This feature might not always be desired, so it has been disabled by default, however can be toggled on or off by the user through pressing the 'I' key (I for intersection). The wire logic at these intersection points, keeps all vertical segments of wires solid, and erases parts of any horizontal segments that they pass through.

Toggling Wire Intersection Detection ON

## Editing Block Parameters Integral to the creation and simulation of block diagrams, is the ability to edit the parameter values related to each block, as this dictates what output value a block produces and how blocks process any inputs that feed into it to produce an output. If supported for the given block, it is also possible to edit the number of input or output sockets a block has. All blocks (aside from connector blocks) can be named as desired by the user. As blocks are spawned they are given a default name which is auto incremented depending on the block type. These user-editable block parameters are editable through a Parameter Window panel that appears on the right hand side of the screen when triggered to do so (refer to Figure 2.9). It can be toggled to open by first selecting a block, then right clicking the mouse; subsequently, it can be closed at any moment by either right clicking again or left clicking anywhere in the screen. Closing the Parameter Window will retain any values that have been edited, but will only update the block parameters once the 'Updated Parameters' button has been clicked.

Parameter Window

When parameters are edited and the user selects update, all editable values within the Parameter Window will be checked to ensure they adhere to the conditions placed on them. If the block title is changed, this will be checked against other existing block titles, to ensure the name of the current block would not be a duplicate. If the block parameters are changed, they will be compared against their required types to ensure they match (e.g. float, int, bool, list, str), and if any further restrictions are placed on these parameters (e.g. matching to certain strings, or being within a certain range), they will be checked against those too. This information is defined internally within the block class, and applied to the block when it is created. User feedback pop-up windows are also connected to this Parameter Window. If the user provides values that are incorrect - be they a duplicate block title, incorrect types or not adhering to further restrictions – the block parameters will not be updated, and an error message will be displayed notifying the user with useful information for where the issue occurred. If all values are correctly inputted, a success message will be displayed in the same area (refer to Figure 2.10).

User-Feedback Messages

## Screenshot Upon creating a block diagram, the user can take a screenshot of all items within the canvas by simply pressing the 'Screenshot' button located within the toolbar. This will save a 4k resolution image of the entire canvas and everything in it. This resolution is chosen due to canvas size potentially being 5x the desktop screen size due to the zooming feature. Due to limited time to further develop this feature, at present time, this image will be saved with the '.png' file extension, under the name 'Scene Picture' in the same folder the interface is run from. **If taking multiple screenshots this way, be aware that this will override any previous screenshot you may have taken.** ## Grid Mode To improve the viewing quality of the screenshots taken, and reduce the amount of visual noise/messiness created by having the background be a grid, an option is available from the toolbar to disable the background by navigating to the 'Grid Mode" button in the toolbar, and selecting 'Off' from the drop down menu. Alternatively, the grid can be displayed in two other modes, Light (the default mode) and Dark (refer to Figure 2.11).

Background Colour Modes

## Grid Snapping To improve the usability of the interface and the user experience when moving/aligning blocks within the canvas, a grid snapping feature has been implemented, where movement of the mouse will be restricted to moving the block in increments of 20 pixels (the width of the smaller grid squares). Additionally, all sockets are indexed in increments of the same value (20 pixels) in order to line up with these smaller grid lines. As such, since wires are automatically drawn between the socket positions, it is much easier to move blocks around in order to make them straight. ## Saving and Loading An integral component to this bdedit tool is the support for saving the current progress made on a block diagram, and the support to load a previously worked on block diagram (provided it is in a compatible format). Block diagrams are saved as JSON files, containing information about the canvas size, all the blocks (its name, on-screen position, parameters, and sockets), and finally all the wires and the sockets they connect to. This information is stored as a dictionary within the JSON file, which is parse-able as key-value pairs, where the key represents the name of the variable or parameter related to the scene, block, socket or wire, and the value representing the value that variable holds. A file can be saved or loaded from through the associated 'Save' or 'Save As' and 'Load' buttons within the toolbar. Upon clicking on one of these buttons, the a file browser window will pop up (allowing the user to browse their devices' file structure), prompting the user to either select a file to load or to choose the location they wish for their file to be saved (and the name of the file if it's the first time saving or if saving the diagram as a new file). Due to a limitation of time to further develop this feature, whether this file is in a compatible format is not checked before it is attempted to be parsed, so any errors that may occur due to an incorrect file being loaded, will result in the crashing of the bdedit tool. # 3. Class Architecture (High Level) The architecture of BDEdit can be summarize through the connectivity between 6 main classes as seen in Figure 3.1. Other classes are also essential, however it is through the interactions between these 6 main classes that the application is able to run. For a full size image of this architecture refer to [APPENDIX A](#h5-sh1). These classes break down into the following:

Bdedit Architecture Diagram

### 1) **The Interface Class** – This class is responsible for the dimensions of the BDEdit window that appears when the application is run, as well as managing the layout of where all the interact-able areas of the application are, these being: * The toolbar; * The left side panel (otherwise named the Library Browser); * The right side panel (otherwise named the Parameter Window); and * The canvas or work-area (which is an instance of the GraphicsScene class, who itself is a child class of the Scene class, AND is connected to a GraphicsView class instance). It is namely the creation of the GraphicsScene class that allows for the graphical representation of Blocks, Sockets and Wires, the culmination of which allows for the making of a Block Diagram. ### 2) **The Scene Class** – This class is responsible for three things. * Storing all instances of any Wires and Blocks (and their Sockets) that are created, i.e. their structures, internal variables, lists, properties, etc. It also manages the adding/removing of these instances. * Creating and storing a GraphicsScene class instance, in which all the GraphicsBlock, GraphicsSocket, and GraphicsWire class instances are added to graphically represent their class respectively. This allows for the all the relevant internal information of the Block, Socket and Wire classes to be represented graphically! * Managing the intersection points at which any two (or more) wires overlap. ### 3) **The GraphicsView Class** – This class instance is connected to the GraphicsScene through the Interface class, and is responsible for monitoring the GraphicsScene and implementing logic to any user interactions within it. These interactions being: key presses, mouse press/release, mouse movement, scroll click and scroll movement events. These detected events are caught in real time, allowing for logic within the GraphicsScene to also be updated in real time. Some examples of this includes: * the creation of a Wire (and subsequent GraphicsWire) when a GraphicsSocket is clicked on; * the real-time updates to how the GraphicsWire is drawn while being pulled from one GraphicsSocket to another; * the real-time updates to how the GraphicsBlock is drawn when the number of sockets on it changes, or when it is selected; * the zooming in and out of, and panning of the canvas (GraphicsScene). ### 4) **The Block Class** – An instance of this class is created when a respective button is clicked from the Library Browser side panel in the Interface. This class is responsible for holding all Block related variables, these beings things like its name, position within the canvas, block type, icon, dimensions, user-editable parameters and a list of input and output Socket instances that are related to this Block. Additionally, this class relates and instance of the **ParamWindow** class and GraphicsBlock class to this Block. The **ParamWindow** is a class which creates a Parameter Window in which are displayed this Blocks' type, title and user-editable parameters, and through which a user can edit the parameters of a given block. When this Parameter Window is opened, it appears in a right side panel within the Interface. Similar to how the GraphicsScene represents a Scene class instance, the GraphicsBlock graphically represents a given Block, and sends that graphical information to the GraphicsScene to display. ### 5) **The Socket Class** – An instance of this class is created and connected to a Block, whenever one is made. This class is responsible for holding all Socket related variables, like its index (from the top of the Block, these are auto incremented as more Socket are made for a Block), the position to be drawn at (Left or Right of the Block), the type of Socket being drawn (Input or Output) and finally a list of all Wires that are connected to this Socket. Additionally, some special blocks (PROD and SUM Blocks) have math operators (+,-,×,÷) drawn alongside their input Sockets depending on what string for one of those block's parameters. For example, that parameter may be the string "`*/*`" in the PROD block, and this will draw a '×', '÷', '×' alongside the first, second, third input sockets respectively. The Socket class also holds an instance of GraphicsSocket which graphically represents the Socket, and is sent to the GraphicsScene to be drawn. ### 6) **The Wire Class** – As was mentioned in the GraphicsView, when it has detected that a GraphicsSocket has been clicked, this will create a Wire instance from that Socket, and a subsequent GraphicsWire from the GraphicsSocket to the mouse cursor, until either the GraphicsWire is clicked off of into an empty space within the GraphicsScene, or the wire is clicked off of onto another GraphicsSocket. If these socket types are different (i.e. both aren't input or output sockets), then the wire is connected and will remain so, even as the GraphicsBlock is moved around. The Wire class is responsible for holding all Wire related variables, these being what Sockets this Wire connects (start/end sockets) and the type of wire being drawn (Direct, Bezier or Step). The wire type dictates the style with which the wire is drawn. Direct draws the wire as a straight line between two points. Bezier draws the wire as a cubic between two points (think sinusoidal wave). Step draws a wire with 90 degree bends at each point the wire must turn to reach the end socket. Although other methods and classes are involved in the process of making this application meet further functional requirements, these 6 classes are what tie everything together. # 4. Making changes to code ## Adding more blocks types to application If the new block type falls under one of the following, already existing categories: Source, Sink, Function, Transfer, Discrete, INPORT, OUTPORT or SUBSYSTEM block (these last three are located within the hierarchy file), then that block simply needs to be added as a class of one of the Python files relating to those block types. These Python files are located in the "**_`bdsim/bdsim/bdedit/Block_Classes`_**" folder (refer to Figure 4.1), with the names seen in Figure 4.2.

Relevant bdsim File Structure Block Type Files

Within each of those files, the first few lines import the block type they inherit, for example, the "**_`block_function_blocks.py`_**" file imports the FunctionBlock class from the "**_`block.py`_**" file (located in "**_`bdsim/bdsim/bdedit`_** ") inheriting its properties. An example of one of the blocks within these files (without the block comments) is given below: ``` python class Function(FunctionBlock): def __init__(self, scene, window, func="Provide Function", nin=1, nout=1, dictionary=False, args=(), kwargs={}, name="Function Block", pos=(0, 0)): super().__init__(scene, window, name, pos) self.setDefaultTitle(name) self.block_type = blockname(self.__class__) self.parameters = [ ["func", str, func, []], ["nin", int, nin, [["range", [0, 1000]]]], ["nout", int, nout, [["range", [0, 1000]]]], ["dict", bool, dictionary, []], ["args", tuple, args, []], ["kwargs", dict, kwargs, []] ] self.inputsNum = nin self.outputsNum = nout self.icon = ":/Icons_Reference/Icons/function.png" self.width = 100 self.height = 100 self._createBlock(self.inputsNum, self.outputsNum) ``` This block class type is constructed based on the definition of the corresponding bdsim block [[3]](#h5-sh3-item3). When adding in a new block, the following template can be copied and adjusted as needed. ``` python # The name of this new block class should be unique, and # it should inherit whichever block type it belongs to. class My_New_Block(Class_the_new_block_relates_to): # Next, the scene and window are required to be passed to the # creation of this block (and will be done so automatically from # the interface). The following input parameters are the parameters # of the block and their respective default values. # Finally comes the block's default name (if the user doesn't provide # one), and the position is set to always spawn the block at the # centre of the work area. def __init__(self, scene, window, param1="parm1_default_value", param2="parm2_default_value", param3="parm3_default_value", name="My_New_Block Block", pos=(0, 0)): super().__init__(scene, window, name, pos) # The chosen default name for this block is passed to # the setDefaultTitle function which will ensure no duplicate # names names of this block exist. self.setDefaultTitle(name) # The block type is set as the given name of this block class self.block_type = blockname(self.__class__) # The parameters of the block are wrapped into a list, where each # parameter sits inside its own list and defines # the name, type, default value, and any further restrictions # for each respective parameter. self.parameters = [ ["parameter 1", str, param1, []], ["parameter 2", str, param2, []], ["parameter 3", str, param3, []] ] # The icon file path is matched to whatever name the icon for # this block was named within the Icons folder (this procedure # will require the icons resource file to be updated for a new # image to be findable within this folder). self.icon = ":/Icons_Reference/Icons/my_new_block.png" # The height and width are set for this block self.width = 100 self.height = 100 # Finally the block is created, with the number of input and # output sockets that have been assigned to this block. (This # will be inherited from the class this block inherits). self._createBlock(self.inputsNum, self.outputsNum) ``` This simply needs to be added to the end of the appropriate file (as chosen from Figure 4.2), and this will make the block automatically appear within the interface. ## Block parameters explained Each block has its own unique parameters, with their own unique names, types, default values and further restrictions (like being restricted to a certain range of allowable numbers). All of a given blocks' parameters are stored within the self.parameters variable list, with each individual parameter being stored as a list within the self.parameters list. Each individual parameter is defined with the following format: * **_parameter = ["name", type, value, [restrictions]]_** _e.g. parameter = [["Gain", float, gain, []], ["Premul", bool, premul, []]]_ The items which make up this list of the parameter, are as follows: * **_name_**: _this is the name of the parameter as a string_ * **_type_**: _this is the type this parameter must be (e.g. int, str, float)_ * **_value_**: _this is the default value the parameter will be set to, if no other value is given. It must also adhere to the required type of the parameter._ * **_restrictions_**: _this is a list (can be list of lists) containing further restrictions applied to the parameter._ As multiple restrictions can be applied to a single parameter, each individual restriction is enclosed as a list. If no restrictions are applied to a parameter, the main restrictions list (inside the parameter list) will simply be an empty list, as seen in the example. These restrictions follow the following structure: * **_restriction = ["restriction name", [condition(s)]]_** What these two items within the restriction list represent is explained below: * **_restriction name_** : _can be only one of the following "keywords", "range", "type" or "signs"._ * **_condition(s)_**: _differ based on the restriction name used, and will be of the following format:_ Currently, only the four restriction types mentioned below are recognized. These must be entered as a string in the first item within the restriction list ("restriction name"), to indicate what kind of restriction is being applied to this parameter. Following this first value, a list containing one or more conditions placed on the restriction is defined. These depend on the type of restriction chosen. Examples of these restrictions are given below: ```python [["keywords", ["sine", "square", "triangle"]]] ``` * This restriction compares the parameter value against the strings defined within the conditions list. This restriction should only be used on parameters whose required type is 'str' (string). If the parameter value doesn't match any of these strings, this will throw an error notifying the user that their input must match one of those strings. * Here the conditions list is just a list of all the variations the parameter value can be. ```python [["range", [-math.inf, math.inf]]] ``` * This restriction compares the parameter value against being within a range of given numbers, defined by a min and a max. This restriction should only be used on parameters whose required type is either 'float' or 'int'. If the parameter value is outside the allowable range, this will throw an error notifying the user that their input must be within the given range. * Here the conditions list simply is made up of a minimum and maximum value. ```python [["type", [type(None), int, float]]] ``` * This restriction compares the type of the parameter value against one of the additional allowable types for this parameter. If for instance, a parameter can either an integer or a float when defined, or a None type otherwise, this means the parameter can be one of 3 different types, and this restriction allows the parameter to pass as long as its type matches one of the defined types. In order to allow other types, the type set for this parameter (as the second item in the parameters list), must also be included in this conditions list, usually as the last item (for consistency sake). Note, when allowing a parameter to have None as a value, since None isn't a type, but rather a NoneType, type(None) is used to extract the type of None. If the type of the parameter value doesn't match one of the other allowable types, this will notify the user of the error, and the allowable types. * Here the conditions list is just a list of all the acceptable types for that parameter, with the required type of the parameter also being in that list (usually as the last value in the list; which is float in this case). ```python [["signs", ["*", "/"]]] or[["signs", ["+", "-"]]] ``` * This restriction compares the parameter value against the various characters that have been defined in the conditions list. This restriction should only be used for SUM and PROD type blocks, as these have input sockets which are labelled according to the signs defined in this condition list. The parameter value for these blocks (SUM and PROD) is entered as a string of characters, and each character for the respective block is checked against the ones that are allowed from the conditions list. If that string is made up of any number of characters that don't match the ones that are allowed, an error will be thrown, notifying the user of the allowable inputs. * Here the conditions list is just a list of all the allowable characters for that parameter ## JSON file structure outline The JSON file structure contains all the necessary information of the reconstruction of blocks, sockets and wires that exist within a block diagram, and represents them as dictionaries, of key-value pairs which represent the name and value of parameters relevant to those items. All of these items (blocks, sockets, and wires) are contained within a Scene as explained in [Section 3](#Class_Architecture(High), hence they follow the following hierarchy: * A Scene is represented as dictionary with: * Dimensions: these are two parameters of the width and height of the scene. * Blocks: this is a list of all the blocks within the diagram, with each block as a dictionary. * Sockets: each block has its own unique sockets, so these are stored as part of the block they belong to, also in a list, with each socket as a dictionary. * Wires: this is a dictionary list of all the wires within the diagram. The following block diagram was made to aid with understanding this structure.

Block Diagram Example for JSON File Stucture

There are 3 blocks, created in the order: 1. Function Block – has 1 input socket, 1 output socket 2. Scope Block – has 2 input sockets, no output sockets 3. Constant Block – has no input sockets, 1 output socket There are also 2 wires, created in the order: 1. Function block -> 1st input socket of Scope Block 2. Constant block -> 2nd input socket of Scope Block Below, is the resulting JSON file that was generated.

JSON File Structure 1 JSON File Structure 2

The important points to take away from this JSON structure, is that each block, socket and wire has unique ID's. Although the ID's of the blocks are less useful (can be helpful for differentiating between different blocks of the same time, although their name should be sufficient), the ID's of the sockets are very important. By definition each wire has a start socket and end socket that it is drawn between, and these are referred to by their unique ID. These unique ID's of the sockets, can then be traced back to the block they belong to, hence providing the crucial information of which block connects to which. As an example, in this block diagram that was made, wire #1 connects the **Function block** from its **output**, to the **1st input** of the **Scope block**. In the wire, these start/end sockets are stored as "start_socket": 2427529113712 and "end_socket": 2427529150816,and if we check the socket id's of the **output socket** of the **Function block**, it is stored as "id": 2427529113712, which we expect to match our "start_socket"(which it does), and doing the same for the **1st input socket** of the **Scope block**, it is stored as "id": 2427529150816, which also matches our ID of the "end_socket". ## How icons were created Icons for bdedit were creating using the free image editing application, paint.net [[4]](#h5-sh3-item4). Paint.net is only compatible with windows operating systems, however there are other options available for image editing applications on Mac or Linux systems. The sought after properties of image editing tools for creating/editing these icons were: for the ability to create layered images, moving layer position in the layer stack, hiding/showing the layer, selecting parts of images and separating them from the background (leaving only the outline/shape), and more importantly, support for transparent backgrounds. Paint.net provides support for all these requirements, but other software options may also, so feel free to use whichever software, as long as the final images are saved in '.png' format and have transparent backgrounds. Paint.net also allows for files to be saved in their separate state as layers with the '.pdn' file type, however only Paint.net can open these files. For Windows users this shouldn't be a problem, but for users on other systems, the layers that make up each icon have been saved separately, which will allow them to be added as layers to which ever software is used on those operating systems. Icons in general, were developed on a 250x250 pixel, transparent background. These dimensions were based off the blocks being 100x100 pixels when drawn within the interface, and the icon being drawn within these blocks having a space of 50x50 pixels to occupy. Having a 250x250 pixel raw icon image allows it to be scaled down to a 50x50 image through PyQt5's scaling tool, which retains the quality of the image better, resulting in it being less pixelated when zoomed in. The items (text, dividing lines, function lines, etc.) within the icons were positioned with the help of a grid (named "layout_grid.png"), located within the Icons folder in which all the icons are stored. There wasn't a specific method for positioning the items within these icons, but depending on what the icon was (text, shape, axis with function) the lines of the grid were used to symmetrically place items and position them to allow for satisfactory visibility. These icons were developed in monochrome mode (black and white). The following values were used for lines and text: * linewidth: * 6 for axis lines and outline of gain, * 8 for bolded thin lines (particularly as dividing lines), * 15 for outline of stop icon * 19 for function lines (clip, constant, piecewise, step, waveform) * text font: Calibri * text size: various amongst the text used, but will be one of the following: * 36, 48, 59, 72, 84, 108, 144 * text bolding: True for all text, apart from the stop icon ## Procedure for updating changes to existing icons, or adding new ones As Python creates some difficulties in accessing absolute or relative file paths, and making this consistent across all users, an alternative available from PyQt5 was used, which is a QReferenceFile. Similar to zipping files into a folder, this reference file packages image files into a Python importable file, which after importing, can then be accessed locally within the same directory. Hence all icons which were used, were placed into the Icons folder, located at "**_`bdsim/bdsim/bdedit/Icons`_**" folder (see Figure 4.1), and a reference file named " Icons.qrc " was made (following the structure below) in the bdedit package, located at "**_`bdsim/bdsim/bdedit`_**" folder (see Figure 4.1). In order to make the "**_`.qrc`_**" (resource) file importable and usable in the Python scripts, it must be converted to a "**_`.py`_**" (Python) file (in our case named "**_`Icons.py`_**"). The steps for this procedure will be outlined below. The following steps should be followed for updating an icon that already exists 1. The new icon (under the same name as the existing icon it's replacing) should be added as a "**_`.png`_**" to the "**_`bdsim/bdsim/bdedit/Icons`_**" folder, replacing the old version of the icon. 2. The "**_Icons.qrc_**" file located at "**_`bdsim/bdsim/bdedit`_**" should be converted to "**_`Icons.py`_**" with the following steps: 1. Via the terminal, navigate to the bdedit directory 2. Via the terminal, type "**_`pyrcc5 Icons.qrc -o Icons.py`_**" to convert and write the contents of the resource file (named "**_`Icons.qrc`_**") to a Python file (named "**_`Icons.py`_**"). The name of the resource file should match that of what was given to the resource file, and the name of the Python file will be what is called when importing into other Python files (as "**_`from bdsim.bdedit.Icons import *`_**"). DO NOT make any changes to this generated Python file, as this could result in unforeseen errors. 3. Assuming the icon that is being replaced, was previously set up and being used, re-running the program after updating the "**_`Icons.py`_**" file will update the changes made to this icon. The following steps should be followed for adding in a new icon, that doesn't exist anywhere in the code: 1. The new icon (with a unique name) should be added as a "**_`.png`_**" to the "**_`bdsim/bdsim/bdedit/Icons`_**" folder. 2. The structure of the "**_`Icons.qrc`_**" resource file located at "**_`bdsim/bdsim/bdedit`_**" should be edited to include the file path to this newly added icon. The following steps should be taken: 1. Open the resource file with any text viewer (should see a file similar to Figure 4.3) 2. Add the file path to the new icon as "**_`Icons/filename.png`_**" enclosed in the <file> and </file> tags, indicating that this icon is located within the Icons folder. Note the qresource prefix name; this will be used for picking out specific icons from this file. 3. Save the resource file and proceed to Step 3. 3. The "**_`Icons.qrc`_**" resource file located at "**_`bdsim/bdsim/bdedit`_**" should be converted to "**_`Icons.py`_**" with the following steps: 1. Via the terminal, navigate to the bdedit directory 2. Via the terminal, type "**_`pyrcc5 Icons.qrc -o Icons.py`_**" to convert and write the contents of the resource file (named "**_`Icons.qrc`_**") to a Python file (named "**_`Icons.py`_**"). The name of the resource file should match that of what was given to the resource file, and the name of the Python file will be what is called when importing into other Python files (as "**_`import Icons`_**"). DO NOT make any changes to this generated Python file, as this could result in unforeseen errors. 4. Next, you should open the file where you planned to use the icon (this should be one of the files in either the "**_`bdsim/bdsim/bdedit/Block_Classes`_**" folder or in the "**_`bdsim/bdsim/bdedit`_**" package). 1. If in any file within the Block_Classes folder, continue to Step 5. 2. If in any other within the bdedit package: 1. Import the "**_`Icons.py`_**" file from the bdedit package as: "**_`from bdsim.bdedit.Icons import *`_**". 5. Still in the same file as Step 4, continue to the code where you want the Icon file path to be defined and insert the following string: "**_`:/Icons_Reference/Icons/filename.png`_**". The "**_`:/`_**" notation is important for navigating the "**_`Icons.py`_**" file. Also, remember from the note in Step 2, the qreference name is used here as "**_`Icons_Reference`_**" to refer to the "**_`Icons.py`_**" file. The "**_`/Icons/filename.png`_**" points to the path defined in the "**_`Icons.qrc`_**" file. Replace "**_`filename`_**" with whatever name you saved the icon under. 6. This should complete the process for adding in a new icon.

QResource File Structure

# 5. Appendices ## APPENDIX A – High Level Class Architecture Diagram

High Level Class Architecture Diagram

## APPENDIX B – Stepped Wire Drawing Logic Drawing the step wire falls into three steps of logic. The first step: when a wire is being pulled from one socket to another, and has not yet been connected. In this step, the wire is simply drawn as a straight light from the starting socket to the mouse cursor, up until the point the wire is connected to another socket. This is when the wire drawing logic falls into the following two steps of logic. The second step: Wire routing logic between two blocks where the input and output sockets are on same sides

Wire Logic Step 2.0

The third step: Wire routing logic between two blocks where the input and output sockets are on opposite sides

Wire Logic Step 3.0

Wire Logic Step 3.1

## Embedded Links 1 - https://github.com/petercorke/bdsim 2 - File located at path: https://github.com/petercorke/bdsim/blob/bdedit/bdsim/bin/bdedit.py 3 - https://petercorke.github.io/bdsim/bdsim.blocks.html?highlight=Function#bdsim.blocks.functions.Function 4 - Free paint.net download link - https://www.getpaint.net/download.html#download ================================================ FILE: bdsim/bdedit/__init__.py ================================================ # print("bdedit package __init__ start") from .block import * from .block_wire import * from .block_socket import * from .block_graphics_block import * from .block_graphics_wire import * from .block_graphics_socket import * # from .block_importer import * from .block_param_window import * from .Icons import * from .interface import * from .interface_scene import * from .interface_graphics_scene import * from .interface_graphics_view import * from .interface_serialize import * from .bdedit import main # print("bdedit package __init__ end") ================================================ FILE: bdsim/bdedit/bdedit.py ================================================ #!/usr/bin/env python3 # Library imports import os import sys import ctypes import argparse from pathlib import Path from sys import platform from pathlib import Path from colored import fg, attr # PyQt5 imports from PyQt5.QtWidgets import * from PyQt5.QtCore import QTimer from PyQt5.QtGui import QIcon # BdEdit imports from bdsim.bdedit.interface_manager import InterfaceWindow # Executable code to launch the BdEdit application window def main(): print(fg("red")) print("bdedit is beta code and prone to random crashing, save your work often") print(attr(0)) # handle command line options, bdedit -h for details parser = argparse.ArgumentParser(description="Interactive edit for bdsim models") parser.add_argument( "file", type=str, nargs="?", help="Load this model into interactive session" ) parser.add_argument( "--print", "-p", nargs="?", action="store", const="", default=None, help="Save model to screenshot and exit, can optionally specify a filename, PDF extension is default", ) parser.add_argument( "--debug", "-d", action="store_const", const=True, default=False, help="Enable debugging", ) parser.add_argument( "--pdb", action="store_const", const=True, default=False, help="Enable pdb for spawned python subprocess", ) parser.add_argument( "--background", "-b", type=str, default="grey", choices=["white", "grey"], help="Set background color", ) parser.add_argument( "--fontsize", "-s", type=int, default="12", help="Set font size of block names" ) parser.add_argument( "--format", "-f", type=str, nargs="?", help="Specify screenshot extension type; PDF (default) or PNG", ) args, unparsed_args = parser.parse_known_args() # args holds all the command line info: # args.file file name if given, else None # args.debug True if -d option given # args.print True if -p option given, load the file, save screenshot, then exit # args.fontsize integer fontsize if given, sets default size of block names # args.format PDF if unspecified, PDF or PNG if specified # insert argv[0] into head of list of remaining args, and hand that to Qt unparsed_args.insert(0, sys.argv[0]) # A QApplication instance is made, which is the window that holds everything app = QApplication(unparsed_args) # The resolution of the user's screen is extracted (used for determining # the size of the application window) screen_resolution = app.desktop().screenGeometry() # Set the desktop toolbar icon for this application icon = Path(__file__).parent.parent / "bdedit" / "Icons" / "bdsim_logo.png" app.setWindowIcon(QIcon(str(icon))) myappid = "bdsim.bdsim.bin.bdedit.application" # arbitrary string for application try: if platform == "win32": ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) elif platform == "darwin": ctypes.cdll.kernel32.SetCurrentProcessExplicitAppUserModelID(myappid) except Exception as e: # Toolbar icon for application could not be set. pass # Finally the window is displayed by creating an instance of Interface, # which holds all the logic for how the application should appear and which # connects all the other Classes through the Interface. # window = Interface(screen_resolution, args.debug) window = InterfaceWindow(screen_resolution, args.debug) window.args = args # Check what command line arguments have been passed, if any if args.file or args.print or args.debug or args.fontsize or args.format: # Call bdedit functionality based on passed args window.centralWidget().scene.block_name_fontsize = args.fontsize if args.file is not None: # Attempt to load the .bd file if os.path.isfile(args.file): window.loadFromFilePath(args.file) else: raise ValueError(f"bdfile {args.file} not found") # fire up the GUI window.centralWidget().scene.grScene.updateBackgroundMode(args.background, True) if args.print is not None: # render a screenshot to file def screenshot(model_path, screenshot_name): # Set the background mode to white, no grid lines window.centralWidget().scene.grScene.updateBackgroundMode( "white", False ) window.centralWidget().scene.grScene.checkMode() # Hide and then unselect all connector blocks present in the model window.centralWidget().scene.hide_connector_blocks = True for block in window.centralWidget().scene.blocks: if block.block_type in ["Connector", "CONNECTOR"]: block.grBlock.setSelected(False) # Update the points where wires overlap within the scene to draw the wire separations if window.centralWidget().scene.wires: window.centralWidget().scene.wires[0].checkIntersections() window.centralWidget().save_image( model_path, screenshot_name ) # in interface.py sys.exit(0) # figure out the filename to save it as file = Path(args.print) if args.print == "": # no filename given on command line # use the model file name, drop the path, and set extension pdf if none given # wait till python 3.9 for the next line to work # path = Path(args.file).with_stem(filename.stem + "-screenshot").with_suffix('.pdf').name if args.format == "png": path = Path(args.file).with_suffix(".png") else: path = Path(args.file).with_suffix(".pdf") path = path.stem + path.suffix else: # filename was given on command line path = Path(args.print) if path.suffix == "": if args.format == "png": path = path.with_suffix(".png") else: path = path.with_suffix(".pdf") # After 100ms non-blocking delay, screenshot the model QTimer.singleShot(100, lambda: screenshot(args.file, str(path))) # run the GUI until it exits sys.exit(app.exec_()) if __name__ == "__main__": main() ================================================ FILE: bdsim/bdedit/block.py ================================================ # Library imports import os import json from collections import OrderedDict # BdEdit imports from bdsim.bdedit.block_socket import * from bdsim.bdedit.block_param_window import ParamWindow from bdsim.bdedit.block_graphics_block import GraphicsBlock # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Socket positioning variables (in relation to where they're drawn on the block) LEFT = 1 TOP = 2 RIGHT = 3 BOTTOM = 4 # Socket type classification variables INPUT = 1 OUTPUT = 2 # List of auto-imported blocks blocklist = [] # Variable for enabling/disabling debug comments DEBUG = False # ============================================================================= # # Defining the parent Block Class, which is inherited by all blocks # # ============================================================================= class Block(Serializable): """ The ``Block`` Class extends the ``Serializable`` Class from BdEdit, and defines how a block is represented, and has all the necessary methods for creating, manipulating and interacting with a block. This class includes information about the blocks': - name; - type; - appearance; - on-screen positioning; - parameters, and their values; - number of inputs and outputs. """ # ----------------------------------------------------------------------------- def __init__(self, scene, window, pos=(0, 0)): """ This method initializes an instance of the ``Block`` Class. It maps the following internal parameters of the block, initializing them to their defaults values. These are overwritten when an instance of the grandchild block is created. The parameters are defined as: - title: the name of the ``Block``. This defaults to the name of the name of the grandchild class, and is incremented if an instance with the same default name already exists. - type: the type of ``Block``. This defaults to the type of the grandchild class, and is determined through calling blockname(self.__class__) when a grandchild class is created. - icon: the icon for this ``Block``. This is a locally referenced string filepath, defined within the grandchild class. - inputs: a list of containing input ``Sockets`` relating to this ``Block``. The number of input sockets is restricted to 0 or n based on the inputsNum variable defined within the child class. - outputs: a list of containing output ``Sockets`` relating to this ``Block``. The number of output sockets is restricted to 0 or n based on the outputsNum variable defined within the child class. - parameters: a list of editable parameters relating to this ``Block``. This defaults to the list defined within the grandchild class, but follows the following structure of being a list of lists, where each 'lists' is a list defining the parameter as below: - parameters = ["name", type, value, [restrictions]] e.g. parameters = [["Gain", float, gain, []], ["Premul", bool, premul, []]] - name: is the name of the variable as a string - type: is the required type the variable should be (e.g. int, str, float) - value: is the default value the variable is set to should one not be provided - restrictions: is a list (can be list of lists) containing further restrictions applied to the parameter. These restrictions follow the following structure, of being a list with a string as the first list element, followed by a list of conditions being applied to the parameter as the second list element: - restriction = ["restriction name", [condition(s)]] - restriction name: can be only one of the following "keywords", "range", "type" or "signs". - condition(s): differ based on the restriction name used, and will be of the following format: - for keywords: a list of string words the parameter must exactly match to, e.g. ["keywords", ["sine", "square", "triangle"]] - for range: a list containing a min and max allowable value for this parameter, e.g. ["range", [0, 1000]] or ["range", [-math.inf, math.inf]] - for type: a list containing alternative types, with the last value repeating the initial type required for this parameter, e.g. ["type", [type(None), tuple]] (initial type = tuple) or ["type", [type(None), bool]] (initial type = bool) - for signs: a list containing each allowable character this parameter can match, e.g. ["signs", ["*", "/"]] or ["signs", ["+", "-"]] currently this is used for drawing signs along certain input sockets, so only these characters are supported, :param scene: a scene (or canvas) in which the Block is stored and shown (or painted into). Provided by the ``Interface``. :type scene: ``Scene``, required :param window: layout information of where all ``Widgets`` are located in the bdedit window. Provided by the ``Interface``. :type window: ``QGridLayout``, required :param title: set to grandchild class's default name "__class__.__name__ Block" when it is created :type title: str, optional :param pos: (x,y) coordinates of the block's positioning within the ``Scene``, defaults to (0,0) :type pos: tuple of 2-ints, optional """ super().__init__() self.scene = scene self.window = window self.position = pos # Set block's orientation to be facing towards the right by default. If flipped is True, this means block is facing left self.flipped = False try: self.setDefaultTitle(self.title) except AttributeError: # When trying to set title of connector block, it will throw an attribute error # This is fine, as connector block isn't supposed to have a title # print("block.py -> Error occured while setting default title") pass # Lists that will contain the input/output sockets of the Block self.inputs = [] self.outputs = [] # Variable for controlling whether or not a ParamWindow should be # displayed for this instance of a Block self._param_visible = False # Initially, parameterWindow is set to None, later replaced by the ParamWindow class, if block should have one self.parameterWindow = None # Minimum spacing distance between Sockets self.socket_spacing = 20 # print("creating block instance - after:") # [print(item) for item in self.__dict__.items()] # print("_______________________________________") # self._createBlock(self.inputsNum, self.outputsNum) # print("after block instance made:") # [print(item) for item in self.__dict__.items()] # print("_______________________________________") # Todo - update docstring and inline comments # ----------------------------------------------------------------------------- def _createBlock(self, inputs, outputs): """ This private method is inherited and called by the grandchild class, and should only be called once when the block is being created. It populates the block's internal parameters; calls for the block to be drawn; creates the input and output sockets of the block; adds it to be stored and drawn in the ``Scene``; and if the block has user-editable parameters, initializes the private _createParamWindow method to create a parameter window linked to editing this block instance's parameters. :param inputs: number of input ``Sockets`` to create for this ``Block`` :type inputs: int, required :param outputs: number of output ``Sockets`` to create for this ``Block`` :type outputs: int, required """ # * the graphics of the blocks are generated, # * the input and output sockets are created and linked to the block, # * the block information is stored within the Scene class, # * the blocks' graphical information is stored within the graphical section of the Scene class # * if the block has user-editable parameters, then a parameter window is generated for this block # if allowed_to_generate: self.grBlock = GraphicsBlock(self) self.makeInputSockets(inputs, LEFT) self.makeOutputSockets(outputs, RIGHT) self.scene.addBlock(self) self.scene.grScene.addItem(self.grBlock) self._createParamWindow() self.scene.has_been_modified = True # ----------------------------------------------------------------------------- def _createParamWindow(self): """ This private method takes no inputs, and should only be called once when generating a parameter window for it's associated block. It creates an instance of the ``ParamWindow`` class relating to this ``Block`` and references the 'self.window' variable stored within the ``Block`` class to make the parameter window part of the bdedit window. """ # Creates a parameter window variable associated to this Block instance, and sets its # visibility based on the private 'self._param_visible' variable # (True - allowed to display, False - cannot be displayed). self.parameterWindow = ParamWindow(self) self.parameterWindow.setVisible(self._param_visible) # ParamWindow instance is added to the application window self.window.addWidget(self.parameterWindow, 1, 10, 9, 1) # ----------------------------------------------------------------------------- def makeInputSockets(self, inputs, position, socketType=INPUT): """ This method is called to create a number of input ``Sockets`` for this ``Block``. :param inputs: number of input sockets to create :type inputs: int, required :param position: an enum representing the position of where to place the ``Socket`` on the ``Block`` currently only supports LEFT (1), or RIGHT (3). :type position: enumerate, required :param socketType: an enum representing the type of ``Socket`` to create. This is used for the graphics of the socket, but was also intended to be used to reduce two methods for creating input and output sockets, to a single method. :type socketType: enumerate, optional, defaults to INPUT(1) """ # Input sockets are created, starting from index 0 to the number of 'inputs' # For each index: # * an instance of a Socket Class is created, which: # ** relates this Socket instance to this Block instance # ** sets the 'index' of this Socket as the current index (counter) # ** sets the 'position' of this Socket to the given position (LEFT or RIGHT) # ** sets the 'socketType' of this Socket to the given socketType (INPUT by default) # * the instance is appended to this Block's list of inputs counter = 0 while counter < inputs: try: if self.input_names: socket = Socket( node=self, index=counter, position=position, socket_type=socketType, socket_label=self.input_names[counter], ) else: socket = Socket( node=self, index=counter, position=position, socket_type=socketType, ) except (AttributeError, IndexError): socket = Socket( node=self, index=counter, position=position, socket_type=socketType ) counter += 1 self.inputs.append(socket) # Some Blocks (PROD and SUM) have additional logic for drawing signs alongside # the input sockets. The method below is checks and applies that logic if required. self.updateSocketSigns() # ----------------------------------------------------------------------------- def makeOutputSockets(self, outputs, position, socketType=OUTPUT): """ This method is called to create a number of outputs ``Sockets`` for this ``Block``. :param outputs: number of output sockets to create :type outputs: int, required :param position: an enum representing the position of where to place the ``Socket`` on the ``Block`` currently only supports LEFT (1), or RIGHT (3). :type position: enumerate, required :param socketType: an enum representing the type of ``Socket`` to create. This is used for the graphics of the socket, but was also intended to be used to reduce two methods for creating input and output sockets, to a single method. :type socketType: enumerate, optional, defaults to OUTPUT(2) """ # Output sockets are created, starting from index 0 to the number of 'outputs' # For each index: # * an instance of a Socket Class is created, which: # ** relates this Socket instance to this Block instance # ** sets the 'index' of this Socket as the current index (counter) # ** sets the 'position' of this Socket to the given position (LEFT or RIGHT) # ** sets the 'socketType' of this Socket to the given socketType (OUTPUT by default) # * the instance is appended to this Block's list of outputs counter = 0 while counter < outputs: try: if self.output_names: socket = Socket( node=self, index=counter, position=position, socket_type=socketType, socket_label=self.output_names[counter], ) else: socket = Socket( node=self, index=counter, position=position, socket_type=socketType, ) except (AttributeError, IndexError): socket = Socket( node=self, index=counter, position=position, socket_type=socketType ) counter += 1 self.outputs.append(socket) # ----------------------------------------------------------------------------- def toggleParamWindow(self): """ This method toggles the visibility of the ``ParamWindow`` of this ``Block`` """ # Sets the visibility of the parameter window to opposite of what the # variable 'self._param_visible' is set to, and flips the boolean state of # that variable (True -> False, or False -> True) if self.parameterWindow: self.parameterWindow.setVisible(not self._param_visible) self._param_visible = not self._param_visible # ----------------------------------------------------------------------------- def closeParamWindow(self): """ This method closes the ``ParamWindow`` of this ``Block`` """ # Sets the visibility of the parameter window to False # and sets 'self._param_visible' False self.parameterWindow.setVisible(False) self._param_visible = False # ----------------------------------------------------------------------------- def updateSocketSigns(self): """ As some Blocks - namely the ``PROD`` and ``SUM`` Blocks - have additional logic for drawing signs (+,-,*,/) alongside the input sockets, this method updates these signs of the socket, if the block type is a ``PROD`` or ``SUM`` Block """ # Checks if the block type is a Prod or Sum block if ( self.block_type == "PROD" or self.block_type == "SUM" or self.block_type == "Prod" or self.block_type == "Sum" ): # Iterates through user-editable parameters stored within the block and checks # if one by the name of 'Operations' or 'Signs' exists (these block types should # have them). These parameters hold characters (+,-,*,/) representing what signs # should be displayed and the order they should be displayed in (left to right # in the parameter, representing top to bottom when displayed on the block). # Parameter is represented as a list of 4 items: # parameter = [name, type, value, special_conditions] for parameter in self.parameters: # If parameter name is equal to: if parameter[0] == "ops" or parameter[0] == "signs": index = 0 # print("\nblock: updatingSocketSigns()") # [print(item) for item in self.__dict__.items()] # print("_______________________________________\n") # Sets the socket_sign of Socket within the block, equal to the respective # character (sign) stored within either the 'Operations' or 'Signs' parameter. # Note, since this method is only ever called after the number of input sockets # has been created, or after this number has been updated (when the user edits # the number of signs to display), the number of input sockets will always # exactly match the number of signs stored within the parameter. for sign in parameter[2]: self.inputs[index].socket_sign = sign index += 1 # ----------------------------------------------------------------------------- def setFocusOfBlocks(self): """ This method sends all ``Block`` instances within the ``Scene`` to back and then sends the currently selected ``Block`` instance to front. """ # Iterates through each Block within block list stored in the Scene Class # and sets the graphical component of each block to a zValue of 0. for block in self.scene.blocks: block.grBlock.setZValue(0.0) # Then sets the graphical component of the currently selected block to a # zValue of 1, which makes it display above all other blocks on screen. self.grBlock.setZValue(1.0) # ----------------------------------------------------------------------------- @property def pos(self): """ This method is called to access the positional coordinates of this ``Block`` in terms of where it is displayed within the ``Scene`` :return: the (x,y) coordinates of this ``Block`` :rtype: tuple (int, int) """ return self.grBlock.pos() # ----------------------------------------------------------------------------- def setPos(self, x, y): """ This method is called to set the positional coordinates of this ``Block`` in terms of where it is displayed within the ``Scene`` :param x: the x coordinate of this ``Block`` :type x: int, required :param y: the y coordinate of this ``Block`` :type y: int, required """ self.grBlock.setPos(x, y) # ----------------------------------------------------------------------------- def setTitle(self, name=None): """ This method is called to determine if a this ``Block`` can be named with the provided name. It applies logic to check if the given name of type `str`, and if no other ``Block`` instance already has the given name. If either of these conditions are not met, the user will be notified to provide a different name. :param name: given name to be set for this ``Block``, defaults to None if not provided :type name: str, optional :return: - nothing (if name successfully set); - [duplicate error message, given name] (if duplicate found); - invalid type error message (if name is not of type `str`). :rtype: - nothing (if name successfully set); - list of [str, str] (if duplicate found); - str (if name is not of type `str`). """ # This method should only do something, if a name is given for the block # Hence, only do something if the name is not None if name is not None: # The name only needs to be checked to be updated if it's different # the this blocks' current name (title) if name != self.title: # If the given name is of type str if isinstance(name, str): # Check if the given name already exists for any other block duplicates = self.scene.checkForDuplicates(name) # If the given name is not found to be a duplicate if not duplicates: # Set this blocks' title to the given name self.title = name return # Else the given name is a duplicate else: # So return a list containing a duplicate error message and the given name return ["@DuplicateName@", name] # Else, the type of the given name is invalid else: return "@InvalidType@" # ----------------------------------------------------------------------------- def setDefaultTitle(self, name, increment=None): """ This method is called to give a block a generic name, and if that name already exists, to increment it by 1. :param name: the generic name to be given to this ``Block`` :type name: str, required :param increment: the number to display next to the generic name, to make it unique. Defaults to None if this is the first instance of this block type. :type increment: int, optional """ # Creates a temporary copy of the given name block_name = name # If the given name is of type str if isinstance(name, str): # If this isn't the first instance of this block type, increment will # be a number greater than 0, and this if statement will be run. if increment is not None: # The temporary copy of the given name has the increment added to it # to later check if that will make it unique block_name += " " + str(increment) # The increment is increased by one in case the previous line doesn't # make the name unique increment += 1 # The copy of the given name is checked against being a duplicate duplicates = self.scene.checkForDuplicates(block_name) # If it is not a duplicate, the name of the current block is set to # the copy of the given name (this will be with an increment, or the # original given name if this is the first instance of this block type). if not duplicates: self.title = block_name # Else, it is a duplicate, and this method will call itself to increment # the given name and check against that name being a duplicate, until # a non-duplicate generic name is found. else: # If this is a second instance of this block type (hence the increment # would be None), this method calls itself with the increment set to 1 if increment is None: self.setDefaultTitle(name, 1) # Else, this is more than a second instance of this block type, and # the increment would of already been set, and internally incremented. else: self.setDefaultTitle(name, increment) # ----------------------------------------------------------------------------- def getSocketPosition(self, index, position): """ This method is called to determine the coordinates of where a given ``Socket`` should be positioned on the sides of the this ``Block``. The returned coordinates are in reference to the coordinates of this ``Block``. :param index: the index of this ``Socket`` in the list of sockets for this ``Block`` :type index: int, required :param position: the (LEFT(1) or RIGHT(3)) side of the block this socket should be displayed on. :type position: enumerate, required :return: the [x,y] coordinates at which to place this ``Socket`` instance. :rtype: list of int, int """ # If the position of the Socket is given to be on the LEFT of the block, # * x is returned as 0. # * y is returned as the index of this socket multiplied by the socket # spacing set within this block class, PLUS an offset, determined by the # height at which the block's title is placed below the block, and # the thickness of this block's outline, and how curved its corners are. if position == LEFT: x = 0 y = ( self.grBlock._padding + self.grBlock.edge_size + self.grBlock.title_height + index * self.socket_spacing ) # Else, the position of the Socket is given to be on the RIGHT of the block, # * x is returned as the width of block. # * y is returned as above. elif position == RIGHT: x = self.grBlock.width y = ( self.grBlock._padding + self.grBlock.edge_size + self.grBlock.title_height + index * self.socket_spacing ) return [x, y] # ----------------------------------------------------------------------------- def updateSocketPositions(self): """ This method updates flips the position (LEFT or RIGHT) of where the input and output sockets needs to be placed within this ``Block``. After the positions of all the Blocks' sockets are updated, the updateConnectedEdges method is called to update the locations for where the wires connect to. """ # Iterates through every input Socket this Block has for i in range(0, len(self.inputs)): # Flips the position of the input sockets (LEFT to RIGHT, or RIGHT to LEFT) if self.inputs[i].position == LEFT: self.inputs[i].position = RIGHT else: self.inputs[i].position = LEFT # Grabs the coordinates for where this Socket should be drawn [x, y] = self.getSocketPosition(i, self.inputs[i].position) # And sets the position of the current socket to these coordinates self.inputs[i].grSocket.setPos(*[float(x), float(y)]) # Iterates through every output Socket this Block has for i in range(0, len(self.outputs)): # Flips the position of the output sockets (RIGHT to LEFT, or LEFT to RIGHT) if self.outputs[i].position == RIGHT: self.outputs[i].position = LEFT else: self.outputs[i].position = RIGHT # Grabs the coordinates for where this Socket should be drawn [x, y] = self.getSocketPosition(i, self.outputs[i].position) # And sets the position of the current socket to these coordinates self.outputs[i].grSocket.setPos(*[float(x), float(y)]) self.updateConnectedEdges() # ----------------------------------------------------------------------------- def updateConnectedEdges(self): """ This method calls for any and all ``Wire`` instances that are connected to a ``Socket`` instance, to update where it is drawn TO and FROM. """ # For each socket that exists in this block for socket in self.inputs + self.outputs: # Update the wire(s) connected to this socket for wire in socket.wires: wire.updatePositions() # ----------------------------------------------------------------------------- def updateWireRoutingLogic(self): """ Wire routing logic is automatically determined by bdedit when moving blocks, however users can adjust certain segments of these wires. When the user interacts with these segments, the wire routing logic follows the custom routing path. When blocks are moved again after wires segments are adjusted by the user, the wiring logic will revert to following the automatic routing logic. This method updates which wires should be drawn following the automatic routing logic. """ for socket in self.inputs + self.outputs: for wire in socket.wires: if wire: # If both blocks connected by these wires are selected, don't do # anything, as both are being moved in respects to each other, # so no need to update wiring logic. start_block = wire.start_socket.node end_block = wire.end_socket.node if ( start_block.grBlock.isSelected() and end_block.grBlock.isSelected() ): pass # wire.grWire.customlogicOverride = True # start_block.updateSocketPositions() # end_block.updateSocketPositions() # Otherwise if only one block is selected, update the wiring logic # to be displayed based on the automatic wire routing logic. else: wire.grWire.customlogicOverride = False # ----------------------------------------------------------------------------- def removeSockets(self, type): """ This method removes all sockets of given type, associated with this ``Block``. :param type: the type of ``Socket`` to remove ("Input" for input type, "Output" for output type) :type type: str, required """ # Depending on the given type, either the inputs or outputs list - stored within # the block - will be cleared. if type == "Input": self.inputs.clear() elif type == "Output": self.outputs.clear() # ----------------------------------------------------------------------------- def remove(self): """ This method is called to remove: - the selected ``Block`` instance; - any sockets associated with this Block; - any wires that were connected to this Blocks' sockets; and - the parameter window associated with this Block (if one existed). """ # For each socket associated with this block, remove the connected wires if DEBUG: print("> Removing Block", self) if DEBUG: print(" - removing all wires from sockets") for socket in self.inputs + self.outputs: for wire in socket.wires.copy(): # for wire in socket.wires: if DEBUG: print(" - removing from socket:", socket, "wire:", wire) wire.remove() # Remove the graphical representation of this block from the scene # This will also remove the associated graphical representation of the # blocks' sockets. if DEBUG: print(" - removing grBlock") self.scene.grScene.removeItem(self.grBlock) self.grBlock = None # Remove the blocks' parameter window if one existed if DEBUG: print(" - removing parameterWindow") if self.parameterWindow: self.window.removeWidget(self.parameterWindow) self.parameterWindow = None # Finally, call the removeBlock method from within the Scene, which # removes this block from the list of blocks stored in the Scene. if DEBUG: print(" - removing block from the scene") self.scene.removeBlock(self) if DEBUG: print(" - everything was done.") # ----------------------------------------------------------------------------- def serialize(self): """ This method is called to create an ordered dictionary of all of this Blocks' parameters - necessary for the reconstruction of this Block - as key-value pairs. This dictionary is later used for writing into a JSON file. :return: an ``OrderedDict`` of [keys, values] pairs of all essential ``Block`` parameters. :rtype: ``OrderedDict`` ([keys, values]*) """ # The sockets associated with this block, have their own parameters that are # required for their reconstruction, so the serialize method within the # Socket class is called to package this information for each socket, also into # an OrderedDict. These ordered dictionaries are then stored in a temporary # inputs/outputs parameter and are returned as part of the OrderedDict of this Block. # Similarly, the user-editable parameters associated with this Block must be # remembered for the reconstruction of this block. However as some of them # need to be stored as tuples and JSON does not support this, hence the # TupleEncoder is used. Additionally, to reconstruct the Block's user-editable # parameters, only the name and value of these parameters are needed, as their # type and special_conditions should never change from their definition within # their Class. (As a reminder, parameter = [name, type, value, special_conditions]) if self.block_type in ["Connector", "CONNECTOR"]: inputs, outputs, parameters = [], [], [] for socket in self.inputs: inputs.append(socket.serialize()) for socket in self.outputs: outputs.append(socket.serialize()) return OrderedDict( [ ("id", self.id), ("block_type", self.block_type), ("pos_x", self.grBlock.scenePos().x()), ("pos_y", self.grBlock.scenePos().y()), ("inputs", inputs), ("outputs", outputs), ] ) else: inputs, outputs, parameters = [], [], [] for socket in self.inputs: inputs.append(socket.serialize()) for socket in self.outputs: outputs.append(socket.serialize()) for parameter in self.parameters: parameters.append([parameter[0], parameter[2]]) return OrderedDict( [ ("id", self.id), ("block_type", self.block_type), ("title", self.title), ("pos_x", self.grBlock.scenePos().x()), ("pos_y", self.grBlock.scenePos().y()), ("width", self.width), ("height", self.height), ("flipped", self.flipped), ("inputsNum", self.inputsNum), ("outputsNum", self.outputsNum), ("inputs", inputs), ("outputs", outputs), ("parameters", parameters), ] ) # ----------------------------------------------------------------------------- def deserialize(self, data, hashmap={}): """ This method is called to reconstruct a ``Block`` when loading a saved JSON file containing all relevant information to recreate the ``Scene`` with all its items. :param data: a Dictionary of essential information for reconstructing a ``Block`` :type data: OrderedDict, required :param hashmap: a Dictionary for directly mapping the essential block parameters to this instance of ``Block``, without having to individually map each parameter :type hashmap: Dict, required :return: True when completed successfully :rtype: Boolean """ # The id of this Block is set to whatever was stored as its id in the JSON file. self.id = data["id"] try: # The remaining parameters associated to this Block are mapped to itself # hashmap[data['id']] = self if self.block_type not in ["Connector", "CONNECTOR"]: self.title = data["title"] self.inputsNum = data["inputsNum"] self.outputsNum = data["outputsNum"] self.width = data["width"] self.height = data["height"] # If a model contains data on whether a block should be flipped, assign variable to that value # If error occurs, model doesn't contain this variable, so ignore try: if data["flipped"]: self.flipped = data["flipped"] except KeyError: pass # The position of the Block within the Scene, are set accordingly. self.setPos(data["pos_x"], data["pos_y"]) # When block is drawn, by default it is created with its allocated number of input/output sockets. # When deserializing a block, we want the sockets to be in the locations where they were saved. # Hence, we must delete the default created input/output sockets to override them with the saved sockets. if self.inputs: # RemoveSockets is a method accessible only through a socket, while self.inputs is a list of sockets # 'removeSockets' removes all sockets associated with a block. self.inputs[0].removeSockets("Input") if self.outputs: # Same usage of 'removeSocket' as above self.outputs[0].removeSockets("Output") # The input and output lists for this Block are cleared self.inputs = [] self.outputs = [] # The saved user-editable parameters associated with the Block, are written over the default ones # this instance of the block was created with, after reconstruction. # Iterator for parameters if self.block_type not in ["Connector", "CONNECTOR"]: i = 0 for paramName, paramVal in data["parameters"]: # If debug mode is enabled, this code will print to console to validate that the # parameters are being overwritten into the same location they were previously stored in. if DEBUG: print("----------------------") if DEBUG: print("Cautionary check") if DEBUG: print( "current value:", [ self.parameters[i][0], self.parameters[i][1], self.parameters[i][2], ], ) if DEBUG: print( "setting to value:", [paramName, self.parameters[i][1], paramVal], ) self.parameters[i][0] = paramName self.parameters[i][2] = paramVal # If there are subsystem, outport or inport blocks with labels for their sockets, extract that information into self.input_names and self.output_names as needed if self.block_type in ["SUBSYSTEM", "OUTPORT", "INPORT"]: if paramName == "inport labels": if paramVal: self.input_names = [str(j) for j in paramVal] if paramName == "outport labels": if paramVal: self.output_names = [str(j) for j in paramVal] i += 1 # And the saved (input and output) sockets are written into these lists respectively, # deserializing the socket-relevant information while doing so. for i, socket_data in enumerate(data["inputs"]): try: if self.input_names: new_socket = Socket( node=self, index=socket_data["index"], position=socket_data["position"], socket_type=socket_data["socket_type"], socket_label=self.input_names[i], ) else: new_socket = Socket( node=self, index=socket_data["index"], position=socket_data["position"], socket_type=socket_data["socket_type"], ) except (AttributeError, IndexError): new_socket = Socket( node=self, index=socket_data["index"], position=socket_data["position"], socket_type=socket_data["socket_type"], ) new_socket.deserialize(socket_data, hashmap) self.inputs.append(new_socket) self.updateSocketSigns() for i, socket_data in enumerate(data["outputs"]): try: if self.output_names: new_socket = Socket( node=self, index=socket_data["index"], position=socket_data["position"], socket_type=socket_data["socket_type"], socket_label=self.output_names[i], ) else: new_socket = Socket( node=self, index=socket_data["index"], position=socket_data["position"], socket_type=socket_data["socket_type"], ) except (AttributeError, IndexError): new_socket = Socket( node=self, index=socket_data["index"], position=socket_data["position"], socket_type=socket_data["socket_type"], ) new_socket.deserialize(socket_data, hashmap) self.outputs.append(new_socket) if self.block_type not in ["Connector", "CONNECTOR"]: if self.parameters: self._createParamWindow() # print("block type, name: ", [self.block_type, self.title]) # print("input sockets:") # for socket in self.inputs: # if socket: # print("socket, id:", [socket, id(socket)]) # print() # print("output sockets:") # for socket in self.outputs: # if socket: # print("socket, id:", [socket, id(socket)]) # print("----------------------") return True except (ValueError, NameError, IndexError): print( f"error deserializing block [{self.block_type}::{self.title}] - maybe JSON file has old function parameters" ) # ----------------------------------------------------------------------------- def block(cls): """ This method is called whenever a grandchild class of the ``Block`` is called, adding it to a list of usable blocks. This code has been adapted from: https://github.com/petercorke/bdsim/blob/bdedit/bdsim/components.py :param cls: the class of the ``Block`` Class's grandchild block Class. :type cls: Class, required """ # If the given Block is a subclass of the Block Class, it is added to the # blocklist global variable if issubclass(cls, Block): blocklist.append(cls) # Otherwise an error message is printed to console else: print("Error: @block used on a non Block subclass") # ----------------------------------------------------------------------------- def blockname(cls): """ This method strips any underscores from a grandchild Class of the ``Block`` Class, and capitalizes the Class.__name__ to be used as the the Class's name. This code has been adapted from: https://github.com/petercorke/bdsim/blob/bdedit/bdsim/bdsim.py :param cls: the class of the ``Block`` Class's grandchild block Class. :type cls: Class, required :return: a reformatted string of the block's class name :rtype: str """ return cls.__name__.strip("_").upper() ================================================ FILE: bdsim/bdedit/block_connector_block.py ================================================ # BdEdit imports from bdsim.bdedit.block import * from bdsim.bdedit.block_socket import * from bdsim.bdedit.block_graphics_block import * # ============================================================================= # # Defining the Connector Class, which allows for the redirecting of Wires # to create more neater Block Diagrams. # # ============================================================================= class Connector(Block): """ The ``Connector`` Class is a subclass of ``Block``, and referred to as a child class of ``Block``. It inherits all the methods and variables of its parent class to behave as a Block. It allows for wires to be more neatly redirected, acting as a node through which the wires can be moved around more freely within the work area. The idea of this Connector block was for it to be a single socket which allows a wire to be redirected through it, however currently it works by mimicking a Block that only has 1 input and 1 output socket. The same socket logic that applies to a Block, also applies to the Connector Block. That being: - an input: can only have 1 Wire connecting into it - an output: can have n Wires connecting into it """ # ----------------------------------------------------------------------------- def __init__(self, scene, window, title="Unnamed Connector Block"): """ This method initializes an instance of the ``Connector`` Block Class. :param scene: inherited through ``Block`` :type scene: ``Scene``, required :param window: inherited through ``Block`` :type window: ``QGridLayout``, required :param title: defaults to "Unnamed Connector Block" :type title: str, optional :param pos: inherited through ``Block`` :type pos: tuple of 2-ints, optional """ super().__init__(scene, window) # Same variables inherited from the Block class self.scene = scene self.window = window # No block title as the Connector Blocks have no name, as # this type of block is more of a tool than a Block # self.position = pos # The Connector Block doesn't have its own subclass like the other Block types, # hence some of the variables are defined in this class level, these being: # * the block type # * the block dimensions (width, height) # * the grBlock (graphical representation of how this block looks) # * the number of spawned input and output sockets (1 of each) # * the paramWindow is disabled (this block doesn't have a paramWindow, as it is a tool) self.block_type = blockname(self.__class__) self.width = 13 self.height = 12 self.title = "" self.parameters = [] self.block_url = "" self.icon = "" self.flipped = False self.flipped_icon = "" self.inputsNum = 1 self.outputsNum = 1 self.grBlock = GraphicsConnectorBlock(self) self.makeInputSockets(self.inputsNum, LEFT, socketType=INPUT) self.makeOutputSockets(self.outputsNum, RIGHT, socketType=OUTPUT) self.parameterWindow = None # The Connector block is automatically stored into the Scene, # and visually added into the GraphicsScene self.scene.addBlock(self) self.scene.grScene.addItem(self.grBlock) self.scene.has_been_modified = True ================================================ FILE: bdsim/bdedit/block_graphics_block.py ================================================ # Library imports import sys import traceback # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # BdEdit imports from bdsim.bdedit.Icons import * # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Socket positioning variables - used for determining what side of the block the # socket should be drawn LEFT = 1 RIGHT = 3 # ============================================================================= # # Defining the GraphicsBlock Class, which is inherited by all Blocks and # controls the graphical appearance of each Block. # # ============================================================================= class GraphicsBlock(QGraphicsItem): """ The ``GraphicsBlock`` Class extends the ``QGraphicsItem`` Class from PyQt5. This class is responsible for graphically drawing Blocks within the GraphicsScene. Using the provided Block dimensions, it specifies the Blocks' shape and colour. """ # ----------------------------------------------------------------------------- def __init__(self, block, parent=None): """ This method initializes an instance of the ``GraphicsBlock`` Class. It inherits the dimensions of its Block, and defines its shape and colour. :param block: the Block this GraphicsBlock instance relates to :type block: Block, required :param parent: the parent widget this class instance belongs to (None) :type parent: NoneType, optional, defaults to None """ super().__init__(parent) # The block properties are inherited from the provided block self.block = block self.icon = self.block.icon self.width = self.block.width self.height = self.block.height # The color mode of the block is also stored (Light or Dark mode) self.mode = self.block.scene.grScene.mode # Internal variable which dictate whether a title needs to be drawn # The first time the Block is drawn, this is True, then it is set to # False, and only changed to True when the title is called to update self._draw_title = True # These dimensions are not updated self._default_width = self.block.width self._default_height = self.block.height # Pen thickness and block-related spacings are defined self.edge_size = 10.0 # How rounded the rectangle corners are self.title_height = ( 25.0 # How many pixels underneath the block the title is displayed at ) self._padding = ( 5.0 # Minimum distance inside the block that things should be displayed at ) self._line_thickness = 3.0 # Thickness of the block outline by default self._selected_line_thickness = ( 5.0 # Thickness of the block outline on selection ) # Colours for pens are defined, and the text font is set self._default_title_color = ( Qt.black ) # Title colour (set to Light mode by default) # self._pen_selected = QPen(QColor("#FFFFA637"), self._selected_line_thickness) self._pen_selected = QPen( QColorConstants.Svg.orange, self._selected_line_thickness ) # Orange self._title_font = QFont("Arial", self.block.scene.block_name_fontsize) # Internal variable for catching fatal errors, and allowing user to save work before crashing self.FATAL_ERROR = False # Methods called to: # * draw the title for the block # * check current colour mode the block should display in (Light/Dark) # * further initialize necessary block settings self.initTitle() self.checkMode() self.initUI() # Variable for storing whether block was moved or not self.wasMoved = False self.lastPos = self.pos() # ----------------------------------------------------------------------------- def initUI(self): """ This method sets flags to allow for this Block to be movable and selectable. """ self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable) self.setAcceptHoverEvents(True) # ----------------------------------------------------------------------------- def initTitle(self): """ This method initializes a QGraphicsTextItem which will graphically represent the title (name) of this Block. """ self.title_item = QGraphicsTextItem(self) self.title_item.setDefaultTextColor(self._default_title_color) self.title_item.setFont(self._title_font) # ----------------------------------------------------------------------------- def titleLength(self): """ This method calculates and returns the length of this Blocks' title in pixels. :return: the pixel length of this Blocks' title :rtype: int """ # Using the font of the text and the block's title, determine the length of the # title in terms of pixels title_pixel_len = QFontMetrics(self._title_font).width(self.block.title) # As the block width is an even number (100 pixels), to center properly, the # width of the title must also be even # If title width is odd, add 1 pixel to it to make it even if title_pixel_len % 2 != 0: title_pixel_len += 1 return title_pixel_len # ----------------------------------------------------------------------------- def getTitle(self): """ This method returns the current title of this Block. :return: Block title :rtype: str """ return self.block.title # ----------------------------------------------------------------------------- def setTitle(self): """ This method updates this Blocks' graphical title to the stored title of the Block. """ # Once the title has been set, this method will handle redrawing the title # Hence the title doesn't need to be redrawn after self._draw_title = False # Graphical title is set to the block's title is set self.title_item.setPlainText(self.block.title) # Title length is found (using self.titleLength()), and centered under the block self.title_item.setPos( (self.width - self._padding - self.titleLength()) / 2, self.height + self._padding, ) # The GraphicsBlock instance is called to be updated self.update() # ----------------------------------------------------------------------------- def checkMode(self): """ This method checks the mode of the GraphicsScene's background (Light, Dark) and updates the colour mode of the pens and brushes used to paint this Block. """ # If dark mode is selected, draw blocks tailored to dark mode # if self.mode == "Dark": # self._title_color = Qt.white # self._pen_default = QPen(Qt.white, self._line_thickness) # self._brush_background = QBrush(Qt.white) # # Else light or off mode is selected (No off mode for blocks), draw blocks tailored to light mode # else: self._title_color = Qt.black # self._pen_default = QPen(QColor("#7F000000"), self._line_thickness) self._pen_default = QPen(QColorConstants.Svg.dimgrey, self._line_thickness) self._brush_background = QBrush(QColor("#FFE1E0E8")) self.title_item.setDefaultTextColor(self._title_color) # Todo - update code # ----------------------------------------------------------------------------- def updateMode(self, value): """ This method updates the mode of the Block to the provided value (should only ever be "Light", "Dark" or "Off"). :param value: current mode of the GraphicsScene's background ("Light", "Dark", "Off") :type value: str, required """ self.mode = value self.checkMode() self.update() # ----------------------------------------------------------------------------- def checkBlockHeight(self): """ This method checks if the current height of the Block is enough to fit all the Sockets that are to be drawn, while following the set socket spacing. It also handles the resizing of the Block (if there isn't enough space for all the sockets), ensuring the sockets are evenly spaced while following the set socket spacing. """ # The offset distance from the top of the Block to the first Socket. # The same offset is used for from the bottom of the Block to the last Socket. socket_spacer = self._padding + self.edge_size + self.title_height # This code grabs the coordinates ([x,y]) of last input and output sockets if any exist if self.block.inputs: last_input = self.block.inputs[-1].getSocketPosition() else: last_input = [0, 0] if self.block.outputs: last_output = self.block.outputs[-1].getSocketPosition() else: last_output = [0, 0] # The max height of the Block could depend on either the input or output sockets # Hence the max height of both types are found (max height is the height at which # the last socket should be placed, in order for sockets to be evenly spaced) # Max height of input/output sockets - adds socket_spacer height to height of last input/output socket max_input_socket_height = last_input[1] + socket_spacer max_output_socket_height = last_output[1] + socket_spacer # Max block height (determined by which ever has more sockets - inputs or outputs) max_block_height = max(max_input_socket_height, max_output_socket_height) # If max_block_height is greater than the default block height, set current_block_height to max_block_height # Otherwise keep it at the default block height if max_block_height > self._default_height: self.block.height = max_block_height else: self.block.height = self._default_height # Update the internal height of the GraphicsBlock to the updated height of the Block self.height = self.block.height self.update() # ----------------------------------------------------------------------------- def hoverEnterEvent(self, event): """ When a ``GraphicsBlock`` is hovered over with the cursor, this method will display a tooltip with the type of block underneath the mouse. :param event: mouse hover detected over block :type event: QGraphicsSceneHoverEvent """ # QToolTip.setFont(QFont("Ubuntu", 10)) self.setToolTip("" + self.block.block_type + " block") # self.setToolTip("" + self.block.block_type + "") # ----------------------------------------------------------------------------- def boundingRect(self): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsBlock`` which returns the area within which the GraphicsBlock can be interacted with. When a mouse click event is detected within this area, this will trigger logic that relates to a Block (that being, selecting/deselecting, moving, deleting, flipping or opening a parameter window). :return: a rectangle within which the Block can be interacted with :rtype: QRectF """ return QRectF(0, 0, self.width, self.height) # ----------------------------------------------------------------------------- def paint(self, painter, style, widget=None): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsBlock``. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene. Before drawing, the dimensions of the Block are checked, to ensure they can hold all the necessary Sockets. Then the following are drawn in order: - the title of the block - the fill of the block (a rounded rectangle) - the outline of the block (a rounded rectangle) - the icon of the block (if one exists) :param painter: a painter (paint brush) that paints and fills the shape of this GraphicsBlock :type painter: QPainter, automatically recognized and overwritten from this method :param style: style of the painter (isn't used but must be defined) :type style: QStyleOptionGraphicsItem, automatically recognized from this method :param widget: the widget this class is being painted on (None) :type widget: NoneType, optional, defaults to None """ try: # Block dimensions are checked for to ensure there's enough space for all sockets self.checkBlockHeight() # Title will be redrawn, if needed if self._draw_title: self.setTitle() # Background (fill) of the block is drawn path_content = QPainterPath() path_content.setFillRule(Qt.WindingFill) path_content.addRoundedRect( 0, 0, self.width, self.height, self.edge_size, self.edge_size ) path_content.addRect(0, self.title_height, self.edge_size, self.edge_size) path_content.addRect( self.width - self.edge_size, self.title_height, self.edge_size, self.edge_size, ) painter.setPen(Qt.NoPen) painter.setBrush(self._brush_background) painter.drawPath(path_content.simplified()) # Outline of the block is drawn path_outline = QPainterPath() path_outline.addRoundedRect( 0, 0, self.width, self.height, self.edge_size, self.edge_size ) painter.setPen( self._pen_default if not self.isSelected() else self._pen_selected ) painter.setBrush(Qt.NoBrush) painter.drawPath(path_outline.simplified()) # Icon of the block is drawn overtop the blocks' background if QtCore.QFile.exists(self.icon): if self.block.flipped and QtCore.QFile.exists(self.block.flipped_icon): icon_image = QImage(self.block.flipped_icon) # icon_item = QPixmap(self.block.flipped_icon).scaledToWidth(50) if self.block.flipped_icon else QPixmap(self.block.flipped_icon) # Icons are scaled down to 50 pixels else: icon_image = QImage(self.icon) # icon_item = QPixmap(self.icon).scaledToWidth(50) if self.icon else QPixmap(self.icon) # target = QRect((self.width - icon_item.width()) / 2, (self.height - icon_item.height()) / 2, self.width, self.height) # source = QRect(0, 0, self.width, self.height) painter.drawImage( QRectF( (self.width - (icon_image.width()) / 5) / 2, (self.height - (icon_image.height()) / 5) / 2, 50, 50, ), icon_image, ) # painter.drawPixmap(target, icon_item, source) except Exception as e: if self.FATAL_ERROR == False: print( "--------------------------------------------------------------------------" ) print( "Caught fatal exception while trying to draw blocks. Please save your work." ) print( "--------------------------------------------------------------------------" ) traceback.print_exc(file=sys.stderr) self.FATAL_ERROR = True # ----------------------------------------------------------------------------- def mousePressEvent(self, event): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsBlock`` to detect, and assign logic to a right mouse press event. Currently a detected mouse press event on the GraphicsBlock will select or deselect it. - If selected, the GraphicsBlock will be sent to front and will appear on top of other blocks. - Additionally, if the right mouse button is pressed and a GraphicsBlock is selected, a parameter window will be toggled for this Block. :param event: a mouse press event (Left, Middle or Right) :type event: QMousePressEvent, automatically recognized by the inbuilt function """ # When the current GraphicsBlock is pressed on, it is sent to the front # of the work area (in the GraphicsScene) self.block.setFocusOfBlocks() # If the GraphicsBlock is currently selected when the right mouse button # is pressed, the parameter window will be toggled (On/Off) if event.button() == Qt.RightButton: self.block.toggleParamWindow() super().mousePressEvent(event) # Todo - add documentation # ----------------------------------------------------------------------------- def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) # If block has been moved, update the variable within the model, to then update the # title of the model, to indicate that there is unsaved progress if self.wasMoved: self.wasMoved = False self.block.scene.has_been_modified = True self.block.scene.history.storeHistory("Block moved") class GraphicsConnectorBlock(QGraphicsItem): """ The ``GraphicsConnectorBlock`` Class extends the ``QGraphicsItem`` Class from PyQt5. This class is responsible for graphically drawing Connector Blocks within the GraphicsScene. """ # ----------------------------------------------------------------------------- def __init__(self, block, parent=None): """ This method initializes an instance of the ``GraphicsConnectorBlock`` Class (otherwise known as the Graphics Class of the Connector Block). :param block: the Connector Block this GraphicsConnectorBlock instance relates to :type block: Connector Block :param parent: the parent widget this class instance belongs to (None) :type parent: NoneType, optional, defaults to None """ super().__init__(parent) self.block = block self.icon = self.block.icon self._draw_title = False self.width = self.block.width self.height = self.block.height # As the connector block consists of two sockets (1 input, 1 output) which # use the following commands in determining where they need to be placed, # these commands must be included, but are set to 0 as no shape is drawn for # the connector block, aside from these two sockets. self.edge_size = 0 self.title_height = 0 self._padding = 0 # Definition for the line thickness when the Connector block is selected # Internal padding is half this value # Corner rounding is by how many pixels the corners are rounded of the selected box that is drawn self._selected_line_thickness = 5.0 self._internal_padding = 2.5 self._corner_rounding = 10 # Color of the selected line is set # self._pen_selected = QPen(QColor("#FFFFA637"), self._selected_line_thickness) # Orange self._pen_selected = QPen( QColorConstants.Svg.orange, self._selected_line_thickness ) # Orange # Color of wire to be drawn between sockets, when connector block is hidden (to make solid line) self._color = QColor("#000000") # Internal variable for catching fatal errors, and allowing user to save work before crashing self.FATAL_ERROR = False # Further initialize necessary Connector Block settings self.initUI() self.wasMoved = False self.lastPos = self.pos() # ----------------------------------------------------------------------------- def initUI(self): """ This method sets flags to allow for this Connector Block to be movable and selectable. """ self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable) # When first created, the Connector block spawns highlighted self.setSelected(True) # ----------------------------------------------------------------------------- def boundingRect(self): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsConnectorBlock`` which returns the area within which the GraphicsConnectorBlock can be interacted with. When a mouse click event is detected within this area, this will trigger logic that relates to a Block (that being, selecting/deselecting, moving, deleting, flipping or opening a parameter window. Or if its Sockets are clicked on, this will trigger a wire to be created or ended). :return: a rectangle within which the Block can be interacted with :rtype: QRectF """ W = self.width P = self._internal_padding # return QRectF( # 1 - W - P, # 1 - W - P, # 3 * W + P, # 2 * W + P # ).normalized() # Alternative selection area that is larger, but will overlap wires directly # one grid block step above the connector block, when the connector block is # selected. return QRectF( 1 - 1.5 * W - P, 1 - 1.5 * W - P, 4 * W + P, 3 * W + P ).normalized() # ----------------------------------------------------------------------------- def paint(self, painter, style, widget=None): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsConnectorBlock`` (otherwise referred to as the Graphics Class of the Connector Block. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene. When the Connector Block is selected, this method will draw an orange outline around the Connector Block, within which it can be interacted with. :param painter:a painter (paint brush) that paints and fills the shape of this GraphicsConnectorBlock :type painter: QPainter, automatically recognized and overwritten from this method :param style: style of the painter (isn't used but must be defined) :type style: QStyleOptionGraphicsItem, automatically recognized from this method :param widget: the widget this class is being painted on (None) :type widget: NoneType, optional, defaults to None """ try: if self.isSelected(): # Draws orange outline around the Connector Block when it is selected path_outline = QPainterPath() # The size of the rectangle drawn, is dictated by the boundingRect (interactive area) path_outline.addRoundedRect( self.boundingRect(), self._corner_rounding, self._corner_rounding ) painter.setPen(self._pen_selected) painter.setBrush(Qt.NoBrush) painter.drawPath(path_outline.simplified()) # If the user has chosen to hide the connector blocks, redraw the sockets to be hidden if self.block.scene.hide_connector_blocks: # Grab the [x,y] coordinates of both the input and output sockets of the # connector block, and create a wire path to be drawn between them input_pos = self.block.inputs[0].getSocketPosition() output_pos = self.block.outputs[0].getSocketPosition() multi = 1 # If connector block is flipped, draw the wire path in the opposite direction if self.block.inputs[0].position == RIGHT: multi = -1 wire_path = QPainterPath( QPointF(input_pos[0] + (multi * 4.5), input_pos[1]) ) wire_path.lineTo(output_pos[0] - (multi * 4.5), output_pos[1]) # Set the paint brush width and colour wire_pen = QPen(self._color) wire_pen.setWidth(5) painter.setPen(wire_pen) painter.drawPath(wire_path) except Exception as e: if self.FATAL_ERROR == False: print( "------------------------------------------------------------------------------------" ) print( "Caught fatal exception while trying to draw connector blocks. Please save your work." ) print( "------------------------------------------------------------------------------------" ) traceback.print_exc(file=sys.stderr) self.FATAL_ERROR = True # ----------------------------------------------------------------------------- def mousePressEvent(self, event): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsConnectorBlock`` to detect, and assign logic to a right mouse press event. Currently a detected mouse press event on the GraphicsConnectorBlock will select or deselect it. Additionally if selected, the GraphicsBlock will be sent to front and will appear on top of other blocks. :param event: a mouse press event (Left, Middle or Right) :type event: QMousePressEvent, automatically recognized by the inbuilt function """ # When the current GraphicsSocketBlock is pressed on, it is sent to the front # of the work area (in the GraphicsScene) self.block.setFocusOfBlocks() super().mousePressEvent(event) # Todo - add documentation # ----------------------------------------------------------------------------- def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) # If block has been moved, update the variable within the model, to then update the # title of the model, to indicate that there is unsaved progress if self.wasMoved: self.wasMoved = False self.block.scene.has_been_modified = True self.block.scene.history.storeHistory("Connector Block moved") ================================================ FILE: bdsim/bdedit/block_graphics_socket.py ================================================ # Library imports import sys import traceback from PIL import ImageFont # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Socket positioning variables - used for determining what side of the block the # socket should be drawn LEFT = 1 RIGHT = 3 # Socket type classification variables INPUT = 1 OUTPUT = 2 # Input Socket sign classification variables PLUS = "+" MINUS = "-" MULTIPLY = "*" DIVIDE = "/" # ============================================================================= # # Defining the GraphicsSocket Class, which is inherited by all Sockets, and # controls the graphical appearance of each Socket. # # ============================================================================= class GraphicsSocket(QGraphicsItem): """ The ``GraphicsSocket`` Class extends the ``QGraphicsItem`` Class from PyQt5. This class is responsible for graphically drawing Sockets on Blocks. It specifies the shape, colour, style and dimensions of the Socket and also implements the logic for drawing signs alongside the input sockets of ``PROD`` and ``SUM``` Blocks. """ # ----------------------------------------------------------------------------- def __init__(self, socket): """ This method initializes an instance of the ``GraphicsSocket`` Class. It defines the shapes and sizes for a given input or output Socket. Depending on the socket_type, stored within the provided Socket, the shape and colour of the given Socket is decided. :param socket: the Socket this GraphicsSocket instance relates to :type socket: Socket """ self.socket = socket # The GraphicsBlock class is initialized for the Block Class this Socket relates to super().__init__(socket.node.grBlock) # Sizes for the various socket shapes are defined self.radius = 6.0 self.triangle_left = 6 self.triangle_right = 8 self.triangle_up = 6 self.square = 6 # Outlines of the shapes and signs are defined self.outline_width = 1.0 self.sign_width = 1.5 # Fill colors of the socket shapes are defined self._color_background_input = QColor("#3483eb") # Blue self._color_background_output = QColor("#f54242") # Red self._color_background_connector = QColor( "#42f587" ) # Lime Green (currently not used) self._color_outline = QColor("#000000") # Black # Painter pens are assigned a colour and outline thickness self._pen = QPen(self._color_outline) self._pen.setWidthF(self.outline_width) self._sign_pen = QPen(self._color_outline) self._sign_pen.setWidthF(self.sign_width) self._char_font = QFont("Arial", 14) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setBrush() # Internal variable for catching fatal errors, and allowing user to save work before crashing self.FATAL_ERROR = False def setBrush(self): # Depending on the socket type, the painter brush is set if self.socket.socket_type == INPUT: self._brush = QBrush(self._color_background_input) elif self.socket.socket_type == OUTPUT: self._brush = QBrush(self._color_background_output) # Todo - update docs, and inline comments # ----------------------------------------------------------------------------- def paint(self, painter, style, widget=None): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsSocket``. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene. It dictates how both input and output Sockets are painted on their relating Block. The position at which to draw the Socket is determined internally by the Socket class, so when the GraphicsSocket class paints the Socket, it is painted with respects to the point where the Socket should be. :param painter: a painter (paint brush) that paints and fills the shape of this GraphicsSocket :type painter: QPainter, automatically recognized and overwritten from this method :param style: style of the painter (isn't used but must be defined) :type style: QStyleOptionGraphicsItem, automatically recognized from this method :param widget: the widget this class is being painted on (None) :type widget: NoneType, optional, Defaults to None """ # Painter pen(outline) and brush(fill) are set painter.setPen(self._pen) try: painter.setBrush(self._brush) except AttributeError as e: # For some reason, brush was not set, set it again based on socket type self.setBrush() # If sockets don't need to be hidden, draw them normally, otherwise don't draw them at all if not self.shouldSocketsBeHidden(): # Multi is an internal variable that dictates the direction the socket shapes # should point (this comes into affect when the block sockets are flipped, and the # output triangle socket shape should point either right (default), or left (flipped)) multi = 1 offset = 0 # If the socket has a name (character) to display, find the characters' dimensions # Dimensions of the socket sign - contains [width, height] of character if self.socket.socket_sign: (char_width, char_height) = self.charDimensions() else: (char_width, char_height) = [0, 0] # If the socket is flipped, adjust the internal multi variable if self.socket.position == RIGHT: multi = -1 if self.socket.socket_sign: offset = char_width # This code paints a square for the input socket if self.socket.socket_type == INPUT: painter.drawRect( -self.square, -self.square, 2 * self.square, 2 * self.square ) # If the input socket has a sign (will be true for the PROD and SUM Blocks) # paint the relevant sign (+,-,*,/) next to the input socket try: if self.socket.socket_sign is not None: path = self.getSignPath(self.socket.socket_sign, multi) if path is None: painter.setPen(self._sign_pen) painter.setFont(self._char_font) painter.drawText( (10 + offset) * multi, char_height, self.socket.socket_sign, ) else: # The painter path for the respective sign depends on the sign relating to this socket painter.setPen(self._sign_pen) painter.drawPath(path) except Exception as e: if self.FATAL_ERROR == False: print( "---------------------------------------------------------------------------------------" ) print( "Caught fatal exception while trying to draw input socket labels. Please save your work." ) print( "---------------------------------------------------------------------------------------" ) traceback.print_exc(file=sys.stderr) self.FATAL_ERROR = True # This code paints a triangle for the output socket if self.socket.socket_type == OUTPUT: path = QPainterPath() path.moveTo(multi * self.triangle_left, multi * self.triangle_up) path.lineTo(-multi * self.triangle_right, 0) path.lineTo(multi * self.triangle_left, -multi * self.triangle_up) path.lineTo(multi * self.triangle_left, multi * self.triangle_up) painter.drawPath(path) try: if self.socket.socket_sign is not None: painter.setPen(self._sign_pen) painter.setFont(self._char_font) painter.drawText( (10 + offset) * multi, char_height, self.socket.socket_sign ) except Exception as e: if self.FATAL_ERROR == False: print( "----------------------------------------------------------------------------------------" ) print( "Caught fatal exception while trying to draw output socket labels. Please save your work." ) print( "----------------------------------------------------------------------------------------" ) traceback.print_exc(file=sys.stderr) self.FATAL_ERROR = True # ----------------------------------------------------------------------------- def paintPlus(self, multi): """ This method creates a painter path for drawing the '+' character next to an input socket. :param multi: internal variable (1 when default orientation, -1 when flipped) :type multi: int, required, 1 or -1 :return: the painter path to draw this sign :rtype: QPainterPath """ path = QPainterPath(QPointF(multi * 2.5 * self.square, 0)) path.lineTo(multi * 4.5 * self.square - multi * 2, 0) path.moveTo(multi * 3.5 * self.square - multi * 1, -self.square + 1) path.lineTo(multi * 3.5 * self.square - multi * 1, self.square - 1) return path # ----------------------------------------------------------------------------- def paintMinus(self, multi): """ This method creates a painter path for drawing the '-' character next to an input socket. :param multi: internal variable (1 when default orientation, -1 when flipped) :type multi: int, required, 1 or -1 :return: the painter path to draw this sign :rtype: QPainterPath """ path = QPainterPath(QPointF(multi * 2.5 * self.square, 0)) path.lineTo(multi * 4.5 * self.square - multi * 2, 0) return path # ----------------------------------------------------------------------------- def paintMultiply(self, multi): """ This method creates a painter path for drawing the 'x' character next to an input socket. :param multi: internal variable (1 when default orientation, -1 when flipped) :type multi: int, required, 1 or -1 :return: the painter path to draw this sign :rtype: QPainterPath """ path = QPainterPath(QPointF(multi * 2.5 * self.square, -self.square + 1)) path.lineTo(multi * 4.5 * self.square - multi * 2, self.square - 1) path.moveTo(multi * 4.5 * self.square - multi * 2, -self.square + 1) path.lineTo(multi * 2.5 * self.square, self.square - 1) return path # ----------------------------------------------------------------------------- def paintDivide(self, multi): """ This method creates a painter path for drawing a divide sign character next to an input socket. :param multi: internal variable (1 when default orientation, -1 when flipped) :type multi: int, required, 1 or -1 :return: the painter path to draw this sign :rtype: QPainterPath """ path = QPainterPath(QPointF(multi * 2.5 * self.square, 0)) path.lineTo(multi * 4.5 * self.square - 1, 0) path.moveTo(multi * 3.5 * self.square - 1, -self.square + 1) path.addEllipse(multi * 3.5 * self.square - 1, -self.square + 1, 1, 1) path.moveTo(multi * 3.5 * self.square - 1, self.square - 2) path.addEllipse(multi * 3.5 * self.square - 1, self.square - 2, 1, 1) return path # Todo - doc comments # ----------------------------------------------------------------------------- def charDimensions(self): # Find how many pixels - height wise - this sockets' character is (width, baseline), ( offset_x, offset_y, ) = self.socket.node.scene._system_font.font.getsize(self.socket.socket_sign) char_width = QFontMetrics(self._char_font).width(self.socket.socket_sign) height = 5 # For letters like: a,c,e,m,n,o,r,s,u,v,w,x,z if baseline == 8 and offset_y == 5: height = 5 # For letters like: b,d,f,h,i,k,l elif baseline == 10 and offset_y == 3: height = 7 # For letters like: g,p,q,y elif baseline == 11 and offset_y == 5: height = 4 # For letter: t elif baseline == 11 and offset_y == 2: height = 7 dimension = [char_width, height] return dimension # ----------------------------------------------------------------------------- def getSignPath(self, sign, multi): """ This method determines and returns what method should be called to paint the sign (+,-,*,/) next to an input socket (for PROD and SUM blocks only). :param sign: the sign associated with the current Socket (can be '+','-','*', or '/') :type sign: str, required :param multi: internal variable (1 when default orientation, -1 when flipped) :type multi: int, required, 1 or -1 :return: the relevant painter path method for drawing the desired sign :rtype: QPainterPath """ if sign == PLUS: return self.paintPlus(multi) if sign == MINUS: return self.paintMinus(multi) if sign == MULTIPLY: return self.paintMultiply(multi) if sign == DIVIDE: return self.paintDivide(multi) # Todo - doc comments # ----------------------------------------------------------------------------- def shouldSocketsBeHidden(self): # Check if the sockets belong to a connector type block if ( self.socket.node.block_type == "CONNECTOR" or self.socket.node.block_type == "Connector" ): # Check if the toolbar checkbox for hiding connector blocks has been selected if self.socket.node.scene.hide_connector_blocks: return True else: return False else: return False # ----------------------------------------------------------------------------- def boundingRect(self): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsSocket`` which returns the area within which the GraphicsSocket can be interacted with. When a mouse click event is detected within this area, this will trigger logic that relates to a Socket (that being, the generating or completion of a Wire). :return: a rectangle within which the Socket can be interacted with :rtype: QRectF """ return QRectF( -self.radius - self.outline_width, -self.radius - self.outline_width, 2 * (self.radius + self.outline_width), 2 * (self.radius + self.outline_width), ) ================================================ FILE: bdsim/bdedit/block_graphics_wire.py ================================================ # Library imports import sys import traceback from math import dist # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Socket positioning variables - used for determining what side of the block the # socket should be drawn LEFT = 1 TOP = 2 RIGHT = 3 BOTTOM = 4 # Variable for enabling/disabling debug comments DEBUG = False DEBUG_COORDINATES = False # ============================================================================= # # Defining the GraphicsWire Class, which is inherited by all Wires and controls # the graphical appearance of each Wire. # # ============================================================================= class GraphicsWire(QGraphicsPathItem): # Handles for displaying cursors when trying to move wire segments handleHSeg1 = 0 handleVSeg1 = 1 handleHSeg2 = 2 handleVSeg2 = 3 handleHSeg3 = 4 handleSize = +4.0 handleCursors = { handleHSeg1: Qt.ArrowCursor, handleVSeg1: Qt.SizeHorCursor, handleHSeg2: Qt.SizeVerCursor, handleVSeg2: Qt.SizeHorCursor, handleHSeg3: Qt.ArrowCursor, } handleSegments = 5 """ The ``GraphicsWire`` Class extends the ``QGraphicsPathItem`` Class from PyQt5. This class is responsible for graphically drawing Wires between Sockets. This class takes a Wire as an input and then looks for the coordinates of the start and end socket defined within it. Then based on these coordinates, connects them with a Wire of the selected type. It is also used to redraw the wires when they are moved around, and if a wire is selected it will redraw the wire thicker and in a different colour. """ # ----------------------------------------------------------------------------- def __init__(self, wire): """ This method initializes an instance of the ``GraphicsWire`` Class. It initially specifies the starting and ending sockets as being None, sets the Wire to always be drawn underneath other items within the GraphicsScene, and defines the colors with which the wire can be drawn. :param wire: the Wire Class instance this GraphicsWire relates to :type wire: Wire """ super().__init__() # Dictionary for storing which mouse cursor to display when mouse hovering over # vertical or horizontal wire segment self.segment_handles = {} self.handleSelected = None self.mousePressPos = None self.wire = wire # Setting the colour, pens, and pen thickness self._color = QColorConstants.Svg.black self._color_selected = QColorConstants.Svg.orange # self._pen = QPen(self._color, 5, Qt.SolidLine, Qt.SquareCap, Qt.BevelJoin) self._pen = QPen(self._color, 5, Qt.SolidLine, Qt.SquareCap, Qt.RoundJoin) self._pen_selected = QPen( self._color_selected, 8, Qt.SolidLine, Qt.SquareCap, Qt.BevelJoin ) # Initializing the start and end sockets self.posSource = [0, 0] self.posDestination = [100, 100] self.posSource_Orientation = None self.posDestination_Orientation = None # Method called to further intialize necessary block settings self.initUI() # Internal variables for: # * knowing if wire segments were moved since diagram was saved # * setting which routing logic to display (custom or hard-coded) # * catching fatal errors, and allowing user to save work before crashing self.wasMoved = False self.customlogicOverride = False self.FATAL_ERROR = False # ----------------------------------------------------------------------------- def initUI(self): """ This method sets flags to allow for this Wire to be selectable and be displayed at the correct z-level. """ # Setting the wire to be selectable and drawn behind all items self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setFlag(QGraphicsItem.ItemIsFocusable) self.setAcceptHoverEvents(True) self.setZValue(-1) # ----------------------------------------------------------------------------- def setSource(self, x, y): """ This method sets the point from where the Wire will start. This is based on the position of the start Socket. :param x: x coordinate of the start Socket :type x: int, required :param y: y coordinate of the start Socket :type y: int, required """ self.posSource = [x, y] # ----------------------------------------------------------------------------- def setDestination(self, x, y): """ This method sets the point of where the Wire will end. This is based on the position of the end Socket. :param x: x coordinate of the end Socket :type x: int, required :param y: y coordinate of the end Socket :type y: int, required """ self.posDestination = [x, y] # ----------------------------------------------------------------------------- def setSourceOrientation(self, orientation): """ This method sets the orientation (position) of the source (start) Socket - in terms of where it is drawn on the block (LEFT/RIGHT) - to the provided orientation. :param orientation: where on the Block the start Socket is drawn (LEFT(1) or RIGHT(2)) :type orientation: enumerate, required """ self.posSource_Orientation = orientation # ----------------------------------------------------------------------------- def setDestinationOrientation(self, orientation): """ This method sets the orientation (position) of the destination (end) Socket - in terms of where it is drawn on the block (LEFT/RIGHT) - to the provided orientation. :param orientation: where on the Block the end Socket is drawn (LEFT(1) or RIGHT(2)) :type orientation: enumerate, required """ self.posDestination_Orientation = orientation # Todo add doc to describe purpose of this method (overrides the shape this drawn path takes, with custom path) # This method is used by PyQt to interpret the bounding box area within which this line can be interacted with # ----------------------------------------------------------------------------- def shape(self): return self.polyPath() # ----------------------------------------------------------------------------- def paint(self, painter, style, widget=None): """ This is an inbuilt method of QGraphicsItem, that is overwritten by ``GraphicsWire``. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene. It sets up the painter object and draws the line based on the path that will be set by the specific implementation of GraphicsWire that is calling paint. Then the painter will select the way the wire will be drawn depending on whether or not the wire is selected. :param painter: a painter that paints the path (line) of this GraphicsWire :type painter: QPainter, automatically recognized and overwritten from this method :param style: style of the painter (isn't used but must be defined) :type style: QStyleOptionGraphicsItem, automatically recognized from this method :param widget: the widget this class is being painted on (None) :type widget: NoneType, optional, Defaults to None """ # Update the wire with the drawn intersection point(s) try: self.setPath(self.updatePath()) painter.setPen(self._pen if not self.isSelected() else self._pen_selected) painter.setBrush(Qt.NoBrush) painter.drawPath(self.path()) # if self.isSelected(): # painter.setRenderHint(QPainter.Antialiasing) # painter.setBrush(QBrush(QColor("#FFFF4538"))) # painter.setPen(QPen(QColor(0, 0, 0, 255), 1.0, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # for handle, rect in self.segment_handles.items(): # if rect: # painter.drawRect(rect) except Exception as e: if self.FATAL_ERROR == False: print( "-------------------------------------------------------------------------" ) print( "Caught fatal exception while trying to draw wires. Please save your work." ) print( "-------------------------------------------------------------------------" ) traceback.print_exc(file=sys.stderr) self.FATAL_ERROR = True # Todo - add docstring to this new method, add inline comments (makes custom polygon around drawn line) # ----------------------------------------------------------------------------- def polyPath(self): if self.wire.wire_coordinates: newpolyPath = QPainterPath() width = 8 self.wire_points = [] (p1x, p1y) = self.wire.wire_coordinates[0] self.wire_points.append(QPointF(p1x, p1y)) for i, (x, y) in enumerate(self.wire.wire_coordinates): if i == 0 or i == len(self.wire.wire_coordinates): self.wire_points.append(QPointF(x, y + width)) else: self.wire_points.append(QPointF(x + width, y + width)) for i, (x, y) in enumerate(reversed(self.wire.wire_coordinates)): if i == 0 or i == len(self.wire.wire_coordinates): self.wire_points.append(QPointF(x, y - width)) else: self.wire_points.append(QPointF(x - width, y - width)) poly = QPolygonF(self.wire_points) newpolyPath.addPolygon(poly) return newpolyPath else: return self.updatePath() # ----------------------------------------------------------------------------- def updatePath(self): """ This method is inherited and overwritten (currently) by the GraphicsWireDirect, GraphicsWireBezier or GraphicsWireStep classes, which dictate the pathing logic of the wire between the start and end socket. """ raise NotImplemented("This method is to be over written by child class") # ----------------------------------------------------------------------------- def updateCursorSegments(self, segments): """ This method updates which mouse cursor should be displayed for each line segment of the wire, depending on how many wire segments there are, which is given as an input. :param segments: Number of line segments in this wire :type segments: int """ # Upate cursor handles if segments == 5: self.handleCursors[self.handleHSeg1] = Qt.ArrowCursor self.handleCursors[self.handleVSeg1] = Qt.SizeHorCursor self.handleCursors[self.handleHSeg2] = Qt.SizeVerCursor self.handleCursors[self.handleVSeg2] = Qt.SizeHorCursor self.handleCursors[self.handleHSeg3] = Qt.ArrowCursor self.handleSegments = 5 elif segments == 3: self.handleCursors = self.handleCursors.fromkeys( self.handleCursors.keys(), Qt.ArrowCursor ) self.handleCursors[self.handleVSeg1] = Qt.SizeHorCursor self.handleSegments = 3 else: self.handleCursors = self.handleCursors.fromkeys( self.handleCursors.keys(), Qt.ArrowCursor ) self.handleSegments = segments # ----------------------------------------------------------------------------- def updateLineSegments(self): """ This method uses the coordinates of the points this wire goes through, to determine the coordinates that make up the horizontal and vertical line segments of this wire (this logic is only applicable to GraphicsWireStep). """ # If the wire coordinates exist if self.wire.wire_coordinates: # Clear current horizontal and vertical segemnt lists, and clear cursor handles self.wire.horizontal_segments.clear() self.wire.vertical_segments.clear() self.segment_handles = self.segment_handles.fromkeys( self.segment_handles.keys(), None ) # Find max number of segments in our line max_seg = len(self.wire.wire_coordinates) - 1 # From first to 2nd last coordinate point of the wire for counter in range(0, max_seg): # Find top left and btm right points top_left = min( ( self.wire.wire_coordinates[counter], self.wire.wire_coordinates[counter + 1], ) ) btm_right = max( ( self.wire.wire_coordinates[counter], self.wire.wire_coordinates[counter + 1], ) ) s = self.handleSize # Line segments always alternate, from horizontal to vertical to horizontal etc. # Even iterations of coordinate points are always the beginning to horizontal segments # Append a line represented as ((x1,y2),(x2,y2)) to either horizontal or vertical line segment list if counter % 2 == 0: # Current and next coordinates are added into the horizontal line segments list self.wire.horizontal_segments.append( [ self.wire.wire_coordinates[counter], self.wire.wire_coordinates[counter + 1], ] ) # Update rectangles for horizontal segments self.segment_handles[counter] = QRectF( QPointF(top_left[0] + s, top_left[1] - s), QPointF(btm_right[0] - s, btm_right[1] + s), ) else: # Current and next coordinates are added into the vertical line segments list self.wire.vertical_segments.append( [ self.wire.wire_coordinates[counter], self.wire.wire_coordinates[counter + 1], ] ) # Update rectangles for vertical segments self.segment_handles[counter] = QRectF( QPointF(top_left[0] - s, top_left[1] + s), QPointF(btm_right[0] + s, btm_right[1] - s), ) # Upate cursor handles if self.handleSegments != max_seg: self.updateCursorSegments(max_seg) # ----------------------------------------------------------------------------- def updateWireCoordinates(self, new_coordinates): """ This method checks if the list of coordinate points this wire passes through, has changed and needs to be updated. This method reduces the computational resources required to otherwise re-write the list of coordinate points for this wire, every time a user-interaction is detected within the GraphicsView. :param new_coordinates: proposed coordinate points for this Wire :type new_coordinates: list """ # Update current wire coordinates, if the new coordinates are different # Also update the horizontal and vertical line segments if new_coordinates != self.wire.wire_coordinates: self.wire.wire_coordinates.clear() self.wire.wire_coordinates = new_coordinates self.updateLineSegments() # If in DEBUG mode, this code will print the coordinates list and the # separated horizontal and vertical line segments of this Wire if DEBUG_COORDINATES: print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") if DEBUG_COORDINATES: print("coordinates:", [self.wire.wire_coordinates]) if DEBUG_COORDINATES: print("\nhorizontal lines:", [self.wire.horizontal_segments]) if DEBUG_COORDINATES: print("\nvertical lines:", [self.wire.vertical_segments]) if DEBUG_COORDINATES: print("\nline segments:") if DEBUG_COORDINATES: [ print("segment" + str(i) + ":", segment) for i, segment in enumerate(self.segment_handles.items()) ] if DEBUG_COORDINATES: print("-------------------------------------------------------") # ----------------------------------------------------------------------------- def handleAt(self, point): """ Returns the resize handle below the given point. """ for ( k, v, ) in self.segment_handles.items(): if v is None: return None elif v.contains(point): return k return None # ----------------------------------------------------------------------------- def hoverMoveEvent(self, moveEvent): """ Executed when the mouse moves over the shape (NOT PRESSED). """ if self.isSelected(): handle = self.handleAt(moveEvent.pos()) # print("handle:", handle) cursor = Qt.ArrowCursor if handle is None else self.handleCursors[handle] self.setCursor(cursor) super().hoverMoveEvent(moveEvent) # ----------------------------------------------------------------------------- def hoverLeaveEvent(self, moveEvent): """ Executed when the mouse leaves the shape (NOT PRESSED). """ self.setCursor(Qt.ArrowCursor) super().hoverLeaveEvent(moveEvent) # ----------------------------------------------------------------------------- def mousePressEvent(self, mouseEvent): """ Executed when the mouse is pressed on the item. """ self.wire.setFocusOfWire() self.handleSelected = self.handleAt(mouseEvent.pos()) if self.handleSelected is not None: self.mousePressPos = mouseEvent.pos() self.mousePressRect = self.segment_handles[self.handleSelected] super().mousePressEvent(mouseEvent) # ----------------------------------------------------------------------------- def mouseMoveEvent(self, mouseEvent): """ Executed when the mouse is being moved over the item while being pressed. """ if self.handleSelected is not None: if not self.customlogicOverride: self.customlogicOverride = True self.interactiveResize(mouseEvent.pos()) self.wasMoved = True self.wire.checkIntersections() else: super().mouseMoveEvent(mouseEvent) # ----------------------------------------------------------------------------- def mouseReleaseEvent(self, mouseEvent): """ Executed when the mouse is released from the item. """ super().mouseReleaseEvent(mouseEvent) self.handleSelected = None self.mousePressPos = None self.mousePressRect = None self.update() # If wire segments have been moved, update the variable within the model, to then # update the title of the model, to indicate that there is unsaved progress if self.wasMoved: self.wasMoved = False self.wire.scene.has_been_modified = True self.wire.scene.history.storeHistory("Wire segment moved") # ----------------------------------------------------------------------------- def interactiveResize(self, mousePos): """ Perform shape interactive resize. """ diff = QPointF(0, 0) x_diff = mousePos.x() - self.mousePressPos.x() y_diff = mousePos.y() - self.mousePressPos.y() # Quantize resizing of grouping box spacing = 10 x = round(x_diff / spacing) * spacing y = round(y_diff / spacing) * spacing # Calculate how far the mouse has moved from the point it was initially clicked from fromCenter = self.mousePressRect.center() toX = fromCenter.x() + x toY = fromCenter.y() + y custom_wire_coordinates = self.wire.wire_coordinates.copy() self.prepareGeometryChange() # If there are three segments, 1st and last segments cannot be moved (which are horizontally attacked to the sockets) # so this leaves only the vertical segment connecting them. If that segment is dragged, adjust the points accordingly. # If any other segments are being dragged, don't do anything if self.handleSegments == 3: if self.handleSelected == self.handleVSeg1: # Only 1 vertical segment in this case, so take the first vert segment [0] segment = self.wire.vertical_segments[0].copy() # Copy that segment, and add the difference the mouse has moved to the x [0] coord of the two points that make up this segment for i, point in enumerate(segment.copy()): new_point = list(point) new_point[0] = toX segment[i] = tuple(new_point) # Override the points making up this semgent in the temporary wire_coordinates -> 2nd and 3rd points [1:3] custom_wire_coordinates[1:3] = segment # Otherwise if there are five segments, since first and last segment cannot be moved, this leaves us with 3 segments which can # Two vertical segments, and one horizontal segment. If any of those three are selected, adjust the points accordingly. elif self.handleSegments == 5: if self.handleSelected == self.handleHSeg2: # There are three horizontal segments to choose from, 1st and 3rd (last) cannot be moved, this means our segment is #2 [1]. segment = self.wire.horizontal_segments[1].copy() # Copy that segment, and add the difference the mouse has moved to the y [1]) coord of the two points that make up this segment for i, point in enumerate(segment.copy()): new_point = list(point) new_point[1] = toY segment[i] = tuple(new_point) # Override the points making up this semgent in the temporary wire_coordinates -> 3rd and 4th points [2:4] custom_wire_coordinates[2:4] = segment elif self.handleSelected in [self.handleVSeg1, self.handleVSeg2]: # There are two vertical segments to choose from, either can be moved as they do not attach directly to a socket. if self.handleSelected == self.handleVSeg1: segment = self.wire.vertical_segments[0].copy() else: segment = self.wire.vertical_segments[1].copy() # Copy that segment, and add the difference the mouse has moved to the x [0]) coord of the two points that make up this segment for i, point in enumerate(segment.copy()): new_point = list(point) new_point[0] = toX segment[i] = tuple(new_point) # Override the points making up this semgent in the temporary wire_coordinates. Since we can either select the 1st or 2nd # vertical segment, these points correspond to 2nd and 3rd points [1:3] or the 4th and 5th points [3:5] if self.handleSelected == self.handleVSeg1: custom_wire_coordinates[1:3] = segment else: custom_wire_coordinates[3:5] = segment self.updateWireCoordinates(custom_wire_coordinates) self.updatePath() # ----------------------------------------------------------------------------- def adjustCustomRoutingWireCoordinates(self): # When both blocks connected to a wire are being moved, both start and end sockets # that this wire is connected to will move the same amount. So we will just grab # the data from the start_socket. # Start sockets could be an input or output socket, so find its true position w.r.t its block start_point = ( self.wire.start_socket.node.grBlock.pos() + self.wire.start_socket.grSocket.pos() ) start_coords = (start_point.x(), start_point.y()) # print("\nsocket coords:", [start_coords]) # print("wire coords:", self.wire.wire_coordinates) # Wire can be drawn from either input to output socket, or output to input socket. This means # our start_socket can either be the first or last point in the wire.wire_coordinates list # Check which of these are closest to our start_socket_coords, then take the difference between those dist1 = dist(start_coords, self.wire.wire_coordinates[0]) dist2 = dist(start_coords, self.wire.wire_coordinates[-1]) # Take difference between start_socket_coords and whichever point is closest from wire.wire_coordinates if dist1 < dist2: diff = [ start_coords[0] - self.wire.wire_coordinates[0][0], start_coords[1] - self.wire.wire_coordinates[0][1], ] else: diff = [ start_coords[0] - self.wire.wire_coordinates[-1][0], start_coords[1] - self.wire.wire_coordinates[-1][1], ] # print("difference:", diff) # If there is a difference, then adjust the wire coordinates if diff == [0, 0]: return # Adjust wire.wire_coordinates by subtracting that diff from each point new_wire_coordinates = [] for point in self.wire.wire_coordinates: new_point = (point[0] + diff[0], point[1] + diff[1]) new_wire_coordinates.append(new_point) # print("old wire coords before:", self.wire.wire_coordinates) # print("new_wire_coords: ", new_wire_coordinates) # Once points are adjusted, replace the wire.wire_coordinates list with this list self.updateWireCoordinates(new_wire_coordinates) # print("old wire coords after: ", self.wire.wire_coordinates) class GraphicsWireDirect(GraphicsWire): """ The ``GraphicsWireDirect`` Class extends the ``GraphicsWire`` Class from BdEdit. This class is responsible for providing a straight painter path that the GraphicsWire class should follow when drawing the Wire. This wire type will draw a straight line between two Sockets. It will take the shortest distance between these two points, and will go through other Blocks to do so. """ # ----------------------------------------------------------------------------- def updatePath(self): """ This method creates a painter path that connects two points (start/end sockets) with a straight line. """ # A straight line is drawn between the two provided points path = QPainterPath(QPointF(self.posSource[0], self.posSource[1])) path.lineTo(self.posDestination[0], self.posDestination[1]) return path class GraphicsWireBezier(GraphicsWire): """ The ``GraphicsWireBezier`` Class extends the ``GraphicsWire`` Class from BdEdit. This class is responsible for providing a bezier painter path that the GraphicsWire class should follow when drawing the Wire. This wire type option looks good in the first direction it is drawn (e.g. left-to-right) but will not wrap logically when blocks are flipped or moved past each other (e.g. such that the wire would now be right-to-left). """ # ----------------------------------------------------------------------------- def updatePath(self): """ This method creates a painter path that connects two points (start/end sockets) with a bezier line. This line is drawn as a cubic (think sine wave). """ # The start and end coordinates are grabbed into variables s and d s = self.posSource d = self.posDestination # This code decides the direction the bezier should point from the start socket # either left or right of the start socket. dist = (d[0] - s[0]) * 0.5 if s[0] > d[0]: dist *= -1 # Then a cubic line is drawn between the two provided points path = QPainterPath(QPointF(self.posSource[0], self.posSource[1])) path.cubicTo( s[0] + dist, s[1], d[0] - dist, d[1] - dist, self.posDestination[0], self.posDestination[1], ) return path class GraphicsWireStep(GraphicsWire): """ The ``GraphicsWireStep`` Class extends the ``GraphicsWire`` Class from BdEdit. This class is responsible for providing a stepped painter path that the GraphicsWire class should follow when drawing the Wire. This is the default wire style used within BdEdit, and has the most supporting logic built around it. This wire option draws a straight line between two sockets when the two blocks side by side at equal heights, are connected with a wire in the middle. Otherwise if the two blocks are at varying heights, a stepped line will be drawn from the starting socket, with 90 degree bends at each point the wire must turn to reach the end socket. """ def updatePath(self): if self.customlogicOverride: self.adjustCustomRoutingWireCoordinates() # The path of the wire is set to be drawn with either # custom routing logic or with hard-coded routing logic for i, (x, y) in enumerate(self.wire.wire_coordinates): if i == 0: path = QPainterPath(QPointF(x, y)) elif i == len(self.wire.wire_coordinates): path.lineTo(x, y) path.moveTo(x, y) else: path.lineTo(x, y) return path else: return self.wireRoutingLogic() # ----------------------------------------------------------------------------- def wireRoutingLogic(self): """ This method creates a painter path that connects two points (start/end sockets) with a stepped line. This path is returned as a straight line if two blocks at equal heights, are connected with a wire on the inside of each of the blocks. Otherwise the path is returned as a stepped line with 90 degree bends at each point the wire must turn. The logic for when this wire should turn is calculated internally. Please refer to the technical document accompanying the BdEdit code for visual explanations for how this logic is determined. """ try: # _____________________________________ Preparing Wire related variables ______________________________________ # List into which the coordinate points of the wire will be appended into when # this wire is updated. This will be compared against the current list of this # wires' coordinates, to check if it needs to be updated temporary_wire_coordinates = [] # This variable prevents a stepped wire from being drawn until it is connected # to the end socket. Until then a straight line will be drawn wire_completed = False # Block padding is the space at which wires will be wrapped around blocks block_padding = 20 # The title height is extracted from the block this wire starts from title_height = self.wire.start_socket.node.grBlock.title_height - 5 # __________________________________ Extracting Logic of Start/End Sockets ____________________________________ # The global (scene) x,y coordinates of the source (start) and destination (end) sockets are extracted sx = self.posSource[0] sy = self.posSource[1] dx = self.posDestination[0] dy = self.posDestination[1] # The horizontal distance between these two sockets is found xDist = (dx - sx) / 2 # The index at which the start socket is drawn on its block is extracted + 1 # (0th index will be our first index, as this variable is used as a multiplier) s_index = self.wire.start_socket.index + 1 # Dimensions of the start sockets' block are extracted source_block_width = self.wire.start_socket.node.width source_block_height = self.wire.start_socket.node.height # The local (in reference to its block) x-y coordinates of the start socket s_Offset = self.wire.start_socket.getSocketPosition() # Same logic (as above) is extracted for the destination (end) socket if it has been set if self.wire.end_socket is not None: d_index = self.wire.end_socket.index + 1 destination_block_width = self.wire.end_socket.node.width destination_block_height = self.wire.end_socket.node.height d_Offset = self.wire.end_socket.getSocketPosition() else: d_index = 0 destination_block_width = 0 destination_block_height = 0 d_Offset = [0, 0] # The previous temporary coordinates of this wire are cleared, and the new start point coordinate is added temporary_wire_coordinates.clear() temporary_wire_coordinates.append((sx, sy)) # ########################################### Start of Wire Logic ########################################## # ====================================== If Wire hasn't been completed ===================================== if self.wire.end_socket is None: # If two sockets haven't been connected yet # Don't do anything, start and end points of the path have already been defined, so a straight line will be drawn if DEBUG: print("Wire style: O") pass # ========================= Start & End Sockets are on the same side of two Blocks ========================= elif self.posSource_Orientation == self.posDestination_Orientation: # If sockets are both on the same side (both coming out of the left or right) # ----------------------------------- Start Socket LEFT OF End Socket ---------------------------------- if sx < dx: # - - - - - - - - - - - - Continue with Extracted Logic of Start/End Sockets - - - - - - - - - - - - - # Continue path from source # Destination_block & Source_block are kept the same pass # ------------------------------ Start Socket EQUAL or RIGHT OF End Socket ----------------------------- else: # - - - - - - - - - - - Re-Extract Logic of Start/End Sockets (Switching them) - - - - - - - - - - - - # Use the same logic as for above, but swap positions of start socket with end socket sx, sy = self.posDestination[0], self.posDestination[1] dx, dy = self.posSource[0], self.posSource[1] # Destination_block & Source_block = node of start_socket and end_socket respectively d_index = self.wire.start_socket.index + 1 destination_block_width = self.wire.start_socket.node.width destination_block_height = self.wire.start_socket.node.height # Switch the indexes of the sockets if self.wire.end_socket is not None: s_index = self.wire.end_socket.index + 1 source_block_width = self.wire.end_socket.node.width source_block_height = self.wire.end_socket.node.height else: s_index = 0 source_block_width = 0 source_block_height = 0 # Restart path from destination temporary_wire_coordinates.clear() temporary_wire_coordinates.append((sx, sy)) # ------------------------------- End Socket on RHS of Destination Block ------------------------------- if self.posDestination_Orientation == RIGHT: # xDist is from RHS of source block, to LHS of destination block xDist = (dx - destination_block_width - sx) / 2 # Top of the destination block is above source block # Should be dy > sy, but graphics view draws the y-axis inverted if dy - d_Offset[1] - (d_index * block_padding) < sy: # Bottom of destination block is above top of source block OR # LHS of destination block is further left than RHS of source block # Wire from multiple sockets spaced from bottom of destination block at index (no overlap) if ( dy - d_Offset[1] + destination_block_height + title_height + (d_index * block_padding) < sy ) or (sx + xDist <= sx + (block_padding / 2)): if DEBUG: print("Wire style: A") # Draw C (inverted equivalent) line from S up to D, clipped to RHS of destination block # ---------------------- # (d-block)-<-| # | # (s-block)->------| # ---------------------- temporary_wire_coordinates.append( (dx + (d_index * block_padding), sy) ) temporary_wire_coordinates.append( (dx + (d_index * block_padding), dy) ) # Bottom of destination block is equal to or below top of source block else: if DEBUG: print("Wire style: B") # Draw wrapped line between source and destination block, then above and around destination block # -------------------------------- # |---------------| # | | # | (d-block)-<-| # | # (s-block)->-| # -------------------------------- temporary_wire_coordinates.append((sx + xDist, sy)) temporary_wire_coordinates.append( ( sx + xDist, dy - d_Offset[1] - (d_index * block_padding), ) ) temporary_wire_coordinates.append( ( dx + d_index * block_padding, dy - d_Offset[1] - (d_index * block_padding), ) ) temporary_wire_coordinates.append( (dx + d_index * block_padding, dy) ) # Top of destination block is equal to or below source block socket else: if DEBUG: print("Wire style: C") # Draw C (inverted equivalent) line from S down to D, clipped to RHS of destination block # ------------------------ # (s-block)->---------| # | # (d-block)-<-| # ------------------------ temporary_wire_coordinates.append( (dx + d_index * block_padding, sy) ) temporary_wire_coordinates.append( (dx + d_index * block_padding, dy) ) # ------------------------------- End Socket on LHS of Destination Block ------------------------------- else: # xDist is from RHS of source block, to LHS of destination block xDist = (dx - (sx + source_block_width)) / 2 # Should be sy > dy, but graphics view draws the y-axis inverted # Top of source block is above destination block if sy - s_Offset[1] - (s_index * block_padding) < dy: # Bottom of source block is above top of destination block OR # RHS of source block further left than LHS of destination block # Wire from multiple sockets spaced from bottom of source block at index (no overlap) if ( sy - s_Offset[1] + source_block_height + title_height + (s_index * block_padding) < dy ) or (dx + xDist <= dx + (block_padding / 2)): if DEBUG: print("Wire style: D") # Draw C line from S down to D, clipped to LHS of source block # ---------------------- # |--<-(s-block) # | # |----->-(d-block) # ---------------------- temporary_wire_coordinates.append( (sx - (s_index * block_padding), sy) ) temporary_wire_coordinates.append( (sx - (s_index * block_padding), dy) ) # Bottom of source block is equal to or below top of destination block else: if DEBUG: print("Wire style: E") # Draw wrapped line above and around the source block, then between the source and destination block # -------------------------------- # |---------------| # | | # |-<-(s-block) | # | # |->-(d-block) # -------------------------------- temporary_wire_coordinates.append( (sx - s_index * block_padding, sy) ) temporary_wire_coordinates.append( ( sx - s_index * block_padding, sy - s_Offset[1] - (s_index * block_padding), ) ) temporary_wire_coordinates.append( ( sx + source_block_width + xDist, sy - s_Offset[1] - (s_index * block_padding), ) ) temporary_wire_coordinates.append( (sx + source_block_width + xDist, dy) ) # Top of source block is equal to or below destination block else: if DEBUG: print("Wire style: F") # Draw C line from S up to D, clipped to LHS of source block # -------------------- # |------->-(d-block) # | # |-<-(s-block) # -------------------- temporary_wire_coordinates.append( (sx - s_index * block_padding, sy) ) temporary_wire_coordinates.append( (sx - s_index * block_padding, dy) ) # Update boolean that wire is completed, as to get here, the end point of the wire must be dropped wire_completed = True # ======================== Start & End Sockets are on opposite sides of two blocks ========================= elif self.posSource_Orientation != self.posDestination_Orientation: # Otherwise sockets are on different sides (out from left into right, or out of right into left) # -------------------------------- Start Socket on LHS of Source Block --------------------------------- if self.posSource_Orientation == LEFT: # - - - - - - - - - - - - Continue with Extracted Logic of Start/End Sockets - - - - - - - - - - - - - # Continue path from source # Destination_block & Source_block are kept the same xDist = (sx - dx) / 2 # -------------------------------- Start Socket on RHS of Source Block --------------------------------- else: # - - - - - - - - - - - Re-Extract Logic of Start/End Sockets (Switching them) - - - - - - - - - - - - # Use the same logic as for above, but swap positions of start socket with end socket sx, sy = self.posDestination[0], self.posDestination[1] dx, dy = self.posSource[0], self.posSource[1] # Destination_block & Source_block = node of start_socket and end_socket respectively d_index = self.wire.start_socket.index + 1 destination_block_width = self.wire.start_socket.node.width destination_block_height = self.wire.start_socket.node.height d_Offset = self.wire.start_socket.getSocketPosition() # Switch the indexes of the sockets if self.wire.end_socket is not None: s_index = self.wire.end_socket.index + 1 source_block_width = self.wire.end_socket.node.width source_block_height = self.wire.end_socket.node.height s_Offset = self.wire.end_socket.getSocketPosition() else: s_index = 0 source_block_width = 0 source_block_height = 0 s_Offset = [0, 0] # Restart path from destination temporary_wire_coordinates.clear() temporary_wire_coordinates.append((sx, sy)) # ---------------------------------- Start Socket RIGHT OF End Socket ---------------------------------- if sx > dx: # If start socket is not on same height as end socket # Otherwise a straight line will be drawn when this logic is passed through if sy != dy: if DEBUG: print("Wire style: G") # Draw normal step line # --------------------------- # |-<-(s-block) # | # (d-block)-<-| # --------------------------- temporary_wire_coordinates.append((sx - xDist, sy)) temporary_wire_coordinates.append((sx - xDist, dy)) # ------------------------------ Start Socket EQUAL or LEFT OF End Socket ------------------------------ else: # Source block is above destination block if sy - s_Offset[1] - (s_index * block_padding) < dy - d_Offset[ 1 ] - (d_index * block_padding): # Distance between bottom of source block and top of destination block yDist = ( (dy - d_Offset[1]) - (sy - s_Offset[1] + source_block_height + title_height) ) / 2 # Bottom of source block is above top of destination block OR # -- if (sy - s_Offset[1] + source_block_height + title_height) < ( dy - d_Offset[1] - block_padding ): if DEBUG: print("Wire style: H") # Draw S line # |---<-(s-block) # | # |--------------| # | # (d-block)-<--| # ------------------ temporary_wire_coordinates.append( (sx - (s_index * block_padding), sy) ) temporary_wire_coordinates.append( ( sx - (s_index * block_padding), dy - d_Offset[1] - yDist, ) ) temporary_wire_coordinates.append( ( dx + (d_index * block_padding), dy - d_Offset[1] - yDist, ) ) temporary_wire_coordinates.append( (dx + (d_index * block_padding), dy) ) # Bottom of source block is at level with or below top of destination block else: # RHS of destination block is further left than RHS of source block if (dx + block_padding) < ( sx + source_block_width + block_padding ): if DEBUG: print("Wire style: I") # Draw line going around the top of the source block, clipped to RHS of source block + padding # -------------------------------------------------- # |--------------| |--------------| # | | | | # |-<-(s-block) | or |-<-(s-block) | # (d-block)-<-------| (d-block)-<-| # -------------------------------------------------- temporary_wire_coordinates.append( (sx - (s_index * block_padding), sy) ) temporary_wire_coordinates.append( ( sx - (s_index * block_padding), sy - s_Offset[1] - (s_index * block_padding), ) ) temporary_wire_coordinates.append( ( sx + source_block_width + (s_index * block_padding), sy - s_Offset[1] - (s_index * block_padding), ) ) temporary_wire_coordinates.append( ( sx + source_block_width + (s_index * block_padding), dy, ) ) # path.lineTo(sx + source_block_width + block_padding, sy - s_Offset[1] - (s_index * block_padding)) # path.lineTo(sx + source_block_width + block_padding, dy) # RHS of destination block is equal to or right of RHS of source block else: if DEBUG: print("Wire style: J") # Draw line going around the top of the source block, clipped to RHS of destination block + padding # ------------------------------------------------------------------------- # |-----------------------------| |---------------------------| # | | | | # |-<-(s-block) | or |-<-(s-block) (d-block)-<-| # (d-block)-<-| # ------------------------------------------------------------------------- temporary_wire_coordinates.append( (sx - (s_index * block_padding), sy) ) temporary_wire_coordinates.append( ( sx - (s_index * block_padding), sy - s_Offset[1] - (s_index * block_padding), ) ) temporary_wire_coordinates.append( ( dx + (d_index * block_padding), sy - s_Offset[1] - (s_index * block_padding), ) ) temporary_wire_coordinates.append( (dx + (d_index * block_padding), dy) ) # Source block is below destination block else: # Distance between top of source block and bottom of destination block yDist = ( (sy - s_Offset[1]) - ( dy - d_Offset[1] + destination_block_height + title_height ) ) / 2 # Top of source block is below bottom of destination block OR # -- if (sy - s_Offset[1] - block_padding) > ( dy - d_Offset[1] + destination_block_height + title_height ): if DEBUG: print("Wire style: K") # Draw Z line # ----------------------- # (d-block)-<--| # | # |------------------| # | # |-<-(s-block) # ----------------------- temporary_wire_coordinates.append( (sx - (s_index * block_padding), sy) ) temporary_wire_coordinates.append( ( sx - (s_index * block_padding), sy - yDist - s_Offset[1], ) ) temporary_wire_coordinates.append( ( dx + (s_index * block_padding), sy - yDist - s_Offset[1], ) ) temporary_wire_coordinates.append( (dx + (s_index * block_padding), dy) ) # Top of source block is at level with or above bottom of destination block else: # LHS of destination is further left than LHS of source block if (dx - destination_block_width - block_padding) < ( sx - block_padding ): if DEBUG: print("Wire style: L") # Draw line going around the top of the destination block, clipped to the LHS of destination block minus padding # ------------------------ # |--------------| # | | # | (d-block)-<-| # | # |----------<-(s-block) # ------------------------ temporary_wire_coordinates.append( ( dx - destination_block_width - (s_index * block_padding), sy, ) ) temporary_wire_coordinates.append( ( dx - destination_block_width - (s_index * block_padding), dy - d_Offset[1] - (d_index * block_padding), ) ) temporary_wire_coordinates.append( ( dx + d_index * block_padding, dy - d_Offset[1] - (d_index * block_padding), ) ) temporary_wire_coordinates.append( (dx + d_index * block_padding, dy) ) # LHS of destination is equal to or right of LHS of source block else: if DEBUG: print("Wire style: M") # Draw line going around the top of the destination block, clipped to the LHS of source block minus padding # ---------------------- # |----------------| # | | # | (d-block)-<-| # | # |-<-(s-block) # ---------------------- temporary_wire_coordinates.append( (sx - s_index * block_padding, sy) ) temporary_wire_coordinates.append( ( sx - s_index * block_padding, dy - d_Offset[1] - (d_index * block_padding), ) ) temporary_wire_coordinates.append( ( dx + d_index * block_padding, dy - d_Offset[1] - (d_index * block_padding), ) ) temporary_wire_coordinates.append( (dx + d_index * block_padding, dy) ) # Update boolean that wire is completed, as to get here, the end point of the wire must be dropped wire_completed = True # ############################################ End of Wire Logic ########################################### # Finally the Wire is finished, by connecting the path to the destination (end) Socket coordinates # that coordinate is also added as the final coordinate point of the wire to the temporary coordinates list temporary_wire_coordinates.append((dx, dy)) # The path of the wire is set to be drawn under the path logic that has just been calculated for i, (x, y) in enumerate(temporary_wire_coordinates): if i == 0: path = QPainterPath(QPointF(x, y)) elif i == len(temporary_wire_coordinates): path.lineTo(x, y) path.moveTo(x, y) else: path.lineTo(x, y) # If the wire has been dropped on a destination socket (and is not being dragged around), update its coordinates if wire_completed: self.updateWireCoordinates(temporary_wire_coordinates) return path except Exception as e: if self.FATAL_ERROR == False: print( "------------------------------------------------------------------------------------" ) print( "Caught fatal exception while trying to calculate wire bends. Please save your work." ) print( "------------------------------------------------------------------------------------" ) traceback.print_exc(file=sys.stderr) self.FATAL_ERROR = True ================================================ FILE: bdsim/bdedit/block_importer.py ================================================ # !/usr/bin/env python # -*- coding: utf-8 -*- import re import inspect import copy import numpy as np import importlib.util import inspect import os.path from pathlib import Path from bdsim.bdedit.block import blockname, blocklist, Block # from examples import docstring_parser as parser import bdsim def import_blocks(scene, window): size_map = { "InPort": [50, 100], "OutPort": [50, 100], "SubSystem": [200, 150], "DiffSteer": [150, 100], "VehiclePlot": [125, 100], "MultiRotor": [125, 100], "IDyn": [125, 100], "FDyn": [125, 100], "Tr2Delta": [125, 100], "MultiRotorMixer": [125, 100], "JTraj": [125, 100], } # Make in/out labels parameter for subsystem blocks inlabels_param = [ "inport labels", list, None, [["type", [type(None), list]]], "List of names for respective inport sockets", ] outlabels_param = [ "outport labels", list, None, [["type", [type(None), list]]], "List of names for respective outport sockets", ] # block_list = parser.docstring_parser() sim = bdsim.BDSim() # create simulator block_list = sim._blocklibrary block_library = [] imported_block_groups = [] # for i, block in enumerate(block_list.items()): for block_type, block_ds in sim.blockinfo().items(): # if i == 4: # blocks is a dic of tuples # (block_type, {block_docstring_data}) # print(f"reading definition of block {block_type}") # block_type = block[0].upper() # Block type # block_ds = block[1] # Block docstring # block_name = block_type.lower().capitalize() + " Block" block_name = block_ds["classname"] + " Block" block_classname = block_ds["classname"] block_parentclass = block_ds["blockclass"] block_path = block_ds["path"] block_icon = os.path.join(block_path[0], "Icons", block_type.lower() + ".png") # # Make a block instance of the given class # try: # # Grab number of input/output sockets for block, once it has been instantiated # if block_ds["nin"] < 0 or block_ds["nout"] < 0: # block_instance = block_ds["class"]() # block_inputsNum = block_instance.nin # block_outputsNum = block_instance.nout # else: # block_inputsNum = block_ds["nin"] # block_outputsNum = block_ds["nout"] # # except Exception as e: # # When exception occurs here it is related to an assertion being raised in bdsim # # This can be ignored as we only need the nin/nout values once the block is instantiated # block_inputsNum = block_instance.nin # block_outputsNum = block_instance.nout # Grab values assigned to this block's nin and nout class variables # If the variables are >= 0, copy the values, otherwise if negative, set equal to 1 (aside from some blocks) # If nin or nout not defined as a class variable, assign value of 0 (no inputs or outputs) if hasattr(block_ds["class"], "nin"): if block_name in ["Sum Block", "Prod Block", "Traj Block"]: if block_name == "Traj Block": block_inputsNum = 0 else: block_inputsNum = 2 else: if block_ds["class"].nin > -1: block_inputsNum = block_ds["class"].nin else: block_inputsNum = 1 else: block_inputsNum = 0 if hasattr(block_ds["class"], "nout"): if block_ds["class"].nout > -1: block_outputsNum = block_ds["class"].nout else: block_outputsNum = 1 else: block_outputsNum = 0 # Grab the names of the input/output sockets block_input_names, block_output_names = [], [] if hasattr(block_ds["class"], "inlabels"): for input_socket_name in block_ds["class"].inlabels: block_input_names.append(input_socket_name) if hasattr(block_ds["class"], "outlabels"): for output_socket_name in block_ds["class"].outlabels: block_output_names.append(output_socket_name) # Reconstruct URL from block type and path block_group = block_ds["module"].split(".")[-1] try: block_url = block_ds["url"] except KeyError: block_url = None block_parameters = ( [] ) # Once name, type, value, restrictions are extracted, this will be populated for param in block_ds["params"].items(): # Extract parameter name param_name = param[0] if param[1][1]: param_tooltip = param[1][1] else: param_tooltip = "" # Extract parameter type # Split string based on white spaces # if one type - no white spaces # if multiple types - several white spaces try: param_type_docstring = param[1][0].split() # Remove any left over commas or dashes (shouldn't be needed for type) for i, item in enumerate(param_type_docstring): param_type_docstring[i] = item.strip(",-.") # 1st value should be the type, with following values being either # * other accepted types, # * the optional keyword (also indicates if value should have a default), # * or human-readable string # 1. go through each word in the split string, searching for likely terms (sequence, string, etc) # 2. match found strings to their desired interpretation, sequence = list/tuple/range, string = str # 3. create list of strings matched to required types # 4. evaluate and assign first item from that list of types, as the param type # 5. create list for holding found restrictions # 5. param_restrictions = [] # --------------------------------------------------------------------------------------- # Section for extracting parameter type information (and restriction if relating to type) # --------------------------------------------------------------------------------------- # 3. found_types = [] found_size_restrictions = [] found_range_restrictions = [] found_symbol_restrictions = [] found_keyword_restrictions = [] # 1. for item in param_type_docstring: # Go through and try to evaluate the first type, if it works assign that as the param type try: # 2. param_type = eval(item) # Check to see if evaluated item is NOT a class type, e.g. NOT . # In this case, wrap the evaluated item in type() if not inspect.isclass(param_type): found_types.append(type(param_type)) # This should only occur for the callable or any, type, but just to be safe we can check that it is one of those # and then append the 'str' type to pass through any user input from bdedit, as this will be validated in bdsim. if isinstance(param_type, type(callable)): found_types.append(str) # If a tuple type has been found, convert this to list if issubclass(param_type, tuple): param_type = list # Otherwise, if item is in the form of a string, append that value to list of found types else: found_types.append(param_type) # If evaluation fails - most likely human readible string instead of actual type except: regex_search = re.findall( r"array_like\([0-9]\)|array_like", item ) # If current word is array_like if regex_search: # Search each array_like word found in type docstring, looking for size restriction for match in regex_search: element_restriction_match = re.findall(r"[0-9]", match) # If an element size restriction has been detected if element_restriction_match: # If size restriction is already entered, this will stop it from being entered multiple times restriction_to_insert = eval( element_restriction_match[0] ) if ( not restriction_to_insert in found_size_restrictions ): found_size_restrictions.append( restriction_to_insert ) # Todo - add size 0 if no regex size found else: pass # If array_like has already been added, this will stop it from being entered multiple times # Lists can be interpreted easily and converted into a numpy array, so in JSON these will be stored as lists # type_to_insert = type(np.array([])) # If size restrictions were found, then this parameter is limited to being either a list of dict if found_size_restrictions: types_to_insert = [list, dict] # If no size restriction was found, then parameter retains all of array_like properties else: types_to_insert = [list, dict, int, float] for found_type in types_to_insert: if found_type not in found_types: found_types.append(found_type) elif item.lower() in ["string"]: if str not in found_types: found_types.append(str) elif item.lower() in ["sequence", "string"]: # 2. if list not in found_types: found_types.append(list) else: pass # 4. # If any known types have been found, set the parameter type if found_types: # Extract first detected type as the default type param_type = found_types[0] else: # If no param type was found from the docstrings, this parameter most likely has a bdsim defined class # These will be passed through as a str type, and evaluated on bdsim's end. If any issues occur, it is # the user's job to ensure a suitable and meaningful value is entered for the parameter param_type = str found_types.append(str) # ----------------------------------------------------------------------------------------- # Section for extracting parameter value information (and restriction if relating to value) # ----------------------------------------------------------------------------------------- param_value = None # Extract parameter value information param_value_docstring = param[1][1].split() # String docstring of commas for i, item in enumerate(param_value_docstring): param_value_docstring[i] = item.strip(",.") # Parse the docstring ignoring words until finding a known "defaults to:" for i in range(0, len(param_value_docstring) - 1): if param_value_docstring[i] in ["range"]: # This parameter must be within a certain range, extract the provided range values # These should be in the form of "range [a,b]" # Hence we can check if evaluating the word after range gives a list of length 2 try: next_item = eval(param_value_docstring[i + 1]) if isinstance(next_item, list) and len(next_item) == 2: # Add a,b of range to restriction items found_range_restrictions.append(next_item[0]) found_range_restrictions.append(next_item[1]) else: # Either not list or greater than 2, so unexpected format, return error # found_range_restrictions.append("Unexpected format for: range restriction") print( "Unexpected format for: range restriction. Occured in:", block_name, param_name, ) except: # Word cannot be evaluated # found_range_restrictions.append("Unexpected format for: range restriction") print( "Unexpected format for: range restriction. Occured in:", block_name, param_name, ) if param_value_docstring[i] in [ "accepted" ] and param_value_docstring[i + 1] in ["characters:"]: # Value is restricted to being certain characters, these are seperated by the word 'or' # Iterate through every 2nd value after 'accepted characters:' (seperated by 'or') for j in range(i + 2, len(param_value_docstring), 2): # If special character (.isalnum() returns False), append to list of detected signs if not param_value_docstring[j].isalnum(): found_symbol_restrictions.append( param_value_docstring[j] ) # If the next value isn't 'or', we have reached end of character list if param_value_docstring[j + 1] not in ["or"]: break if param_value_docstring[i] in [ "defaults" ] and param_value_docstring[i + 1] in ["to"]: # Value only has 1 default, which should follow after the word 'to' # Try to evaluate value, if successful, this is some form of non string try: param_value = eval(param_value_docstring[i + 2]) # If unsuccesful, trying to eval a string, so set default value as str instead except NameError: param_value = param_value_docstring[i + 2] elif param_value_docstring[i] in ["one"] and param_value_docstring[ i + 1 ] in ["of:"]: # Value has list of possible options, parse list to find value after which is '[default]' found_default_value = False for j in range(i + 2, len(param_value_docstring)): # Append found value (if not '[default]', to list of keyword restrictions) if param_value_docstring[j] not in ["[default]"]: found_keyword_restrictions.append( param_value_docstring[j].strip("'").lower() ) # Check if this is the default value, (next value should say '[default]') if not found_default_value and param_value_docstring[ j + 1 ] in ["[default]"]: # Default value found in list of possible values # Try to evaluate value, if successful, then some form of non string try: param_value = eval(param_value_docstring[j]) # If unsuccesful, trying to eval a string, so set default value as str instead except NameError: param_value = param_value_docstring[j] found_default_value = True # If no defualt value was found, param_value will stay as None for safety # if not found_default_value: # param_value = None # Important break! # Once the inner loop has gone through the remainder of the words from the docstring, and # found the default value and the other keywords, we need to break out of the outer loop, to # continue to the next parameter break # If any keyword restrictions were found if found_keyword_restrictions: restriction = ["keywords"] # Append the found keyword restrictions into the restriction restriction.append(found_keyword_restrictions) param_restrictions.append(restriction) # If any range restrictions were found if found_range_restrictions: restriction = ["range"] # Append the found range restrictions into the restriction restriction.append(found_range_restrictions) param_restrictions.append(restriction) # If any size restrictions were found if found_size_restrictions: restriction = ["size"] # Append the found size restrictions into the restriction restriction.append(found_size_restrictions) param_restrictions.append(restriction) # If any symbol restrictions were found if found_symbol_restrictions: restriction = ["symbol"] # Append the found symbol restrictions into the restriction restriction.append(found_symbol_restrictions) param_restrictions.append(restriction) # Check if parameter value is None, if so, add NoneType to type restriction if param_value is None: # If Nonetype has already been added, this will stop it from being entered multiple times type_to_insert = type(None) if not type_to_insert in found_types: found_types.append(type_to_insert) # If several types for a parameter are detected, create a parameter restriction accordingly if len(found_types) > 1 or type(None) in found_types: found_type_restrictions = [] for item in found_types: found_type_restrictions.insert(0, item) restriction = ["type"] restriction.append(found_type_restrictions) param_restrictions.append(restriction) # Using the extracted information, construct the parameter list block_parameters.append( [ param_name, param_type, param_value, param_restrictions, param_tooltip, ] ) except Exception as e: print( "@@@@@@@ Fatal error: Cannot parse parameter info to construct parameter for block: -> '" + block_type + "', parameter name: -> '" + param_name + "'.@@@@@@" ) print("@@@@@@@ Printing exception below: @@@@@@@") print(e) if block_classname in ["SubSystem", "OutPort"]: block_parameters.append(inlabels_param) if block_classname in ["SubSystem", "InPort"]: block_parameters.append(outlabels_param) # ----------------------------------------------------------------------------------------------------- # Section for importing block class from its module, and assigning to it the extracted information # ----------------------------------------------------------------------------------------------------- try: def __block_init__(self): Block.__init__(self, scene, window) self.title = copy.copy(self.title) self.block_type = copy.copy(self.block_type) self.parameters = copy.deepcopy(self.parameters) self.inputsNum = copy.copy(self.inputsNum) self.outputsNum = copy.copy(self.outputsNum) self.input_names = copy.deepcopy(self.input_names) self.output_names = copy.deepcopy(self.output_names) self.icon = copy.copy(self.icon) self.flipped_icon = os.path.join( os.path.splitext(copy.copy(self.icon))[0] + "_flipped.png" ) self.block_url = copy.copy(self.block_url) self.width = copy.copy(self.width) self.height = copy.copy(self.height) self._createBlock(self.inputsNum, self.outputsNum) # Default size for all blocks size = [100, 100] try: size = size_map[block_classname] except KeyError: pass # Dynamically create class for this block # Assign the extracted block variables to this block new_block_class = type( block_classname, (Block,), { "__init__": __block_init__, "title": block_name, "block_type": block_type, "parameters": block_parameters, "inputsNum": block_inputsNum, "outputsNum": block_outputsNum, "input_names": block_input_names, "output_names": block_output_names, "icon": block_icon, "block_url": block_url, "width": size[0], "height": size[1], }, ) # Add this block to blocklist blocklist.append(new_block_class) # # If this block belongs to a new group of blocks, for which a list hasn't been made yet # # then make a list in the block_library, to hold those blocks # if block_parentclass not in imported_block_groups: # imported_block_groups.append(block_parentclass) # block_library.append([block_parentclass, []]) # # # Add this block to the group of blocks it belongs to # for i, group in enumerate(block_library): # # 1st element of group, will always be the group name (sinks, sources, functions, etc) # if group[0] == block_parentclass: # block_library[i][1].append([blockname(new_block_class), new_block_class]) # break # ----------------------------------------------------------------------------------------------- # If this block belongs to a new group of blocks, for which a list hasn't been made yet # then make a list in the block_library, to hold those blocks if block_group not in imported_block_groups: imported_block_groups.append(block_group) block_library.append([block_group, []]) # Add this block to the group of blocks it belongs to for i, group in enumerate(block_library): # 1st element of group, will always be the group name (sinks, sources, functions, etc) if group[0] == block_group: block_library[i][1].append( [blockname(new_block_class), new_block_class] ) break except KeyError: print( "Error: attempted to create a block class that isn't supported. Attempted class type: ", block_classname, ) return block_library ================================================ FILE: bdsim/bdedit/block_main_block.py ================================================ # BdEdit imports from bdsim.bdedit.block import * from bdsim.bdedit.block_socket import * from bdsim.bdedit.block_graphics_block import * # ============================================================================= # # Defining the Main Block Class, which allows for the loaded/built model to # be invoked as a subprocess when that model is run # # ============================================================================= # Todo - update doc string class Main(Block): """ """ # ----------------------------------------------------------------------------- def __init__(self, scene, window, file_name=None, name="Main Block", pos=(0, 0)): """ """ super().__init__(scene, window) # Same variables inherited from the Block class self.scene = scene self.window = window self.block_type = blockname(self.__class__) self.width = 100 self.height = 100 self.setDefaultTitle(name) self.parameters = [["file name", str, file_name, []]] self.block_url = "" self.icon = ":/Icons_Reference/Icons/main.png" self.flipped = False self.flipped_icon = os.path.join( os.path.splitext(self.icon)[0] + "_flipped.png" ) self.inputsNum = 0 self.outputsNum = 0 self.grBlock = GraphicsBlock(self) self.parameterWindow = None # The Main block is automatically stored into the Scene, # and visually added into the GraphicsScene self.scene.addBlock(self) self.scene.grScene.addItem(self.grBlock) self._createParamWindow() self.scene.has_been_modified = True ================================================ FILE: bdsim/bdedit/block_param_window.py ================================================ # Library imports import ast import math # PyQt5 Imports from PyQt5.QtWidgets import * from PyQt5.QtGui import QPixmap, QDesktopServices from PyQt5.QtCore import Qt, QRect # BdEdit imports from bdsim.bdedit.Icons import * # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Socket positioning variables - used for determining what side of the block the # socket should be drawn LEFT = 1 RIGHT = 3 # Socket sign variables - used for logic associated with drawing symbols for the # PROD and SUM blocks PLUS = "+" MINUS = "-" MULTIPLY = "*" DIVIDE = "/" # Variable for enabling/disabling debug comments DEBUG = False # ============================================================================= # # Defining the ParamWindow Class, which holds information of how the parameter # window - that holds all the user-editable block parameters - appears. It also # contains the logic for creating the ParamWindow and the logic for sanity # checking the block parameters once they have been edited by the user. # # ============================================================================= class ParamWindow(QWidget): """ The ``ParamWindow`` Class extends the ``QWidget`` Class from PyQt5. The ParamWindow Class controls: - how the parameter window appears visually, - where it is located within the BdEdit application window, - the displayed parameters from the Block the parameter window is related to, - sanity checking on user-edits to the Block parameters, - pop-up user feedback for successful or unsuccessful Block parameter edits. """ # ----------------------------------------------------------------------------- def __init__(self, block, parent=None): """ This method initializes an instance of the ``ParamWindow`` Class. :param block: the Block this ParamWindow instance relates to :type block: ``Block`` :param parent: the parent widget this ParamWindow belongs to (should be None) :type parent: None, optional """ super().__init__(parent) # The Block this parameter window relates to is stored internally, # so are its parameters self.block = block self.parameters = self.block.parameters # Parameter_values is a list into which the text responses of the user # will be appended to, when they make changes to a Block parameter self.parameter_values = [] # The parameter window will display vertically on the RHS of the BdEdit # application window, hence its layout manager will be a vertical one self.layout = QVBoxLayout() # Definition of with of the parameter window, and the lines holding # the block parameters inside it. The width is fixed to 300 pixels, scaled # to what 300 pixels should look like on 2560 screen width resolution self._parameter_line_width = 150 self._width = 300 * self.block.window.scale self.setFixedWidth(self._width) # Variable to update title of model when parameter values have been changed, # to indicate that there is unsaved progress self.paramsWereChanged = False # Further initializing necessary parameter window settings, and calling # the method that will build the it self.initUI() self.buildParamWindow() # ----------------------------------------------------------------------------- def initUI(self): """ This method adds a 'Parameter Window' label to the ParamWindow, sets the alignment of items within the parameter window, sets the background to auto fill, and finally, sets the layout manager of the ParamWindow. """ # The follow label is added to the parameter window, naming it # Items within the parameter window layout manager are set to align towards the top param_window_label = QLabel("Block settings") param_window_label.setAlignment(Qt.AlignCenter) self.layout.addWidget(param_window_label) self.layout.setAlignment(Qt.AlignTop) # Its background is filled self.setAutoFillBackground(True) self.setLayout(self.layout) # ----------------------------------------------------------------------------- def displayDocumentationURL(self): """ This method opens the url link associated with this blocks' documentation. """ QDesktopServices.openUrl(QtCore.QUrl(self.block.block_url)) # ----------------------------------------------------------------------------- def closeQMessageBox(self): """ This method closes a pop-up user-feedback message if one already exists within the parameter window. This prevents multiple messages from being displayed at the same time, and ensure the message the user sees, is the most relevant one. """ # If a message widget already exists in the ParamWindow, remove it # ParamWindow (self) has several widgets already, some of which hold QLabels, QEditLines or a QPushButton # Iterate through all of ParamWindow's widgets for widget in self.children(): # Grab the children of the ParamWindow's child widget widgets_children = widget.children() # Iterate through all this widget's children widgets for child in widgets_children: # If a child widget happens to be a QMessageBox, then remove that widget from the ParamWindow layout if child.__class__ == QMessageBox: self.layout.removeWidget(widget) break # ----------------------------------------------------------------------------- def displayPopUpMessage(self, title, text, messageType): """ This method displays a pop-up user-feedback message within the parameter window, notifying them of either a successful or unsuccessful update to the Blocks' parameters. This method will also trigger the message to auto-close, after 1.5 seconds for a successful update, and after 5 seconds for an unsuccessful update. If at any point the user updates the Block parameters before this time is elapsed, the current (old) message will be removed and replaced by the most relevant message. An unsuccessful update attempt will also issue an error sound when the message is opened. :param title: the title of the message box displayed :type title: str :param text: the text that fills the body of the displayed message box :type text: str :param messageType: variable that calls what type of message to display ("Success", "Error") :type messageType: str """ def closeMessage(): self.timer.stop() self.message.close() # If a message box is already displayed within the ParamWindow, it will be closed self.closeQMessageBox() # Create a widget to wrap the QMessageBox into, that it may be displayed in the ParamWindow self.popUp = QWidget() self.popUp.layout = QVBoxLayout() self.message = QMessageBox(self) # Manually set the width of the QMessageBox layout to control text wrapping # The layout is a grid, so setting width of 1st row/column cell will set width of the widget self.message.item = self.message.layout().itemAtPosition(0, 1) self.message.item.widget().setFixedWidth(self.width() - 100) # If an error message - critical icon, if success message - green tick icon if messageType == "Error": self.message.setIcon(QMessageBox.Critical) self.message.setIconPixmap(QPixmap(":/Icons_Reference/Icons/error.png")) message_duration = 5 elif messageType == "Success": self.message.setIconPixmap(QPixmap(":/Icons_Reference/Icons/success.png")) message_duration = 1.5 # Set the title and text for the message self.message.setText("" + title + "") self.message.setInformativeText(text) # Set style (adding a black border around messagebox) self.message.setStyleSheet("QMessageBox { border : 1px solid black;}") # Set message modality to be non-blocking (by default, QMessageBox blocks other actions until closed) self.message.setWindowModality(Qt.NonModal) # Add the QMessageBox to the popUp widget, then add to the layout of the ParamWindow widget self.popUp.layout.addWidget(self.message) self.popUp.setLayout(self.popUp.layout) self.layout.addWidget(self.popUp) # Create timer to keep message opened for 5 seconds if error, 1.5 seconds if success self.timer = QtCore.QTimer() self.timer.setInterval(int(message_duration * 1000)) self.timer.timeout.connect(closeMessage) self.timer.start() # ----------------------------------------------------------------------------- def buildParamWindow(self): """ This method handles the building of the ParamWindow instance. These instances will always look exactly the same in terms of the information they contain, however the Block parameters and the block type will vary depending on the Block this ParamWindow relates to. The ParamWindow is built by adding items into the QWidget that represents this parameter window. For each item added into the QWidget, a label is displayed on the left-hand-side, and either the non-editable value (label) is displayed on the right-hand-side, or an editable line is displayed and populated with the respective parameters' current value. The ParamWindow is populated by, first, adding a non-editable line displaying the selected Blocks' type. Next, the Blocks' current title is added with an editable line. Following this, each Block parameter looped through and added with an editable line in which the current value of the Block parameter is populated. Changing this value will prompt the sanity checking of the value entered, which is handled by the updateBlockParameters() method. """ # Make the first row, which contains two labels for the block's type self.row1 = QWidget() self.row1.layout = QHBoxLayout() # Make a label of the block type self.block_type_label = QLabel("type: ") # Make an editable line for the type, and populate it with the current block type self.block_type = QLabel( "" + str(self.block.block_type) + " Block " ) # Set the width of the editable line to the above-defined width (150 pixels) self.block_type.setFixedWidth(self._parameter_line_width) # Add the label and editable line into our row widget self.row1.layout.addWidget(self.block_type_label, alignment=Qt.AlignCenter) self.row1.layout.addWidget(self.block_type, alignment=Qt.AlignCenter) # Add out type row widget to the layout of the parameter window self.row1.setLayout(self.row1.layout) self.layout.addWidget(self.row1) # Make the second row, which contains the label and editable line for the block's title self.row2 = QWidget() self.row2.layout = QHBoxLayout() # Make a label of the block title self.title_label = QLabel("name: ") # Make an editable line for the title, and populate it with the current block title self.title_line = QLineEdit(self.block.title) # Set the width of the editable line to the above-defined width (150 pixels) self.title_line.setFixedWidth(self._parameter_line_width) # Add the label and editable line into our row widget self.row2.layout.addWidget(self.title_label, alignment=Qt.AlignCenter) self.row2.layout.addWidget(self.title_line) # Add out title row widget to the layout of the parameter window self.row2.setLayout(self.row2.layout) self.layout.addWidget(self.row2) # If the block has parameters, build part of the paramWindow that displays those if self.parameters: # For each block parameter for parameter in self.parameters: # Create a QWidget that will represent the row that parameter is displayed in self.row_x = QWidget() self.row_x.layout = QHBoxLayout() # Make a label of that parameters' name self.label = QLabel( "" + parameter[0] + ": " + "" ) try: self.label.setToolTip(parameter[4]) except IndexError: print(parameter) # If the parameter type is a boolean, create the intractable space as a checkbox, otherwise # make an editable line for that parameter, and populate it with the parameters' current value if not issubclass(parameter[1], bool): self.line = QLineEdit(str(parameter[2])) else: try: self.line = QCheckBox() self.line.setChecked(parameter[2]) except TypeError: print("bad thing") # Set the width of the editable line to the above-defined width (150 pixels) self.line.setFixedWidth(self._parameter_line_width) # Append the current value of the parameter into the following list (for later comparison) self.parameter_values.append(self.line) # Add the label and editable line into our row widget self.row_x.layout.addWidget(self.label, alignment=Qt.AlignCenter) self.row_x.layout.addWidget(self.line, alignment=Qt.AlignCenter) # Add out row widget to the layout of the parameter window self.row_x.setLayout(self.row_x.layout) self.layout.addWidget(self.row_x) # Finally add an update button, which will trigger the sanity checking of the # current values within the editable lines self.update_button = QPushButton("Update Parameters") self.update_button.clicked.connect(self.updateBlockParameters) self.layout.addWidget(self.update_button) # Add a button linked to the URL of this block's documentation self.block_url_Button = QPushButton("View documentation") self.block_url_Button.clicked.connect(self.displayDocumentationURL) self.layout.addWidget(self.block_url_Button) # ----------------------------------------------------------------------------- def updateBlockParameters(self): """ This method calls for each of the parameters within the parameter window to be sanity checked by the getSafeValue() method, which determines whether or not a provided value is compatible with the parameter type (defined in the Block Class) and is safe to override the current value. If that check is returned to be safe, this method will handle the updating of the Block parameters, as well as triggering a successful update attempt message. If the check is returned as not safe, meaning the given value is not compatible, an unsuccessful update attempt error message will be prompted, notifying the user of the incompatible parameter value they have set, and either what the compatible types or values are. This logic also applies when a user changes the blocks' title to one that already exists, which will cause a duplicate error message to display. Some parameters values directly affect the GraphicsBlock of the Block they relate to. For example, blocks that can have multiple input or output sockets, will have a parameter that controls how many of these sockets the block has. And once edited, this method will trigger an appropriate number of sockets to be created or deleted. This parameter can also affect the GraphicsBlock when too many sockets are created, requiring the block to be resized. The triggering of this resizing will also be issued from within this method. """ # ----------------------------------------------------------------------------- def makeBadInputErrorMsg(options): """ This method returns the correct text that should be displayed within the body of the pop-up user-feedback message. The text that is displayed here is extracted from the associated extra-options as defined in the relating Block. :param options: the extra-options associated with this parameter (defined in this Block) :type options: list, of str, list :return: the formatted message to be displayed :rtype: str """ # Start with an empty message message = "" # Num_options is the number of lists (of accepted keywords, types, range, symbol) inside the option list num_options = len(options) # Check the options that have been assigned to this parameter for option in options: # If the options consist keywords that this parameter is restricted to if option[0] == "keywords": message += "one of " + str(option[1]) + "" # If the options consist of a range this parameters is restricted to elif option[0] == "range": message += ( "a value between " + str(option[1][0]) + " and " + str(option[1][1]) + "" ) # If the options consist of additional types this parameter is restricted to elif option[0] == "type": typeString = [] for optionType in option[1]: typeString.append(optionType.__name__) message += "of type " + str(typeString) + "" # If the options consist of certain sign characters this parameter is restricted to elif option[0] == "symbol": message += "a combination of " + str(option[1]) + "" # If the options consists of a size restriction on how many elements are allowed in the list elif option[0] == "size": message += ( "limited to a size of " + str(option[1]) + "" ) # If a parameter has multiple options (e.g. range and type restrictions) # separate the options with "or" until only 1 option remains if num_options > 1: message += " or " num_options -= 1 return message # Update the title duplicate_title = [] # UpdateName is returned as None if there are no issues with setting a title # If title is a duplicate, a list of ["@DuplicateName@", given_name] is returned updateName = self.block.setTitle(self.title_line.text()) self.block.grBlock._draw_title = True if updateName: if updateName[0] == "@DuplicateName@": duplicate_title.append(updateName[1]) else: self.paramsWereChanged = True # Iterator for loop i = -1 # An error message can consist of an invalid_input (incorrect parameter type) # or a bad_input (incompatible with option restrictions), and initially these # are set to being empty. If parameters cannot be set due to errors, these will # be appended into these empty lists invalid_input, bad_inputs, bad_socket_labels = [], [], [] # Check if the block has parameters, if so, go through the sanity checking of each parameter value if self.parameters: # For each definition of a parameter, in the blocks' defined parameters for [paramName, paramType, paramVal, paramOptions, _] in self.parameters: i += 1 # If parameter type is boolean, then retrieve checked state of checkbox, otherwise # extract the text from the editable line as the value to set the parameter to if not issubclass(paramType, bool): inputValue = self.parameter_values[i].text() else: inputValue = str(self.parameter_values[i].isChecked()) # If a value has been provided for that parameter, perform sanity checking on that input if inputValue: if self.parameters[i][0] in ["nin", "ops", "signs", "nout"]: inputInCompatibleFormat = self.getSafeValue( inputValue, paramType, paramOptions ) else: if inputValue.startswith("="): inputInCompatibleFormat = inputValue else: inputInCompatibleFormat = self.getSafeValue( inputValue, paramType, paramOptions ) # If in DEBUG mode, this code will return the name, type, current value of the parameter attempting to update # and then for the value that will override the parameter, the value, type(of the value), and whether it is or isn't compatible if DEBUG: print( "paramName, paramType, paramVal - inputValue, type, compatible", [ paramName, paramType, paramVal, "-", inputValue, type(inputValue), inputInCompatibleFormat, ], ) # Once the sanity check has been performed, if the type is valid, check for parameter restrictions if inputInCompatibleFormat != "@InvalidType@": # If the sanity check returns that the parameter doesn't meet its restrictions if inputInCompatibleFormat == "@BadFormat@": # Append the parameter edited, and the parameter options, as a bad_input bad_inputs.append([paramName, paramOptions]) else: # Otherwise if both sanity checks pass, value is safe to update, so # Check if the given value is different to what is already set as the param value # If it is the same, don't update the value if inputInCompatibleFormat == paramVal: pass # Otherwise update the value else: # Set the current parameter equal to edited parameter value self.parameters[i][2] = inputInCompatibleFormat self.paramsWereChanged = True # If self.parameter relates to controlling the number of inputs a block has if self.parameters[i][0] in ["nin", "ops", "signs"]: # Grab the number of required input sockets if self.parameters[i][0] == "nin": num_sockets = self.parameters[i][2] else: num_sockets = len( self.parameter_values[i].text() ) # Don't do anything, if the provided number of input sockets matches the number the block already has, # or if the symbols (+,-,*,/) for the block haven't changed # if len(self.block.inputs) == num_sockets and inputInCompatibleFormat == paramVal: # pass # else: # If the given number of input sockets matches the number the block already has, or if the number of # symbols (+,-,*,/) for the block hasn't changed if len(self.block.inputs) == num_sockets: # If the values of the signs hasn't changed, then don't do anything if inputInCompatibleFormat == paramVal: pass # If the values of the signs has changed (but the number of signs is still the same), just # update the signs without removing the wires else: # Split the socket signs by number of characters given chars = [ char for char in inputInCompatibleFormat ] # Go through each of the chars from the string of symbols for this block's signs # and set the respective socket to that symbol for j, char in enumerate(chars): self.block.inputs[j].socket_sign = char else: # If the block already has input sockets, grab their orientation (LEFT / RIGHT) then delete # Else, draw input socket with default orientation (LEFT) and no need to delete as block has no input sockets if self.block.inputs: orientation = self.block.inputs[0].position # Remove all current input sockets self.block.inputs[0].removeSockets("Input") else: orientation = LEFT # Recreate input sockets to the number provided # If self.block is a SUM or PROD block, this will also update the input sockets' sign (+,_,*,/) self.block.inputsNum = num_sockets self.block.makeInputSockets( self.block.inputsNum, orientation ) # If self.parameter relates to controlling the number of outputs a block has if self.parameters[i][0] in ["nout"]: # Grab number of required output sockets num_sockets = self.parameters[i][2] # If provided number of output sockets matches the number the block already has, don't do anything if len(self.block.outputs) == num_sockets: pass else: # If the block already has output sockets, grab their orientation (LEFT / RIGHT) then delete # Else, draw output socket with default orientation (RIGHT) and no need to delete as block has no output sockets if self.block.outputs: orientation = self.block.outputs[0].position # Remove all current output sockets self.block.outputs[0].removeSockets( "Output" ) else: orientation = RIGHT # Recreate output sockets to the number provided self.block.outputsNum = num_sockets self.block.makeOutputSockets( self.block.outputsNum, orientation ) # If self.parameter relates to controlling the names of inport labels on subsystem blocks if self.parameters[i][0] == "inport labels": # First check if labels are given for the sockets, if not, don't do anything special if self.parameters[i][2] is not None: input_length = len(self.parameters[i][2]) if input_length > 0: # Find number of inputs controlled by nin found_nin = False for params in self.parameters: if params[0] == "nin": num_nin_sockets = params[2] found_nin = True break # Check if nin parameter was found, if not, return error as we need to know how many sockets to draw if found_nin == False: print( "Error: Cannot draw InPort labels as no 'nin' parameter was found to know how many sockets to draw." ) else: # If nin parameter was found, check if number of given InPort labels matches number of sockets if input_length != num_nin_sockets: bad_socket_labels.append( [paramName, paramOptions] ) else: # If parameter value hasn't changed, don't do anything if ( inputInCompatibleFormat == paramVal ): pass else: # If the block already has input sockets, grab their orientation (LEFT / RIGHT) then delete # Else, draw input socket with default orientation (LEFT) and no need to delete as block has no input sockets self.block.input_names = [ str(j) for j in self.parameters[i][ 2 ] ] if self.block.inputs: for k, socket in enumerate( self.block.inputs ): socket.updateSocketSign( self.block.input_names[ k ] ) # Otherwise if the parameter value is None or [], then remove all the socket labels for a block if there are any. elif not self.parameters[i][2]: if self.block.inputs: if self.block.input_names: for socket in self.block.inputs: socket.updateSocketSign(None) # If self.parameter relates to controlling the names of outport labels on subsystem blocks if self.parameters[i][0] == "outport labels": # First check if labels are given for the sockets, if not, don't do anything special if self.parameters[i][2] is not None: input_length = len(self.parameters[i][2]) if input_length > 0: # Find number of outputs controlled by nout found_nout = False for params in self.parameters: if params[0] == "nout": num_nout_sockets = params[2] found_nout = True break # Check if nout parameter was found, if not, return error as we need to know how many sockets to draw if found_nout == False: print( "Error: Cannot draw OutPort labels as no 'nout' parameter was found to know how many sockets to draw." ) else: # If nout parameter was found, check if number of given OutPort labels matches number of sockets if input_length != num_nout_sockets: bad_socket_labels.append( [paramName, paramOptions] ) else: # If parameter value hasn't changed, don't do anything if ( inputInCompatibleFormat == paramVal ): pass else: # If the block already has output sockets, grab their orientation (LEFT / RIGHT) then delete # Else, draw output socket with default orientation (RIGHT) and no need to delete as block has no output sockets self.block.output_names = [ str(j) for j in self.parameters[i][ 2 ] ] if self.block.outputs: for k, socket in enumerate( self.block.outputs ): socket.updateSocketSign( self.block.output_names[ k ] ) # Otherwise if the parameter value is None or [], then remove all the socket labels for a block if there are any. elif not self.parameters[i][2]: if self.block.outputs: if self.block.output_names: for socket in self.block.outputs: socket.updateSocketSign(None) # Else the edited value is of the wrong type, display an error message else: # Append the parameter edited, and the required type, as an invalid_input invalid_input.append([paramName, paramType]) # Else no value was given for that input, display an error message else: # Append the parameter edited, and the required type, as an invalid_input invalid_input.append([paramName, paramType]) # Once all the parameters are sanity checked, # If the title has been set to a duplicate name, display a duplicate error message if duplicate_title: errorMessageText = "" errorMessageTitle = "Duplicate Block Title" errorMessageText += ( "A block named '" + duplicate_title[0] + "' already exists, please choose another." ) self.displayPopUpMessage(errorMessageTitle, errorMessageText, "Error") # If any parameters have been returned with invalid types, display an invalid type error message elif invalid_input: errorMessageText = "" errorMessageTitle = "Input Types Not Compatible" for incompatibleInputs in invalid_input: errorMessageText += ( "Expected '" + "" + incompatibleInputs[0] + "' to be type " + incompatibleInputs[1].__name__ + "" ) errorMessageText += "
" self.displayPopUpMessage(errorMessageTitle, errorMessageText, "Error") # If any parameters don't meet their option restrictions, display a bad input error message elif bad_inputs: errorMessageText = "" errorMessageTitle = "Input Value Not Allowed" for badInput in bad_inputs: # Different error message based on type of option errorMessageText += ( "Parameter '" + "" + badInput[0] + "' must be " ) errorMessageText += makeBadInputErrorMsg(badInput[1]) errorMessageText += "
" self.displayPopUpMessage(errorMessageTitle, errorMessageText, "Error") # If labels are given for InPort, OutPort or SubSystem blocks which don't match the respective nin/nout value for number of sockets, display a bad socket label input error message elif bad_socket_labels: errorMessageText = "" errorMessageTitle = "Inconsistent Number of Given Socket Labels" for badSocketLabel in bad_socket_labels: # Different error message based on type of option errorMessageText += ( "Parameter '" + "" + badSocketLabel[0] + "' must correspond to number of nin/nout sockets." ) self.displayPopUpMessage(errorMessageTitle, errorMessageText, "Error") # Otherwise if there were no issues with updating the block parameters, display a success message, yay! else: successMessageText = "Successfully updated block parameter values!" successMessageTitle = "Success!" self.displayPopUpMessage(successMessageTitle, successMessageText, "Success") # If a parameter update has changed a value then update the title of the model, # to indicate that there is unsaved progress if self.paramsWereChanged: self.paramsWereChanged = False self.block.scene.has_been_modified = True self.block.scene.history.storeHistory("Block parameters updated") # Finally, notify the GraphicsBlock to update itself, should the number of sockets, or the block # height have been called to change self.block.grBlock.update() @staticmethod # ----------------------------------------------------------------------------- def getSafeValue(inputValue, requiredType, requiredOptions): """ This method takes an input value (which is the value a parameter is being checked if it can be updated to), and checks whether it matches an allowable type that has been defined for that parameter within the grandchild Block Class. If the input value doesn't match the required type, an invalid type str will be returned. If the input value does match the required type, it is further checked, whether it matches any further restrictions placed onto that parameter from within the grandchild Block Class. If the value doesn't meet the criteria of the restrictions, a bad input str will be returned. If the input does match the criteria of the restriction, it will be converted to the type it must be in and returned. :param inputValue: the value which the parameter would be updated to :type inputValue: str :param requiredType: the value type which is required for this parameter :type requiredType: type, determined by the grandchild Block Class :param requiredOptions: a list of restrictions placed onto this parameter :type requiredOptions: list :return: - str (if incompatible type or restriction criteria not met), - requiredType (if compatible type and restrictions are met) :rtype: type, determined by the grandchild Block Class """ # ----------------------------------------------------------------------------- def isValueInOption(value, options): """ This method checks whether the edited parameter value meets the option restrictions placed on it by the Block it was defined in. The option restrictions consist of a list equivalent to: - [["restriction type1" [restrictions]], ["restriction type2" [restrictions]]]. The edited parameter is checked whether it meets the conditions of 'restrictions' for each 'restriction type'. :param value: the edited parameter value being checked :type value: any :param options: the list of restriction options placed on this parameter :type options: list :return: - value (if meets criteria of placed restriction), - bad_format (if criteria is not met) :rtype: - any (if criteria met), - str (if criteria not met) """ # If the parameter has restrictions placed on it if options: returnValue = "@BadFormat@" # For each restriction that is placed onto the parameter for option in options: # If the placed restriction is a set of keywords, if option[0] == "keywords": # Check if the given value matches one of those keywords if value.lower() in option[1]: returnValue = value break # If the placed restriction is a range of values elif option[0] == "range": # Check if given value is within that range if option[1][0] <= value <= option[1][1]: returnValue = value break # If the placed restriction are additional accepted types elif option[0] == "type": # Check if the None is one of the accepted types if type(None) in option[1]: # If the value given is None, return None if isinstance(value, str) and value.lower() in ["none"]: returnValue = None break # Check if given value matches any accepted types if type(value) in option[1]: returnValue = value break # If the placed restriction are a set of allowable character symbol elif option[0] == "symbol": # Check if value is a string if isinstance(value, str): # First get the max possible wrong characters in this string num_of_wrong_symbols = len(value) # Check each character within the given value against the set of allowable characters for sign in value: # For every character in the set of allowable characters, reduce the number # of wrong characters by 1 if sign in option[1]: num_of_wrong_symbols -= 1 # If all characters within the string match a set of allowable characters if num_of_wrong_symbols == 0: returnValue = value break # Otherwise return badformat else: returnValue = "@BadFormat@" break # If the placed restriction are to the number of elements the parameter can have elif option[0] == "size": # Try to evalute the param value, and catch if it is a string try: # If input isn't a word (is a tuple, list, dict, num) try to evaluate it try: param_val = eval(value) # If a type error is thrown, then the input might already have been evaluated previously except TypeError: param_val = value # Check if the size of parameter is measureable, if not then its an int or float try: param_length = len(param_val) # If the size of the parameter meets one of the allowable sizes (e.g. 0, 2 or 4) then return the value if param_length in option[1]: returnValue = value else: returnValue = "@BadFormat@" break # If parameter isn't measureable, return bad format as param is an int/float, and size of a singular number cannot be checked except TypeError: returnValue = "@BadFormat@" break # If input is a word, evaluating it will try to match it to a variable name, which doesn't exist, # so in this case, don't check the size restriction on this parameter value except NameError: pass # Return the value that has been set to be returned return returnValue # Else, if no restrictions are placed on this parameter, return the value else: return value try: # If input must be string, only strings can be accepted if issubclass(requiredType, str): # If the input is not a number, but an acceptable string if isinstance(inputValue, str): # Check if no input has been given if len(inputValue) == 0: return "@InvalidType@" else: # return isValueInOption(inputValue.lower(), requiredOptions) return isValueInOption(inputValue, requiredOptions) # # Check if the input is made up of normal characters [a-zA-z0-9] # if inputValue.isalnum(): # return isValueInOption(inputValue.lower(), requiredOptions) # # # Otherwise the input has special characters (=,-,+,*,/,:,etc) # else: # # Check if string contains '=', as this is restricted to variable name definitions, and is not allowed for regular strings # if "=" in inputValue: # outcome = isValueInOption(inputValue, requiredOptions) # if outcome == "@BadFormat@": # return outcome # else: # return "@InvalidType@" # else: # return isValueInOption(inputValue.lower(), requiredOptions) else: return "@InvalidType@" # If input must be bool, only booleans can be accepted elif issubclass(requiredType, bool): # All input starts off as text, so True/False will be 'True'/'False' if isinstance(inputValue, str): # Check if the string matches true/false, and return accordingly if inputValue in ["True", "true"]: return True elif inputValue in ["False", "false"]: return False # Otherwise if string is None, check if this boolean parameter allows that type elif inputValue.lower() in ["none"]: return isValueInOption(inputValue.lower(), requiredOptions) else: return "@InvalidType@" else: return "@InvalidType@" # If input must be int, floats can be converted to int elif issubclass(requiredType, int): # Try applying int() to the input value, if possible, check restrictions on value try: requiredType(inputValue) return isValueInOption(requiredType(inputValue), requiredOptions) except ValueError: # If applying int() doesn't work but value is None, check if this is an allowable type if inputValue.lower() in ["none"]: return isValueInOption(inputValue, requiredOptions) else: return "@InvalidType@" # If input must be float, int can be converted to float elif issubclass(requiredType, float): # All inputValues come in as a string, so first check if this evaluated string is a float try: evaluated_value = eval(inputValue) # If value can be evaluated safely, check if the value is an instance of float if isinstance(evaluated_value, float): # If so, check further restrictions on this parameter, return the outcome return isValueInOption(evaluated_value, requiredOptions) # If value is not a float, but is none, check if this is an allowable type elif inputValue.lower() in ["none"]: return isValueInOption(inputValue, requiredOptions) # If value is not a float, and None is not allowed, return invalid type else: try: float_value = float(evaluated_value) return isValueInOption(float_value, requiredOptions) except (ValueError, TypeError): return "@InvalidType@" # If any exceptions arise while trying to evaluate, then the value is incorrect except: return "@InvalidType@" # If input must be a list, only list can be accepted elif ( issubclass(requiredType, list) or issubclass(requiredType, tuple) or issubclass(requiredType, dict) ): try: # If the input value can be evaluated as a list, tuple or dict, check restrictions on value ast.literal_eval(inputValue) if inputValue.lower() in ["none"]: return isValueInOption(inputValue, requiredOptions) else: return isValueInOption( ast.literal_eval(inputValue), requiredOptions ) except ValueError: return "@InvalidType@" # If input can be of type 'any', allow any value to be saved. This will also process the 'callable' type, in the same way. elif issubclass(requiredType, type(any)): # Return the outcome of check this input against any restrictions for this parameter # Type any or callable should be allowed to pass through a string return isValueInOption(inputValue, requiredOptions) except Exception as e: print(e) print( "Fatal Error: Recent changes to a parameter have caused an unforseen error.\nKnown info about parameter causing the error: given value ->", inputValue, "expected type ->", requiredType, "parameter restrictions ->", requiredOptions, ) return "@InvalidType@" ================================================ FILE: bdsim/bdedit/block_socket.py ================================================ # Library imports from collections import OrderedDict # BdEdit imports from bdsim.bdedit.interface_serialize import Serializable from bdsim.bdedit.block_graphics_socket import GraphicsSocket # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Socket positioning variables (in relation to where they're drawn on the block) LEFT = 1 TOP = 2 RIGHT = 3 BOTTOM = 4 # Socket type classification variables INPUT = 1 OUTPUT = 2 CONNECTOR = 3 # Variable for enabling/disabling debug comments DEBUG = False # ============================================================================= # # Defining the Socket Class, which controls the type of Socket drawn, its # position within the Block it relates to, and allows for Wires to connect # to the Block through these Sockets. # # ============================================================================= class Socket(Serializable): """ The ``Socket`` Class extends the ``Serializable`` Class from BdEdit, and defines how a socket is represented, and has all the necessary methods for creating, manipulating and interacting with a socket. This class allows for Wires to be connected to Blocks, while also controlling where on the Block the Sockets are drawn, and how they appear. This class includes information about the sockets': - type; - index; - position; - appearance; - parent Block; and - connected wire(s). """ # ----------------------------------------------------------------------------- def __init__( self, node, index=0, position=LEFT, socket_type=INPUT, multi_wire=True, socket_label=None, ): """ This method initializes an instance of the ``Socket`` Class. :param node: the associated Block this Socket relates to :type node: Block, required :param index: the height (along the side of the Block) this Socket should be drawn at :type index: int, optional, defaults to 0 :param position: the side ( LEFT(1) or RIGHT(3) ) this Socket should be drawn on :type position: enumerate, optional, defaults to LEFT(1) :param socket_type: this Socket's type (INPUT(1) or OUTPUT(2)) :type socket_type: enumerate, optional, defaults to INPUT(1) :param multi_wire: boolean of whether this Socket has multiple wires :type multi_wire: bool, optional, defaults to True """ super().__init__() self.node = node self.index = index self.position = position self.socket_type = socket_type self.socket_sign = socket_label self.is_multi_wire = multi_wire self.grSocket = GraphicsSocket(self) self.grSocket.setPos(*self.node.getSocketPosition(index, position)) self.wires = [] # ----------------------------------------------------------------------------- def getSocketPosition(self): """ This method retrieves and returns the [x,y] coordinates for where the current Socket should be drawn. :return: the [x,y] coordinates at which to place this Socket. :rtype: list of int, int """ res = self.node.getSocketPosition(self.index, self.position) return res # ----------------------------------------------------------------------------- def setConnectedEdge(self, wire): """ This method adds the given wire to the list of Wires that are connected to this Socket. :param wire: the wire connecting to this Socket :type wire: Wire, required """ self.wires.append(wire) # ----------------------------------------------------------------------------- def hasEdge(self): """ This method returns True if the current Socket has Wires connected to it, and False if no Wires are connected to it. :return: - True (If wires are connected to this Socket) - False (If no wires are connected to this Socket) :rtype: bool """ return self.wires is not None # ----------------------------------------------------------------------------- def removeWire(self, wire): """ This method removes the given Wire from the list of Wires connected to this Socket, if it is connected to this Socket. :param wire: the wire to be removed :type wire: Wire, required """ if wire in self.wires: self.wires.remove(wire) else: print("Socket remove edge not in list") # ----------------------------------------------------------------------------- def removeAllWires(self): """ This method removes all Wires connected to this Socket. """ # While there are Wires in this Socket's list of Wires, remove the first Wire while self.wires: wire = self.wires.pop(0) wire.remove() # ----------------------------------------------------------------------------- def isInputSocket(self): """ This method returns True if the current Socket is an input socket. :return: - True (If current Socket is an input Socket) - False (If current Socket is not an input Socket) :rtype: bool """ isInputSocket = False # Compare the id of the current socket against the id's of sockets # within the associated Blocks' list of input sockets for socket in self.node.inputs: # If current socket is in that list, it is an input socket if self.id == socket.id: isInputSocket = True break return isInputSocket # ----------------------------------------------------------------------------- def isOutputSocket(self): """ This method returns True if the current Socket is an output socket. :return: - True (If current Socket is an output Socket) - False (If current Socket is not an output Socket) :rtype: bool """ isOutputSocket = False # Compare the id of the current socket against the id's of sockets # within the associated Blocks' list of output sockets for socket in self.node.outputs: # If current socket is in that list, it is an output socket if self.id == socket.id: isOutputSocket = True break return isOutputSocket # ----------------------------------------------------------------------------- def updateSocketSign(self, value): """ This method updates the value of the socket label assigned to this socket. As none but a select few blocks (SubSystem, OutPort, InPort) support dynamic updating of the socket labels, this method should not be used unless it is for one of the mentioned blocks. :param value: new value to assign for socket label :type value: str, int, None or [] """ try: self.socket_sign = value except Exception: self.socket_sign = None # ----------------------------------------------------------------------------- def removeSockets(self, type): """ This method removes all of the input or output Sockets, relating to this Block, as specified by the type. This method removes all sockets of given type, associated with this ``Block``. :param type: the type of Socket to remove ("Input" or "Output") :type type: str, required """ # If in DEBUG mode, this code will return the type of Socket that has just # been removed, and that the socket removal process has started. When this # process is finished, a done message will be printed. if DEBUG: print("# Removing " + type + " Sockets", self) if DEBUG: print(" - removing grSockets") # This allows one method to be used for deleting input OR output sockets if type == "Input": socketTypes = self.node.inputs elif type == "Output": socketTypes = self.node.outputs # For each socket within the list determined by either Input or Output # socket type for socket in socketTypes: # Remove the graphics parent item (the associated graphics block class) # Remove the graphics socket class socket.grSocket.setParentItem(None) socket.grSocket = None # If this socket has any wires attached to it, remove them if socket.hasEdge(): for wire in socket.wires: wire.remove() # Finally remove the socket class from the associated block self.node.removeSockets(type) if DEBUG: print(" - everything is done.") # ----------------------------------------------------------------------------- def serialize(self): """ This method is called to create an ordered dictionary of all of this Sockets' parameters - necessary for the reconstruction of this Socket - as key-value pairs. This dictionary is later used for writing into a JSON file. :return: an ``OrderedDict`` of [keys, values] pairs of all essential ``Socket`` parameters. :rtype: ``OrderedDict`` ([keys, values]*) """ return OrderedDict( [ ("id", self.id), ("index", self.index), ("multi_wire", self.is_multi_wire), ("position", self.position), ("socket_type", self.socket_type), ] ) # ----------------------------------------------------------------------------- def deserialize(self, data, hashmap={}): """ This method is called to reconstruct a ``Socket`` when loading a saved JSON file containing all relevant information to recreate the ``Scene`` with all its items. :param data: a Dictionary of essential information for reconstructing a ``Socket`` :type data: OrderedDict, required :param hashmap: a Dictionary for directly mapping the essential socket variables to this instance of ``Socket``, without having to individually map each variable :type hashmap: Dict, required :return: True when completed successfully :rtype: Boolean """ # The id of this Socket is set to whatever was stored as its id in the JSON file. self.id = data["id"] # The remaining variables associated to this Socket are mapped to itself hashmap[data["id"]] = self return True ================================================ FILE: bdsim/bdedit/block_wire.py ================================================ # Library imports import time import copy from collections import OrderedDict # BdEdit imports from bdsim.bdedit.block_graphics_wire import * from bdsim.bdedit.interface_serialize import Serializable # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Wire type variables for choosing between the 3 possible wire styles WIRE_TYPE_DIRECT = 1 WIRE_TYPE_BEZIER = 2 WIRE_TYPE_STEP = 3 # Variable for enabling/disabling debug comments DEBUG = False DEBUG_OVERLAP = False # ============================================================================= # # Defining the Wire Class, which is used to define the Wires that connect the # blocks via the sockets, each wire has a start socket and a end socket and a # wire type that defines how the wires will be drawn. The code also updates # the wires as the blocks are moved around so they stay connected. # # ============================================================================= class Wire(Serializable): """ The ``Wire`` Class extends the ``Serializable`` Class from BdEdit, and defines how a wire is represented, and has all the necessary methods for creating, manipulating and interacting with a wire. This class connects start and end sockets to a created wire. The style of wire being drawn is also controlled by this Class: - a straight wire will have type DIRECT(1), - a curved or wave-like wire will have type BEZIER(2), - a stepped wire will have type STEP(3) This class includes information about the wires': - style; - end_socket; - start_socket; - point-to-point coordinates; - horizontal and vertical line segments; - intersection points with other wires (has been disabled). """ # ----------------------------------------------------------------------------- def __init__(self, scene, start_socket=None, end_socket=None, wire_type=3): """ This method initializes an instance of the ``Wire`` Class. :param scene: a scene (or canvas) in which the Wire is stored and shown (or painted into). Provided by the ``Interface``. :type scene: ``Scene``, required :param start_socket: the start Socket of this Wire :type start_socket: Socket, optional, defaults to None (automatically set) :param end_socket: the end Socket of this Wire :type end_socket: Socket, optional, defaults to None (automatically set) :param wire_type: the wire style of this Wire (DIRECT(1), BEZIER(2), STEP(3)) :type wire_type: enumerate, optional, defaults to STEP(3) (automatically set) """ super().__init__() self.scene = scene # By default the Wire starts with no sockets, these are automatically # set by decorators when a Wire instance is created self._start_socket = None self._end_socket = None self.start_socket = start_socket self.end_socket = end_socket self.wire_type = wire_type self.wire_coordinates = [] self.horizontal_segments = [] self.vertical_segments = [] self.intersections = [] # Once created, the wire is added to the Scene self.scene.addWire(self) # ----------------------------------------------------------------------------- @property def start_socket(self): """ This method is a decorate that gets the start socket of this Wire. :return: the start Socket of this Wire :rtype: Socket """ return self._start_socket # ----------------------------------------------------------------------------- @start_socket.setter def start_socket(self, value): """ This method is a decorator that sets the start socket of this Wire to the given value (which is a Socket). :param value: the Socket being assigned :type value: Socket """ # If this wire was assigned to some socket before, delete this wire instance from that socket if self._start_socket is not None: self._start_socket.removeWire(self) # Assign a new start socket for this wire self._start_socket = value # Add this wire to the associated Socket (in the Socket Class) if self.start_socket is not None: self.start_socket.setConnectedEdge(self) # ----------------------------------------------------------------------------- @property def end_socket(self): """ This method is a decorate that gets the end socket of this Wire. :return: the end Socket of this Wire :rtype: Socket """ return self._end_socket # ----------------------------------------------------------------------------- @end_socket.setter def end_socket(self, value): """ This method is a decorator that sets the end socket of this Wire to the given value (which is a Socket). :param value: the Socket being assigned :type value: Socket """ # If this wire was assigned to some socket before, delete this wire instance from that socket if self._end_socket is not None: self._end_socket.removeWire(self) # Assign a new start socket for this wire self._end_socket = value # Add this wire to the associated Socket (in the Socket Class) if self.end_socket is not None: self.end_socket.setConnectedEdge(self) # ----------------------------------------------------------------------------- @property def wire_type(self): """ This method is a decorate that gets the wire type (or style) of this Wire. :return: the style of this Wire (DIRECT(1), BEZIER(2), STEP(3)) :rtype: enumerate """ return self._wire_type # ----------------------------------------------------------------------------- @wire_type.setter def wire_type(self, value): """ This method is a decorator that sets the wire type (or style) of this Wire to the given value (which is an enum). This method will determine how the wire is drawn. By default the wire type is set to draw STEP(3) wires, however other line styles can also be added here. The wire type dictate which corresponding GraphicsWire class is called to draw the wire. :param value: the wire type this Wire is being set to :type value: enumerate """ # If this was already previously assigned, remove its GraphicsWire if hasattr(self, "grWire") and self.grWire is not None: self.scene.grScene.removeItem(self.grWire) # Assign a new wire type for this wire self._wire_type = value # Depending on the updated wire_type of this Wire, create a # instance of a GraphicsWire for this Wire if self.wire_type == WIRE_TYPE_DIRECT: self.grWire = GraphicsWireDirect(self) elif self.wire_type == WIRE_TYPE_STEP: self.grWire = GraphicsWireStep(self) else: self.grWire = GraphicsWireBezier(self) # Add the wire to the GraphicsScene self.scene.grScene.addItem(self.grWire) # If a start socket has been assigned, update where the Wire is drawn from if self.start_socket is not None: self.updatePositions() def setFocusOfWire(self): """ This method sends all ``Wire`` instances within the ``Scene`` to back and then sends the currently selected ``Wire`` instance to front. """ # Iterates through each wire within wire list stored in the Scene Class # and sets the graphical component of each wire to a zValue of -2. for wire in self.scene.wires: wire.grWire.setZValue(-2) # Then sets the graphical component of the currently selected wire to a # zValue of -1, which makes it display above all other wires on screen. self.grWire.setZValue(-1) # ----------------------------------------------------------------------------- def updatePositions(self): """ This method grabs the new positions of sockets on blocks as they are moved around within the scene, in order to determine the positions which the wire should connect. The redrawing of the wire to these positions will also be handled within this method. """ # Grabs the current position (LEFT/RIGHT) of the starting socket # and sets the associated source (start) socket orientation (position) within # the GraphicsWire Class source_pos_orientation = self.start_socket.position self.grWire.setSourceOrientation(source_pos_orientation) # Grabs the position of the start socket and splits it into x,y coordinates # then assigns the position of the source (start) socket to these coordinates source_pos = self.start_socket.getSocketPosition() source_pos[0] += self.start_socket.node.grBlock.pos().x() source_pos[1] += self.start_socket.node.grBlock.pos().y() self.grWire.setSource(*source_pos) # If an end socket has been set for this wire, the same logic as above will # be applied, otherwise the destination (end) socket will be set to the coordinates # of the source (start) socket. This will be fixed when the wire is completed and remade. if self.end_socket is not None: destination_pos_orientation = self.end_socket.position self.grWire.setDestinationOrientation(destination_pos_orientation) end_pos = self.end_socket.getSocketPosition() end_pos[0] += self.end_socket.node.grBlock.pos().x() end_pos[1] += self.end_socket.node.grBlock.pos().y() self.grWire.setDestination(*end_pos) else: self.grWire.setDestination(*source_pos) # The wire is called to be updated self.grWire.update() # ----------------------------------------------------------------------------- def remove_from_sockets(self): """ This method will un-assign the start and end sockets of this Wire. """ self.end_socket = None self.start_socket = None # ----------------------------------------------------------------------------- def remove(self): """ This method will remove the selected Wire from the Scene, un-assign the Sockets that related to it, and remove the Wire from these Sockets. """ if self in self.scene.wires: if DEBUG: print("# Removing Wire", self) if DEBUG: print(" - hiding grWire") self.grWire.hide() if DEBUG: print(" - removing grWire") self.scene.grScene.removeItem(self.grWire) if DEBUG: print(" - removing wire from all sockets", self) self.remove_from_sockets() if DEBUG: print(" - removing wire from scene") try: self.scene.removeWire(self) except ValueError as e: print("Error removing wire:", e) pass if DEBUG: print(" - updating wire intersection points") if self.scene.wires: self.scene.wires[0].checkIntersections() if DEBUG: print(" - everything is done.") else: if DEBUG: print("# Wire already removed") # ----------------------------------------------------------------------------- def serialize(self): """ This method is called to create an ordered dictionary of all of this Wires' parameters - necessary for the reconstruction of this Wire - as key-value pairs. This dictionary is later used for writing into a JSON file. :return: an ``OrderedDict`` of [keys, values] pairs of all essential ``Wire`` parameters. :rtype: OrderedDict, ([keys, values]*) """ if self.grWire.customlogicOverride: wire_coords = copy.copy(self.wire_coordinates) else: wire_coords = [] return OrderedDict( [ ("id", self.id), ("start_socket", self.start_socket.id), ("end_socket", self.end_socket.id), ("wire_type", self.wire_type), ("custom_routing", self.grWire.customlogicOverride), ("wire_coordinates", wire_coords), ] ) # ----------------------------------------------------------------------------- def deserialize(self, data, hashmap={}): """ This method is called to reconstruct a ``Wire`` when loading a saved JSON file containing all relevant information to recreate the ``Scene`` with all its items. :param data: a Dictionary of essential information for reconstructing a ``Wire`` :type data: OrderedDict, required :param hashmap: a Dictionary for directly mapping the essential wire variables to this instance of ``Wire``, without having to individually map each variable :type hashmap: Dict, required :return: True when completed successfully :rtype: Boolean """ # The id, and other variables of this Wire are set to whatever was stored # as its id and other variables in the JSON file. self.id = data["id"] # self.start_socket = data['start_socket'] # self.end_socket = data['end_socket'] self.start_socket = hashmap[data["start_socket"]] self.end_socket = hashmap[data["end_socket"]] self.wire_type = data["wire_type"] # For newer custom routing logic. If custom_routing exists within the saved JSON # data, and that variable is true, override the current wire_coordiantes of the wire # to the ones saved in the file. try: if data["custom_routing"]: self.grWire.customlogicOverride = data["custom_routing"] try: if data["wire_coordinates"]: # Wire coordinates is supposed to be a list of tuples, but in JSON they # are stored as a list of lists. So convert the points to tuples new_wire_coordinates = [] for point in data["wire_coordinates"]: new_wire_coordinates.append(tuple(point)) self.grWire.updateWireCoordinates(new_wire_coordinates) except KeyError: pass except KeyError: pass return True # ----------------------------------------------------------------------------- def checkIntersections(self): """ This method checks all active wires in the scene for intersections with other wires. This method will be called any time a mouse movement is detected in the GraphicsView class, which will cause the GraphicsScene to draw points at these intersections to separate the wires. To reduce computation for finding these intersection points, only vertical line segments of wires are checked for intersections. This is because an intersection point can only occur when a horizontal line segment of one wire meets a vertical line segment of another wire, and every single wire has a horizontal segment (as sockets are drawn on the LEFT or RIGHT sides of a Block). When this method is called, the current intersection points are deleted, as all wires are checked against in this method, and as such, any new (or previous) intersection points will be appended into a list of intersection points that is stored within the GraphicsScene Class. """ # Clear the intersection list stored within the scene of any previous intersection points self.scene.intersection_list.clear() # Grab the number of wires currently in the scene number_of_wires = len(self.scene.wires) # If there are more than 1 wires, check for overlapping if number_of_wires > 1: # Iterate through each wire in list of wires for i in range(0, number_of_wires): # If the wire has a vertical segment, check intersections against other wires # Else ignore (as wire only has horizontal segments, and these cant create an intersection point # unless a vertical line passes through them) wire_has_vert = self.scene.wires[i].vertical_segments if wire_has_vert: # Check each vertical segment against horizontal segments of other wires for vertical_segment in wire_has_vert: for j in range(0, number_of_wires): # If j==i this means the same wire is being checked, ignore checking this wire (cannot overlap with itself) if j == i: pass # Or if wire 'j' starts from the same socket as 'i', ignore this wire elif ( self.scene.wires[j].start_socket == self.scene.wires[i].start_socket ): pass else: # Iterate through each horizontal segments of the wire being checked for horizontal_segment in self.scene.wires[ j ].horizontal_segments: # In a vertical wire with points [(a1,b1), (a2,b2)], the horizontal coordinates will be # equal, hence the wire is essentially [(a,b1), (a,b2)] # In a horizontal wire with points [(x1,y1), (x2,y2)], the vertical coordinates will be # equal, hence the wire is essentially [(x1,y), (x2,y)] # If vertical points of wire with a vertical segment, are intersecting the y coordinate # of a horizontal segment of the wire being checked against # Essentially checking if b1 <= y <= b2 (if y is between b1 and b2) if ( vertical_segment[0][1] <= horizontal_segment[0][1] <= vertical_segment[1][1] or vertical_segment[0][1] >= horizontal_segment[0][1] >= vertical_segment[1][1] ): if DEBUG_OVERLAP: print( "y coords of vert segment within y coord of horizontal seg" ) # There may be a possible intersection, so now # check if the horizontal points of wire with a vertical segment, intersects through # the x coordinate of a horizontal segment of the wire being checked against # Essentially checking if x1 <= a <= x2 (if a is between x1 and x2) if ( horizontal_segment[0][0] <= vertical_segment[0][0] <= horizontal_segment[1][0] or horizontal_segment[0][0] >= vertical_segment[0][0] >= horizontal_segment[1][0] ): if DEBUG_OVERLAP: print( "x coord of vert segment within x coords of horizontal seg" ) # The intersection point is (a, y) # (a -> x coord from vertical segment, y -> y coord from horizontal segment) # An intersection is found, append the point to the wire's list of intersections # self.intersections.append((vertical_segment[0][0], horizontal_segment[0][1])) # Append intersection point into list of intersection points (stored within the scene) if ( vertical_segment[0][0], horizontal_segment[0][1], ) not in self.scene.intersection_list: self.scene.intersection_list.append( ( vertical_segment[0][0], horizontal_segment[0][1], ) ) ================================================ FILE: bdsim/bdedit/docs/.buildinfo ================================================ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. config: 9b83c4d3849f0701fab540b588e30695 tags: 645f666f9bcd5a90fca523b33c5a78b7 ================================================ FILE: bdsim/bdedit/docs/_sources/bdedit.rst.txt ================================================ bdedit package ============== Submodules ---------- bdedit.Icons module ------------------- .. automodule:: bdedit.Icons :members: :undoc-members: :show-inheritance: bdedit.block module ------------------- .. automodule:: bdedit.block :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.block\_graphics\_block module ------------------------------------ .. automodule:: bdedit.block_graphics_block :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.block\_graphics\_socket module ------------------------------------- .. automodule:: bdedit.block_graphics_socket :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.block\_graphics\_wire module ----------------------------------- .. automodule:: bdedit.block_graphics_wire :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.block\_param\_window module ---------------------------------- .. automodule:: bdedit.block_param_window :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.block\_socket module --------------------------- .. automodule:: bdedit.block_socket :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.block\_socket\_block module ---------------------------------- .. automodule:: bdedit.block_socket_block :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.block\_wire module ------------------------- .. automodule:: bdedit.block_wire :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.interface module ----------------------- .. automodule:: bdedit.interface :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.interface\_graphics\_scene module ---------------------------------------- .. automodule:: bdedit.interface_graphics_scene :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.interface\_graphics\_view module --------------------------------------- .. automodule:: bdedit.interface_graphics_view :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.interface\_scene module ------------------------------ .. automodule:: bdedit.interface_scene :members: :special-members: __init__ :undoc-members: :show-inheritance: bdedit.interface\_serialize module ---------------------------------- .. automodule:: bdedit.interface_serialize :members: :special-members: __init__ :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: bdedit :members: :special-members: __init__ :undoc-members: :show-inheritance: ================================================ FILE: bdsim/bdedit/docs/_sources/index.rst.txt ================================================ .. BdEdit documentation master file, created by sphinx-quickstart on Wed Jun 9 03:42:11 2021. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to BdEdit's documentation! ================================== .. toctree:: :maxdepth: 2 :caption: Contents: modules Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ================================================ FILE: bdsim/bdedit/docs/_sources/modules.rst.txt ================================================ bdedit ====== .. toctree:: :maxdepth: 4 bdedit ================================================ FILE: bdsim/bdedit/docs/_static/alabaster.css ================================================ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: Georgia, serif; font-size: 17px; background-color: #fff; color: #000; margin: 0; padding: 0; } div.document { width: 940px; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 220px; } div.sphinxsidebar { width: 220px; font-size: 14px; line-height: 1.5; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #fff; color: #3E4349; padding: 0 30px 0 30px; } div.body > .section { text-align: left; } div.footer { width: 940px; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } p.caption { font-family: inherit; font-size: inherit; } div.relations { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0; margin: -10px 0 0 0px; text-align: center; } div.sphinxsidebarwrapper h1.logo { margin-top: -10px; text-align: center; margin-bottom: 5px; text-align: left; } div.sphinxsidebarwrapper h1.logo-name { margin-top: 0px; } div.sphinxsidebarwrapper p.blurb { margin-top: 0; font-style: normal; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: Georgia, serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar ul li.toctree-l1 > a { font-size: 120%; } div.sphinxsidebar ul li.toctree-l2 > a { font-size: 110%; } div.sphinxsidebar input { border: 1px solid #CCC; font-family: Georgia, serif; font-size: 1em; } div.sphinxsidebar hr { border: none; height: 1px; color: #AAA; background: #AAA; text-align: left; margin-left: 0; width: 50%; } div.sphinxsidebar .badge { border-bottom: none; } div.sphinxsidebar .badge:hover { border-bottom: none; } /* To address an issue with donation coming after search */ div.sphinxsidebar h3.donation { margin-top: 10px; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: Georgia, serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #DDD; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #EAEAEA; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { margin: 20px 0px; padding: 10px 30px; background-color: #EEE; border: 1px solid #CCC; } div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { background-color: #FBFBFB; border-bottom: 1px solid #fafafa; } div.admonition p.admonition-title { font-family: Georgia, serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: #fff; } dt:target, .highlight { background: #FAF3E8; } div.warning { background-color: #FCC; border: 1px solid #FAA; } div.danger { background-color: #FCC; border: 1px solid #FAA; -moz-box-shadow: 2px 2px 4px #D52C2C; -webkit-box-shadow: 2px 2px 4px #D52C2C; box-shadow: 2px 2px 4px #D52C2C; } div.error { background-color: #FCC; border: 1px solid #FAA; -moz-box-shadow: 2px 2px 4px #D52C2C; -webkit-box-shadow: 2px 2px 4px #D52C2C; box-shadow: 2px 2px 4px #D52C2C; } div.caution { background-color: #FCC; border: 1px solid #FAA; } div.attention { background-color: #FCC; border: 1px solid #FAA; } div.important { background-color: #EEE; border: 1px solid #CCC; } div.note { background-color: #EEE; border: 1px solid #CCC; } div.tip { background-color: #EEE; border: 1px solid #CCC; } div.hint { background-color: #EEE; border: 1px solid #CCC; } div.seealso { background-color: #EEE; border: 1px solid #CCC; } div.topic { background-color: #EEE; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt, code { font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } .hll { background-color: #FFC; margin: 0 -12px; padding: 0 12px; display: block; } img.screenshot { } tt.descname, tt.descclassname, code.descname, code.descclassname { font-size: 0.95em; } tt.descname, code.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #EEE; -webkit-box-shadow: 2px 2px 4px #EEE; box-shadow: 2px 2px 4px #EEE; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #EEE; -webkit-box-shadow: 2px 2px 4px #EEE; box-shadow: 2px 2px 4px #EEE; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #EEE; background: #FDFDFD; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.field-list p { margin-bottom: 0.8em; } /* Cloned from * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 */ .field-name { -moz-hyphens: manual; -ms-hyphens: manual; -webkit-hyphens: manual; hyphens: manual; } table.footnote td.label { width: .1px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { /* Matches the 30px from the narrow-screen "li > ul" selector below */ margin: 10px 0 10px 30px; padding: 0; } pre { background: #EEE; padding: 7px 30px; margin: 15px 0px; line-height: 1.3em; } div.viewcode-block:target { background: #ffd; } dl pre, blockquote pre, li pre { margin-left: 0; padding-left: 30px; } tt, code { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, code.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid #fff; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } /* Don't put an underline on images */ a.image-reference, a.image-reference:hover { border-bottom: none; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt, a:hover code { background: #EEE; } @media screen and (max-width: 870px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } li > ul { /* Matches the 30px from the "ul, ol" selector above */ margin-left: 30px; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } @media screen and (max-width: 875px) { body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: #fff; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: #FFF; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: #fff; } div.sphinxsidebar a { color: #AAA; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; } } /* misc. */ .revsys-inline { display: none!important; } /* Make nested-list/multi-paragraph items look better in Releases changelog * pages. Without this, docutils' magical list fuckery causes inconsistent * formatting between different release sub-lists. */ div#changelog > div.section > ul > li > p:only-child { margin-bottom: 0; } /* Hide fugly table cell borders in ..bibliography:: directive output */ table.docutils.citation, table.docutils.citation td, table.docutils.citation th { border: none; /* Below needed in some edge cases; if not applied, bottom shadows appear */ -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } /* relbar */ .related { line-height: 30px; width: 100%; font-size: 0.9rem; } .related.top { border-bottom: 1px solid #EEE; margin-bottom: 20px; } .related.bottom { border-top: 1px solid #EEE; } .related ul { padding: 0; margin: 0; list-style: none; } .related li { display: inline; } nav#rellinks { float: right; } nav#rellinks li+li:before { content: "|"; } nav#breadcrumbs li+li:before { content: "\00BB"; } /* Hide certain items when printing */ @media print { div.related { display: none; } } ================================================ FILE: bdsim/bdedit/docs/_static/basic.css ================================================ /* * basic.css * ~~~~~~~~~ * * Sphinx stylesheet -- basic theme. * * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /* -- main layout ----------------------------------------------------------- */ div.clearer { clear: both; } div.section::after { display: block; content: ''; clear: left; } /* -- relbar ---------------------------------------------------------------- */ div.related { width: 100%; font-size: 90%; } div.related h3 { display: none; } div.related ul { margin: 0; padding: 0 0 0 10px; list-style: none; } div.related li { display: inline; } div.related li.right { float: right; margin-right: 5px; } /* -- sidebar --------------------------------------------------------------- */ div.sphinxsidebarwrapper { padding: 10px 5px 0 10px; } div.sphinxsidebar { float: left; width: 230px; margin-left: -100%; font-size: 90%; word-wrap: break-word; overflow-wrap : break-word; } div.sphinxsidebar ul { list-style: none; } div.sphinxsidebar ul ul, div.sphinxsidebar ul.want-points { margin-left: 20px; list-style: square; } div.sphinxsidebar ul ul { margin-top: 0; margin-bottom: 0; } div.sphinxsidebar form { margin-top: 10px; } div.sphinxsidebar input { border: 1px solid #98dbcc; font-family: sans-serif; font-size: 1em; } div.sphinxsidebar #searchbox form.search { overflow: hidden; } div.sphinxsidebar #searchbox input[type="text"] { float: left; width: 80%; padding: 0.25em; box-sizing: border-box; } div.sphinxsidebar #searchbox input[type="submit"] { float: left; width: 20%; border-left: none; padding: 0.25em; box-sizing: border-box; } img { border: 0; max-width: 100%; } /* -- search page ----------------------------------------------------------- */ ul.search { margin: 10px 0 0 20px; padding: 0; } ul.search li { padding: 5px 0 5px 20px; background-image: url(file.png); background-repeat: no-repeat; background-position: 0 7px; } ul.search li a { font-weight: bold; } ul.search li p.context { color: #888; margin: 2px 0 0 30px; text-align: left; } ul.keywordmatches li.goodmatch a { font-weight: bold; } /* -- index page ------------------------------------------------------------ */ table.contentstable { width: 90%; margin-left: auto; margin-right: auto; } table.contentstable p.biglink { line-height: 150%; } a.biglink { font-size: 1.3em; } span.linkdescr { font-style: italic; padding-top: 5px; font-size: 90%; } /* -- general index --------------------------------------------------------- */ table.indextable { width: 100%; } table.indextable td { text-align: left; vertical-align: top; } table.indextable ul { margin-top: 0; margin-bottom: 0; list-style-type: none; } table.indextable > tbody > tr > td > ul { padding-left: 0em; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2; } img.toggler { margin-right: 3px; margin-top: 3px; cursor: pointer; } div.modindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } div.genindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } /* -- domain module index --------------------------------------------------- */ table.modindextable td { padding: 2px; border-collapse: collapse; } /* -- general body styles --------------------------------------------------- */ div.body { min-width: 450px; max-width: 800px; } div.body p, div.body dd, div.body li, div.body blockquote { -moz-hyphens: auto; -ms-hyphens: auto; -webkit-hyphens: auto; hyphens: auto; } a.headerlink { visibility: hidden; } a.brackets:before, span.brackets > a:before{ content: "["; } a.brackets:after, span.brackets > a:after { content: "]"; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink, caption:hover > a.headerlink, p.caption:hover > a.headerlink, div.code-block-caption:hover > a.headerlink { visibility: visible; } div.body p.caption { text-align: inherit; } div.body td { text-align: left; } .first { margin-top: 0 !important; } p.rubric { margin-top: 30px; font-weight: bold; } img.align-left, figure.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } img.align-right, figure.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } img.align-center, figure.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } img.align-default, figure.align-default, .figure.align-default { display: block; margin-left: auto; margin-right: auto; } .align-left { text-align: left; } .align-center { text-align: center; } .align-default { text-align: center; } .align-right { text-align: right; } /* -- sidebars -------------------------------------------------------------- */ div.sidebar, aside.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; padding: 7px; background-color: #ffe; width: 40%; float: right; clear: right; overflow-x: auto; } p.sidebar-title { font-weight: bold; } div.admonition, div.topic, blockquote { clear: left; } /* -- topics ---------------------------------------------------------------- */ div.topic { border: 1px solid #ccc; padding: 7px; margin: 10px 0 10px 0; } p.topic-title { font-size: 1.1em; font-weight: bold; margin-top: 10px; } /* -- admonitions ----------------------------------------------------------- */ div.admonition { margin-top: 10px; margin-bottom: 10px; padding: 7px; } div.admonition dt { font-weight: bold; } p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; } div.body p.centered { text-align: center; margin-top: 25px; } /* -- content of sidebars/topics/admonitions -------------------------------- */ div.sidebar > :last-child, aside.sidebar > :last-child, div.topic > :last-child, div.admonition > :last-child { margin-bottom: 0; } div.sidebar::after, aside.sidebar::after, div.topic::after, div.admonition::after, blockquote::after { display: block; content: ''; clear: both; } /* -- tables ---------------------------------------------------------------- */ table.docutils { margin-top: 10px; margin-bottom: 10px; border: 0; border-collapse: collapse; } table.align-center { margin-left: auto; margin-right: auto; } table.align-default { margin-left: auto; margin-right: auto; } table caption span.caption-number { font-style: italic; } table caption span.caption-text { } table.docutils td, table.docutils th { padding: 1px 8px 1px 5px; border-top: 0; border-left: 0; border-right: 0; border-bottom: 1px solid #aaa; } table.footnote td, table.footnote th { border: 0 !important; } th { text-align: left; padding-right: 5px; } table.citation { border-left: solid 1px gray; margin-left: 1px; } table.citation td { border-bottom: none; } th > :first-child, td > :first-child { margin-top: 0px; } th > :last-child, td > :last-child { margin-bottom: 0px; } /* -- figures --------------------------------------------------------------- */ div.figure, figure { margin: 0.5em; padding: 0.5em; } div.figure p.caption, figcaption { padding: 0.3em; } div.figure p.caption span.caption-number, figcaption span.caption-number { font-style: italic; } div.figure p.caption span.caption-text, figcaption span.caption-text { } /* -- field list styles ----------------------------------------------------- */ table.field-list td, table.field-list th { border: 0 !important; } .field-list ul { margin: 0; padding-left: 1em; } .field-list p { margin: 0; } .field-name { -moz-hyphens: manual; -ms-hyphens: manual; -webkit-hyphens: manual; hyphens: manual; } /* -- hlist styles ---------------------------------------------------------- */ table.hlist { margin: 1em 0; } table.hlist td { vertical-align: top; } /* -- object description styles --------------------------------------------- */ .sig { font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; } .sig-name, code.descname { background-color: transparent; font-weight: bold; } .sig-name { font-size: 1.1em; } code.descname { font-size: 1.2em; } .sig-prename, code.descclassname { background-color: transparent; } .optional { font-size: 1.3em; } .sig-paren { font-size: larger; } .sig-param.n { font-style: italic; } /* C++ specific styling */ .sig-inline.c-texpr, .sig-inline.cpp-texpr { font-family: unset; } .sig.c .k, .sig.c .kt, .sig.cpp .k, .sig.cpp .kt { color: #0033B3; } .sig.c .m, .sig.cpp .m { color: #1750EB; } .sig.c .s, .sig.c .sc, .sig.cpp .s, .sig.cpp .sc { color: #067D17; } /* -- other body styles ----------------------------------------------------- */ ol.arabic { list-style: decimal; } ol.loweralpha { list-style: lower-alpha; } ol.upperalpha { list-style: upper-alpha; } ol.lowerroman { list-style: lower-roman; } ol.upperroman { list-style: upper-roman; } :not(li) > ol > li:first-child > :first-child, :not(li) > ul > li:first-child > :first-child { margin-top: 0px; } :not(li) > ol > li:last-child > :last-child, :not(li) > ul > li:last-child > :last-child { margin-bottom: 0px; } ol.simple ol p, ol.simple ul p, ul.simple ol p, ul.simple ul p { margin-top: 0; } ol.simple > li:not(:first-child) > p, ul.simple > li:not(:first-child) > p { margin-top: 0; } ol.simple p, ul.simple p { margin-bottom: 0; } dl.footnote > dt, dl.citation > dt { float: left; margin-right: 0.5em; } dl.footnote > dd, dl.citation > dd { margin-bottom: 0em; } dl.footnote > dd:after, dl.citation > dd:after { content: ""; clear: both; } dl.field-list { display: grid; grid-template-columns: fit-content(30%) auto; } dl.field-list > dt { font-weight: bold; word-break: break-word; padding-left: 0.5em; padding-right: 5px; } dl.field-list > dt:after { content: ":"; } dl.field-list > dd { padding-left: 0.5em; margin-top: 0em; margin-left: 0em; margin-bottom: 0em; } dl { margin-bottom: 15px; } dd > :first-child { margin-top: 0px; } dd ul, dd table { margin-bottom: 10px; } dd { margin-top: 3px; margin-bottom: 10px; margin-left: 30px; } dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; } dt:target, span.highlighted { background-color: #fbe54e; } rect.highlighted { fill: #fbe54e; } dl.glossary dt { font-weight: bold; font-size: 1.1em; } .versionmodified { font-style: italic; } .system-message { background-color: #fda; padding: 5px; border: 3px solid red; } .footnote:target { background-color: #ffa; } .line-block { display: block; margin-top: 1em; margin-bottom: 1em; } .line-block .line-block { margin-top: 0; margin-bottom: 0; margin-left: 1.5em; } .guilabel, .menuselection { font-family: sans-serif; } .accelerator { text-decoration: underline; } .classifier { font-style: oblique; } .classifier:before { font-style: normal; margin: 0.5em; content: ":"; } abbr, acronym { border-bottom: dotted 1px; cursor: help; } /* -- code displays --------------------------------------------------------- */ pre { overflow: auto; overflow-y: hidden; /* fixes display issues on Chrome browsers */ } pre, div[class*="highlight-"] { clear: both; } span.pre { -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; } div[class*="highlight-"] { margin: 1em 0; } td.linenos pre { border: 0; background-color: transparent; color: #aaa; } table.highlighttable { display: block; } table.highlighttable tbody { display: block; } table.highlighttable tr { display: flex; } table.highlighttable td { margin: 0; padding: 0; } table.highlighttable td.linenos { padding-right: 0.5em; } table.highlighttable td.code { flex: 1; overflow: hidden; } .highlight .hll { display: block; } div.highlight pre, table.highlighttable pre { margin: 0; } div.code-block-caption + div { margin-top: 0; } div.code-block-caption { margin-top: 1em; padding: 2px 5px; font-size: small; } div.code-block-caption code { background-color: transparent; } table.highlighttable td.linenos, span.linenos, div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ user-select: none; -webkit-user-select: text; /* Safari fallback only */ -webkit-user-select: none; /* Chrome/Safari */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* IE10+ */ } div.code-block-caption span.caption-number { padding: 0.1em 0.3em; font-style: italic; } div.code-block-caption span.caption-text { } div.literal-block-wrapper { margin: 1em 0; } code.xref, a code { background-color: transparent; font-weight: bold; } h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { background-color: transparent; } .viewcode-link { float: right; } .viewcode-back { float: right; font-family: sans-serif; } div.viewcode-block:target { margin: -1px -10px; padding: 0 10px; } /* -- math display ---------------------------------------------------------- */ img.math { vertical-align: middle; } div.body div.math p { text-align: center; } span.eqno { float: right; } span.eqno a.headerlink { position: absolute; z-index: 1; } div.math:hover a.headerlink { visibility: visible; } /* -- printout stylesheet --------------------------------------------------- */ @media print { div.document, div.documentwrapper, div.bodywrapper { margin: 0 !important; width: 100%; } div.sphinxsidebar, div.related, div.footer, #top-link { display: none; } } ================================================ FILE: bdsim/bdedit/docs/_static/css/badge_only.css ================================================ .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} ================================================ FILE: bdsim/bdedit/docs/_static/css/theme.css ================================================ html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li span.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li span.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li span.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li span.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li span.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li span.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li span.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p.caption .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.btn .wy-menu-vertical li span.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p.caption .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.nav .wy-menu-vertical li span.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p.caption .btn .headerlink,.rst-content p.caption .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li span.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol li,.rst-content ol.arabic li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content ol.arabic li p:last-child,.rst-content ol.arabic li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover span.toctree-expand,.wy-menu-vertical li.on a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover span.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover span.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp{user-select:none;pointer-events:none}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink{visibility:hidden;font-size:14px}.rst-content .code-block-caption .headerlink:after,.rst-content .toctree-wrapper>p.caption .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after{content:"\f0c1";font-family:FontAwesome}.rst-content .code-block-caption:hover .headerlink:after,.rst-content .toctree-wrapper>p.caption:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl dt span.classifier:before{content:" : "}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code,html.writer-html4 .rst-content dl:not(.docutils) tt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} ================================================ FILE: bdsim/bdedit/docs/_static/custom.css ================================================ /* This file intentionally left blank. */ ================================================ FILE: bdsim/bdedit/docs/_static/doctools.js ================================================ /* * doctools.js * ~~~~~~~~~~~ * * Sphinx JavaScript utilities for all documentation. * * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /** * select a different prefix for underscore */ $u = _.noConflict(); /** * make the code below compatible with browsers without * an installed firebug like debugger if (!window.console || !console.firebug) { var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; window.console = {}; for (var i = 0; i < names.length; ++i) window.console[names[i]] = function() {}; } */ /** * small helper function to urldecode strings * * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL */ jQuery.urldecode = function(x) { if (!x) { return x } return decodeURIComponent(x.replace(/\+/g, ' ')); }; /** * small helper function to urlencode strings */ jQuery.urlencode = encodeURIComponent; /** * This function returns the parsed url parameters of the * current request. Multiple values per key are supported, * it will always return arrays of strings for the value parts. */ jQuery.getQueryParameters = function(s) { if (typeof s === 'undefined') s = document.location.search; var parts = s.substr(s.indexOf('?') + 1).split('&'); var result = {}; for (var i = 0; i < parts.length; i++) { var tmp = parts[i].split('=', 2); var key = jQuery.urldecode(tmp[0]); var value = jQuery.urldecode(tmp[1]); if (key in result) result[key].push(value); else result[key] = [value]; } return result; }; /** * highlight a given string on a jquery object by wrapping it in * span elements with the given class name. */ jQuery.fn.highlightText = function(text, className) { function highlight(node, addItems) { if (node.nodeType === 3) { var val = node.nodeValue; var pos = val.toLowerCase().indexOf(text); if (pos >= 0 && !jQuery(node.parentNode).hasClass(className) && !jQuery(node.parentNode).hasClass("nohighlight")) { var span; var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); if (isInSVG) { span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); } else { span = document.createElement("span"); span.className = className; } span.appendChild(document.createTextNode(val.substr(pos, text.length))); node.parentNode.insertBefore(span, node.parentNode.insertBefore( document.createTextNode(val.substr(pos + text.length)), node.nextSibling)); node.nodeValue = val.substr(0, pos); if (isInSVG) { var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); var bbox = node.parentElement.getBBox(); rect.x.baseVal.value = bbox.x; rect.y.baseVal.value = bbox.y; rect.width.baseVal.value = bbox.width; rect.height.baseVal.value = bbox.height; rect.setAttribute('class', className); addItems.push({ "parent": node.parentNode, "target": rect}); } } } else if (!jQuery(node).is("button, select, textarea")) { jQuery.each(node.childNodes, function() { highlight(this, addItems); }); } } var addItems = []; var result = this.each(function() { highlight(this, addItems); }); for (var i = 0; i < addItems.length; ++i) { jQuery(addItems[i].parent).before(addItems[i].target); } return result; }; /* * backward compatibility for jQuery.browser * This will be supported until firefox bug is fixed. */ if (!jQuery.browser) { jQuery.uaMatch = function(ua) { ua = ua.toLowerCase(); var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; return { browser: match[ 1 ] || "", version: match[ 2 ] || "0" }; }; jQuery.browser = {}; jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; } /** * Small JavaScript module for the documentation. */ var Documentation = { init : function() { this.fixFirefoxAnchorBug(); this.highlightSearchWords(); this.initIndexTable(); if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { this.initOnKeyListeners(); } }, /** * i18n support */ TRANSLATIONS : {}, PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, LOCALE : 'unknown', // gettext and ngettext don't access this so that the functions // can safely bound to a different name (_ = Documentation.gettext) gettext : function(string) { var translated = Documentation.TRANSLATIONS[string]; if (typeof translated === 'undefined') return string; return (typeof translated === 'string') ? translated : translated[0]; }, ngettext : function(singular, plural, n) { var translated = Documentation.TRANSLATIONS[singular]; if (typeof translated === 'undefined') return (n == 1) ? singular : plural; return translated[Documentation.PLURALEXPR(n)]; }, addTranslations : function(catalog) { for (var key in catalog.messages) this.TRANSLATIONS[key] = catalog.messages[key]; this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); this.LOCALE = catalog.locale; }, /** * add context elements like header anchor links */ addContextElements : function() { $('div[id] > :header:first').each(function() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this headline')). appendTo(this); }); $('dt[id]').each(function() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this definition')). appendTo(this); }); }, /** * workaround a firefox stupidity * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 */ fixFirefoxAnchorBug : function() { if (document.location.hash && $.browser.mozilla) window.setTimeout(function() { document.location.href += ''; }, 10); }, /** * highlight the search words provided in the url in the text */ highlightSearchWords : function() { var params = $.getQueryParameters(); var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; if (terms.length) { var body = $('div.body'); if (!body.length) { body = $('body'); } window.setTimeout(function() { $.each(terms, function() { body.highlightText(this.toLowerCase(), 'highlighted'); }); }, 10); $('') .appendTo($('#searchbox')); } }, /** * init the domain index toggle buttons */ initIndexTable : function() { var togglers = $('img.toggler').click(function() { var src = $(this).attr('src'); var idnum = $(this).attr('id').substr(7); $('tr.cg-' + idnum).toggle(); if (src.substr(-9) === 'minus.png') $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); else $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); }).css('display', ''); if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { togglers.click(); } }, /** * helper function to hide the search marks again */ hideSearchWords : function() { $('#searchbox .highlight-link').fadeOut(300); $('span.highlighted').removeClass('highlighted'); }, /** * make the url absolute */ makeURL : function(relativeURL) { return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; }, /** * get the current relative url */ getCurrentURL : function() { var path = document.location.pathname; var parts = path.split(/\//); $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { if (this === '..') parts.pop(); }); var url = parts.join('/'); return path.substring(url.lastIndexOf('/') + 1, path.length - 1); }, initOnKeyListeners: function() { $(document).keydown(function(event) { var activeElementType = document.activeElement.tagName; // don't navigate when in search box, textarea, dropdown or button if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { switch (event.keyCode) { case 37: // left var prevHref = $('link[rel="prev"]').prop('href'); if (prevHref) { window.location.href = prevHref; return false; } case 39: // right var nextHref = $('link[rel="next"]').prop('href'); if (nextHref) { window.location.href = nextHref; return false; } } } }); } }; // quick alias for translations _ = Documentation.gettext; $(document).ready(function() { Documentation.init(); }); ================================================ FILE: bdsim/bdedit/docs/_static/documentation_options.js ================================================ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), VERSION: '', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', FILE_SUFFIX: '.html', LINK_SUFFIX: '.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt', NAVIGATION_WITH_KEYS: false }; ================================================ FILE: bdsim/bdedit/docs/_static/jquery-3.5.1.js ================================================ /*! * jQuery JavaScript Library v3.5.1 * https://jquery.com/ * * Includes Sizzle.js * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * * Date: 2020-05-04T22:49Z */ ( function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { // For CommonJS and CommonJS-like environments where a proper `window` // is present, execute the factory and get jQuery. // For environments that do not have a `window` with a `document` // (such as Node.js), expose a factory as module.exports. // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); // See ticket #14549 for more info. module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } // Pass this if window is not defined yet } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { // Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 // throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode // arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common // enough that all such attempts are guarded in a try block. "use strict"; var arr = []; var getProto = Object.getPrototypeOf; var slice = arr.slice; var flat = arr.flat ? function( array ) { return arr.flat.call( array ); } : function( array ) { return arr.concat.apply( [], array ); }; var push = arr.push; var indexOf = arr.indexOf; var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); var support = {}; var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML elements // (i.e., `typeof document.createElement( "object" ) === "function"`). // We don't want to classify *any* DOM node as a function. return typeof obj === "function" && typeof obj.nodeType !== "number"; }; var isWindow = function isWindow( obj ) { return obj != null && obj === obj.window; }; var document = window.document; var preservedScriptAttributes = { type: true, src: true, nonce: true, noModule: true }; function DOMEval( code, node, doc ) { doc = doc || document; var i, val, script = doc.createElement( "script" ); script.text = code; if ( node ) { for ( i in preservedScriptAttributes ) { // Support: Firefox 64+, Edge 18+ // Some browsers don't support the "nonce" property on scripts. // On the other hand, just using `getAttribute` is not enough as // the `nonce` attribute is reset to an empty string whenever it // becomes browsing-context connected. // See https://github.com/whatwg/html/issues/2369 // See https://html.spec.whatwg.org/#nonce-attributes // The `node.getAttribute` check was added for the sake of // `jQuery.globalEval` so that it can fake a nonce-containing node // via an object. val = node[ i ] || node.getAttribute && node.getAttribute( i ); if ( val ) { script.setAttribute( i, val ); } } } doc.head.appendChild( script ).parentNode.removeChild( script ); } function toType( obj ) { if ( obj == null ) { return obj + ""; } // Support: Android <=2.3 only (functionish RegExp) return typeof obj === "object" || typeof obj === "function" ? class2type[ toString.call( obj ) ] || "object" : typeof obj; } /* global Symbol */ // Defining this global in .eslintrc.json would create a danger of using the global // unguarded in another place, it seems safer to define global only for this module var version = "3.5.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); }; jQuery.fn = jQuery.prototype = { // The current version of jQuery being used jquery: version, constructor: jQuery, // The default length of a jQuery object is 0 length: 0, toArray: function() { return slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { // Return all the elements in a clean array if ( num == null ) { return slice.call( this ); } // Return just the one element from the set return num < 0 ? this[ num + this.length ] : this[ num ]; }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. each: function( callback ) { return jQuery.each( this, callback ); }, map: function( callback ) { return this.pushStack( jQuery.map( this, function( elem, i ) { return callback.call( elem, i, elem ); } ) ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ) ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, even: function() { return this.pushStack( jQuery.grep( this, function( _elem, i ) { return ( i + 1 ) % 2; } ) ); }, odd: function() { return this.pushStack( jQuery.grep( this, function( _elem, i ) { return i % 2; } ) ); }, eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); }, end: function() { return this.prevObject || this.constructor(); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: arr.sort, splice: arr.splice }; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( ( options = arguments[ i ] ) != null ) { // Extend the base object for ( name in options ) { copy = options[ name ]; // Prevent Object.prototype pollution // Prevent never-ending loop if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { src = target[ name ]; // Ensure proper type for the source value if ( copyIsArray && !Array.isArray( src ) ) { clone = []; } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { clone = {}; } else { clone = src; } copyIsArray = false; // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend( { // Unique for each copy of jQuery on the page expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), // Assume jQuery is ready without the ready module isReady: true, error: function( msg ) { throw new Error( msg ); }, noop: function() {}, isPlainObject: function( obj ) { var proto, Ctor; // Detect obvious negatives // Use toString instead of jQuery.type to catch host objects if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } proto = getProto( obj ); // Objects with no prototype (e.g., `Object.create( null )`) are plain if ( !proto ) { return true; } // Objects with prototype are plain iff they were constructed by a global Object function Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; }, // Evaluates a script in a provided context; falls back to the global one // if not specified. globalEval: function( code, options, doc ) { DOMEval( code, { nonce: options && options.nonce }, doc ); }, each: function( obj, callback ) { var length, i = 0; if ( isArrayLike( obj ) ) { length = obj.length; for ( ; i < length; i++ ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } else { for ( i in obj ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } return obj; }, // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; if ( arr != null ) { if ( isArrayLike( Object( arr ) ) ) { jQuery.merge( ret, typeof arr === "string" ? [ arr ] : arr ); } else { push.call( ret, arr ); } } return ret; }, inArray: function( elem, arr, i ) { return arr == null ? -1 : indexOf.call( arr, elem, i ); }, // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit merge: function( first, second ) { var len = +second.length, j = 0, i = first.length; for ( ; j < len; j++ ) { first[ i++ ] = second[ j ]; } first.length = i; return first; }, grep: function( elems, callback, invert ) { var callbackInverse, matches = [], i = 0, length = elems.length, callbackExpect = !invert; // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { callbackInverse = !callback( elems[ i ], i ); if ( callbackInverse !== callbackExpect ) { matches.push( elems[ i ] ); } } return matches; }, // arg is for internal usage only map: function( elems, callback, arg ) { var length, value, i = 0, ret = []; // Go through the array, translating each of the items to their new values if ( isArrayLike( elems ) ) { length = elems.length; for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } // Go through every key on the object, } else { for ( i in elems ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } } // Flatten any nested arrays return flat( ret ); }, // A global GUID counter for objects guid: 1, // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support } ); if ( typeof Symbol === "function" ) { jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; } // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), function( _i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); function isArrayLike( obj ) { // Support: real iOS 8.2 only (not reproducible in simulator) // `in` check used to prevent JIT error (gh-2145) // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, type = toType( obj ); if ( isFunction( obj ) || isWindow( obj ) ) { return false; } return type === "array" || length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj; } var Sizzle = /*! * Sizzle CSS Selector Engine v2.3.5 * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://js.foundation/ * * Date: 2020-03-14 */ ( function( window ) { var i, support, Expr, getText, isXML, tokenize, compile, select, outermostContext, sortInput, hasDuplicate, // Local document vars setDocument, document, docElem, documentIsHTML, rbuggyQSA, rbuggyMatches, matches, contains, // Instance-specific data expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), nonnativeSelectorCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; }, // Instance methods hasOwn = ( {} ).hasOwnProperty, arr = [], pop = arr.pop, pushNative = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf as it's faster than native // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { if ( list[ i ] === elem ) { return i; } } return -1; }, booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + "ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] // or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { "ID": new RegExp( "^#(" + identifier + ")" ), "CLASS": new RegExp( "^\\.(" + identifier + ")" ), "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, rnative = /^[^{]+\{\s*\[native \w/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, // CSS escapes // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), funescape = function( escape, nonHex ) { var high = "0x" + escape.slice( 1 ) - 0x10000; return nonHex ? // Strip the backslash prefix from a non-hex escape sequence nonHex : // Replace a hexadecimal escape sequence with the encoded Unicode code point // Support: IE <=11+ // For values outside the Basic Multilingual Plane (BMP), manually construct a // surrogate pair high < 0 ? String.fromCharCode( high + 0x10000 ) : String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, // CSS string/identifier serialization // https://drafts.csswg.org/cssom/#common-serializing-idioms rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, fcssescape = function( ch, asCodePoint ) { if ( asCodePoint ) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if ( ch === "\0" ) { return "\uFFFD"; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped return "\\" + ch; }, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }, inDisabledFieldset = addCombinator( function( elem ) { return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; }, { dir: "parentNode", next: "legend" } ); // Optimize for push.apply( _, NodeList ) try { push.apply( ( arr = slice.call( preferredDoc.childNodes ) ), preferredDoc.childNodes ); // Support: Android<4.0 // Detect silently failing push.apply // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { pushNative.apply( target, slice.call( els ) ); } : // Support: IE<9 // Otherwise append directly function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length while ( ( target[ j++ ] = els[ i++ ] ) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; } // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { setDocument( context ); context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { // ID selector if ( ( m = match[ 1 ] ) ) { // Document context if ( nodeType === 9 ) { if ( ( elem = context.getElementById( m ) ) ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } // Element context } else { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( newContext && ( elem = newContext.getElementById( m ) ) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Type selector } else if ( match[ 2 ] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll if ( support.qsa && !nonnativeSelectorCache[ selector + " " ] && ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && // Support: IE 8 only // Exclude object elements ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { newSelector = selector; newContext = context; // qSA considers elements outside a scoping root when evaluating child or // descendant combinators, which is not what we want. // In such cases, we work around the behavior by prefixing every selector in the // list with an ID selector referencing the scope context. // The technique has to be used as well when a leading combinator is used // as such selectors are not recognized by querySelectorAll. // Thanks to Andrew Dupont for this technique. if ( nodeType === 1 && ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; // We can use :scope instead of the ID hack if the browser // supports it & if we're not changing the context. if ( newContext !== context || !support.scope ) { // Capture the context ID, setting it first if necessary if ( ( nid = context.getAttribute( "id" ) ) ) { nid = nid.replace( rcssescape, fcssescape ); } else { context.setAttribute( "id", ( nid = expando ) ); } } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + toSelector( groups[ i ] ); } newSelector = groups.join( "," ); } try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch ( qsaError ) { nonnativeSelectorCache( selector, true ); } finally { if ( nid === expando ) { context.removeAttribute( "id" ); } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); } /** * Create key-value caches of limited size * @returns {function(string, object)} Returns the Object data after storing it on itself with * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) * deleting the oldest entry */ function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } return ( cache[ key + " " ] = value ); } return cache; } /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } /** * Support testing using an element * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { var el = document.createElement( "fieldset" ); try { return !!fn( el ); } catch ( e ) { return false; } finally { // Remove from its parent by default if ( el.parentNode ) { el.parentNode.removeChild( el ); } // release memory in IE el = null; } } /** * Adds the same handler for all of the specified attrs * @param {String} attrs Pipe-separated list of attributes * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { var arr = attrs.split( "|" ), i = arr.length; while ( i-- ) { Expr.attrHandle[ arr[ i ] ] = handler; } } /** * Checks document order of two siblings * @param {Element} a * @param {Element} b * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b */ function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes if ( diff ) { return diff; } // Check if b follows a if ( cur ) { while ( ( cur = cur.nextSibling ) ) { if ( cur === b ) { return -1; } } } return a ? 1 : -1; } /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === type; }; } /** * Returns a function to use in pseudos for buttons * @param {String} type */ function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return ( name === "input" || name === "button" ) && elem.type === type; }; } /** * Returns a function to use in pseudos for :enabled/:disabled * @param {Boolean} disabled true for :disabled; false for :enabled */ function createDisabledPseudo( disabled ) { // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable return function( elem ) { // Only certain elements can match :enabled or :disabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled if ( "form" in elem ) { // Check for inherited disabledness on relevant non-disabled elements: // * listed form-associated elements in a disabled fieldset // https://html.spec.whatwg.org/multipage/forms.html#category-listed // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled // * option elements in a disabled optgroup // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled // All such elements have a "form" property. if ( elem.parentNode && elem.disabled === false ) { // Option elements defer to a parent optgroup if present if ( "label" in elem ) { if ( "label" in elem.parentNode ) { return elem.parentNode.disabled === disabled; } else { return elem.disabled === disabled; } } // Support: IE 6 - 11 // Use the isDisabled shortcut property to check for disabled fieldset ancestors return elem.isDisabled === disabled || // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; // Try to winnow out elements that can't be disabled before trusting the disabled property. // Some victims get caught in our net (label, legend, menu, track), but it shouldn't // even exist on them, let alone have a boolean value. } else if ( "label" in elem ) { return elem.disabled === disabled; } // Remaining elements are neither :enabled nor :disabled return false; }; } /** * Returns a function to use in pseudos for positionals * @param {Function} fn */ function createPositionalPseudo( fn ) { return markFunction( function( argument ) { argument = +argument; return markFunction( function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { if ( seed[ ( j = matchIndexes[ i ] ) ] ) { seed[ j ] = !( matches[ j ] = seed[ j ] ); } } } ); } ); } /** * Checks a node for validity as a Sizzle context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience support = Sizzle.support = {}; /** * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { var namespace = elem.namespaceURI, docElem = ( elem.ownerDocument || elem ).documentElement; // Support: IE <=8 // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes // https://bugs.jquery.com/ticket/4833 return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); }; /** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML( document ); // Support: IE 9 - 11+, Edge 12 - 18+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( preferredDoc != document && ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { // Support: IE 11, Edge if ( subWindow.addEventListener ) { subWindow.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only } else if ( subWindow.attachEvent ) { subWindow.attachEvent( "onunload", unloadHandler ); } } // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, // Safari 4 - 5 only, Opera <=11.6 - 12.x only // IE/Edge & older browsers don't support the :scope pseudo-class. // Support: Safari 6.0 only // Safari 6.0 supports :scope but it's an alias of :root there. support.scope = assert( function( el ) { docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); return typeof el.querySelectorAll !== "undefined" && !el.querySelectorAll( ":scope fieldset div" ).length; } ); /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert( function( el ) { el.className = "i"; return !el.getAttribute( "className" ); } ); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert( function( el ) { el.appendChild( document.createComment( "" ) ); return !el.getElementsByTagName( "*" ).length; } ); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test support.getById = assert( function( el ) { docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; } ); // ID filter and find if ( support.getById ) { Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute( "id" ) === attrId; }; }; Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode( "id" ); return node && node.value === attrId; }; }; // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); if ( elem ) { // Verify the id attribute node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; while ( ( elem = elems[ i++ ] ) ) { node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } } } return []; } }; } // Tag Expr.find[ "TAG" ] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); // DocumentFragment nodes don't have gEBTN } else if ( support.qsa ) { return context.querySelectorAll( tag ); } } : function( tag, context ) { var elem, tmp = [], i = 0, // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { while ( ( elem = results[ i++ ] ) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } } return tmp; } return results; }; // Class Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; /* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { // Build QSA regex // Regex strategy adopted from Diego Perini assert( function( el ) { var input; // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // https://bugs.jquery.com/ticket/12359 docElem.appendChild( el ).innerHTML = "" + ""; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly if ( !el.querySelectorAll( "[selected]" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { rbuggyQSA.push( "~=" ); } // Support: IE 11+, Edge 15 - 18+ // IE 11/Edge don't find elements on a `[name='']` query in some cases. // Adding a temporary attribute to the document before the selection works // around the issue. // Interestingly, IE 10 & older don't seem to have the issue. input = document.createElement( "input" ); input.setAttribute( "name", "" ); el.appendChild( input ); if ( !el.querySelectorAll( "[name='']" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + whitespace + "*(?:''|\"\")" ); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !el.querySelectorAll( ":checked" ).length ) { rbuggyQSA.push( ":checked" ); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibling-combinator selector` fails if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { rbuggyQSA.push( ".#.+[+~]" ); } // Support: Firefox <=3.6 - 5 only // Old Firefox doesn't throw on a badly-escaped identifier. el.querySelectorAll( "\\\f" ); rbuggyQSA.push( "[\\r\\n\\f]" ); } ); assert( function( el ) { el.innerHTML = "" + ""; // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment var input = document.createElement( "input" ); input.setAttribute( "type", "hidden" ); el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute if ( el.querySelectorAll( "[name=d]" ).length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: IE9-11+ // IE's :disabled selector does not pick up the children of disabled fieldsets docElem.appendChild( el ).disabled = true; if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: Opera 10 - 11 only // Opera 10-11 does not throw on post-comma invalid pseudos el.querySelectorAll( "*,:x" ); rbuggyQSA.push( ",.*:" ); } ); } if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector ) ) ) ) { assert( function( el ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( el, "*" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); } ); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully self-exclusive // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 ) ); } : function( a, b ) { if ( b ) { while ( ( b = b.parentNode ) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { // Choose the first element that is related to our preferred document // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( a == document || a.ownerDocument == preferredDoc && contains( preferredDoc, a ) ) { return -1; } // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( b == document || b.ownerDocument == preferredDoc && contains( preferredDoc, b ) ) { return 1; } // Maintain original order return sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. /* eslint-disable eqeqeq */ return a == document ? -1 : b == document ? 1 : /* eslint-enable eqeqeq */ aup ? -1 : bup ? 1 : sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison cur = a; while ( ( cur = cur.parentNode ) ) { ap.unshift( cur ); } cur = b; while ( ( cur = cur.parentNode ) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[ i ] === bp[ i ] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[ i ], bp[ i ] ) : // Otherwise nodes in our document sort first // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. /* eslint-disable eqeqeq */ ap[ i ] == preferredDoc ? -1 : bp[ i ] == preferredDoc ? 1 : /* eslint-enable eqeqeq */ 0; }; return document; }; Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); }; Sizzle.matchesSelector = function( elem, expr ) { setDocument( elem ); if ( support.matchesSelector && documentIsHTML && !nonnativeSelectorCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch ( e ) { nonnativeSelectorCache( expr, true ); } } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { // Set document vars if needed // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( ( context.ownerDocument || context ) != document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { // Set document vars if needed // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( ( elem.ownerDocument || elem ) != document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; }; Sizzle.escape = function( sel ) { return ( sel + "" ).replace( rcssescape, fcssescape ); }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Document sorting and removing duplicates * @param {ArrayLike} results */ Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { while ( ( elem = results[ i++ ] ) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } // Clear input after sorting to release objects // See https://github.com/jquery/sizzle/pull/225 sortInput = null; return results; }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // If no nodeType, this is expected to be an array while ( ( node = elem[ i++ ] ) ) { // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } // Do not include comment or processing instruction nodes return ret; }; Expr = Sizzle.selectors = { // Can be adjusted by the user cacheLength: 50, createPseudo: markFunction, match: matchExpr, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }, preFilter: { "ATTR": function( match ) { match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ).replace( runescape, funescape ); if ( match[ 2 ] === "~=" ) { match[ 3 ] = " " + match[ 3 ] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[ 1 ] = match[ 1 ].toLowerCase(); if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { // nth-* requires argument if ( !match[ 3 ] ) { Sizzle.error( match[ 0 ] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[ 4 ] = +( match[ 4 ] ? match[ 5 ] + ( match[ 6 ] || 1 ) : 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); // other types prohibit arguments } else if ( match[ 3 ] ) { Sizzle.error( match[ 0 ] ); } return match; }, "PSEUDO": function( match ) { var excess, unquoted = !match[ 6 ] && match[ 2 ]; if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { return null; } // Accept quoted arguments as-is if ( match[ 3 ] ) { match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && // Get excess from tokenize (recursively) ( excess = tokenize( unquoted, true ) ) && // advance to the next closing parenthesis ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { // excess is a negative index match[ 0 ] = match[ 0 ].slice( 0, excess ); match[ 2 ] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) return match.slice( 0, 3 ); } }, filter: { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; }, "CLASS": function( className ) { var pattern = classCache[ className + " " ]; return pattern || ( pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( className, function( elem ) { return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute( "class" ) || "" ); } ); }, "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } result += ""; /* eslint-disable max-len */ return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; /* eslint-enable max-len */ }; }, "CHILD": function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; return first === 1 && last === 0 ? // Shortcut for :nth-*(n) function( elem ) { return !!elem.parentNode; } : function( elem, _context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), useCache = !xml && !ofType, diff = false; if ( parent ) { // :(first|last|only)-(child|of-type) if ( simple ) { while ( dir ) { node = elem; while ( ( node = node[ dir ] ) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { return false; } } // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } return true; } start = [ forward ? parent.firstChild : parent.lastChild ]; // non-xml :nth-child(...) stores cache data on `parent` if ( forward && useCache ) { // Seek `elem` from a previously-cached index // ...in a gzip-friendly way node = parent; outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; while ( ( node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start ( diff = nodeIndex = 0 ) || start.pop() ) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } } else { // Use previously-cached element index if available if ( useCache ) { // ...in a gzip-friendly way node = elem; outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { // Use the same loop as above to seek `elem` from the start while ( ( node = ++nodeIndex && node && node[ dir ] || ( diff = nodeIndex = 0 ) || start.pop() ) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { // Cache the index of each encountered element if ( useCache ) { outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || ( outerCache[ node.uniqueID ] = {} ); uniqueCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { break; } } } } } // Incorporate the offset, then check against cycle size diff -= last; return diff === first || ( diff % first === 0 && diff / first >= 0 ); } }; }, "PSEUDO": function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Sizzle does if ( fn[ expando ] ) { return fn( argument ); } // But maintain support for old signatures if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? markFunction( function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { idx = indexOf( seed, matched[ i ] ); seed[ idx ] = !( matches[ idx ] = matched[ i ] ); } } ) : function( elem ) { return fn( elem, 0, args ); }; } return fn; } }, pseudos: { // Potentially complex pseudos "not": markFunction( function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? markFunction( function( seed, matches, _context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { if ( ( elem = unmatched[ i ] ) ) { seed[ i ] = !( matches[ i ] = elem ); } } } ) : function( elem, _context, xml ) { input[ 0 ] = elem; matcher( input, null, xml, results ); // Don't keep the element (issue #299) input[ 0 ] = null; return !results.pop(); }; } ), "has": markFunction( function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; } ), "contains": markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; } ), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test( lang || "" ) ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { if ( ( elemLang = documentIsHTML ? elem.lang : elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); return false; }; } ), // Miscellaneous "target": function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, "root": function( elem ) { return elem === docElem; }, "focus": function( elem ) { return elem === document.activeElement && ( !document.hasFocus || document.hasFocus() ) && !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties "enabled": createDisabledPseudo( false ), "disabled": createDisabledPseudo( true ), "checked": function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); return ( nodeName === "input" && !!elem.checked ) || ( nodeName === "option" && !!elem.selected ); }, "selected": function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { // eslint-disable-next-line no-unused-expressions elem.parentNode.selectedIndex; } return elem.selected === true; }, // Contents "empty": function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { if ( elem.nodeType < 6 ) { return false; } } return true; }, "parent": function( elem ) { return !Expr.pseudos[ "empty" ]( elem ); }, // Element/input types "header": function( elem ) { return rheader.test( elem.nodeName ); }, "input": function( elem ) { return rinputs.test( elem.nodeName ); }, "button": function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, "text": function( elem ) { var attr; return elem.nodeName.toLowerCase() === "input" && elem.type === "text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" ( ( attr = elem.getAttribute( "type" ) ) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection "first": createPositionalPseudo( function() { return [ 0 ]; } ), "last": createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; } ), "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; } ), "even": createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; } ), "odd": createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; } ), "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument > length ? length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; } ), "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; } ) } }; Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { Expr.pseudos[ i ] = createInputPseudo( i ); } for ( i in { submit: true, reset: true } ) { Expr.pseudos[ i ] = createButtonPseudo( i ); } // Easy API for creating new setFilters function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); tokenize = Sizzle.tokenize = function( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); } soFar = selector; groups = []; preFilters = Expr.preFilter; while ( soFar ) { // Comma and first run if ( !matched || ( match = rcomma.exec( soFar ) ) ) { if ( match ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[ 0 ].length ) || soFar; } groups.push( ( tokens = [] ) ); } matched = false; // Combinators if ( ( match = rcombinators.exec( soFar ) ) ) { matched = match.shift(); tokens.push( { value: matched, // Cast descendant combinators to space type: match[ 0 ].replace( rtrim, " " ) } ); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || ( match = preFilters[ type ]( match ) ) ) ) { matched = match.shift(); tokens.push( { value: matched, type: type, matches: match } ); soFar = soFar.slice( matched.length ); } } if ( !matched ) { break; } } // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens return parseOnly ? soFar.length : soFar ? Sizzle.error( selector ) : // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; function toSelector( tokens ) { var i = 0, len = tokens.length, selector = ""; for ( ; i < len; i++ ) { selector += tokens[ i ].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, skip = combinator.next, key = skip || dir, checkNonElements = base && key === "parentNode", doneName = done++; return combinator.first ? // Check against closest ancestor/preceding element function( elem, context, xml ) { while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } return false; } : // Check against all ancestor/preceding elements function( elem, context, xml ) { var oldCache, uniqueCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; } } } } else { while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || ( elem[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ elem.uniqueID ] || ( outerCache[ elem.uniqueID ] = {} ); if ( skip && skip === elem.nodeName.toLowerCase() ) { elem = elem[ dir ] || elem; } else if ( ( oldCache = uniqueCache[ key ] ) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements return ( newCache[ 2 ] = oldCache[ 2 ] ); } else { // Reuse newcache so results back-propagate to previous elements uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { return true; } } } } } return false; }; } function elementMatcher( matchers ) { return matchers.length > 1 ? function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { if ( !matchers[ i ]( elem, context, xml ) ) { return false; } } return true; } : matchers[ 0 ]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { Sizzle( selector, contexts[ i ], results ); } return results; } function condense( unmatched, map, filter, context, xml ) { var elem, newUnmatched = [], i = 0, len = unmatched.length, mapped = map != null; for ( ; i < len; i++ ) { if ( ( elem = unmatched[ i ] ) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { map.push( i ); } } } } return newUnmatched; } function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } return markFunction( function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } // Apply postFilter if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( ( elem = temp[ i ] ) ) { matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); } } } if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( ( elem = matcherOut[ i ] ) ) { // Restore matcherIn since elem is not yet a final match temp.push( ( matcherIn[ i ] = elem ) ); } } postFinder( null, ( matcherOut = [] ), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( ( elem = matcherOut[ i ] ) && ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { seed[ temp ] = !( results[ temp ] = elem ); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { postFinder( null, results, matcherOut, xml ); } else { push.apply( results, matcherOut ); } } } ); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, leadingRelative = Expr.relative[ tokens[ 0 ].type ], implicitRelative = leadingRelative || Expr.relative[ " " ], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) matchContext = addCombinator( function( elem ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( ( checkContext = context ).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; } else { matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[ j ].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` tokens .slice( 0, i - 1 ) .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } return elementMatcher( matchers ); } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, matchedCount = 0, i = "0", unmatched = seed && [], setMatched = [], contextBackup = outermostContext, // We must always have either seed elements or outermost context elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), len = elems.length; if ( outermost ) { // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq outermostContext = context == document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { if ( byElement && elem ) { j = 0; // Support: IE 11+, Edge 17 - 18+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq if ( !context && elem.ownerDocument != document ) { setDocument( elem ); xml = !documentIsHTML; } while ( ( matcher = elementMatchers[ j++ ] ) ) { if ( matcher( elem, context || document, xml ) ) { results.push( elem ); break; } } if ( outermost ) { dirruns = dirrunsUnique; } } // Track unmatched elements for set filters if ( bySet ) { // They will have gone through all possible matchers if ( ( elem = !matcher && elem ) ) { matchedCount--; } // Lengthen the array for every element, matched or not if ( seed ) { unmatched.push( elem ); } } } // `i` is now the count of elements visited above, and adding it to `matchedCount` // makes the latter nonnegative. matchedCount += i; // Apply set filters to unmatched elements // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` // equals `i`), unless we didn't visit _any_ elements in the above loop because we have // no element matchers and no seed. // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that // case, which will result in a "00" `matchedCount` that differs from `i` but is also // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; while ( ( matcher = setMatchers[ j++ ] ) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { if ( !( unmatched[ i ] || setMatched[ i ] ) ) { setMatched[ i ] = pop.call( results ); } } } // Discard index placeholder values to get only actual matches setMatched = condense( setMatched ); } // Add matches to results push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { Sizzle.uniqueSort( results ); } } // Override manipulation of globals by nested matchers if ( outermost ) { dirruns = dirrunsUnique; outermostContext = contextBackup; } return unmatched; }; return bySet ? markFunction( superMatcher ) : superMatcher; } compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { cached = matcherFromTokens( match[ i ] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { elementMatchers.push( cached ); } } // Cache the compiled function cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; }; /** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( ( selector = compiled.selector || selector ) ); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees us context) if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID tokens = match[ 0 ] = match[ 0 ].slice( 0 ); if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { context = ( Expr.find[ "ID" ]( token.matches[ 0 ] .replace( runescape, funescape ), context ) || [] )[ 0 ]; if ( !context ) { return results; // Precompiled matchers will still verify ancestry, so step up a level } else if ( compiled ) { context = context.parentNode; } selector = selector.slice( tokens.shift().value.length ); } // Fetch a seed set for right-to-left matching i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[ i ]; // Abort if we hit a combinator if ( Expr.relative[ ( type = token.type ) ] ) { break; } if ( ( find = Expr.find[ type ] ) ) { // Search, expanding context for leading sibling combinators if ( ( seed = find( token.matches[ 0 ].replace( runescape, funescape ), rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || context ) ) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above ( compiled || compile( selector, match ) )( seed, context, !documentIsHTML, results, !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }; // One-time assignments // Sort stability support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* support.sortDetached = assert( function( el ) { // Should return 1, but returns 4 (following) return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; } ); // Support: IE<8 // Prevent attribute/property "interpolation" // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !assert( function( el ) { el.innerHTML = ""; return el.firstChild.getAttribute( "href" ) === "#"; } ) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } } ); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert( function( el ) { el.innerHTML = ""; el.firstChild.setAttribute( "value", "" ); return el.firstChild.getAttribute( "value" ) === ""; } ) ) { addHandle( "value", function( elem, _name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } } ); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies if ( !assert( function( el ) { return el.getAttribute( "disabled" ) == null; } ) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; } } ); } return Sizzle; } )( window ); jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; // Deprecated jQuery.expr[ ":" ] = jQuery.expr.pseudos; jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; jQuery.escapeSelector = Sizzle.escape; var dir = function( elem, dir, until ) { var matched = [], truncate = until !== undefined; while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { if ( elem.nodeType === 1 ) { if ( truncate && jQuery( elem ).is( until ) ) { break; } matched.push( elem ); } } return matched; }; var siblings = function( n, elem ) { var matched = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { matched.push( n ); } } return matched; }; var rneedsContext = jQuery.expr.match.needsContext; function nodeName( elem, name ) { return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); }; var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); // Implement the identical functionality for filter and not function winnow( elements, qualifier, not ) { if ( isFunction( qualifier ) ) { return jQuery.grep( elements, function( elem, i ) { return !!qualifier.call( elem, i, elem ) !== not; } ); } // Single element if ( qualifier.nodeType ) { return jQuery.grep( elements, function( elem ) { return ( elem === qualifier ) !== not; } ); } // Arraylike of elements (jQuery, arguments, Array) if ( typeof qualifier !== "string" ) { return jQuery.grep( elements, function( elem ) { return ( indexOf.call( qualifier, elem ) > -1 ) !== not; } ); } // Filtered directly for both simple and complex selectors return jQuery.filter( qualifier, elements, not ); } jQuery.filter = function( expr, elems, not ) { var elem = elems[ 0 ]; if ( not ) { expr = ":not(" + expr + ")"; } if ( elems.length === 1 && elem.nodeType === 1 ) { return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; } return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { return elem.nodeType === 1; } ) ); }; jQuery.fn.extend( { find: function( selector ) { var i, ret, len = this.length, self = this; if ( typeof selector !== "string" ) { return this.pushStack( jQuery( selector ).filter( function() { for ( i = 0; i < len; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } } ) ); } ret = this.pushStack( [] ); for ( i = 0; i < len; i++ ) { jQuery.find( selector, self[ i ], ret ); } return len > 1 ? jQuery.uniqueSort( ret ) : ret; }, filter: function( selector ) { return this.pushStack( winnow( this, selector || [], false ) ); }, not: function( selector ) { return this.pushStack( winnow( this, selector || [], true ) ); }, is: function( selector ) { return !!winnow( this, // If this is a positional/relative selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". typeof selector === "string" && rneedsContext.test( selector ) ? jQuery( selector ) : selector || [], false ).length; } } ); // Initialize a jQuery object // A central reference to the root jQuery(document) var rootjQuery, // A simple way to check for HTML strings // Prioritize #id over to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) // Shortcut simple #id case for speed rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, init = jQuery.fn.init = function( selector, context, root ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Method init() accepts an alternate rootjQuery // so migrate can support jQuery.sub (gh-2101) root = root || rootjQuery; // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } // Match html or make sure no context is specified for #id if ( match && ( match[ 1 ] || !context ) ) { // HANDLE: $(html) -> $(array) if ( match[ 1 ] ) { context = context instanceof jQuery ? context[ 0 ] : context; // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[ 1 ], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible if ( isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else { elem = document.getElementById( match[ 2 ] ); if ( elem ) { // Inject the element directly into the jQuery object this[ 0 ] = elem; this.length = 1; } return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || root ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { this[ 0 ] = selector; this.length = 1; return this; // HANDLE: $(function) // Shortcut for document ready } else if ( isFunction( selector ) ) { return root.ready !== undefined ? root.ready( selector ) : // Execute immediately if ready is not present selector( jQuery ); } return jQuery.makeArray( selector, this ); }; // Give the init function the jQuery prototype for later instantiation init.prototype = jQuery.fn; // Initialize central reference rootjQuery = jQuery( document ); var rparentsprev = /^(?:parents|prev(?:Until|All))/, // Methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend( { has: function( target ) { var targets = jQuery( target, this ), l = targets.length; return this.filter( function() { var i = 0; for ( ; i < l; i++ ) { if ( jQuery.contains( this, targets[ i ] ) ) { return true; } } } ); }, closest: function( selectors, context ) { var cur, i = 0, l = this.length, matched = [], targets = typeof selectors !== "string" && jQuery( selectors ); // Positional selectors never match, since there's no _selection_ context if ( !rneedsContext.test( selectors ) ) { for ( ; i < l; i++ ) { for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { // Always skip document fragments if ( cur.nodeType < 11 && ( targets ? targets.index( cur ) > -1 : // Don't pass non-elements to Sizzle cur.nodeType === 1 && jQuery.find.matchesSelector( cur, selectors ) ) ) { matched.push( cur ); break; } } } } return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); }, // Determine the position of an element within the set index: function( elem ) { // No argument, return index in parent if ( !elem ) { return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; } // Index in selector if ( typeof elem === "string" ) { return indexOf.call( jQuery( elem ), this[ 0 ] ); } // Locate the position of the desired element return indexOf.call( this, // If it receives a jQuery object, the first element is used elem.jquery ? elem[ 0 ] : elem ); }, add: function( selector, context ) { return this.pushStack( jQuery.uniqueSort( jQuery.merge( this.get(), jQuery( selector, context ) ) ) ); }, addBack: function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); } } ); function sibling( cur, dir ) { while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} return cur; } jQuery.each( { parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return dir( elem, "parentNode" ); }, parentsUntil: function( elem, _i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { return sibling( elem, "nextSibling" ); }, prev: function( elem ) { return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { return dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, nextUntil: function( elem, _i, until ) { return dir( elem, "nextSibling", until ); }, prevUntil: function( elem, _i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return siblings( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return siblings( elem.firstChild ); }, contents: function( elem ) { if ( elem.contentDocument != null && // Support: IE 11+ // elements with no `data` attribute has an object // `contentDocument` with a `null` prototype. getProto( elem.contentDocument ) ) { return elem.contentDocument; } // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only // Treat the template element as a regular one in browsers that // don't support it. if ( nodeName( elem, "template" ) ) { elem = elem.content || elem; } return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var matched = jQuery.map( this, fn, until ); if ( name.slice( -5 ) !== "Until" ) { selector = until; } if ( selector && typeof selector === "string" ) { matched = jQuery.filter( selector, matched ); } if ( this.length > 1 ) { // Remove duplicates if ( !guaranteedUnique[ name ] ) { jQuery.uniqueSort( matched ); } // Reverse order for parents* and prev-derivatives if ( rparentsprev.test( name ) ) { matched.reverse(); } } return this.pushStack( matched ); }; } ); var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); // Convert String-formatted options into Object-formatted ones function createOptions( options ) { var object = {}; jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { object[ flag ] = true; } ); return object; } /* * Create a callback list using the following parameters: * * options: an optional list of space-separated options that will change how * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) options = typeof options === "string" ? createOptions( options ) : jQuery.extend( {}, options ); var // Flag to know if list is currently firing firing, // Last fire value for non-forgettable lists memory, // Flag to know if list was already fired fired, // Flag to prevent firing locked, // Actual callback list list = [], // Queue of execution data for repeatable lists queue = [], // Index of currently firing callback (modified by add/remove as needed) firingIndex = -1, // Fire callbacks fire = function() { // Enforce single-firing locked = locked || options.once; // Execute callbacks for all pending executions, // respecting firingIndex overrides and runtime changes fired = firing = true; for ( ; queue.length; firingIndex = -1 ) { memory = queue.shift(); while ( ++firingIndex < list.length ) { // Run callback and check for early termination if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && options.stopOnFalse ) { // Jump to end and forget the data so .add doesn't re-fire firingIndex = list.length; memory = false; } } } // Forget the data if we're done with it if ( !options.memory ) { memory = false; } firing = false; // Clean up if we're done firing for good if ( locked ) { // Keep an empty list if we have data for future add calls if ( memory ) { list = []; // Otherwise, this object is spent } else { list = ""; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { // If we have memory from a past run, we should fire after adding if ( memory && !firing ) { firingIndex = list.length - 1; queue.push( memory ); } ( function add( args ) { jQuery.each( args, function( _, arg ) { if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); } } ); } )( arguments ); if ( memory && !firing ) { fire(); } } return this; }, // Remove a callback from the list remove: function() { jQuery.each( arguments, function( _, arg ) { var index; while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes if ( index <= firingIndex ) { firingIndex--; } } } ); return this; }, // Check if a given callback is in the list. // If no argument is given, return whether or not list has callbacks attached. has: function( fn ) { return fn ? jQuery.inArray( fn, list ) > -1 : list.length > 0; }, // Remove all callbacks from the list empty: function() { if ( list ) { list = []; } return this; }, // Disable .fire and .add // Abort any current/pending executions // Clear all callbacks and values disable: function() { locked = queue = []; list = memory = ""; return this; }, disabled: function() { return !list; }, // Disable .fire // Also disable .add unless we have memory (since it would have no effect) // Abort any pending executions lock: function() { locked = queue = []; if ( !memory && !firing ) { list = memory = ""; } return this; }, locked: function() { return !!locked; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( !locked ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; queue.push( args ); if ( !firing ) { fire(); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!fired; } }; return self; }; function Identity( v ) { return v; } function Thrower( ex ) { throw ex; } function adoptValue( value, resolve, reject, noValue ) { var method; try { // Check for promise aspect first to privilege synchronous behavior if ( value && isFunction( ( method = value.promise ) ) ) { method.call( value ).done( resolve ).fail( reject ); // Other thenables } else if ( value && isFunction( ( method = value.then ) ) ) { method.call( value, resolve, reject ); // Other non-thenables } else { // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: // * false: [ value ].slice( 0 ) => resolve( value ) // * true: [ value ].slice( 1 ) => resolve() resolve.apply( undefined, [ value ].slice( noValue ) ); } // For Promises/A+, convert exceptions into rejections // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in // Deferred#then to conditionally suppress rejection. } catch ( value ) { // Support: Android 4.0 only // Strict mode functions invoked without .call/.apply get global-object context reject.apply( undefined, [ value ] ); } } jQuery.extend( { Deferred: function( func ) { var tuples = [ // action, add listener, callbacks, // ... .then handlers, argument index, [final state] [ "notify", "progress", jQuery.Callbacks( "memory" ), jQuery.Callbacks( "memory" ), 2 ], [ "resolve", "done", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 0, "resolved" ], [ "reject", "fail", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 1, "rejected" ] ], state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, "catch": function( fn ) { return promise.then( null, fn ); }, // Keep pipe for back-compat pipe: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred( function( newDefer ) { jQuery.each( tuples, function( _i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; // deferred.progress(function() { bind to newDefer or newDefer.notify }) // deferred.done(function() { bind to newDefer or newDefer.resolve }) // deferred.fail(function() { bind to newDefer or newDefer.reject }) deferred[ tuple[ 1 ] ]( function() { var returned = fn && fn.apply( this, arguments ); if ( returned && isFunction( returned.promise ) ) { returned.promise() .progress( newDefer.notify ) .done( newDefer.resolve ) .fail( newDefer.reject ); } else { newDefer[ tuple[ 0 ] + "With" ]( this, fn ? [ returned ] : arguments ); } } ); } ); fns = null; } ).promise(); }, then: function( onFulfilled, onRejected, onProgress ) { var maxDepth = 0; function resolve( depth, deferred, handler, special ) { return function() { var that = this, args = arguments, mightThrow = function() { var returned, then; // Support: Promises/A+ section 2.3.3.3.3 // https://promisesaplus.com/#point-59 // Ignore double-resolution attempts if ( depth < maxDepth ) { return; } returned = handler.apply( that, args ); // Support: Promises/A+ section 2.3.1 // https://promisesaplus.com/#point-48 if ( returned === deferred.promise() ) { throw new TypeError( "Thenable self-resolution" ); } // Support: Promises/A+ sections 2.3.3.1, 3.5 // https://promisesaplus.com/#point-54 // https://promisesaplus.com/#point-75 // Retrieve `then` only once then = returned && // Support: Promises/A+ section 2.3.4 // https://promisesaplus.com/#point-64 // Only check objects and functions for thenability ( typeof returned === "object" || typeof returned === "function" ) && returned.then; // Handle a returned thenable if ( isFunction( then ) ) { // Special processors (notify) just wait for resolution if ( special ) { then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ) ); // Normal processors (resolve) also hook into progress } else { // ...and disregard older resolution values maxDepth++; then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ), resolve( maxDepth, deferred, Identity, deferred.notifyWith ) ); } // Handle all other returned values } else { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Identity ) { that = undefined; args = [ returned ]; } // Process the value(s) // Default process is resolve ( special || deferred.resolveWith )( that, args ); } }, // Only normal processors (resolve) catch and reject exceptions process = special ? mightThrow : function() { try { mightThrow(); } catch ( e ) { if ( jQuery.Deferred.exceptionHook ) { jQuery.Deferred.exceptionHook( e, process.stackTrace ); } // Support: Promises/A+ section 2.3.3.3.4.1 // https://promisesaplus.com/#point-61 // Ignore post-resolution exceptions if ( depth + 1 >= maxDepth ) { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Thrower ) { that = undefined; args = [ e ]; } deferred.rejectWith( that, args ); } } }; // Support: Promises/A+ section 2.3.3.3.1 // https://promisesaplus.com/#point-57 // Re-resolve promises immediately to dodge false rejection from // subsequent errors if ( depth ) { process(); } else { // Call an optional hook to record the stack, in case of exception // since it's otherwise lost when execution goes async if ( jQuery.Deferred.getStackHook ) { process.stackTrace = jQuery.Deferred.getStackHook(); } window.setTimeout( process ); } }; } return jQuery.Deferred( function( newDefer ) { // progress_handlers.add( ... ) tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith ) ); // fulfilled_handlers.add( ... ) tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, isFunction( onFulfilled ) ? onFulfilled : Identity ) ); // rejected_handlers.add( ... ) tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, isFunction( onRejected ) ? onRejected : Thrower ) ); } ).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } }, deferred = {}; // Add list-specific methods jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 5 ]; // promise.progress = list.add // promise.done = list.add // promise.fail = list.add promise[ tuple[ 1 ] ] = list.add; // Handle state if ( stateString ) { list.add( function() { // state = "resolved" (i.e., fulfilled) // state = "rejected" state = stateString; }, // rejected_callbacks.disable // fulfilled_callbacks.disable tuples[ 3 - i ][ 2 ].disable, // rejected_handlers.disable // fulfilled_handlers.disable tuples[ 3 - i ][ 3 ].disable, // progress_callbacks.lock tuples[ 0 ][ 2 ].lock, // progress_handlers.lock tuples[ 0 ][ 3 ].lock ); } // progress_handlers.fire // fulfilled_handlers.fire // rejected_handlers.fire list.add( tuple[ 3 ].fire ); // deferred.notify = function() { deferred.notifyWith(...) } // deferred.resolve = function() { deferred.resolveWith(...) } // deferred.reject = function() { deferred.rejectWith(...) } deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; // deferred.notifyWith = list.fireWith // deferred.resolveWith = list.fireWith // deferred.rejectWith = list.fireWith deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } ); // Make the deferred a promise promise.promise( deferred ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( singleValue ) { var // count of uncompleted subordinates remaining = arguments.length, // count of unprocessed arguments i = remaining, // subordinate fulfillment data resolveContexts = Array( i ), resolveValues = slice.call( arguments ), // the master Deferred master = jQuery.Deferred(), // subordinate callback factory updateFunc = function( i ) { return function( value ) { resolveContexts[ i ] = this; resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; if ( !( --remaining ) ) { master.resolveWith( resolveContexts, resolveValues ); } }; }; // Single- and empty arguments are adopted like Promise.resolve if ( remaining <= 1 ) { adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, !remaining ); // Use .then() to unwrap secondary thenables (cf. gh-3000) if ( master.state() === "pending" || isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { return master.then(); } } // Multiple arguments are aggregated like Promise.all array elements while ( i-- ) { adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } return master.promise(); } } ); // These usually indicate a programmer mistake during development, // warn about them ASAP rather than swallowing them by default. var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; jQuery.Deferred.exceptionHook = function( error, stack ) { // Support: IE 8 - 9 only // Console exists when dev tools are open, which can happen at any time if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); } }; jQuery.readyException = function( error ) { window.setTimeout( function() { throw error; } ); }; // The deferred used on DOM ready var readyList = jQuery.Deferred(); jQuery.fn.ready = function( fn ) { readyList .then( fn ) // Wrap jQuery.readyException in a function so that the lookup // happens at the time of error handling instead of callback // registration. .catch( function( error ) { jQuery.readyException( error ); } ); return this; }; jQuery.extend( { // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Handle when the DOM is ready ready: function( wait ) { // Abort if there are pending holds or we're already ready if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); } } ); jQuery.ready.then = readyList.then; // The ready event handler and self cleanup method function completed() { document.removeEventListener( "DOMContentLoaded", completed ); window.removeEventListener( "load", completed ); jQuery.ready(); } // Catch cases where $(document).ready() is called // after the browser event has already occurred. // Support: IE <=9 - 10 only // Older IE sometimes signals "interactive" too soon if ( document.readyState === "complete" || ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { // Handle it asynchronously to allow scripts the opportunity to delay ready window.setTimeout( jQuery.ready ); } else { // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed ); // A fallback to window.onload, that will always work window.addEventListener( "load", completed ); } // Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { var i = 0, len = elems.length, bulk = key == null; // Sets many values if ( toType( key ) === "object" ) { chainable = true; for ( i in key ) { access( elems, fn, i, key[ i ], true, emptyGet, raw ); } // Sets one value } else if ( value !== undefined ) { chainable = true; if ( !isFunction( value ) ) { raw = true; } if ( bulk ) { // Bulk operations run against the entire set if ( raw ) { fn.call( elems, value ); fn = null; // ...except when executing function values } else { bulk = fn; fn = function( elem, _key, value ) { return bulk.call( jQuery( elem ), value ); }; } } if ( fn ) { for ( ; i < len; i++ ) { fn( elems[ i ], key, raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) ) ); } } } if ( chainable ) { return elems; } // Gets if ( bulk ) { return fn.call( elems ); } return len ? fn( elems[ 0 ], key ) : emptyGet; }; // Matches dashed string for camelizing var rmsPrefix = /^-ms-/, rdashAlpha = /-([a-z])/g; // Used by camelCase as callback to replace() function fcamelCase( _all, letter ) { return letter.toUpperCase(); } // Convert dashed to camelCase; used by the css and data modules // Support: IE <=9 - 11, Edge 12 - 15 // Microsoft forgot to hump their vendor prefix (#9572) function camelCase( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); } var acceptData = function( owner ) { // Accepts only: // - Node // - Node.ELEMENT_NODE // - Node.DOCUMENT_NODE // - Object // - Any return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); }; function Data() { this.expando = jQuery.expando + Data.uid++; } Data.uid = 1; Data.prototype = { cache: function( owner ) { // Check if the owner object already has a cache var value = owner[ this.expando ]; // If not, create one if ( !value ) { value = {}; // We can accept data for non-element nodes in modern browsers, // but we should not, see #8335. // Always return an empty object. if ( acceptData( owner ) ) { // If it is a node unlikely to be stringify-ed or looped over // use plain assignment if ( owner.nodeType ) { owner[ this.expando ] = value; // Otherwise secure it in a non-enumerable property // configurable must be true to allow the property to be // deleted when data is removed } else { Object.defineProperty( owner, this.expando, { value: value, configurable: true } ); } } } return value; }, set: function( owner, data, value ) { var prop, cache = this.cache( owner ); // Handle: [ owner, key, value ] args // Always use camelCase key (gh-2257) if ( typeof data === "string" ) { cache[ camelCase( data ) ] = value; // Handle: [ owner, { properties } ] args } else { // Copy the properties one-by-one to the cache object for ( prop in data ) { cache[ camelCase( prop ) ] = data[ prop ]; } } return cache; }, get: function( owner, key ) { return key === undefined ? this.cache( owner ) : // Always use camelCase key (gh-2257) owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; }, access: function( owner, key, value ) { // In cases where either: // // 1. No key was specified // 2. A string key was specified, but no value provided // // Take the "read" path and allow the get method to determine // which value to return, respectively either: // // 1. The entire cache object // 2. The data stored at the key // if ( key === undefined || ( ( key && typeof key === "string" ) && value === undefined ) ) { return this.get( owner, key ); } // When the key is not a string, or both a key and value // are specified, set or extend (existing objects) with either: // // 1. An object of properties // 2. A key and value // this.set( owner, key, value ); // Since the "set" path can have two possible entry points // return the expected data based on which path was taken[*] return value !== undefined ? value : key; }, remove: function( owner, key ) { var i, cache = owner[ this.expando ]; if ( cache === undefined ) { return; } if ( key !== undefined ) { // Support array or space separated string of keys if ( Array.isArray( key ) ) { // If key is an array of keys... // We always set camelCase keys, so remove that. key = key.map( camelCase ); } else { key = camelCase( key ); // If a key with the spaces exists, use it. // Otherwise, create an array by matching non-whitespace key = key in cache ? [ key ] : ( key.match( rnothtmlwhite ) || [] ); } i = key.length; while ( i-- ) { delete cache[ key[ i ] ]; } } // Remove the expando if there's no more data if ( key === undefined || jQuery.isEmptyObject( cache ) ) { // Support: Chrome <=35 - 45 // Webkit & Blink performance suffers when deleting properties // from DOM nodes, so set to undefined instead // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) if ( owner.nodeType ) { owner[ this.expando ] = undefined; } else { delete owner[ this.expando ]; } } }, hasData: function( owner ) { var cache = owner[ this.expando ]; return cache !== undefined && !jQuery.isEmptyObject( cache ); } }; var dataPriv = new Data(); var dataUser = new Data(); // Implementation Summary // // 1. Enforce API surface and semantic compatibility with 1.9.x branch // 2. Improve the module's maintainability by reducing the storage // paths to a single mechanism. // 3. Use the same single mechanism to support "private" and "user" data. // 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) // 5. Avoid exposing implementation details on user objects (eg. expando properties) // 6. Provide a clear path for implementation upgrade to WeakMap in 2014 var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, rmultiDash = /[A-Z]/g; function getData( data ) { if ( data === "true" ) { return true; } if ( data === "false" ) { return false; } if ( data === "null" ) { return null; } // Only convert to a number if it doesn't change the string if ( data === +data + "" ) { return +data; } if ( rbrace.test( data ) ) { return JSON.parse( data ); } return data; } function dataAttr( elem, key, data ) { var name; // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = getData( data ); } catch ( e ) {} // Make sure we set the data so it isn't changed later dataUser.set( elem, key, data ); } else { data = undefined; } } return data; } jQuery.extend( { hasData: function( elem ) { return dataUser.hasData( elem ) || dataPriv.hasData( elem ); }, data: function( elem, name, data ) { return dataUser.access( elem, name, data ); }, removeData: function( elem, name ) { dataUser.remove( elem, name ); }, // TODO: Now that all calls to _data and _removeData have been replaced // with direct calls to dataPriv methods, these can be deprecated. _data: function( elem, name, data ) { return dataPriv.access( elem, name, data ); }, _removeData: function( elem, name ) { dataPriv.remove( elem, name ); } } ); jQuery.fn.extend( { data: function( key, value ) { var i, name, data, elem = this[ 0 ], attrs = elem && elem.attributes; // Gets all values if ( key === undefined ) { if ( this.length ) { data = dataUser.get( elem ); if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { i = attrs.length; while ( i-- ) { // Support: IE 11 only // The attrs elements can be null (#14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { name = camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } } dataPriv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { return this.each( function() { dataUser.set( this, key ); } ); } return access( this, function( value ) { var data; // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the // `value` parameter was not undefined. An empty jQuery object // will result in `undefined` for elem = this[ 0 ] which will // throw an exception if an attempt to read a data cache is made. if ( elem && value === undefined ) { // Attempt to get data from the cache // The key will always be camelCased in Data data = dataUser.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs data = dataAttr( elem, key ); if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn't exist. return; } // Set the data... this.each( function() { // We always store the camelCased key dataUser.set( this, key, value ); } ); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each( function() { dataUser.remove( this, key ); } ); } } ); jQuery.extend( { queue: function( elem, type, data ) { var queue; if ( elem ) { type = ( type || "fx" ) + "queue"; queue = dataPriv.get( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !queue || Array.isArray( data ) ) { queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); } else { queue.push( data ); } } return queue || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), startLength = queue.length, fn = queue.shift(), hooks = jQuery._queueHooks( elem, type ), next = function() { jQuery.dequeue( elem, type ); }; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); startLength--; } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } // Clear up the last queue stop function delete hooks.stop; fn.call( elem, next, hooks ); } if ( !startLength && hooks ) { hooks.empty.fire(); } }, // Not public - generate a queueHooks object, or return the current one _queueHooks: function( elem, type ) { var key = type + "queueHooks"; return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { empty: jQuery.Callbacks( "once memory" ).add( function() { dataPriv.remove( elem, [ type + "queue", key ] ); } ) } ); } } ); jQuery.fn.extend( { queue: function( type, data ) { var setter = 2; if ( typeof type !== "string" ) { data = type; type = "fx"; setter--; } if ( arguments.length < setter ) { return jQuery.queue( this[ 0 ], type ); } return data === undefined ? this : this.each( function() { var queue = jQuery.queue( this, type, data ); // Ensure a hooks for this queue jQuery._queueHooks( this, type ); if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { jQuery.dequeue( this, type ); } } ); }, dequeue: function( type ) { return this.each( function() { jQuery.dequeue( this, type ); } ); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, obj ) { var tmp, count = 1, defer = jQuery.Deferred(), elements = this, i = this.length, resolve = function() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } }; if ( typeof type !== "string" ) { obj = type; type = undefined; } type = type || "fx"; while ( i-- ) { tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); if ( tmp && tmp.empty ) { count++; tmp.empty.add( resolve ); } } resolve(); return defer.promise( obj ); } } ); var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; var documentElement = document.documentElement; var isAttached = function( elem ) { return jQuery.contains( elem.ownerDocument, elem ); }, composed = { composed: true }; // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only // Check attachment across shadow DOM boundaries when possible (gh-3504) // Support: iOS 10.0-10.2 only // Early iOS 10 versions support `attachShadow` but not `getRootNode`, // leading to errors. We need to check for `getRootNode`. if ( documentElement.getRootNode ) { isAttached = function( elem ) { return jQuery.contains( elem.ownerDocument, elem ) || elem.getRootNode( composed ) === elem.ownerDocument; }; } var isHiddenWithinTree = function( elem, el ) { // isHiddenWithinTree might be called from jQuery#filter function; // in that case, element will be second argument elem = el || elem; // Inline style trumps all return elem.style.display === "none" || elem.style.display === "" && // Otherwise, check computed style // Support: Firefox <=43 - 45 // Disconnected elements can have computed display: none, so first confirm that elem is // in the document. isAttached( elem ) && jQuery.css( elem, "display" ) === "none"; }; function adjustCSS( elem, prop, valueParts, tween ) { var adjusted, scale, maxIterations = 20, currentValue = tween ? function() { return tween.cur(); } : function() { return jQuery.css( elem, prop, "" ); }, initial = currentValue(), unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches initialInUnit = elem.nodeType && ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { // Support: Firefox <=54 // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) initial = initial / 2; // Trust units reported by jQuery.css unit = unit || initialInUnit[ 3 ]; // Iteratively approximate from a nonzero starting point initialInUnit = +initial || 1; while ( maxIterations-- ) { // Evaluate and update our best guess (doubling guesses that zero out). // Finish if the scale equals or crosses 1 (making the old*new product non-positive). jQuery.style( elem, prop, initialInUnit + unit ); if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { maxIterations = 0; } initialInUnit = initialInUnit / scale; } initialInUnit = initialInUnit * 2; jQuery.style( elem, prop, initialInUnit + unit ); // Make sure we update the tween properties later on valueParts = valueParts || []; } if ( valueParts ) { initialInUnit = +initialInUnit || +initial || 0; // Apply relative offset (+=/-=) if specified adjusted = valueParts[ 1 ] ? initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : +valueParts[ 2 ]; if ( tween ) { tween.unit = unit; tween.start = initialInUnit; tween.end = adjusted; } } return adjusted; } var defaultDisplayMap = {}; function getDefaultDisplay( elem ) { var temp, doc = elem.ownerDocument, nodeName = elem.nodeName, display = defaultDisplayMap[ nodeName ]; if ( display ) { return display; } temp = doc.body.appendChild( doc.createElement( nodeName ) ); display = jQuery.css( temp, "display" ); temp.parentNode.removeChild( temp ); if ( display === "none" ) { display = "block"; } defaultDisplayMap[ nodeName ] = display; return display; } function showHide( elements, show ) { var display, elem, values = [], index = 0, length = elements.length; // Determine new display value for elements that need to change for ( ; index < length; index++ ) { elem = elements[ index ]; if ( !elem.style ) { continue; } display = elem.style.display; if ( show ) { // Since we force visibility upon cascade-hidden elements, an immediate (and slow) // check is required in this first loop unless we have a nonempty display value (either // inline or about-to-be-restored) if ( display === "none" ) { values[ index ] = dataPriv.get( elem, "display" ) || null; if ( !values[ index ] ) { elem.style.display = ""; } } if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { values[ index ] = getDefaultDisplay( elem ); } } else { if ( display !== "none" ) { values[ index ] = "none"; // Remember what we're overwriting dataPriv.set( elem, "display", display ); } } } // Set the display of the elements in a second loop to avoid constant reflow for ( index = 0; index < length; index++ ) { if ( values[ index ] != null ) { elements[ index ].style.display = values[ index ]; } } return elements; } jQuery.fn.extend( { show: function() { return showHide( this, true ); }, hide: function() { return showHide( this ); }, toggle: function( state ) { if ( typeof state === "boolean" ) { return state ? this.show() : this.hide(); } return this.each( function() { if ( isHiddenWithinTree( this ) ) { jQuery( this ).show(); } else { jQuery( this ).hide(); } } ); } } ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); ( function() { var fragment = document.createDocumentFragment(), div = fragment.appendChild( document.createElement( "div" ) ), input = document.createElement( "input" ); // Support: Android 4.0 - 4.3 only // Check state lost if the name is set (#11217) // Support: Windows Web Apps (WWA) // `name` and `type` must use .setAttribute for WWA (#14901) input.setAttribute( "type", "radio" ); input.setAttribute( "checked", "checked" ); input.setAttribute( "name", "t" ); div.appendChild( input ); // Support: Android <=4.1 only // Older WebKit doesn't clone checked state correctly in fragments support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; // Support: IE <=11 only // Make sure textarea (and checkbox) defaultValue is properly cloned div.innerHTML = ""; support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; // Support: IE <=9 only // IE <=9 replaces "; support.option = !!div.lastChild; } )(); // We have to close these tags to support XHTML (#13200) var wrapMap = { // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten // this by omitting or other required elements. thead: [ 1, "", "
" ], col: [ 2, "", "
" ], tr: [ 2, "", "
" ], td: [ 3, "", "
" ], _default: [ 0, "", "" ] }; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; // Support: IE <=9 only if ( !support.option ) { wrapMap.optgroup = wrapMap.option = [ 1, "" ]; } function getAll( context, tag ) { // Support: IE <=9 - 11 only // Use typeof to avoid zero-argument method invocation on host objects (#15151) var ret; if ( typeof context.getElementsByTagName !== "undefined" ) { ret = context.getElementsByTagName( tag || "*" ); } else if ( typeof context.querySelectorAll !== "undefined" ) { ret = context.querySelectorAll( tag || "*" ); } else { ret = []; } if ( tag === undefined || tag && nodeName( context, tag ) ) { return jQuery.merge( [ context ], ret ); } return ret; } // Mark scripts as having already been evaluated function setGlobalEval( elems, refElements ) { var i = 0, l = elems.length; for ( ; i < l; i++ ) { dataPriv.set( elems[ i ], "globalEval", !refElements || dataPriv.get( refElements[ i ], "globalEval" ) ); } } var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { var elem, tmp, tag, wrap, attached, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, l = elems.length; for ( ; i < l; i++ ) { elem = elems[ i ]; if ( elem || elem === 0 ) { // Add nodes directly if ( toType( elem ) === "object" ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // Convert non-html into a text node } else if ( !rhtml.test( elem ) ) { nodes.push( context.createTextNode( elem ) ); // Convert html into DOM nodes } else { tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); // Deserialize a standard representation tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; // Descend through wrappers to the right content j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; } // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; // Ensure the created nodes are orphaned (#12392) tmp.textContent = ""; } } } // Remove wrapper from fragment fragment.textContent = ""; i = 0; while ( ( elem = nodes[ i++ ] ) ) { // Skip elements already in the context collection (trac-4087) if ( selection && jQuery.inArray( elem, selection ) > -1 ) { if ( ignored ) { ignored.push( elem ); } continue; } attached = isAttached( elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history if ( attached ) { setGlobalEval( tmp ); } // Capture executables if ( scripts ) { j = 0; while ( ( elem = tmp[ j++ ] ) ) { if ( rscriptType.test( elem.type || "" ) ) { scripts.push( elem ); } } } } return fragment; } var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, rtypenamespace = /^([^.]*)(?:\.(.+)|)/; function returnTrue() { return true; } function returnFalse() { return false; } // Support: IE <=9 - 11+ // focus() and blur() are asynchronous, except when they are no-op. // So expect focus to be synchronous when the element is already active, // and blur to be synchronous when the element is not already active. // (focus and blur are always synchronous in other supported browsers, // this just defines when we can count on it). function expectSync( elem, type ) { return ( elem === safeActiveElement() ) === ( type === "focus" ); } // Support: IE <=9 only // Accessing document.activeElement can throw unexpectedly // https://bugs.jquery.com/ticket/13393 function safeActiveElement() { try { return document.activeElement; } catch ( err ) { } } function on( elem, types, selector, data, fn, one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { on( elem, type, selector, data, types[ type ], one ); } return elem; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return elem; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return elem.each( function() { jQuery.event.add( this, types, fn, data, selector ); } ); } /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { global: {}, add: function( elem, types, handler, data, selector ) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); // Only attach events to objects that accept data if ( !acceptData( elem ) ) { return; } // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Ensure that invalid selectors throw exceptions at attach time // Evaluate against documentElement in case elem is a non-element node (e.g., document) if ( selector ) { jQuery.find.matchesSelector( documentElement, selector ); } // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } // Handle multiple events separated by a space types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers handleObj = jQuery.extend( { type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join( "." ) }, handleObjIn ); // Init the event handler queue if we're the first if ( !( handlers = events[ type ] ) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } }, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); if ( !elemData || !( events = elemData.events ) ) { return; } // Once for each type.namespace in types; type may be omitted types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector ? special.delegateType : special.bindType ) || type; handlers = events[ type ] || []; tmp = tmp[ 2 ] && new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !tmp || tmp.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { handlers.splice( j, 1 ); if ( handleObj.selector ) { handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( origCount && !handlers.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove data and the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { dataPriv.remove( elem, "handle events" ); } }, dispatch: function( nativeEvent ) { var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( nativeEvent ), handlers = ( dataPriv.get( this, "events" ) || Object.create( null ) )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[ 0 ] = event; for ( i = 1; i < arguments.length; i++ ) { args[ i ] = arguments[ i ]; } event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us i = 0; while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { // If the event is namespaced, then each handler is only invoked if it is // specially universal or its namespaces are a superset of the event's. if ( !event.rnamespace || handleObj.namespace === false || event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || handleObj.handler ).apply( matched.elem, args ); if ( ret !== undefined ) { if ( ( event.result = ret ) === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; }, handlers: function( event, handlers ) { var i, handleObj, sel, matchedHandlers, matchedSelectors, handlerQueue = [], delegateCount = handlers.delegateCount, cur = event.target; // Find delegate handlers if ( delegateCount && // Support: IE <=9 // Black-hole SVG instance trees (trac-13180) cur.nodeType && // Support: Firefox <=42 // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click // Support: IE 11 only // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) !( event.type === "click" && event.button >= 1 ) ) { for ( ; cur !== this; cur = cur.parentNode || this ) { // Don't check non-elements (#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { matchedHandlers = []; matchedSelectors = {}; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matchedSelectors[ sel ] === undefined ) { matchedSelectors[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) > -1 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( matchedSelectors[ sel ] ) { matchedHandlers.push( handleObj ); } } if ( matchedHandlers.length ) { handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); } } } } // Add the remaining (directly-bound) handlers cur = this; if ( delegateCount < handlers.length ) { handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); } return handlerQueue; }, addProp: function( name, hook ) { Object.defineProperty( jQuery.Event.prototype, name, { enumerable: true, configurable: true, get: isFunction( hook ) ? function() { if ( this.originalEvent ) { return hook( this.originalEvent ); } } : function() { if ( this.originalEvent ) { return this.originalEvent[ name ]; } }, set: function( value ) { Object.defineProperty( this, name, { enumerable: true, configurable: true, writable: true, value: value } ); } } ); }, fix: function( originalEvent ) { return originalEvent[ jQuery.expando ] ? originalEvent : new jQuery.Event( originalEvent ); }, special: { load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, click: { // Utilize native event to ensure correct state for checkable inputs setup: function( data ) { // For mutual compressibility with _default, replace `this` access with a local var. // `|| data` is dead code meant only to preserve the variable through minification. var el = this || data; // Claim the first handler if ( rcheckableType.test( el.type ) && el.click && nodeName( el, "input" ) ) { // dataPriv.set( el, "click", ... ) leverageNative( el, "click", returnTrue ); } // Return false to allow normal processing in the caller return false; }, trigger: function( data ) { // For mutual compressibility with _default, replace `this` access with a local var. // `|| data` is dead code meant only to preserve the variable through minification. var el = this || data; // Force setup before triggering a click if ( rcheckableType.test( el.type ) && el.click && nodeName( el, "input" ) ) { leverageNative( el, "click" ); } // Return non-false to allow normal event-path propagation return true; }, // For cross-browser consistency, suppress native .click() on links // Also prevent it if we're currently inside a leveraged native-event stack _default: function( event ) { var target = event.target; return rcheckableType.test( target.type ) && target.click && nodeName( target, "input" ) && dataPriv.get( target, "click" ) || nodeName( target, "a" ); } }, beforeunload: { postDispatch: function( event ) { // Support: Firefox 20+ // Firefox doesn't alert if the returnValue field is not set. if ( event.result !== undefined && event.originalEvent ) { event.originalEvent.returnValue = event.result; } } } } }; // Ensure the presence of an event listener that handles manually-triggered // synthetic events by interrupting progress until reinvoked in response to // *native* events that it fires directly, ensuring that state changes have // already occurred before other listeners are invoked. function leverageNative( el, type, expectSync ) { // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add if ( !expectSync ) { if ( dataPriv.get( el, type ) === undefined ) { jQuery.event.add( el, type, returnTrue ); } return; } // Register the controller as a special universal handler for all event namespaces dataPriv.set( el, type, false ); jQuery.event.add( el, type, { namespace: false, handler: function( event ) { var notAsync, result, saved = dataPriv.get( this, type ); if ( ( event.isTrigger & 1 ) && this[ type ] ) { // Interrupt processing of the outer synthetic .trigger()ed event // Saved data should be false in such cases, but might be a leftover capture object // from an async native handler (gh-4350) if ( !saved.length ) { // Store arguments for use when handling the inner native event // There will always be at least one argument (an event object), so this array // will not be confused with a leftover capture object. saved = slice.call( arguments ); dataPriv.set( this, type, saved ); // Trigger the native event and capture its result // Support: IE <=9 - 11+ // focus() and blur() are asynchronous notAsync = expectSync( this, type ); this[ type ](); result = dataPriv.get( this, type ); if ( saved !== result || notAsync ) { dataPriv.set( this, type, false ); } else { result = {}; } if ( saved !== result ) { // Cancel the outer synthetic event event.stopImmediatePropagation(); event.preventDefault(); return result.value; } // If this is an inner synthetic event for an event with a bubbling surrogate // (focus or blur), assume that the surrogate already propagated from triggering the // native event and prevent that from happening again here. // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the // bubbling surrogate propagates *after* the non-bubbling base), but that seems // less bad than duplication. } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { event.stopPropagation(); } // If this is a native event triggered above, everything is now in order // Fire an inner synthetic event with the original arguments } else if ( saved.length ) { // ...and capture the result dataPriv.set( this, type, { value: jQuery.event.trigger( // Support: IE <=9 - 11+ // Extend with the prototype to reset the above stopImmediatePropagation() jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), saved.slice( 1 ), this ) } ); // Abort handling of the native event event.stopImmediatePropagation(); } } } ); } jQuery.removeEvent = function( elem, type, handle ) { // This "if" is needed for plain objects if ( elem.removeEventListener ) { elem.removeEventListener( type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !( this instanceof jQuery.Event ) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && // Support: Android <=2.3 only src.returnValue === false ? returnTrue : returnFalse; // Create target properties // Support: Safari <=6 - 7 only // Target should not be a text node (#504, #13143) this.target = ( src.target && src.target.nodeType === 3 ) ? src.target.parentNode : src.target; this.currentTarget = src.currentTarget; this.relatedTarget = src.relatedTarget; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || Date.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { constructor: jQuery.Event, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, isSimulated: false, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if ( e && !this.isSimulated ) { e.preventDefault(); } }, stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopPropagation(); } }, stopImmediatePropagation: function() { var e = this.originalEvent; this.isImmediatePropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopImmediatePropagation(); } this.stopPropagation(); } }; // Includes all common event props including KeyEvent and MouseEvent specific props jQuery.each( { altKey: true, bubbles: true, cancelable: true, changedTouches: true, ctrlKey: true, detail: true, eventPhase: true, metaKey: true, pageX: true, pageY: true, shiftKey: true, view: true, "char": true, code: true, charCode: true, key: true, keyCode: true, button: true, buttons: true, clientX: true, clientY: true, offsetX: true, offsetY: true, pointerId: true, pointerType: true, screenX: true, screenY: true, targetTouches: true, toElement: true, touches: true, which: function( event ) { var button = event.button; // Add which for key events if ( event.which == null && rkeyEvent.test( event.type ) ) { return event.charCode != null ? event.charCode : event.keyCode; } // Add which for click: 1 === left; 2 === middle; 3 === right if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { if ( button & 1 ) { return 1; } if ( button & 2 ) { return 3; } if ( button & 4 ) { return 2; } return 0; } return event.which; } }, jQuery.event.addProp ); jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { jQuery.event.special[ type ] = { // Utilize native event if possible so blur/focus sequence is correct setup: function() { // Claim the first handler // dataPriv.set( this, "focus", ... ) // dataPriv.set( this, "blur", ... ) leverageNative( this, type, expectSync ); // Return false to allow normal processing in the caller return false; }, trigger: function() { // Force setup before trigger leverageNative( this, type ); // Return non-false to allow normal event-path propagation return true; }, delegateType: delegateType }; } ); // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout // // Support: Safari 7 only // Safari sends mouseenter too often; see: // https://bugs.chromium.org/p/chromium/issues/detail?id=470258 // for the description of the bug (it existed in older Chrome versions as well). jQuery.each( { mouseenter: "mouseover", mouseleave: "mouseout", pointerenter: "pointerover", pointerleave: "pointerout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var ret, target = this, related = event.relatedTarget, handleObj = event.handleObj; // For mouseenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; } ); jQuery.fn.extend( { on: function( types, selector, data, fn ) { return on( this, types, selector, data, fn ); }, one: function( types, selector, data, fn ) { return on( this, types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { var handleObj, type; if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each( function() { jQuery.event.remove( this, types, fn, selector ); } ); } } ); var // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ rnoInnerhtml = /\s*$/g; // Prefer a tbody over its parent table for containing new rows function manipulationTarget( elem, content ) { if ( nodeName( elem, "table" ) && nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { return jQuery( elem ).children( "tbody" )[ 0 ] || elem; } return elem; } // Replace/restore the type attribute of script elements for safe DOM manipulation function disableScript( elem ) { elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; return elem; } function restoreScript( elem ) { if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { elem.type = elem.type.slice( 5 ); } else { elem.removeAttribute( "type" ); } return elem; } function cloneCopyEvent( src, dest ) { var i, l, type, pdataOld, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; } // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { pdataOld = dataPriv.get( src ); events = pdataOld.events; if ( events ) { dataPriv.remove( dest, "handle events" ); for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type, events[ type ][ i ] ); } } } } // 2. Copy user data if ( dataUser.hasData( src ) ) { udataOld = dataUser.access( src ); udataCur = jQuery.extend( {}, udataOld ); dataUser.set( dest, udataCur ); } } // Fix IE bugs, see support tests function fixInput( src, dest ) { var nodeName = dest.nodeName.toLowerCase(); // Fails to persist the checked state of a cloned checkbox or radio button. if ( nodeName === "input" && rcheckableType.test( src.type ) ) { dest.checked = src.checked; // Fails to return the selected option to the default selected state when cloning options } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } } function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays args = flat( args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, l = collection.length, iNoClone = l - 1, value = args[ 0 ], valueIsFunction = isFunction( value ); // We can't cloneNode fragments that contain checked, in WebKit if ( valueIsFunction || ( l > 1 && typeof value === "string" && !support.checkClone && rchecked.test( value ) ) ) { return collection.each( function( index ) { var self = collection.eq( index ); if ( valueIsFunction ) { args[ 0 ] = value.call( this, index, self.html() ); } domManip( self, args, callback, ignored ); } ); } if ( l ) { fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { fragment = first; } // Require either new content or an interest in ignored elements to invoke the callback if ( first || ignored ) { scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); hasScripts = scripts.length; // Use the original fragment for the last item // instead of the first because it can end up // being emptied incorrectly in certain situations (#8070). for ( ; i < l; i++ ) { node = fragment; if ( i !== iNoClone ) { node = jQuery.clone( node, true, true ); // Keep references to cloned scripts for later restoration if ( hasScripts ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( scripts, getAll( node, "script" ) ); } } callback.call( collection[ i ], node, i ); } if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; // Reenable scripts jQuery.map( scripts, restoreScript ); // Evaluate executable scripts on first document insertion for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && !dataPriv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present if ( jQuery._evalUrl && !node.noModule ) { jQuery._evalUrl( node.src, { nonce: node.nonce || node.getAttribute( "nonce" ) }, doc ); } } else { DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } } } } } return collection; } function remove( elem, selector, keepData ) { var node, nodes = selector ? jQuery.filter( selector, elem ) : elem, i = 0; for ( ; ( node = nodes[ i ] ) != null; i++ ) { if ( !keepData && node.nodeType === 1 ) { jQuery.cleanData( getAll( node ) ); } if ( node.parentNode ) { if ( keepData && isAttached( node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); } } return elem; } jQuery.extend( { htmlPrefilter: function( html ) { return html; }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), inPage = isAttached( elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); srcElements = getAll( elem ); for ( i = 0, l = srcElements.length; i < l; i++ ) { fixInput( srcElements[ i ], destElements[ i ] ); } } // Copy the events from the original to the clone if ( dataAndEvents ) { if ( deepDataAndEvents ) { srcElements = srcElements || getAll( elem ); destElements = destElements || getAll( clone ); for ( i = 0, l = srcElements.length; i < l; i++ ) { cloneCopyEvent( srcElements[ i ], destElements[ i ] ); } } else { cloneCopyEvent( elem, clone ); } } // Preserve script evaluation history destElements = getAll( clone, "script" ); if ( destElements.length > 0 ) { setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); } // Return the cloned set return clone; }, cleanData: function( elems ) { var data, elem, type, special = jQuery.event.special, i = 0; for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { if ( acceptData( elem ) ) { if ( ( data = elem[ dataPriv.expando ] ) ) { if ( data.events ) { for ( type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } } // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataPriv.expando ] = undefined; } if ( elem[ dataUser.expando ] ) { // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataUser.expando ] = undefined; } } } } } ); jQuery.fn.extend( { detach: function( selector ) { return remove( this, selector, true ); }, remove: function( selector ) { return remove( this, selector ); }, text: function( value ) { return access( this, function( value ) { return value === undefined ? jQuery.text( this ) : this.empty().each( function() { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { this.textContent = value; } } ); }, null, value, arguments.length ); }, append: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.appendChild( elem ); } } ); }, prepend: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.insertBefore( elem, target.firstChild ); } } ); }, before: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this ); } } ); }, after: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this.nextSibling ); } } ); }, empty: function() { var elem, i = 0; for ( ; ( elem = this[ i ] ) != null; i++ ) { if ( elem.nodeType === 1 ) { // Prevent memory leaks jQuery.cleanData( getAll( elem, false ) ); // Remove any remaining nodes elem.textContent = ""; } } return this; }, clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? false : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function() { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); } ); }, html: function( value ) { return access( this, function( value ) { var elem = this[ 0 ] || {}, i = 0, l = this.length; if ( value === undefined && elem.nodeType === 1 ) { return elem.innerHTML; } // See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { value = jQuery.htmlPrefilter( value ); try { for ( ; i < l; i++ ) { elem = this[ i ] || {}; // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch ( e ) {} } if ( elem ) { this.empty().append( value ); } }, null, value, arguments.length ); }, replaceWith: function() { var ignored = []; // Make the changes, replacing each non-ignored context element with the new content return domManip( this, arguments, function( elem ) { var parent = this.parentNode; if ( jQuery.inArray( this, ignored ) < 0 ) { jQuery.cleanData( getAll( this ) ); if ( parent ) { parent.replaceChild( elem, this ); } } // Force callback invocation }, ignored ); } } ); jQuery.each( { appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var elems, ret = [], insert = jQuery( selector ), last = insert.length - 1, i = 0; for ( ; i <= last; i++ ) { elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems ); // Support: Android <=4.0 only, PhantomJS 1 only // .get() because push.apply(_, arraylike) throws on ancient WebKit push.apply( ret, elems.get() ); } return this.pushStack( ret ); }; } ); var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var getStyles = function( elem ) { // Support: IE <=11 only, Firefox <=30 (#15098, #14150) // IE throws on elements created in popups // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" var view = elem.ownerDocument.defaultView; if ( !view || !view.opener ) { view = window; } return view.getComputedStyle( elem ); }; var swap = function( elem, options, callback ) { var ret, name, old = {}; // Remember the old values, and insert the new ones for ( name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } ret = callback.call( elem ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } return ret; }; var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); ( function() { // Executing both pixelPosition & boxSizingReliable tests require only one layout // so they're executed at the same time to save the second computation. function computeStyleTests() { // This is a singleton, we need to execute it only once if ( !div ) { return; } container.style.cssText = "position:absolute;left:-11111px;width:60px;" + "margin-top:1px;padding:0;border:0"; div.style.cssText = "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + "margin:auto;border:1px;padding:1px;" + "width:60%;top:1%"; documentElement.appendChild( container ).appendChild( div ); var divStyle = window.getComputedStyle( div ); pixelPositionVal = divStyle.top !== "1%"; // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 // Some styles come back with percentage values, even though they shouldn't div.style.right = "60%"; pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; // Support: IE 9 - 11 only // Detect misreporting of content dimensions for box-sizing:border-box elements boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; // Support: IE 9 only // Detect overflow:scroll screwiness (gh-3699) // Support: Chrome <=64 // Don't get tricked when zoom affects offsetWidth (gh-4029) div.style.position = "absolute"; scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; documentElement.removeChild( container ); // Nullify the div so it wouldn't be stored in the memory and // it will also be a sign that checks already performed div = null; } function roundPixelMeasures( measure ) { return Math.round( parseFloat( measure ) ); } var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, reliableTrDimensionsVal, reliableMarginLeftVal, container = document.createElement( "div" ), div = document.createElement( "div" ); // Finish early in limited (non-browser) environments if ( !div.style ) { return; } // Support: IE <=9 - 11 only // Style of cloned element affects source element cloned (#8908) div.style.backgroundClip = "content-box"; div.cloneNode( true ).style.backgroundClip = ""; support.clearCloneStyle = div.style.backgroundClip === "content-box"; jQuery.extend( support, { boxSizingReliable: function() { computeStyleTests(); return boxSizingReliableVal; }, pixelBoxStyles: function() { computeStyleTests(); return pixelBoxStylesVal; }, pixelPosition: function() { computeStyleTests(); return pixelPositionVal; }, reliableMarginLeft: function() { computeStyleTests(); return reliableMarginLeftVal; }, scrollboxSize: function() { computeStyleTests(); return scrollboxSizeVal; }, // Support: IE 9 - 11+, Edge 15 - 18+ // IE/Edge misreport `getComputedStyle` of table rows with width/height // set in CSS while `offset*` properties report correct values. // Behavior in IE 9 is more subtle than in newer versions & it passes // some versions of this test; make sure not to make it pass there! reliableTrDimensions: function() { var table, tr, trChild, trStyle; if ( reliableTrDimensionsVal == null ) { table = document.createElement( "table" ); tr = document.createElement( "tr" ); trChild = document.createElement( "div" ); table.style.cssText = "position:absolute;left:-11111px"; tr.style.height = "1px"; trChild.style.height = "9px"; documentElement .appendChild( table ) .appendChild( tr ) .appendChild( trChild ); trStyle = window.getComputedStyle( tr ); reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; documentElement.removeChild( table ); } return reliableTrDimensionsVal; } } ); } )(); function curCSS( elem, name, computed ) { var width, minWidth, maxWidth, ret, // Support: Firefox 51+ // Retrieving style before computed somehow // fixes an issue with getting wrong values // on detached elements style = elem.style; computed = computed || getStyles( elem ); // getPropertyValue is needed for: // .css('filter') (IE 9 only, #12537) // .css('--customProperty) (#3144) if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; if ( ret === "" && !isAttached( elem ) ) { ret = jQuery.style( elem, name ); } // A tribute to the "awesome hack by Dean Edwards" // Android Browser returns percentage for some values, // but width seems to be reliably pixels. // This is against the CSSOM draft spec: // https://drafts.csswg.org/cssom/#resolved-values if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { // Remember the original values width = style.width; minWidth = style.minWidth; maxWidth = style.maxWidth; // Put in the new values to get a computed value out style.minWidth = style.maxWidth = style.width = ret; ret = computed.width; // Revert the changed values style.width = width; style.minWidth = minWidth; style.maxWidth = maxWidth; } } return ret !== undefined ? // Support: IE <=9 - 11 only // IE returns zIndex value as an integer. ret + "" : ret; } function addGetHookIf( conditionFn, hookFn ) { // Define the hook, we'll check on the first run if it's really needed. return { get: function() { if ( conditionFn() ) { // Hook not needed (or it's not possible to use it due // to missing dependency), remove it. delete this.get; return; } // Hook needed; redefine it so that the support test is not executed again. return ( this.get = hookFn ).apply( this, arguments ); } }; } var cssPrefixes = [ "Webkit", "Moz", "ms" ], emptyStyle = document.createElement( "div" ).style, vendorProps = {}; // Return a vendor-prefixed property or undefined function vendorPropName( name ) { // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; while ( i-- ) { name = cssPrefixes[ i ] + capName; if ( name in emptyStyle ) { return name; } } } // Return a potentially-mapped jQuery.cssProps or vendor prefixed property function finalPropName( name ) { var final = jQuery.cssProps[ name ] || vendorProps[ name ]; if ( final ) { return final; } if ( name in emptyStyle ) { return name; } return vendorProps[ name ] = vendorPropName( name ) || name; } var // Swappable if display is none or starts with table // except "table", "table-cell", or "table-caption" // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, rcustomProp = /^--/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { letterSpacing: "0", fontWeight: "400" }; function setPositiveNumber( _elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point var matches = rcssNum.exec( value ); return matches ? // Guard against undefined "subtract", e.g., when used as in cssHooks Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : value; } function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { var i = dimension === "width" ? 1 : 0, extra = 0, delta = 0; // Adjustment may not be necessary if ( box === ( isBorderBox ? "border" : "content" ) ) { return 0; } for ( ; i < 4; i += 2 ) { // Both box models exclude margin if ( box === "margin" ) { delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } // If we get here with a content-box, we're seeking "padding" or "border" or "margin" if ( !isBorderBox ) { // Add padding delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); // For "border" or "margin", add border if ( box !== "padding" ) { delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); // But still keep track of it otherwise } else { extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } // If we get here with a border-box (content + padding + border), we're seeking "content" or // "padding" or "margin" } else { // For "content", subtract padding if ( box === "content" ) { delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } // For "content" or "padding", subtract border if ( box !== "margin" ) { delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } // Account for positive content-box scroll gutter when requested by providing computedVal if ( !isBorderBox && computedVal >= 0 ) { // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border // Assuming integer scroll gutter, subtract the rest and round down delta += Math.max( 0, Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - computedVal - delta - extra - 0.5 // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter // Use an explicit zero to avoid NaN (gh-3964) ) ) || 0; } return delta; } function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style var styles = getStyles( elem ), // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). // Fake content-box until we know it's needed to know the true value. boxSizingNeeded = !support.boxSizingReliable() || extra, isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", valueIsBorderBox = isBorderBox, val = curCSS( elem, dimension, styles ), offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); // Support: Firefox <=54 // Return a confounding non-pixel value or feign ignorance, as appropriate. if ( rnumnonpx.test( val ) ) { if ( !extra ) { return val; } val = "auto"; } // Support: IE 9 - 11 only // Use offsetWidth/offsetHeight for when box sizing is unreliable. // In those cases, the computed value can be trusted to be border-box. if ( ( !support.boxSizingReliable() && isBorderBox || // Support: IE 10 - 11+, Edge 15 - 18+ // IE/Edge misreport `getComputedStyle` of table rows with width/height // set in CSS while `offset*` properties report correct values. // Interestingly, in some cases IE 9 doesn't suffer from this issue. !support.reliableTrDimensions() && nodeName( elem, "tr" ) || // Fall back to offsetWidth/offsetHeight when value is "auto" // This happens for inline elements with no explicit setting (gh-3571) val === "auto" || // Support: Android <=4.1 - 4.3 only // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && // Make sure the element is visible & connected elem.getClientRects().length ) { isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; // Where available, offsetWidth/offsetHeight approximate border box dimensions. // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the // retrieved value as a content box dimension. valueIsBorderBox = offsetProp in elem; if ( valueIsBorderBox ) { val = elem[ offsetProp ]; } } // Normalize "" and auto val = parseFloat( val ) || 0; // Adjust for the element's box model return ( val + boxModelAdjustment( elem, dimension, extra || ( isBorderBox ? "border" : "content" ), valueIsBorderBox, styles, // Provide the current computed size to request scroll gutter calculation (gh-3589) val ) ) + "px"; } jQuery.extend( { // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity var ret = curCSS( elem, "opacity" ); return ret === "" ? "1" : ret; } } } }, // Don't automatically add "px" to these possibly-unitless properties cssNumber: { "animationIterationCount": true, "columnCount": true, "fillOpacity": true, "flexGrow": true, "flexShrink": true, "fontWeight": true, "gridArea": true, "gridColumn": true, "gridColumnEnd": true, "gridColumnStart": true, "gridRow": true, "gridRowEnd": true, "gridRowStart": true, "lineHeight": true, "opacity": true, "order": true, "orphans": true, "widows": true, "zIndex": true, "zoom": true }, // Add in properties whose names you wish to fix before // setting or getting the value cssProps: {}, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; } // Make sure that we're working with the right name var ret, type, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ), style = elem.style; // Make sure that we're working with the right name. We don't // want to query the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Gets hook for the prefixed version, then unprefixed version hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // Check if we're setting a value if ( value !== undefined ) { type = typeof value; // Convert "+=" or "-=" to relative numbers (#7345) if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { value = adjustCSS( elem, name, ret ); // Fixes bug #9237 type = "number"; } // Make sure that null and NaN values aren't set (#7116) if ( value == null || value !== value ) { return; } // If a number was passed in, add the unit (except for certain CSS properties) // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append // "px" to a few hardcoded values. if ( type === "number" && !isCustomProp ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } // background-* props affect original clone's values if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { style[ name ] = "inherit"; } // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !( "set" in hooks ) || ( value = hooks.set( elem, value, extra ) ) !== undefined ) { if ( isCustomProp ) { style.setProperty( name, value ); } else { style[ name ] = value; } } } else { // If a hook was provided get the non-computed value from there if ( hooks && "get" in hooks && ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { return ret; } // Otherwise just get the value from the style object return style[ name ]; } }, css: function( elem, name, extra, styles ) { var val, num, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ); // Make sure that we're working with the right name. We don't // want to modify the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Try prefixed name followed by the unprefixed name hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // If a hook was provided get the computed value from there if ( hooks && "get" in hooks ) { val = hooks.get( elem, true, extra ); } // Otherwise, if a way to get the computed value exists, use that if ( val === undefined ) { val = curCSS( elem, name, styles ); } // Convert "normal" to computed value if ( val === "normal" && name in cssNormalTransform ) { val = cssNormalTransform[ name ]; } // Make numeric if forced or a qualifier was provided and val looks numeric if ( extra === "" || extra ) { num = parseFloat( val ); return extra === true || isFinite( num ) ? num || 0 : val; } return val; } } ); jQuery.each( [ "height", "width" ], function( _i, dimension ) { jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { // Certain elements can have dimension info if we invisibly show them // but it must have a current display style that would benefit return rdisplayswap.test( jQuery.css( elem, "display" ) ) && // Support: Safari 8+ // Table columns in Safari have non-zero offsetWidth & zero // getBoundingClientRect().width unless display is changed. // Support: IE <=11 only // Running getBoundingClientRect on a disconnected node // in IE throws an error. ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? swap( elem, cssShow, function() { return getWidthOrHeight( elem, dimension, extra ); } ) : getWidthOrHeight( elem, dimension, extra ); } }, set: function( elem, value, extra ) { var matches, styles = getStyles( elem ), // Only read styles.position if the test has a chance to fail // to avoid forcing a reflow. scrollboxSizeBuggy = !support.scrollboxSize() && styles.position === "absolute", // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) boxSizingNeeded = scrollboxSizeBuggy || extra, isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", subtract = extra ? boxModelAdjustment( elem, dimension, extra, isBorderBox, styles ) : 0; // Account for unreliable border-box dimensions by comparing offset* to computed and // faking a content-box to get border and padding (gh-3699) if ( isBorderBox && scrollboxSizeBuggy ) { subtract -= Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - parseFloat( styles[ dimension ] ) - boxModelAdjustment( elem, dimension, "border", false, styles ) - 0.5 ); } // Convert to pixels if value adjustment is needed if ( subtract && ( matches = rcssNum.exec( value ) ) && ( matches[ 3 ] || "px" ) !== "px" ) { elem.style[ dimension ] = value; value = jQuery.css( elem, dimension ); } return setPositiveNumber( elem, value, subtract ); } }; } ); jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, function( elem, computed ) { if ( computed ) { return ( parseFloat( curCSS( elem, "marginLeft" ) ) || elem.getBoundingClientRect().left - swap( elem, { marginLeft: 0 }, function() { return elem.getBoundingClientRect().left; } ) ) + "px"; } } ); // These hooks are used by animate to expand properties jQuery.each( { margin: "", padding: "", border: "Width" }, function( prefix, suffix ) { jQuery.cssHooks[ prefix + suffix ] = { expand: function( value ) { var i = 0, expanded = {}, // Assumes a single number if not a string parts = typeof value === "string" ? value.split( " " ) : [ value ]; for ( ; i < 4; i++ ) { expanded[ prefix + cssExpand[ i ] + suffix ] = parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; } return expanded; } }; if ( prefix !== "margin" ) { jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; } } ); jQuery.fn.extend( { css: function( name, value ) { return access( this, function( elem, name, value ) { var styles, len, map = {}, i = 0; if ( Array.isArray( name ) ) { styles = getStyles( elem ); len = name.length; for ( ; i < len; i++ ) { map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); } return map; } return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }, name, value, arguments.length > 1 ); } } ); function Tween( elem, options, prop, end, easing ) { return new Tween.prototype.init( elem, options, prop, end, easing ); } jQuery.Tween = Tween; Tween.prototype = { constructor: Tween, init: function( elem, options, prop, end, easing, unit ) { this.elem = elem; this.prop = prop; this.easing = easing || jQuery.easing._default; this.options = options; this.start = this.now = this.cur(); this.end = end; this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); }, cur: function() { var hooks = Tween.propHooks[ this.prop ]; return hooks && hooks.get ? hooks.get( this ) : Tween.propHooks._default.get( this ); }, run: function( percent ) { var eased, hooks = Tween.propHooks[ this.prop ]; if ( this.options.duration ) { this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration ); } else { this.pos = eased = percent; } this.now = ( this.end - this.start ) * eased + this.start; if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } if ( hooks && hooks.set ) { hooks.set( this ); } else { Tween.propHooks._default.set( this ); } return this; } }; Tween.prototype.init.prototype = Tween.prototype; Tween.propHooks = { _default: { get: function( tween ) { var result; // Use a property on the element directly when it is not a DOM element, // or when there is no matching style property that exists. if ( tween.elem.nodeType !== 1 || tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { return tween.elem[ tween.prop ]; } // Passing an empty string as a 3rd parameter to .css will automatically // attempt a parseFloat and fallback to a string if the parse fails. // Simple values such as "10px" are parsed to Float; // complex values such as "rotate(1rad)" are returned as-is. result = jQuery.css( tween.elem, tween.prop, "" ); // Empty strings, null, undefined and "auto" are converted to 0. return !result || result === "auto" ? 0 : result; }, set: function( tween ) { // Use step hook for back compat. // Use cssHook if its there. // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); } else if ( tween.elem.nodeType === 1 && ( jQuery.cssHooks[ tween.prop ] || tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; } } } }; // Support: IE <=9 only // Panic based approach to setting things on disconnected nodes Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { set: function( tween ) { if ( tween.elem.nodeType && tween.elem.parentNode ) { tween.elem[ tween.prop ] = tween.now; } } }; jQuery.easing = { linear: function( p ) { return p; }, swing: function( p ) { return 0.5 - Math.cos( p * Math.PI ) / 2; }, _default: "swing" }; jQuery.fx = Tween.prototype.init; // Back compat <1.8 extension point jQuery.fx.step = {}; var fxNow, inProgress, rfxtypes = /^(?:toggle|show|hide)$/, rrun = /queueHooks$/; function schedule() { if ( inProgress ) { if ( document.hidden === false && window.requestAnimationFrame ) { window.requestAnimationFrame( schedule ); } else { window.setTimeout( schedule, jQuery.fx.interval ); } jQuery.fx.tick(); } } // Animations created synchronously will run synchronously function createFxNow() { window.setTimeout( function() { fxNow = undefined; } ); return ( fxNow = Date.now() ); } // Generate parameters to create a standard animation function genFx( type, includeWidth ) { var which, i = 0, attrs = { height: type }; // If we include width, step value is 1 to do all cssExpand values, // otherwise step value is 2 to skip over Left and Right includeWidth = includeWidth ? 1 : 0; for ( ; i < 4; i += 2 - includeWidth ) { which = cssExpand[ i ]; attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; } if ( includeWidth ) { attrs.opacity = attrs.width = type; } return attrs; } function createTween( value, prop, animation ) { var tween, collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), index = 0, length = collection.length; for ( ; index < length; index++ ) { if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { // We're done with this property return tween; } } } function defaultPrefilter( elem, props, opts ) { var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, isBox = "width" in props || "height" in props, anim = this, orig = {}, style = elem.style, hidden = elem.nodeType && isHiddenWithinTree( elem ), dataShow = dataPriv.get( elem, "fxshow" ); // Queue-skipping animations hijack the fx hooks if ( !opts.queue ) { hooks = jQuery._queueHooks( elem, "fx" ); if ( hooks.unqueued == null ) { hooks.unqueued = 0; oldfire = hooks.empty.fire; hooks.empty.fire = function() { if ( !hooks.unqueued ) { oldfire(); } }; } hooks.unqueued++; anim.always( function() { // Ensure the complete handler is called before this completes anim.always( function() { hooks.unqueued--; if ( !jQuery.queue( elem, "fx" ).length ) { hooks.empty.fire(); } } ); } ); } // Detect show/hide animations for ( prop in props ) { value = props[ prop ]; if ( rfxtypes.test( value ) ) { delete props[ prop ]; toggle = toggle || value === "toggle"; if ( value === ( hidden ? "hide" : "show" ) ) { // Pretend to be hidden if this is a "show" and // there is still data from a stopped show/hide if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { hidden = true; // Ignore all other no-op show/hide data } else { continue; } } orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); } } // Bail out if this is a no-op like .hide().hide() propTween = !jQuery.isEmptyObject( props ); if ( !propTween && jQuery.isEmptyObject( orig ) ) { return; } // Restrict "overflow" and "display" styles during box animations if ( isBox && elem.nodeType === 1 ) { // Support: IE <=9 - 11, Edge 12 - 15 // Record all 3 overflow attributes because IE does not infer the shorthand // from identically-valued overflowX and overflowY and Edge just mirrors // the overflowX value there. opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; // Identify a display type, preferring old show/hide data over the CSS cascade restoreDisplay = dataShow && dataShow.display; if ( restoreDisplay == null ) { restoreDisplay = dataPriv.get( elem, "display" ); } display = jQuery.css( elem, "display" ); if ( display === "none" ) { if ( restoreDisplay ) { display = restoreDisplay; } else { // Get nonempty value(s) by temporarily forcing visibility showHide( [ elem ], true ); restoreDisplay = elem.style.display || restoreDisplay; display = jQuery.css( elem, "display" ); showHide( [ elem ] ); } } // Animate inline elements as inline-block if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { if ( jQuery.css( elem, "float" ) === "none" ) { // Restore the original display value at the end of pure show/hide animations if ( !propTween ) { anim.done( function() { style.display = restoreDisplay; } ); if ( restoreDisplay == null ) { display = style.display; restoreDisplay = display === "none" ? "" : display; } } style.display = "inline-block"; } } } if ( opts.overflow ) { style.overflow = "hidden"; anim.always( function() { style.overflow = opts.overflow[ 0 ]; style.overflowX = opts.overflow[ 1 ]; style.overflowY = opts.overflow[ 2 ]; } ); } // Implement show/hide animations propTween = false; for ( prop in orig ) { // General show/hide setup for this element animation if ( !propTween ) { if ( dataShow ) { if ( "hidden" in dataShow ) { hidden = dataShow.hidden; } } else { dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); } // Store hidden/visible for toggle so `.stop().toggle()` "reverses" if ( toggle ) { dataShow.hidden = !hidden; } // Show elements before animating them if ( hidden ) { showHide( [ elem ], true ); } /* eslint-disable no-loop-func */ anim.done( function() { /* eslint-enable no-loop-func */ // The final step of a "hide" animation is actually hiding the element if ( !hidden ) { showHide( [ elem ] ); } dataPriv.remove( elem, "fxshow" ); for ( prop in orig ) { jQuery.style( elem, prop, orig[ prop ] ); } } ); } // Per-property setup propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); if ( !( prop in dataShow ) ) { dataShow[ prop ] = propTween.start; if ( hidden ) { propTween.end = propTween.start; propTween.start = 0; } } } } function propFilter( props, specialEasing ) { var index, name, easing, value, hooks; // camelCase, specialEasing and expand cssHook pass for ( index in props ) { name = camelCase( index ); easing = specialEasing[ name ]; value = props[ index ]; if ( Array.isArray( value ) ) { easing = value[ 1 ]; value = props[ index ] = value[ 0 ]; } if ( index !== name ) { props[ name ] = value; delete props[ index ]; } hooks = jQuery.cssHooks[ name ]; if ( hooks && "expand" in hooks ) { value = hooks.expand( value ); delete props[ name ]; // Not quite $.extend, this won't overwrite existing keys. // Reusing 'index' because we have the correct "name" for ( index in value ) { if ( !( index in props ) ) { props[ index ] = value[ index ]; specialEasing[ index ] = easing; } } } else { specialEasing[ name ] = easing; } } } function Animation( elem, properties, options ) { var result, stopped, index = 0, length = Animation.prefilters.length, deferred = jQuery.Deferred().always( function() { // Don't match elem in the :animated selector delete tick.elem; } ), tick = function() { if ( stopped ) { return false; } var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), // Support: Android 2.3 only // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, length = animation.tweens.length; for ( ; index < length; index++ ) { animation.tweens[ index ].run( percent ); } deferred.notifyWith( elem, [ animation, percent, remaining ] ); // If there's more to do, yield if ( percent < 1 && length ) { return remaining; } // If this was an empty animation, synthesize a final progress notification if ( !length ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); } // Resolve the animation and report its conclusion deferred.resolveWith( elem, [ animation ] ); return false; }, animation = deferred.promise( { elem: elem, props: jQuery.extend( {}, properties ), opts: jQuery.extend( true, { specialEasing: {}, easing: jQuery.easing._default }, options ), originalProperties: properties, originalOptions: options, startTime: fxNow || createFxNow(), duration: options.duration, tweens: [], createTween: function( prop, end ) { var tween = jQuery.Tween( elem, animation.opts, prop, end, animation.opts.specialEasing[ prop ] || animation.opts.easing ); animation.tweens.push( tween ); return tween; }, stop: function( gotoEnd ) { var index = 0, // If we are going to the end, we want to run all the tweens // otherwise we skip this part length = gotoEnd ? animation.tweens.length : 0; if ( stopped ) { return this; } stopped = true; for ( ; index < length; index++ ) { animation.tweens[ index ].run( 1 ); } // Resolve when we played the last frame; otherwise, reject if ( gotoEnd ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); deferred.resolveWith( elem, [ animation, gotoEnd ] ); } else { deferred.rejectWith( elem, [ animation, gotoEnd ] ); } return this; } } ), props = animation.props; propFilter( props, animation.opts.specialEasing ); for ( ; index < length; index++ ) { result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); if ( result ) { if ( isFunction( result.stop ) ) { jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = result.stop.bind( result ); } return result; } } jQuery.map( props, createTween, animation ); if ( isFunction( animation.opts.start ) ) { animation.opts.start.call( elem, animation ); } // Attach callbacks from options animation .progress( animation.opts.progress ) .done( animation.opts.done, animation.opts.complete ) .fail( animation.opts.fail ) .always( animation.opts.always ); jQuery.fx.timer( jQuery.extend( tick, { elem: elem, anim: animation, queue: animation.opts.queue } ) ); return animation; } jQuery.Animation = jQuery.extend( Animation, { tweeners: { "*": [ function( prop, value ) { var tween = this.createTween( prop, value ); adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); return tween; } ] }, tweener: function( props, callback ) { if ( isFunction( props ) ) { callback = props; props = [ "*" ]; } else { props = props.match( rnothtmlwhite ); } var prop, index = 0, length = props.length; for ( ; index < length; index++ ) { prop = props[ index ]; Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; Animation.tweeners[ prop ].unshift( callback ); } }, prefilters: [ defaultPrefilter ], prefilter: function( callback, prepend ) { if ( prepend ) { Animation.prefilters.unshift( callback ); } else { Animation.prefilters.push( callback ); } } } ); jQuery.speed = function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !isFunction( easing ) && easing }; // Go to the end state if fx are off if ( jQuery.fx.off ) { opt.duration = 0; } else { if ( typeof opt.duration !== "number" ) { if ( opt.duration in jQuery.fx.speeds ) { opt.duration = jQuery.fx.speeds[ opt.duration ]; } else { opt.duration = jQuery.fx.speeds._default; } } } // Normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing opt.old = opt.complete; opt.complete = function() { if ( isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } }; return opt; }; jQuery.fn.extend( { fadeTo: function( speed, to, easing, callback ) { // Show any hidden elements after setting opacity to 0 return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() // Animate to the value specified .end().animate( { opacity: to }, speed, easing, callback ); }, animate: function( prop, speed, easing, callback ) { var empty = jQuery.isEmptyObject( prop ), optall = jQuery.speed( speed, easing, callback ), doAnimation = function() { // Operate on a copy of prop so per-property easing won't be lost var anim = Animation( this, jQuery.extend( {}, prop ), optall ); // Empty animations, or finishing resolves immediately if ( empty || dataPriv.get( this, "finish" ) ) { anim.stop( true ); } }; doAnimation.finish = doAnimation; return empty || optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, stop: function( type, clearQueue, gotoEnd ) { var stopQueue = function( hooks ) { var stop = hooks.stop; delete hooks.stop; stop( gotoEnd ); }; if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; type = undefined; } if ( clearQueue ) { this.queue( type || "fx", [] ); } return this.each( function() { var dequeue = true, index = type != null && type + "queueHooks", timers = jQuery.timers, data = dataPriv.get( this ); if ( index ) { if ( data[ index ] && data[ index ].stop ) { stopQueue( data[ index ] ); } } else { for ( index in data ) { if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { stopQueue( data[ index ] ); } } } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && ( type == null || timers[ index ].queue === type ) ) { timers[ index ].anim.stop( gotoEnd ); dequeue = false; timers.splice( index, 1 ); } } // Start the next in the queue if the last step wasn't forced. // Timers currently will call their complete callbacks, which // will dequeue but only if they were gotoEnd. if ( dequeue || !gotoEnd ) { jQuery.dequeue( this, type ); } } ); }, finish: function( type ) { if ( type !== false ) { type = type || "fx"; } return this.each( function() { var index, data = dataPriv.get( this ), queue = data[ type + "queue" ], hooks = data[ type + "queueHooks" ], timers = jQuery.timers, length = queue ? queue.length : 0; // Enable finishing flag on private data data.finish = true; // Empty the queue first jQuery.queue( this, type, [] ); if ( hooks && hooks.stop ) { hooks.stop.call( this, true ); } // Look for any active animations, and finish them for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && timers[ index ].queue === type ) { timers[ index ].anim.stop( true ); timers.splice( index, 1 ); } } // Look for any animations in the old queue and finish them for ( index = 0; index < length; index++ ) { if ( queue[ index ] && queue[ index ].finish ) { queue[ index ].finish.call( this ); } } // Turn off finishing flag delete data.finish; } ); } } ); jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? cssFn.apply( this, arguments ) : this.animate( genFx( name, true ), speed, easing, callback ); }; } ); // Generate shortcuts for custom animations jQuery.each( { slideDown: genFx( "show" ), slideUp: genFx( "hide" ), slideToggle: genFx( "toggle" ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; } ); jQuery.timers = []; jQuery.fx.tick = function() { var timer, i = 0, timers = jQuery.timers; fxNow = Date.now(); for ( ; i < timers.length; i++ ) { timer = timers[ i ]; // Run the timer and safely remove it when done (allowing for external removal) if ( !timer() && timers[ i ] === timer ) { timers.splice( i--, 1 ); } } if ( !timers.length ) { jQuery.fx.stop(); } fxNow = undefined; }; jQuery.fx.timer = function( timer ) { jQuery.timers.push( timer ); jQuery.fx.start(); }; jQuery.fx.interval = 13; jQuery.fx.start = function() { if ( inProgress ) { return; } inProgress = true; schedule(); }; jQuery.fx.stop = function() { inProgress = null; }; jQuery.fx.speeds = { slow: 600, fast: 200, // Default speed _default: 400 }; // Based off of the plugin by Clint Helfers, with permission. // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ jQuery.fn.delay = function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; return this.queue( type, function( next, hooks ) { var timeout = window.setTimeout( next, time ); hooks.stop = function() { window.clearTimeout( timeout ); }; } ); }; ( function() { var input = document.createElement( "input" ), select = document.createElement( "select" ), opt = select.appendChild( document.createElement( "option" ) ); input.type = "checkbox"; // Support: Android <=4.3 only // Default value for a checkbox should be "on" support.checkOn = input.value !== ""; // Support: IE <=11 only // Must access selectedIndex to make default options select support.optSelected = opt.selected; // Support: IE <=11 only // An input loses its value after becoming a radio input = document.createElement( "input" ); input.value = "t"; input.type = "radio"; support.radioValue = input.value === "t"; } )(); var boolHook, attrHandle = jQuery.expr.attrHandle; jQuery.fn.extend( { attr: function( name, value ) { return access( this, jQuery.attr, name, value, arguments.length > 1 ); }, removeAttr: function( name ) { return this.each( function() { jQuery.removeAttr( this, name ); } ); } } ); jQuery.extend( { attr: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set attributes on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } // Fallback to prop when attributes are not supported if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } // Attribute hooks are determined by the lowercase version // Grab necessary hook if one is defined if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { hooks = jQuery.attrHooks[ name.toLowerCase() ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return; } if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } elem.setAttribute( name, value + "" ); return value; } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } ret = jQuery.find.attr( elem, name ); // Non-existent attributes return null, we normalize to undefined return ret == null ? undefined : ret; }, attrHooks: { type: { set: function( elem, value ) { if ( !support.radioValue && value === "radio" && nodeName( elem, "input" ) ) { var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } } }, removeAttr: function( elem, value ) { var name, i = 0, // Attribute names can contain non-HTML whitespace characters // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 attrNames = value && value.match( rnothtmlwhite ); if ( attrNames && elem.nodeType === 1 ) { while ( ( name = attrNames[ i++ ] ) ) { elem.removeAttribute( name ); } } } } ); // Hooks for boolean attributes boolHook = { set: function( elem, value, name ) { if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { elem.setAttribute( name, name ); } return name; } }; jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { var ret, handle, lowercaseName = name.toLowerCase(); if ( !isXML ) { // Avoid an infinite loop by temporarily removing this function from the getter handle = attrHandle[ lowercaseName ]; attrHandle[ lowercaseName ] = ret; ret = getter( elem, name, isXML ) != null ? lowercaseName : null; attrHandle[ lowercaseName ] = handle; } return ret; }; } ); var rfocusable = /^(?:input|select|textarea|button)$/i, rclickable = /^(?:a|area)$/i; jQuery.fn.extend( { prop: function( name, value ) { return access( this, jQuery.prop, name, value, arguments.length > 1 ); }, removeProp: function( name ) { return this.each( function() { delete this[ jQuery.propFix[ name ] || name ]; } ); } } ); jQuery.extend( { prop: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set properties on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } return ( elem[ name ] = value ); } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } return elem[ name ]; }, propHooks: { tabIndex: { get: function( elem ) { // Support: IE <=9 - 11 only // elem.tabIndex doesn't always return the // correct value when it hasn't been explicitly set // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ // Use proper attribute retrieval(#12072) var tabindex = jQuery.find.attr( elem, "tabindex" ); if ( tabindex ) { return parseInt( tabindex, 10 ); } if ( rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ) { return 0; } return -1; } } }, propFix: { "for": "htmlFor", "class": "className" } } ); // Support: IE <=11 only // Accessing the selectedIndex property // forces the browser to respect setting selected // on the option // The getter ensures a default option is selected // when in an optgroup // eslint rule "no-unused-expressions" is disabled for this code // since it considers such accessions noop if ( !support.optSelected ) { jQuery.propHooks.selected = { get: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent && parent.parentNode ) { parent.parentNode.selectedIndex; } return null; }, set: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } } }; } jQuery.each( [ "tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable" ], function() { jQuery.propFix[ this.toLowerCase() ] = this; } ); // Strip and collapse whitespace according to HTML spec // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace function stripAndCollapse( value ) { var tokens = value.match( rnothtmlwhite ) || []; return tokens.join( " " ); } function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; } function classesToArray( value ) { if ( Array.isArray( value ) ) { return value; } if ( typeof value === "string" ) { return value.match( rnothtmlwhite ) || []; } return []; } jQuery.fn.extend( { addClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); } ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { if ( cur.indexOf( " " + clazz + " " ) < 0 ) { cur += clazz + " "; } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, removeClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); } ); } if ( !arguments.length ) { return this.attr( "class", "" ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); // This expression is here for better compressibility (see addClass) cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { // Remove *all* instances while ( cur.indexOf( " " + clazz + " " ) > -1 ) { cur = cur.replace( " " + clazz + " ", " " ); } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isValidValue = type === "string" || Array.isArray( value ); if ( typeof stateVal === "boolean" && isValidValue ) { return stateVal ? this.addClass( value ) : this.removeClass( value ); } if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( value.call( this, i, getClass( this ), stateVal ), stateVal ); } ); } return this.each( function() { var className, i, self, classNames; if ( isValidValue ) { // Toggle individual class names i = 0; self = jQuery( this ); classNames = classesToArray( value ); while ( ( className = classNames[ i++ ] ) ) { // Check each className given, space separated list if ( self.hasClass( className ) ) { self.removeClass( className ); } else { self.addClass( className ); } } // Toggle whole class name } else if ( value === undefined || type === "boolean" ) { className = getClass( this ); if ( className ) { // Store className if set dataPriv.set( this, "__className__", className ); } // If the element has a class name or if we're passed `false`, // then remove the whole classname (if there was one, the above saved it). // Otherwise bring back whatever was previously saved (if anything), // falling back to the empty string if nothing was stored. if ( this.setAttribute ) { this.setAttribute( "class", className || value === false ? "" : dataPriv.get( this, "__className__" ) || "" ); } } } ); }, hasClass: function( selector ) { var className, elem, i = 0; className = " " + selector + " "; while ( ( elem = this[ i++ ] ) ) { if ( elem.nodeType === 1 && ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { return true; } } return false; } } ); var rreturn = /\r/g; jQuery.fn.extend( { val: function( value ) { var hooks, ret, valueIsFunction, elem = this[ 0 ]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && ( ret = hooks.get( elem, "value" ) ) !== undefined ) { return ret; } ret = elem.value; // Handle most common string cases if ( typeof ret === "string" ) { return ret.replace( rreturn, "" ); } // Handle cases where value is null/undef or number return ret == null ? "" : ret; } return; } valueIsFunction = isFunction( value ); return this.each( function( i ) { var val; if ( this.nodeType !== 1 ) { return; } if ( valueIsFunction ) { val = value.call( this, i, jQuery( this ).val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( Array.isArray( val ) ) { val = jQuery.map( val, function( value ) { return value == null ? "" : value + ""; } ); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } } ); } } ); jQuery.extend( { valHooks: { option: { get: function( elem ) { var val = jQuery.find.attr( elem, "value" ); return val != null ? val : // Support: IE <=10 - 11 only // option.text throws exceptions (#14686, #14858) // Strip and collapse whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace stripAndCollapse( jQuery.text( elem ) ); } }, select: { get: function( elem ) { var value, option, i, options = elem.options, index = elem.selectedIndex, one = elem.type === "select-one", values = one ? null : [], max = one ? index + 1 : options.length; if ( index < 0 ) { i = max; } else { i = one ? index : 0; } // Loop through all the selected options for ( ; i < max; i++ ) { option = options[ i ]; // Support: IE <=9 only // IE8-9 doesn't update selected after form reset (#2551) if ( ( option.selected || i === index ) && // Don't return options that are disabled or in a disabled optgroup !option.disabled && ( !option.parentNode.disabled || !nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } return values; }, set: function( elem, value ) { var optionSet, option, options = elem.options, values = jQuery.makeArray( value ), i = options.length; while ( i-- ) { option = options[ i ]; /* eslint-disable no-cond-assign */ if ( option.selected = jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) { optionSet = true; } /* eslint-enable no-cond-assign */ } // Force browsers to behave consistently when non-matching value is set if ( !optionSet ) { elem.selectedIndex = -1; } return values; } } } } ); // Radios and checkboxes getter/setter jQuery.each( [ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { set: function( elem, value ) { if ( Array.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); } } }; if ( !support.checkOn ) { jQuery.valHooks[ this ].get = function( elem ) { return elem.getAttribute( "value" ) === null ? "on" : elem.value; }; } } ); // Return jQuery for attributes-only inclusion support.focusin = "onfocusin" in window; var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, stopPropagationCallback = function( e ) { e.stopPropagation(); }; jQuery.extend( jQuery.event, { trigger: function( event, data, elem, onlyHandlers ) { var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, eventPath = [ elem || document ], type = hasOwn.call( event, "type" ) ? event.type : event, namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; cur = lastElement = tmp = elem = elem || document; // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "." ) > -1 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split( "." ); type = namespaces.shift(); namespaces.sort(); } ontype = type.indexOf( ":" ) < 0 && "on" + type; // Caller can pass in a jQuery.Event object, Object, or just an event type string event = event[ jQuery.expando ] ? event : new jQuery.Event( type, typeof event === "object" && event ); // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) event.isTrigger = onlyHandlers ? 2 : 3; event.namespace = namespaces.join( "." ); event.rnamespace = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : null; // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list data = data == null ? [ event ] : jQuery.makeArray( data, [ event ] ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { cur = cur.parentNode; } for ( ; cur; cur = cur.parentNode ) { eventPath.push( cur ); tmp = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( tmp === ( elem.ownerDocument || document ) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } } // Fire handlers on the event path i = 0; while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { lastElement = cur; event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Native handler handle = ontype && cur[ ontype ]; if ( handle && handle.apply && acceptData( cur ) ) { event.result = handle.apply( cur, data ); if ( event.result === false ) { event.preventDefault(); } } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( ( !special._default || special._default.apply( eventPath.pop(), data ) === false ) && acceptData( elem ) ) { // Call a native DOM method on the target with the same name as the event. // Don't do default actions on window, that's where global variables be (#6170) if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method tmp = elem[ ontype ]; if ( tmp ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; if ( event.isPropagationStopped() ) { lastElement.addEventListener( type, stopPropagationCallback ); } elem[ type ](); if ( event.isPropagationStopped() ) { lastElement.removeEventListener( type, stopPropagationCallback ); } jQuery.event.triggered = undefined; if ( tmp ) { elem[ ontype ] = tmp; } } } } return event.result; }, // Piggyback on a donor event to simulate a different one // Used only for `focus(in | out)` events simulate: function( type, elem, event ) { var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true } ); jQuery.event.trigger( e, null, elem ); } } ); jQuery.fn.extend( { trigger: function( type, data ) { return this.each( function() { jQuery.event.trigger( type, data, this ); } ); }, triggerHandler: function( type, data ) { var elem = this[ 0 ]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } } } ); // Support: Firefox <=44 // Firefox doesn't have focus(in | out) events // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 // // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 // focus(in | out) events fire after focus & blur events, // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 if ( !support.focusin ) { jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler on the document while someone wants focusin/focusout var handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); }; jQuery.event.special[ fix ] = { setup: function() { // Handle: regular nodes (via `this.ownerDocument`), window // (via `this.document`) & document (via `this`). var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { doc.addEventListener( orig, handler, true ); } dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { doc.removeEventListener( orig, handler, true ); dataPriv.remove( doc, fix ); } else { dataPriv.access( doc, fix, attaches ); } } }; } ); } var location = window.location; var nonce = { guid: Date.now() }; var rquery = ( /\?/ ); // Cross-browser xml parsing jQuery.parseXML = function( data ) { var xml; if ( !data || typeof data !== "string" ) { return null; } // Support: IE 9 - 11 only // IE throws on parseFromString with invalid input. try { xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); } catch ( e ) { xml = undefined; } if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }; var rbracket = /\[\]$/, rCRLF = /\r?\n/g, rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, rsubmittable = /^(?:input|select|textarea|keygen)/i; function buildParams( prefix, obj, traditional, add ) { var name; if ( Array.isArray( obj ) ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // Item is non-scalar (array or object), encode its numeric index. buildParams( prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", v, traditional, add ); } } ); } else if ( !traditional && toType( obj ) === "object" ) { // Serialize object item. for ( name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // Serialize an array of form elements or a set of // key/values into a query string jQuery.param = function( a, traditional ) { var prefix, s = [], add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value var value = isFunction( valueOrFunction ) ? valueOrFunction() : valueOrFunction; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value == null ? "" : value ); }; if ( a == null ) { return ""; } // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); } ); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ); }; jQuery.fn.extend( { serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map( function() { // Can add propHook for "elements" to filter or add form elements var elements = jQuery.prop( this, "elements" ); return elements ? jQuery.makeArray( elements ) : this; } ) .filter( function() { var type = this.type; // Use .is( ":disabled" ) so that fieldset[disabled] works return this.name && !jQuery( this ).is( ":disabled" ) && rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && ( this.checked || !rcheckableType.test( type ) ); } ) .map( function( _i, elem ) { var val = jQuery( this ).val(); if ( val == null ) { return null; } if ( Array.isArray( val ) ) { return jQuery.map( val, function( val ) { return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ); } return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ).get(); } } ); var r20 = /%20/g, rhash = /#.*$/, rantiCache = /([?&])_=[^&]*/, rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, // #7653, #8125, #8152: local protocol detection rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}, // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = "*/".concat( "*" ), // Anchor tag for parsing the document origin originAnchor = document.createElement( "a" ); originAnchor.href = location.href; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport function addToPrefiltersOrTransports( structure ) { // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } var dataType, i = 0, dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; if ( isFunction( func ) ) { // For each dataType in the dataTypeExpression while ( ( dataType = dataTypes[ i++ ] ) ) { // Prepend if requested if ( dataType[ 0 ] === "+" ) { dataType = dataType.slice( 1 ) || "*"; ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); // Otherwise append } else { ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); } } } }; } // Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { var inspected = {}, seekingTransport = ( structure === transports ); function inspect( dataType ) { var selected; inspected[ dataType ] = true; jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) { options.dataTypes.unshift( dataTypeOrTransport ); inspect( dataTypeOrTransport ); return false; } else if ( seekingTransport ) { return !( selected = dataTypeOrTransport ); } } ); return selected; } return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); } // A special extend for ajax options // that takes "flat" options (not to be deep extended) // Fixes #9887 function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; for ( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } } if ( deep ) { jQuery.extend( true, target, deep ); } return target; } /* Handles responses to an ajax request: * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jqXHR, responses ) { var ct, type, finalDataType, firstDataType, contents = s.contents, dataTypes = s.dataTypes; // Remove auto dataType and get content-type in the process while ( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } /* Chain conversions given the request and the original response * Also sets the responseXXX fields on the jqXHR instance */ function ajaxConvert( s, response, jqXHR, isSuccess ) { var conv2, current, conv, tmp, prev, converters = {}, // Work with a copy of dataTypes in case we need to modify it for conversion dataTypes = s.dataTypes.slice(); // Create converters map with lowercased keys if ( dataTypes[ 1 ] ) { for ( conv in s.converters ) { converters[ conv.toLowerCase() ] = s.converters[ conv ]; } } current = dataTypes.shift(); // Convert to each sequential dataType while ( current ) { if ( s.responseFields[ current ] ) { jqXHR[ s.responseFields[ current ] ] = response; } // Apply the dataFilter if provided if ( !prev && isSuccess && s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } prev = current; current = dataTypes.shift(); if ( current ) { // There's only work to do if current dataType is non-auto if ( current === "*" ) { current = prev; // Convert response if prev dataType is non-auto and differs from current } else if ( prev !== "*" && prev !== current ) { // Seek a direct converter conv = converters[ prev + " " + current ] || converters[ "* " + current ]; // If none found, seek a pair if ( !conv ) { for ( conv2 in converters ) { // If conv2 outputs current tmp = conv2.split( " " ); if ( tmp[ 1 ] === current ) { // If prev can be converted to accepted input conv = converters[ prev + " " + tmp[ 0 ] ] || converters[ "* " + tmp[ 0 ] ]; if ( conv ) { // Condense equivalence converters if ( conv === true ) { conv = converters[ conv2 ]; // Otherwise, insert the intermediate dataType } else if ( converters[ conv2 ] !== true ) { current = tmp[ 0 ]; dataTypes.unshift( tmp[ 1 ] ); } break; } } } } // Apply converter (if not an equivalence) if ( conv !== true ) { // Unless errors are allowed to bubble, catch and return them if ( conv && s.throws ) { response = conv( response ); } else { try { response = conv( response ); } catch ( e ) { return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; } } } } } } return { state: "success", data: response }; } jQuery.extend( { // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {}, ajaxSettings: { url: location.href, type: "GET", isLocal: rlocalProtocol.test( location.protocol ), global: true, processData: true, async: true, contentType: "application/x-www-form-urlencoded; charset=UTF-8", /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, throws: false, traditional: false, headers: {}, */ accepts: { "*": allTypes, text: "text/plain", html: "text/html", xml: "application/xml, text/xml", json: "application/json, text/javascript" }, contents: { xml: /\bxml\b/, html: /\bhtml/, json: /\bjson\b/ }, responseFields: { xml: "responseXML", text: "responseText", json: "responseJSON" }, // Data converters // Keys separate source (or catchall "*") and destination types with a single space converters: { // Convert anything to text "* text": String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": JSON.parse, // Parse text as xml "text xml": jQuery.parseXML }, // For options that shouldn't be deep extended: // you can add your own custom options here if // and when you create one that shouldn't be // deep extended (see ajaxExtend) flatOptions: { url: true, context: true } }, // Creates a full fledged settings object into target // with both ajaxSettings and settings fields. // If target is omitted, writes into ajaxSettings. ajaxSetup: function( target, settings ) { return settings ? // Building a settings object ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : // Extending ajaxSettings ajaxExtend( jQuery.ajaxSettings, target ); }, ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), ajaxTransport: addToPrefiltersOrTransports( transports ), // Main method ajax: function( url, options ) { // If url is an object, simulate pre-1.5 signature if ( typeof url === "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var transport, // URL without anti-cache param cacheURL, // Response headers responseHeadersString, responseHeaders, // timeout handle timeoutTimer, // Url cleanup var urlAnchor, // Request state (becomes false upon send and true upon completion) completed, // To know if global events are to be dispatched fireGlobals, // Loop variable i, // uncached part of the url uncached, // Create the final options object s = jQuery.ajaxSetup( {}, options ), // Callbacks context callbackContext = s.context || s, // Context for global events is callbackContext if it is a DOM node or jQuery collection globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, requestHeadersNames = {}, // Default abort message strAbort = "canceled", // Fake xhr jqXHR = { readyState: 0, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( completed ) { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[ 1 ].toLowerCase() + " " ] = ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) .concat( match[ 2 ] ); } } match = responseHeaders[ key.toLowerCase() + " " ]; } return match == null ? null : match.join( ", " ); }, // Raw string getAllResponseHeaders: function() { return completed ? responseHeadersString : null; }, // Caches the header setRequestHeader: function( name, value ) { if ( completed == null ) { name = requestHeadersNames[ name.toLowerCase() ] = requestHeadersNames[ name.toLowerCase() ] || name; requestHeaders[ name ] = value; } return this; }, // Overrides response content-type header overrideMimeType: function( type ) { if ( completed == null ) { s.mimeType = type; } return this; }, // Status-dependent callbacks statusCode: function( map ) { var code; if ( map ) { if ( completed ) { // Execute the appropriate callbacks jqXHR.always( map[ jqXHR.status ] ); } else { // Lazy-add the new callbacks in a way that preserves old ones for ( code in map ) { statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } } } return this; }, // Cancel the request abort: function( statusText ) { var finalText = statusText || strAbort; if ( transport ) { transport.abort( finalText ); } done( 0, finalText ); return this; } }; // Attach deferreds deferred.promise( jqXHR ); // Add protocol if not provided (prefilters might expect it) // Handle falsy url in the settings object (#10093: consistency with old signature) // We also use the url parameter if available s.url = ( ( url || s.url || location.href ) + "" ) .replace( rprotocol, location.protocol + "//" ); // Alias method option to type as per ticket #12004 s.type = options.method || options.type || s.method || s.type; // Extract dataTypes list s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; // A cross-domain request is in order when the origin doesn't match the current origin. if ( s.crossDomain == null ) { urlAnchor = document.createElement( "a" ); // Support: IE <=8 - 11, Edge 12 - 15 // IE throws exception on accessing the href property if url is malformed, // e.g. http://example.com:80x/ try { urlAnchor.href = s.url; // Support: IE <=8 - 11 only // Anchor's host property isn't correctly set when s.url is relative urlAnchor.href = urlAnchor.href; s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== urlAnchor.protocol + "//" + urlAnchor.host; } catch ( e ) { // If there is an error parsing the URL, assume it is crossDomain, // it can be rejected by the transport if it is invalid s.crossDomain = true; } } // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); } // Apply prefilters inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // If request was aborted inside a prefilter, stop there if ( completed ) { return jqXHR; } // We can fire global events as of now if asked to // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) fireGlobals = jQuery.event && s.global; // Watch for a new set of requests if ( fireGlobals && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content s.hasContent = !rnoContent.test( s.type ); // Save the URL in case we're toying with the If-Modified-Since // and/or If-None-Match header later on // Remove hash to simplify url manipulation cacheURL = s.url.replace( rhash, "" ); // More options handling for requests with no content if ( !s.hasContent ) { // Remember the hash so we can put it back uncached = s.url.slice( cacheURL.length ); // If data is available and should be processed, append data to url if ( s.data && ( s.processData || typeof s.data === "string" ) ) { cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry delete s.data; } // Add or update anti-cache param if needed if ( s.cache === false ) { cacheURL = cacheURL.replace( rantiCache, "$1" ); uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + uncached; } // Put hash and anti-cache on the URL that will be requested (gh-1732) s.url = cacheURL + uncached; // Change '%20' to '+' if this is encoded form body content (gh-2658) } else if ( s.data && s.processData && ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { s.data = s.data.replace( r20, "+" ); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( jQuery.lastModified[ cacheURL ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); } if ( jQuery.etag[ cacheURL ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); } } // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } // Set the Accepts header for the server, depending on the dataType jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : s.accepts[ "*" ] ); // Check for headers option for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { // Abort if not done already and return return jqXHR.abort(); } // Aborting is no longer a cancellation strAbort = "abort"; // Install callbacks on deferreds completeDeferred.add( s.complete ); jqXHR.done( s.success ); jqXHR.fail( s.error ); // Get transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1; // Send global event if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // If request was aborted inside ajaxSend, stop there if ( completed ) { return jqXHR; } // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = window.setTimeout( function() { jqXHR.abort( "timeout" ); }, s.timeout ); } try { completed = false; transport.send( requestHeaders, done ); } catch ( e ) { // Rethrow post-completion exceptions if ( completed ) { throw e; } // Propagate others as results done( -1, e ); } } // Callback for when everything is done function done( status, nativeStatusText, responses, headers ) { var isSuccess, success, error, response, modified, statusText = nativeStatusText; // Ignore repeat invocations if ( completed ) { return; } completed = true; // Clear timeout if it exists if ( timeoutTimer ) { window.clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jqXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; // Determine if successful isSuccess = status >= 200 && status < 300 || status === 304; // Get response data if ( responses ) { response = ajaxHandleResponses( s, jqXHR, responses ); } // Use a noop converter for missing script if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { s.converters[ "text script" ] = function() {}; } // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); // If successful, handle type chaining if ( isSuccess ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { modified = jqXHR.getResponseHeader( "Last-Modified" ); if ( modified ) { jQuery.lastModified[ cacheURL ] = modified; } modified = jqXHR.getResponseHeader( "etag" ); if ( modified ) { jQuery.etag[ cacheURL ] = modified; } } // if no content if ( status === 204 || s.type === "HEAD" ) { statusText = "nocontent"; // if not modified } else if ( status === 304 ) { statusText = "notmodified"; // If we have data, let's convert it } else { statusText = response.state; success = response.data; error = response.error; isSuccess = !error; } } else { // Extract error from statusText and normalize for non-aborts error = statusText; if ( status || !statusText ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } // Set data for the fake xhr object jqXHR.status = status; jqXHR.statusText = ( nativeStatusText || statusText ) + ""; // Success/Error if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } // Status-dependent callbacks jqXHR.statusCode( statusCode ); statusCode = undefined; if ( fireGlobals ) { globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", [ jqXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } } return jqXHR; }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, getScript: function( url, callback ) { return jQuery.get( url, undefined, callback, "script" ); } } ); jQuery.each( [ "get", "post" ], function( _i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted if ( isFunction( data ) ) { type = type || callback; callback = data; data = undefined; } // The url can be an options object (which then must have .url) return jQuery.ajax( jQuery.extend( { url: url, type: method, dataType: type, data: data, success: callback }, jQuery.isPlainObject( url ) && url ) ); }; } ); jQuery.ajaxPrefilter( function( s ) { var i; for ( i in s.headers ) { if ( i.toLowerCase() === "content-type" ) { s.contentType = s.headers[ i ] || ""; } } } ); jQuery._evalUrl = function( url, options, doc ) { return jQuery.ajax( { url: url, // Make this explicit, since user can override this through ajaxSetup (#11264) type: "GET", dataType: "script", cache: true, async: false, global: false, // Only evaluate the response if it is successful (gh-4126) // dataFilter is not invoked for failure responses, so using it instead // of the default converter is kludgy but it works. converters: { "text script": function() {} }, dataFilter: function( response ) { jQuery.globalEval( response, options, doc ); } } ); }; jQuery.fn.extend( { wrapAll: function( html ) { var wrap; if ( this[ 0 ] ) { if ( isFunction( html ) ) { html = html.call( this[ 0 ] ); } // The elements to wrap the target around wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); if ( this[ 0 ].parentNode ) { wrap.insertBefore( this[ 0 ] ); } wrap.map( function() { var elem = this; while ( elem.firstElementChild ) { elem = elem.firstElementChild; } return elem; } ).append( this ); } return this; }, wrapInner: function( html ) { if ( isFunction( html ) ) { return this.each( function( i ) { jQuery( this ).wrapInner( html.call( this, i ) ); } ); } return this.each( function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } } ); }, wrap: function( html ) { var htmlIsFunction = isFunction( html ); return this.each( function( i ) { jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); } ); }, unwrap: function( selector ) { this.parent( selector ).not( "body" ).each( function() { jQuery( this ).replaceWith( this.childNodes ); } ); return this; } } ); jQuery.expr.pseudos.hidden = function( elem ) { return !jQuery.expr.pseudos.visible( elem ); }; jQuery.expr.pseudos.visible = function( elem ) { return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); }; jQuery.ajaxSettings.xhr = function() { try { return new window.XMLHttpRequest(); } catch ( e ) {} }; var xhrSuccessStatus = { // File protocol always yields status code 0, assume 200 0: 200, // Support: IE <=9 only // #1450: sometimes IE returns 1223 when it should be 204 1223: 204 }, xhrSupported = jQuery.ajaxSettings.xhr(); support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); support.ajax = xhrSupported = !!xhrSupported; jQuery.ajaxTransport( function( options ) { var callback, errorCallback; // Cross domain only allowed if supported through XMLHttpRequest if ( support.cors || xhrSupported && !options.crossDomain ) { return { send: function( headers, complete ) { var i, xhr = options.xhr(); xhr.open( options.type, options.url, options.async, options.username, options.password ); // Apply custom fields if provided if ( options.xhrFields ) { for ( i in options.xhrFields ) { xhr[ i ] = options.xhrFields[ i ]; } } // Override mime type if needed if ( options.mimeType && xhr.overrideMimeType ) { xhr.overrideMimeType( options.mimeType ); } // X-Requested-With header // For cross-domain requests, seeing as conditions for a preflight are // akin to a jigsaw puzzle, we simply never set it to be sure. // (it can always be set on a per-request basis or even using ajaxSetup) // For same-domain requests, won't change header if already provided. if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { headers[ "X-Requested-With" ] = "XMLHttpRequest"; } // Set headers for ( i in headers ) { xhr.setRequestHeader( i, headers[ i ] ); } // Callback callback = function( type ) { return function() { if ( callback ) { callback = errorCallback = xhr.onload = xhr.onerror = xhr.onabort = xhr.ontimeout = xhr.onreadystatechange = null; if ( type === "abort" ) { xhr.abort(); } else if ( type === "error" ) { // Support: IE <=9 only // On a manual native abort, IE9 throws // errors on any property access that is not readyState if ( typeof xhr.status !== "number" ) { complete( 0, "error" ); } else { complete( // File: protocol always yields status 0; see #8605, #14207 xhr.status, xhr.statusText ); } } else { complete( xhrSuccessStatus[ xhr.status ] || xhr.status, xhr.statusText, // Support: IE <=9 only // IE9 has no XHR2 but throws on binary (trac-11426) // For XHR2 non-text, let the caller handle it (gh-2498) ( xhr.responseType || "text" ) !== "text" || typeof xhr.responseText !== "string" ? { binary: xhr.response } : { text: xhr.responseText }, xhr.getAllResponseHeaders() ); } } }; }; // Listen to events xhr.onload = callback(); errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); // Support: IE 9 only // Use onreadystatechange to replace onabort // to handle uncaught aborts if ( xhr.onabort !== undefined ) { xhr.onabort = errorCallback; } else { xhr.onreadystatechange = function() { // Check readyState before timeout as it changes if ( xhr.readyState === 4 ) { // Allow onerror to be called first, // but that will not handle a native abort // Also, save errorCallback to a variable // as xhr.onerror cannot be accessed window.setTimeout( function() { if ( callback ) { errorCallback(); } } ); } }; } // Create the abort callback callback = callback( "abort" ); try { // Do send the request (this may raise an exception) xhr.send( options.hasContent && options.data || null ); } catch ( e ) { // #14683: Only rethrow if this hasn't been notified as an error yet if ( callback ) { throw e; } } }, abort: function() { if ( callback ) { callback(); } } }; } } ); // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) jQuery.ajaxPrefilter( function( s ) { if ( s.crossDomain ) { s.contents.script = false; } } ); // Install script dataType jQuery.ajaxSetup( { accepts: { script: "text/javascript, application/javascript, " + "application/ecmascript, application/x-ecmascript" }, contents: { script: /\b(?:java|ecma)script\b/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } } ); // Handle cache's special case and crossDomain jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; } } ); // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { // This transport only deals with cross domain or forced-by-attrs requests if ( s.crossDomain || s.scriptAttrs ) { var script, callback; return { send: function( _, complete ) { script = jQuery( "

bdedit package

Submodules

bdedit.Icons module

bdedit.Icons.qCleanupResources()
bdedit.Icons.qInitResources()

bdedit.block module

class bdedit.block.Block(scene, window, name='Unnamed Block', pos=(0, 0))

Bases: bdedit.interface_serialize.Serializable

The Block Class extends the Serializable Class from BdEdit, and defines how a block is represented, and has all the necessary methods for creating, manipulating and interacting with a block.

This class includes information about the blocks’:

  • name;

  • type;

  • appearance;

  • on-screen positioning;

  • parameters, and their values;

  • number of inputs and outputs.

__init__(scene, window, name='Unnamed Block', pos=(0, 0))

This method initializes an instance of the Block Class. It maps the following internal parameters of the block, initializing them to their defaults values. These are overwritten when an instance of the grandchild block is created. The parameters are defined as:

  • title: the name of the Block. This defaults to the name of the name of the grandchild class, and is incremented if an instance with the same default name already exists.

  • type: the type of Block. This defaults to the type of the grandchild class, and is determined through calling blockname(self.__class__) when a grandchild class is created.

  • icon: the icon for this Block. This is a locally referenced string filepath, defined within the grandchild class.

  • inputs: a list of containing input Sockets relating to this Block. The number of input sockets is restricted to 0 or n based on the inputsNum variable defined within the child class.

  • outputs: a list of containing output Sockets relating to this Block. The number of output sockets is restricted to 0 or n based on the outputsNum variable defined within the child class.

  • parameters: a list of editable parameters relating to this Block. This defaults to the list defined within the grandchild class, but follows the following structure of being a list of lists, where each ‘lists’ is a list defining the parameter as below:

    • parameters = [“name”, type, value, [restrictions]] e.g. parameters = [[“Gain”, float, gain, []], [“Premul”, bool, premul, []]]

    • name: is the name of the variable as a string

    • type: is the required type the variable should be (e.g. int, str, float)

    • value: is the default value the variable is set to should one not be provided

    • restrictions: is a list (can be list of lists) containing further restrictions applied to the parameter. These restrictions follow the following structure, of being a list with a string as the first list element, followed by a list of conditions being applied to the parameter as the second list element:

      • restriction = [“restriction name”, [condition(s)]]

      • restriction name: can be only one of the following “keywords”, “range”, “type” or “signs”.

      • condition(s): differ based on the restriction name used, and will be of the following format:

        • for keywords: a list of string words the parameter must exactly match to, e.g. [“keywords”, [“sine”, “square”, “triangle”]]

        • for range: a list containing a min and max allowable value for this parameter, e.g. [“range”, [0, 1000]] or [“range”, [-math.inf, math.inf]]

        • for type: a list containing alternative types, with the last value repeating the initial type required for this parameter, e.g. [“type”, [type(None), tuple]] (initial type = tuple) or [“type”, [type(None), bool]] (initial type = bool)

        • for signs: a list containing each allowable character this parameter can match, e.g. [“signs”, [“*”, “/”]] or [“signs”, [“+”, “-“]] currently this is used for drawing signs along certain input sockets, so only these characters are supported,

Parameters
  • scene (Scene, required) – a scene (or canvas) in which the Block is stored and shown (or painted into). Provided by the Interface.

  • window (QGridLayout, required) – layout information of where all Widgets are located in the bdedit window. Provided by the Interface.

  • name (str, optional) – set to grandchild class’s default name “__class__.__name__ Block” when it is created

  • pos (tuple of 2-ints, optional) – (x,y) coordinates of the block’s positioning within the Scene, defaults to (0,0)

closeParamWindow()

This method closes the ParamWindow of this Block

deserialize(data, hashmap={})

This method is called to reconstruct a Block when loading a saved JSON file containing all relevant information to recreate the Scene with all its items.

Parameters
  • data (OrderedDict, required) – a Dictionary of essential information for reconstructing a Block

  • hashmap (Dict, required) – a Dictionary for directly mapping the essential block parameters to this instance of Block, without having to individually map each parameter

Returns

True when completed successfully

Return type

Boolean

getSocketPosition(index, position)

This method is called to determine the coordinates of where a given Socket should be positioned on the sides of the this Block. The returned coordinates are in reference to the coordinates of this Block.

Parameters
  • index (int, required) – the index of this Socket in the list of sockets for this Block

  • position – the (LEFT(1) or RIGHT(3)) side of the block this socket

should be displayed on. :type position: enumerate, required :return: the [x,y] coordinates at which to place this Socket instance. :rtype: list of int, int

makeInputSockets(inputs, position, socketType=1)

This method is called to create a number of input Sockets for this Block.

Parameters
  • inputs (int, required) – number of input sockets to create

  • position – an enum representing the position of where to place

the Socket on the Block currently only supports LEFT (1), or RIGHT (3). :type position: enumerate, required :param socketType: an enum representing the type of Socket to create. This is used for the graphics of the socket, but was also intended to be used to reduce two methods for creating input and output sockets, to a single method. :type socketType: enumerate, optional, defaults to INPUT(1)

makeOutputSockets(outputs, position, socketType=2)

This method is called to create a number of outputs Sockets for this Block.

Parameters
  • outputs (int, required) – number of output sockets to create

  • position – an enum representing the position of where to place

the Socket on the Block currently only supports LEFT (1), or RIGHT (3). :type position: enumerate, required :param socketType: an enum representing the type of Socket to create. This is used for the graphics of the socket, but was also intended to be used to reduce two methods for creating input and output sockets, to a single method. :type socketType: enumerate, optional, defaults to OUTPUT(2)

property pos

This method is called to access the positional coordinates of this Block in terms of where it is displayed within the Scene

Returns

the (x,y) coordinates of this Block

Return type

tuple (int, int)

remove()

This method is called to remove:

  • the selected Block instance;

  • any sockets associated with this Block;

  • any wires that were connected to this Blocks’ sockets; and

  • the parameter window associated with this Block (if one existed).

removeSockets(type)

This method removes all sockets of given type, associated with this Block.

Parameters

type – the type of Socket to remove (“Input” for input type,

“Output” for output type) :type type: str, required

serialize()

This method is called to create an ordered dictionary of all of this Blocks’ parameters - necessary for the reconstruction of this Block - as key-value pairs. This dictionary is later used for writing into a JSON file.

Returns

an OrderedDict of [keys, values] pairs of all essential Block parameters.

Return type

OrderedDict ([keys, values]*)

setDefaultTitle(name, increment=None)

This method is called to give a block a generic name, and if that name already exists, to increment it by 1.

Parameters
  • name (str, required) – the generic name to be given to this Block

  • increment – the number to display next to the generic name, to make

it unique. Defaults to None if this is the first instance of this block type. :type increment: int, optional

setFocusOfBlocks()

This method sends all Block instances within the Scene to back and then sends the currently selected Block instance to front.

setPos(x, y)

This method is called to set the positional coordinates of this Block in terms of where it is displayed within the Scene

Parameters
  • x (int, required) – the x coordinate of this Block

  • y (int, required) – the y coordinate of this Block

setTitle(name=None)

This method is called to determine if a this Block can be named with the provided name. It applies logic to check if the given name of type str, and if no other Block instance already has the given name. If either of these conditions are not met, the user will be notified to provide a different name.

Parameters

name – given name to be set for this Block, defaults to None if

not provided :type name: str, optional :return: - nothing (if name successfully set);

  • [duplicate error message, given name] (if duplicate found);

  • invalid type error message (if name is not of type str).

Return type

  • nothing (if name successfully set);

  • list of [str, str] (if duplicate found);

  • str (if name is not of type str).

toggleParamWindow()

This method toggles the visibility of the ParamWindow of this Block

static tuple_decoder(obj)

This method is called when deserializing a JSON file to generate a saved copy of the Scene with all the Blocks, Sockets and Wires. It’s purpose is for decoding an encoded representation for a tuple (encoded with the TupleEncoder Class) when the JSON file was written. This decoder/encoder combination is required as JSON does not support saving under type tuple, and instead saves that information as a type list.

This code has been adapted from: https://stackoverflow.com/a/15721641

Parameters

obj (Union [int, slice], required) – the string object being decoded

Returns

the string object wrapped as a tuple (if decoded to have a __tuple__ key)

the string object (otherwise) :rtype: - tuple (if decoded to have a __tuple__ key); - any (otherwise)

updateConnectedEdges()

This method calls for any and all Wire instances that are connected to a Socket instance, to update where it is drawn TO and FROM.

updateSocketPositions()

This method updates flips the position (LEFT or RIGHT) of where the input and output sockets needs to be placed within this Block. After the positions of all the Blocks’ sockets are updated, the updateConnectedEdges method is called to update the locations for where the wires connect to.

updateSocketSigns()

As some Blocks - namely the PROD and SUM Blocks - have additional logic for drawing signs (+,-,*,/) alongside the input sockets, this method updates these signs of the socket, if the block type is a PROD or SUM Block

class bdedit.block.DiscreteBlock(scene, window, name='Unnamed Discrete Block', pos=(0, 0))

Bases: bdedit.block.Block

The DiscreteBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed Discrete Block', pos=(0, 0))

This method initializes an instance of the DiscreteBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name – overwritten to the name of grandchild class’s block name,

defaults to “Unnamed Discrete Block” :type name: str, optional :param pos: inherited through Block :type pos: tuple of 2-ints, optional

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.FunctionBlock(scene, window, name='Unnamed Function Block', pos=(0, 0))

Bases: bdedit.block.Block

The FunctionBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed Function Block', pos=(0, 0))

This method initializes an instance of the FunctionBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name – overwritten to the name of grandchild class’s block name,

defaults to “Unnamed Function Block” :type name: str, optional :param pos: inherited through Block :type pos: tuple of 2-ints, optional

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.INPORTBlock(scene, window, name='Unnamed INPORT Block', pos=(0, 0))

Bases: bdedit.block.Block

The INPORTBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed INPORT Block', pos=(0, 0))

This method initializes an instance of the INPORTBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name (str, optional) – overwritten to the name of grandchild class’s block name, defaults to “Unnamed INPORT Block”

  • pos (tuple of 2-ints, optional) – inherited through Block

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.OUTPORTBlock(scene, window, name='Unnamed OUTPORT Block', pos=(0, 0))

Bases: bdedit.block.Block

The OUTPORTBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed OUTPORT Block', pos=(0, 0))

This method initializes an instance of the OUTPORTBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name – overwritten to the name of grandchild class’s block name,

defaults to “Unnamed OUTPORT Block” :type name: str, optional :param pos: inherited through Block :type pos: tuple of 2-ints, optional

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.SUBSYSTEMBlock(scene, window, name='Unnamed SUBSYSTEM Block', pos=(0, 0))

Bases: bdedit.block.Block

The SUBSYSTEMBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed SUBSYSTEM Block', pos=(0, 0))

This method initializes an instance of the SUBSYSTEMBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name – overwritten to the name of grandchild class’s block name,

defaults to “Unnamed SUBSYSTEM Block” :type name: str, optional :param pos: inherited through Block :type pos: tuple of 2-ints, optional

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.SinkBlock(scene, window, name='Unnamed Sink Block', pos=(0, 0))

Bases: bdedit.block.Block

The SinkBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed Sink Block', pos=(0, 0))

This method initializes an instance of the SinkBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name – overwritten to the name of grandchild class’s block name,

defaults to “Unnamed Sink Block” :type name: str, optional :param pos: inherited through Block :type pos: tuple of 2-ints, optional

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.SourceBlock(scene, window, name='Unnamed Source Block', pos=(0, 0))

Bases: bdedit.block.Block

The SourceBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed Source Block', pos=(0, 0))

This method initializes an instance of the SourceBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name – overwritten to the name of grandchild class’s block name,

defaults to “Unnamed Source Block” :type name: str, optional :param pos: inherited through Block :type pos: tuple of 2-ints, optional

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.TransferBlock(scene, window, name='Unnamed Transfer Block', pos=(0, 0))

Bases: bdedit.block.Block

The TransferBlock Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and parameters of its parent class and controls the number of input or output Sockets any subclass (referred to as a grandchild class of Block) that inherits it has.

__init__(scene, window, name='Unnamed Transfer Block', pos=(0, 0))

This method initializes an instance of the TransferBlock Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name – overwritten to the name of grandchild class’s block name,

defaults to “Unnamed Transfer Block” :type name: str, optional :param pos: inherited through Block :type pos: tuple of 2-ints, optional

numInputs()

This method returns the number of input sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

numOutputs()

This method returns the number of output sockets this Block has.

Returns

number of input sockets associated with this Block.

Return type

int

class bdedit.block.TupleEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)

Bases: json.encoder.JSONEncoder

This Class inherits JSONEncoder from the json library, and is used to encode user-editable parameters associated with a Block which need to be stored as a type tuple. This code is necessary as JSON does not support storing data as tuples. After the encoder has been used to serialize (save) the Block parameter data, when the Block is deserialized (loaded), this encoded representation of a tuple will be decoded and stored as a tuple.

This code is adapted from: https://stackoverflow.com/a/15721641

encode(item)

This method determines whether a given user-editable block parameter is of type tuple, and converts it to a dictionary with a “__tuple__” key with value True (signifying this parameter should be represented as a tuple), and an “item’s” key with value item (this being the value of the user-editable parameter).

Parameters

item (any) – the user-editable parameter’s value

Returns

  • a Dictionary defined as above (if item is tuple);

  • the item (otherwise)

Return type

  • Dict (if item is tuple);

  • any (otherwise)

bdedit.block.block(cls)

This method is called whenever a grandchild class of the Block is called, adding it to a list of usable blocks.

This code has been adapted from: https://github.com/petercorke/bdsim/blob/bdedit/bdsim/components.py

Parameters

cls (Class, required) – the class of the Block Class’s grandchild block Class.

bdedit.block.blockname(cls)

This method strips any underscores from a grandchild Class of the Block Class, and capitalizes the Class.__name__ to be used as the the Class’s name.

This code has been adapted from: https://github.com/petercorke/bdsim/blob/bdedit/bdsim/bdsim.py

Parameters

cls (Class, required) – the class of the Block Class’s grandchild block Class.

Returns

a reformatted string of the block’s class name

Return type

str

bdedit.block_graphics_block module

class bdedit.block_graphics_block.GraphicsBlock(block, parent=None)

Bases: PyQt5.QtWidgets.QGraphicsItem

The GraphicsBlock Class extends the QGraphicsItem Class from PyQt5. This class is responsible for graphically drawing Blocks within the GraphicsScene. Using the provided Block dimensions, it specifies the Blocks’ shape and colour.

__init__(block, parent=None)

This method initializes an instance of the GraphicsBlock Class. It inherits the dimensions of its Block, and defines its shape and colour.

Parameters
  • block (Block, required) – the Block this GraphicsBlock instance relates to

  • parent (NoneType, optional, defaults to None) – the parent widget this class instance belongs to (None)

boundingRect()

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsBlock which returns the area within which the GraphicsBlock can be interacted with. When a mouse click event is detected within this area, this will trigger logic that relates to a Block (that being, selecting/deselecting, moving, deleting, flipping or opening a parameter window).

Returns

a rectangle within which the Block can be interacted with

Return type

QRectF

checkBlockHeight()

This method checks if the current height of the Block is enough to fit all the Sockets that are to be drawn, while following the set socket spacing. It also handles the resizing of the Block (if there isn’t enough space for all the sockets), ensuring the sockets are evenly spaced while following the set socket spacing.

checkMode()

This method checks the mode of the GraphicsScene’s background (Light, Dark) and updates the colour mode of the pens and brushes used to paint this Block.

getTitle()

This method returns the current title of this Block.

Returns

Block title

Return type

str

initTitle()

This method initializes a QGraphicsTextItem which will graphically represent the title (name) of this Block.

initUI()

This method sets flags to allow for this Block to be movable and selectable.

mouseMoveEvent(event)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsBlock to detect, and assign logic to mouse movement.

Currently, the following logic is applied to the Block on mouse movement:

  • a detected mouse move event on this GraphicsBlock will enforce grid-snapping on the selected GraphicsBlock (making it move only in increments of the same size as the smaller grid squares of the background).

  • the selected GraphicsBlock will be prevented from moving outside the maximum zoomed out border of the work area (the GraphicsScene).

  • the connection points of any wires connected to the selected block AND any other block it is connected to, will be updated, as the block is moved around.

Parameters

event (QMouseMoveEvent, automatically recognized by the inbuilt function) – a mouse movement event

mousePressEvent(event)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsBlock to detect, and assign logic to a right mouse press event.

Currently a detected mouse press event on the GraphicsBlock will select or deselect it.

  • If selected, the GraphicsBlock will be sent to front and will appear on top of other blocks.

  • Additionally, if the right mouse button is pressed and a GraphicsBlock is selected, a parameter window will be toggled for this Block.

Parameters

event (QMousePressEvent, automatically recognized by the inbuilt function) – a mouse press event (Left, Middle or Right)

paint(painter, style, widget=None)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsBlock. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene.

Before drawing, the dimensions of the Block are checked, to ensure they can hold all the necessary Sockets. Then the following are drawn in order:

  • the title of the block

  • the fill of the block (a rounded rectangle)

  • the outline of the block (a rounded rectangle)

  • the icon of the block (if one exists)

Parameters
  • painter (QPainter, automatically recognized and overwritten from this method) – a painter (paint brush) that paints and fills the shape of this GraphicsBlock

  • style (QStyleOptionGraphicsItem, automatically recognized from this method) – style of the painter (isn’t used but must be defined)

  • widget (NoneType, optional, defaults to None) – the widget this class is being painted on (None)

setTitle()

This method updates this Blocks’ graphical title to the stored title of the Block.

titleLength()

This method calculates and returns the length of this Blocks’ title in pixels.

Returns

the pixel length of this Blocks’ title

Return type

int

updateMode(value)

This method updates the mode of the Block to the provided value (should only ever be “Light”, “Dark” or “Off”).

Parameters

value (str, required) – current mode of the GraphicsScene’s background (“Light”, “Dark”, “Off”)

class bdedit.block_graphics_block.GraphicsSocketBlock(block, parent=None)

Bases: PyQt5.QtWidgets.QGraphicsItem

The GraphicsSocketBlock Class extends the QGraphicsItem Class from PyQt5. This class is responsible for graphically drawing Connector Blocks within the GraphicsScene.

__init__(block, parent=None)

This method initializes an instance of the GraphicsSocketBlock Class (otherwise known as the Graphics Class of the Connector Block).

Parameters
  • block (Connector Block) – the Connector Block this GraphicsSocketBlock instance relates to

  • parent (NoneType, optional, defaults to None) – the parent widget this class instance belongs to (None)

boundingRect()

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsSocketBlock which returns the area within which the GraphicsSocketBlock can be interacted with. When a mouse click event is detected within this area, this will trigger logic that relates to a Block (that being, selecting/deselecting, moving, deleting, flipping or opening a parameter window. Or if its Sockets are clicked on, this will trigger a wire to be created or ended).

Returns

a rectangle within which the Block can be interacted with

Return type

QRectF

initUI()

This method sets flags to allow for this Connector Block to be movable and selectable.

mouseMoveEvent(event)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsSocketBlock to detect, and assign logic to mouse movement.

Currently, the following logic is applied to the Connector Block on mouse movement:

  • a detected mouse move event on this GraphicsSocketBlock will enforce grid-snapping on the selected GraphicsSocketBlock (making it move only in increments of the same size as the smaller grid squares of the background).

  • the selected GraphicsSocketBlock will be prevented from moving outside the maximum zoomed out border of the work area (the GraphicsScene).

  • the connection points of any wires connected to the selected Connector Block AND any other block it is connected to, will be updated, as the block is moved around.

Parameters

event (QMouseMoveEvent, automatically recognized by the inbuilt function) – a mouse movement event

mousePressEvent(event)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsSocketBlock to detect, and assign logic to a right mouse press event.

Currently a detected mouse press event on the GraphicsSocketBlock will select or deselect it.

Additionally if selected, the GraphicsBlock will be sent to front and will appear on top of other blocks.

Parameters

event (QMousePressEvent, automatically recognized by the inbuilt function) – a mouse press event (Left, Middle or Right)

paint(painter, style, widget=None)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsSocketBlock (otherwise referred to as the Graphics Class of the Connector Block. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene.

When the Connector Block is selected, this method will draw an orange outline around the Connector Block, within which it can be interacted with.

:param painter:a painter (paint brush) that paints and fills the shape of this GraphicsSocketBlock :type painter: QPainter, automatically recognized and overwritten from this method :param style: style of the painter (isn’t used but must be defined) :type style: QStyleOptionGraphicsItem, automatically recognized from this method :param widget: the widget this class is being painted on (None) :type widget: NoneType, optional, defaults to None

bdedit.block_graphics_socket module

class bdedit.block_graphics_socket.GraphicsSocket(socket)

Bases: PyQt5.QtWidgets.QGraphicsItem

The GraphicsSocket Class extends the QGraphicsItem Class from PyQt5. This class is responsible for graphically drawing Sockets on Blocks. It specifies the shape, colour, style and dimensions of the Socket and also implements the logic for drawing signs alongside the input sockets of PROD and SUM` Blocks.

__init__(socket)

This method initializes an instance of the GraphicsSocket Class. It defines the shapes and sizes for a given input or output Socket. Depending on the socket_type, stored within the provided Socket, the shape and colour of the given Socket is decided.

Parameters

socket (Socket) – the Socket this GraphicsSocket instance relates to

boundingRect()

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsSocket which returns the area within which the GraphicsSocket can be interacted with. When a mouse click event is detected within this area, this will trigger logic that relates to a Socket (that being, the generating or completion of a Wire).

Returns

a rectangle within which the Socket can be interacted with

Return type

QRectF

getSignPath(sign, multi)

This method determines and returns what method should be called to paint the sign (+,-,*,/) next to an input socket (for PROD and SUM blocks only).

Parameters
  • sign (str, required) – the sign associated with the current Socket (can be ‘+’,’-‘,’*’, or ‘/’)

  • multi (int, required, 1 or -1) – internal variable (1 when default orientation, -1 when flipped)

Returns

the relevant painter path method for drawing the desired sign

Return type

QPainterPath

paint(painter, style, widget=None)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsSocket. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene.

It dictates how both input and output Sockets are painted on their relating Block. The position at which to draw the Socket is determined internally by the Socket class, so when the GraphicsSocket class paints the Socket, it is painted with respects to the point where the Socket should be.

Parameters
  • painter (QPainter, automatically recognized and overwritten from this method) – a painter (paint brush) that paints and fills the shape of this GraphicsSocket

  • style (QStyleOptionGraphicsItem, automatically recognized from this method) – style of the painter (isn’t used but must be defined)

  • widget (NoneType, optional, Defaults to None) – the widget this class is being painted on (None)

paintDivide(multi)

This method creates a painter path for drawing a divide sign character next to an input socket.

Parameters

multi (int, required, 1 or -1) – internal variable (1 when default orientation, -1 when flipped)

Returns

the painter path to draw this sign

Return type

QPainterPath

paintMinus(multi)

This method creates a painter path for drawing the ‘-‘ character next to an input socket.

Parameters

multi (int, required, 1 or -1) – internal variable (1 when default orientation, -1 when flipped)

Returns

the painter path to draw this sign

Return type

QPainterPath

paintMultiply(multi)

This method creates a painter path for drawing the ‘x’ character next to an input socket.

Parameters

multi (int, required, 1 or -1) – internal variable (1 when default orientation, -1 when flipped)

Returns

the painter path to draw this sign

Return type

QPainterPath

paintPlus(multi)

This method creates a painter path for drawing the ‘+’ character next to an input socket.

Parameters

multi (int, required, 1 or -1) – internal variable (1 when default orientation, -1 when flipped)

Returns

the painter path to draw this sign

Return type

QPainterPath

bdedit.block_graphics_wire module

class bdedit.block_graphics_wire.GraphicsWire(wire)

Bases: PyQt5.QtWidgets.QGraphicsPathItem

The GraphicsWire Class extends the QGraphicsPathItem Class from PyQt5. This class is responsible for graphically drawing Wires between Sockets.

This class takes a Wire as an input and then looks for the coordinates of the start and end socket defined within it. Then based on these coordinates, connects them with a Wire of the selected type. It is also used to redraw the wires when they are moved around, and if a wire is selected it will redraw the wire thicker and in a different colour.

__init__(wire)

This method initializes an instance of the GraphicsWire Class. It initially specifies the starting and ending sockets as being None, sets the Wire to always be drawn underneath other items within the GraphicsScene, and defines the colors with which the wire can be drawn.

Parameters

wire (Wire) – the Wire Class instance this GraphicsWire relates to

paint(painter, style, widget=None)

This is an inbuilt method of QGraphicsItem, that is overwritten by GraphicsWire. This method is automatically called by the GraphicsView Class whenever even a slight user-interaction is detected within the Scene.

It sets up the painter object and draws the line based on the path that will be set by the specific implementation of GraphicsWire that is calling paint. Then the painter will select the way the wire will be drawn depending on whether or not the wire is selected.

Parameters
  • painter (QPainter, automatically recognized and overwritten from this method) – a painter that paints the path (line) of this GraphicsWire

  • style (QStyleOptionGraphicsItem, automatically recognized from this method) – style of the painter (isn’t used but must be defined)

  • widget (NoneType, optional, Defaults to None) – the widget this class is being painted on (None)

setDestination(x, y)

This method sets the point of where the Wire will end. This is based on the position of the end Socket.

Parameters
  • x (int, required) – x coordinate of the end Socket

  • y (int, required) – y coordinate of the end Socket

setDestinationOrientation(orientation)

This method sets the orientation (position) of the destination (end) Socket - in terms of where it is drawn on the block (LEFT/RIGHT) - to the provided orientation.

Parameters

orientation (enumerate, required) – where on the Block the end Socket is drawn (LEFT(1) or RIGHT(2))

setSource(x, y)

This method sets the point from where the Wire will start. This is based on the position of the start Socket.

Parameters
  • x (int, required) – x coordinate of the start Socket

  • y (int, required) – y coordinate of the start Socket

setSourceOrientation(orientation)

This method sets the orientation (position) of the source (start) Socket - in terms of where it is drawn on the block (LEFT/RIGHT) - to the provided orientation.

Parameters

orientation (enumerate, required) – where on the Block the start Socket is drawn (LEFT(1) or RIGHT(2))

updateLineSegments()

This method uses the coordinates of the points this wire goes through, to determine the coordinates that make up the horizontal and vertical line segments of this wire (this logic is only applicable to GraphicsWireStep).

updatePath()

This method is inherited and overwritten (currently) by the GraphicsWireDirect, GraphicsWireBezier or GraphicsWireStep classes, which dictate the pathing logic of the wire between the start and end socket.

updateWireCoordinates(new_coordinates)

This method checks if the list of coordinate points this wire passes through, has changed and needs to be updated. This method reduces the computational resources required to otherwise re-write the list of coordinate points for this wire, every time a user-interaction is detected within the GraphicsView.

Parameters

new_coordinates (list) – proposed coordinate points for this Wire

class bdedit.block_graphics_wire.GraphicsWireBezier(wire)

Bases: bdedit.block_graphics_wire.GraphicsWire

The GraphicsWireBezier Class extends the GraphicsWire Class from BdEdit. This class is responsible for providing a bezier painter path that the GraphicsWire class should follow when drawing the Wire. This wire type option looks good in the first direction it is drawn (e.g. left-to-right) but will not wrap logically when blocks are flipped or moved past each other (e.g. such that the wire would now be right-to-left).

updatePath()

This method creates a painter path that connects two points (start/end sockets) with a bezier line. This line is drawn as a cubic (think sine wave).

class bdedit.block_graphics_wire.GraphicsWireDirect(wire)

Bases: bdedit.block_graphics_wire.GraphicsWire

The GraphicsWireDirect Class extends the GraphicsWire Class from BdEdit. This class is responsible for providing a straight painter path that the GraphicsWire class should follow when drawing the Wire. This wire type will draw a straight line between two Sockets. It will take the shortest distance between these two points, and will go through other Blocks to do so.

updatePath()

This method creates a painter path that connects two points (start/end sockets) with a straight line.

class bdedit.block_graphics_wire.GraphicsWireStep(wire)

Bases: bdedit.block_graphics_wire.GraphicsWire

The GraphicsWireStep Class extends the GraphicsWire Class from BdEdit. This class is responsible for providing a stepped painter path that the GraphicsWire class should follow when drawing the Wire. This is the default wire style used within BdEdit, and has the most supporting logic built around it.

This wire option draws a straight line between two sockets when the two blocks side by side at equal heights, are connected with a wire in the middle.

Otherwise if the two blocks are at varying heights, a stepped line will be drawn from the starting socket, with 90 degree bends at each point the wire must turn to reach the end socket.

updatePath()

This method creates a painter path that connects two points (start/end sockets) with a stepped line. This path is returned as a straight line if two blocks at equal heights, are connected with a wire on the inside of each of the blocks. Otherwise the path is returned as a stepped line with 90 degree bends at each point the wire must turn.

The logic for when this wire should turn is calculated internally. Please refer to the technical document accompanying the BdEdit code for visual explanations for how this logic is determined.

bdedit.block_param_window module

class bdedit.block_param_window.ParamWindow(block, parent=None)

Bases: PyQt5.QtWidgets.QWidget

The ParamWindow Class extends the QWidget Class from PyQt5. The ParamWindow Class controls:

  • how the parameter window appears visually,

  • where it is located within the BdEdit application window,

  • the displayed parameters from the Block the parameter window is related to,

  • sanity checking on user-edits to the Block parameters,

  • pop-up user feedback for successful or unsuccessful Block parameter edits.

__init__(block, parent=None)

This method initializes an instance of the ParamWindow Class.

Parameters
  • block (Block) – the Block this ParamWindow instance relates to

  • parent (None, optional) – the parent widget this ParamWindow belongs to (should be None)

buildParamWindow()

This method handles the building of the ParamWindow instance. These instances will always look exactly the same in terms of the information they contain, however the Block parameters and the block type will vary depending on the Block this ParamWindow relates to.

The ParamWindow is built by adding items into the QWidget that represents this parameter window. For each item added into the QWidget, a label is displayed on the left-hand-side, and either the non-editable value (label) is displayed on the right-hand-side, or an editable line is displayed and populated with the respective parameters’ current value.

The ParamWindow is populated by, first, adding a non-editable line displaying the selected Blocks’ type. Next, the Blocks’ current title is added with an editable line. Following this, each Block parameter looped through and added with an editable line in which the current value of the Block parameter is populated. Changing this value will prompt the sanity checking of the value entered, which is handled by the updateBlockParameters() method.

closeQMessageBox()

This method closes a pop-up user-feedback message if one already exists within the parameter window. This prevents multiple messages from being displayed at the same time, and ensure the message the user sees, is the most relevant one.

displayPopUpMessage(title, text, messageType)

This method displays a pop-up user-feedback message within the parameter window, notifying them of either a successful or unsuccessful update to the Blocks’ parameters. This method will also trigger the message to auto-close, after 1.5 seconds for a successful update, and after 5 seconds for an unsuccessful update. If at any point the user updates the Block parameters before this time is elapsed, the current (old) message will be removed and replaced by the most relevant message. An unsuccessful update attempt will also issue an error sound when the message is opened.

Parameters
  • title (str) – the title of the message box displayed

  • text (str) – the text that fills the body of the displayed message box

  • messageType (str) – variable that calls what type of message to display (“Success”, “Error”)

static getSafeValue(inputValue, requiredType, requiredOptions)

This method takes an input value (which is the value a parameter is being checked if it can be updated to), and checks whether it matches an allowable type that has been defined for that parameter within the grandchild Block Class. If the input value doesn’t match the required type, an invalid type str will be returned. If the input value does match the required type, it is further checked, whether it matches any further restrictions placed onto that parameter from within the grandchild Block Class. If the value doesn’t meet the criteria of the restrictions, a bad input str will be returned. If the input does match the criteria of the restriction, it will be converted to the type it must be in and returned.

Parameters
  • inputValue (str) – the value which the parameter would be updated to

  • requiredType (type, determined by the grandchild Block Class) – the value type which is required for this parameter

  • requiredOptions (list) – a list of restrictions placed onto this parameter

Returns

  • str (if incompatible type or restriction criteria not met),

  • requiredType (if compatible type and restrictions are met)

Return type

type, determined by the grandchild Block Class

initUI()

This method adds a ‘Parameter Window’ label to the ParamWindow, sets the alignment of items within the parameter window, sets the background to auto fill, and finally, sets the layout manager of the ParamWindow.

updateBlockParameters()

This method calls for each of the parameters within the parameter window to be sanity checked by the getSafeValue() method, which determines whether or not a provided value is compatible with the parameter type (defined in the Block Class) and is safe to override the current value. If that check is returned to be safe, this method will handle the updating of the Block parameters, as well as triggering a successful update attempt message.

If the check is returned as not safe, meaning the given value is not compatible, an unsuccessful update attempt error message will be prompted, notifying the user of the incompatible parameter value they have set, and either what the compatible types or values are. This logic also applies when a user changes the blocks’ title to one that already exists, which will cause a duplicate error message to display.

Some parameters values directly affect the GraphicsBlock of the Block they relate to. For example, blocks that can have multiple input or output sockets, will have a parameter that controls how many of these sockets the block has. And once edited, this method will trigger an appropriate number of sockets to be created or deleted. This parameter can also affect the GraphicsBlock when too many sockets are created, requiring the block to be resized. The triggering of this resizing will also be issued from within this method.

bdedit.block_socket module

class bdedit.block_socket.Socket(node, index=0, position=1, socket_type=1, multi_wire=True)

Bases: bdedit.interface_serialize.Serializable

The Socket Class extends the Serializable Class from BdEdit, and defines how a socket is represented, and has all the necessary methods for creating, manipulating and interacting with a socket. This class allows for Wires to be connected to Blocks, while also controlling where on the Block the Sockets are drawn, and how they appear.

This class includes information about the sockets’:

  • type;

  • index;

  • position;

  • appearance;

  • parent Block; and

  • connected wire(s).

__init__(node, index=0, position=1, socket_type=1, multi_wire=True)

This method initializes an instance of the Socket Class.

Parameters
  • node (Block, required) – the associated Block this Socket relates to

  • index (int, optional, defaults to 0) – the height (along the side of the Block) this Socket should be drawn at

  • position (enumerate, optional, defaults to LEFT(1)) – the side ( LEFT(1) or RIGHT(3) ) this Socket should be drawn on

  • socket_type (enumerate, optional, defaults to INPUT(1)) – this Socket’s type (INPUT(1) or OUTPUT(2))

  • multi_wire (bool, optional, defaults to True) – boolean of whether this Socket has multiple wires

deserialize(data, hashmap={})

This method is called to reconstruct a Socket when loading a saved JSON file containing all relevant information to recreate the Scene with all its items.

Parameters
  • data (OrderedDict, required) – a Dictionary of essential information for reconstructing a Socket

  • hashmap (Dict, required) – a Dictionary for directly mapping the essential socket variables to this instance of Socket, without having to individually map each variable

Returns

True when completed successfully

Return type

Boolean

getSocketPosition()

This method retrieves and returns the [x,y] coordinates for where the current Socket should be drawn.

Returns

the [x,y] coordinates at which to place this Socket.

Return type

list of int, int

hasEdge()

This method returns True if the current Socket has Wires connected to it, and False if no Wires are connected to it.

Returns

  • True (If wires are connected to this Socket)

  • False (If no wires are connected to this Socket)

Return type

bool

isInputSocket()

This method returns True if the current Socket is an input socket.

Returns

  • True (If current Socket is an input Socket)

  • False (If current Socket is not an input Socket)

Return type

bool

isOutputSocket()

This method returns True if the current Socket is an output socket.

Returns

  • True (If current Socket is an output Socket)

  • False (If current Socket is not an output Socket)

Return type

bool

removeAllWires()

This method removes all Wires connected to this Socket.

removeSockets(type)

This method removes all of the input or output Sockets, relating to this Block, as specified by the type.

This method removes all sockets of given type, associated with this Block.

Parameters

type (str, required) – the type of Socket to remove (“Input” or “Output”)

removeWire(wire)

This method removes the given Wire from the list of Wires connected to this Socket, if it is connected to this Socket.

Parameters

wire (Wire, required) – the wire to be removed

serialize()

This method is called to create an ordered dictionary of all of this Sockets’ parameters - necessary for the reconstruction of this Socket - as key-value pairs. This dictionary is later used for writing into a JSON file.

Returns

an OrderedDict of [keys, values] pairs of all essential Socket parameters.

Return type

OrderedDict ([keys, values]*)

setConnectedEdge(wire)

This method adds the given wire to the list of Wires that are connected to this Socket.

Parameters

wire (Wire, required) – the wire connecting to this Socket

bdedit.block_socket_block module

class bdedit.block_socket_block.Connector(scene, window, name='Unnamed Connector Block', pos=(0, 0))

Bases: bdedit.block.Block

The Connector Class is a subclass of Block, and referred to as a child class of Block. It inherits all the methods and variables of its parent class to behave as a Block. It allows for wires to be more neatly redirected, acting as a node through which the wires can be moved around more freely within the work area.

The idea of this Connector block was for it to be a single socket which allows a wire to be redirected through it, however currently it works by mimicking a Block that only has 1 input and 1 output socket. The same socket logic that applies to a Block, also applies to the Connector Block.

That being:

  • an input: can only have 1 Wire connecting into it

  • an output: can have n Wires connecting into it

__init__(scene, window, name='Unnamed Connector Block', pos=(0, 0))

This method initializes an instance of the Connector Block Class.

Parameters
  • scene (Scene, required) – inherited through Block

  • window (QGridLayout, required) – inherited through Block

  • name (str, optional) – defaults to “Unnamed Connector Block”

  • pos (tuple of 2-ints, optional) – inherited through Block

bdedit.block_wire module

class bdedit.block_wire.Wire(scene, start_socket=None, end_socket=None, wire_type=3)

Bases: bdedit.interface_serialize.Serializable

The Wire Class extends the Serializable Class from BdEdit, and defines how a wire is represented, and has all the necessary methods for creating, manipulating and interacting with a wire. This class connects start and end sockets to a created wire. The style of wire being drawn is also controlled by this Class:

  • a straight wire will have type DIRECT(1),

  • a curved or wave-like wire will have type BEZIER(2),

  • a stepped wire will have type STEP(3)

This class includes information about the wires’:

  • style;

  • end_socket;

  • start_socket;

  • point-to-point coordinates;

  • horizontal and vertical line segments;

  • intersection points with other wires (has been disabled).

__init__(scene, start_socket=None, end_socket=None, wire_type=3)

This method initializes an instance of the Wire Class.

Parameters
  • scene (Scene, required) – a scene (or canvas) in which the Wire is stored and shown (or painted into). Provided by the Interface.

  • start_socket (Socket, optional, defaults to None (automatically set)) – the start Socket of this Wire

  • end_socket (Socket, optional, defaults to None (automatically set)) – the end Socket of this Wire

  • wire_type (enumerate, optional, defaults to STEP(3) (automatically set)) – the wire style of this Wire (DIRECT(1), BEZIER(2), STEP(3))

checkIntersections()

This method checks all active wires in the scene for intersections with other wires. This method will be called any time a mouse movement is detected in the GraphicsView class, which will cause the GraphicsScene to draw points at these intersections to separate the wires.

To reduce computation for finding these intersection points, only vertical line segments of wires are checked for intersections. This is because an intersection point can only occur when a horizontal line segment of one wire meets a vertical line segment of another wire, and every single wire has a horizontal segment (as sockets are drawn on the LEFT or RIGHT sides of a Block).

When this method is called, the current intersection points are deleted, as all wires are checked against in this method, and as such, any new (or previous) intersection points will be appended into a list of intersection points that is stored within the GraphicsScene Class.

deserialize(data, hashmap={})

This method is called to reconstruct a Wire when loading a saved JSON file containing all relevant information to recreate the Scene with all its items.

Parameters
  • data (OrderedDict, required) – a Dictionary of essential information for reconstructing a Wire

  • hashmap (Dict, required) – a Dictionary for directly mapping the essential wire variables to this instance of Wire, without having to individually map each variable

Returns

True when completed successfully

Return type

Boolean

property end_socket

This method is a decorate that gets the end socket of this Wire.

Returns

the end Socket of this Wire

Return type

Socket

remove()

This method will remove the selected Wire from the Scene, un-assign the Sockets that related to it, and remove the Wire from these Sockets.

remove_from_sockets()

This method will un-assign the start and end sockets of this Wire.

serialize()

This method is called to create an ordered dictionary of all of this Wires’ parameters - necessary for the reconstruction of this Wire - as key-value pairs. This dictionary is later used for writing into a JSON file.

Returns

an OrderedDict of [keys, values] pairs of all essential Wire parameters.

Return type

OrderedDict, ([keys, values]*)

property start_socket

This method is a decorate that gets the start socket of this Wire.

Returns

the start Socket of this Wire

Return type

Socket

updatePositions()

This method grabs the new positions of sockets on blocks as they are moved around within the scene, in order to determine the positions which the wire should connect. The redrawing of the wire to these positions will also be handled within this method.

property wire_type

This method is a decorate that gets the wire type (or style) of this Wire.

Returns

the style of this Wire (DIRECT(1), BEZIER(2), STEP(3))

Return type

enumerate

bdedit.interface module

class bdedit.interface.Interface(resolution, parent=None)

Bases: PyQt5.QtWidgets.QWidget

The Interface Class extends the QWidget Class from PyQt5, and houses the necessary methods for controlling how the application appears. It also connects the other Classes to allow for Block Diagrams to be constructed.

This class includes information about the:

  • toolbar, its buttons and their connections

  • library Browser menu, its buttons and their connections

  • scene, its appearance (Background and Foreground) and its items (Blocks, Sockets, Wires)

  • application layout, in terms of where the toolbar, library Browser, scene and parameter window is displayed.

Note, the toolbar, library Browser, scene and parameter window will be referred to as application components.

__init__(resolution, parent=None)

This method initializes an instance of the Interface.

Parameters
  • resolution (PyQt5.QtCore.QRect(0, 0, screen_width, screen_height), required) – the desktop screen resolution of the user

  • parent (None, optional) – the parent widget this interface belongs to (should be None)

initUI(resolution)

This method is responsible for controlling the size of the application window. It is also responsible for each application component, in terms of:

  • setting its dimensions ;

  • setting its position within the application layout;

  • creating its relevant buttons and connecting them to desired actions;

  • populating it with the created buttons; and

  • spacing out these buttons within the application component itself.

Parameters

resolution (PyQt5.QtCore.QRect(0, 0, screen_width, screen_height), required) – the desktop screen resolution of the user

Returns

a QWidget that is displayed within the application from bdEdit.py

Return type

QWidget

loadFromFile()

This method opens a QFileDialog window, prompting the user to select a file to load from.

on_click(scene)

This method creates a Wire instance from the Socket that was clicked on, to the mouse pointer.

Parameters

scene (Scene, required) – the scene within which the Block and Socket are located

resizeEvent(event)

This is an inbuilt method of QWidget, that is overwritten by Interface to update the dimensions of the Scene when the application window has been resized.

Parameters

event (QResizeEvent, automatically recognized by the inbuilt function) – an interaction event that has occurred with this QWidget

saveAsToFile()

This method opens a QFileDialog window, prompting the user to enter a name under which the current file will be saved. This file will automatically be given a .json file type.

saveToFile()

This method calls the method from within the Scene to save a copy of the current Scene, with all its items under a file with the current filename. If this is the first time a user is saving their file, they will be prompted to name the file and to choose where it will be saved.

save_image(picture_name)

This method takes a filename and saves a snapshot of all the items within the Scene into it. Currently the resolution of this image is set to 4K resolution (3840 x 2160).

Parameters

picture_name (str, required) – the name under which the image will be saved

toggleCanvasItems()

This method toggles hiding/expanding all Canvas related Items. Currently these include only the Connector Block.

toggleDiscreteBlocks()

This method toggles hiding/expanding all Discrete Blocks. These depend on the Blocks defined within the auto-imported files.

toggleFunctionBlocks()

This method toggles hiding/expanding all Function Blocks. These depend on the Blocks defined within the auto-imported files.

toggleSinkBlocks()

This method toggles hiding/expanding all Sink Blocks. These depend on the Blocks defined within the auto-imported files.

toggleSourceBlocks()

This method toggles hiding/expanding all Source Blocks. These depend on the Blocks defined within the auto-imported files.

toggleSubSystemBlocks()

This method toggles hiding/expanding all SubSystem Blocks. These depend on the Blocks defined within the auto-imported files.

toggleTransferBlocks()

This method toggles hiding/expanding all Transfer Blocks. These depend on the Blocks defined within the auto-imported files.

updateColorMode()

This method is called to update the color mode with which the Scene background should be displayed. The options are:

  • Light: light gray grid lines, with dark outlines for blocks

  • Dark: dark gray grid lines, with light outlines for blocks

  • Off: no grid lines, with dark outlines for blocks

updateSceneDimensions()

This method updates the dimensions of the scene based on current window size (will change as the application window is resized).

bdedit.interface.importBlocks()

This method is called at the beginning of an Interface instance, to auto import all grandchild Classes of the Block class from a locally referenced dictionary called Block_Classes.

This code is adapted from: https://github.com/petercorke/bdsim/blob/bdedit/bdsim/bdsim.py

Returns

a list of imported blocks

Return type

list

bdedit.interface_graphics_scene module

class bdedit.interface_graphics_scene.GraphicsScene(scene, parent=None)

Bases: PyQt5.QtWidgets.QGraphicsScene

The GraphicsScene Class extends the QGraphicsScene Class from PyQt5, and controls the basic appearance of the Scene it belongs to. The things it controls include its background, and foreground.

__init__(scene, parent=None)

This method creates an QGraphicsScene instance and associates it to this GraphicsScene instance. The GraphicsScene dictates how all items within the Scene are visually represented.

Parameters
  • scene (Scene, required) – the Scene to which this GraphicsScene belongs to

  • parent (None, optional) – the parent widget this GraphicsScene belongs to (should be None)

checkMode()

This method updates the colors used for painting the background of the GraphicsScene.

drawBackground(painter, rect)

This is an inbuilt method of QGraphicsScene, that is overwritten by GraphicsScene to draw a grid background, or a plain background, depending on what grid mode is chosen. This background is drawn behind all other items within the GraphicsScene.

Parameters
  • painter (QPainter, automatically recognized and overwritten from this method) – a painter (paint brush) that paints the background of this GraphicsScene

  • rect (QRect, automatically recognized by the inbuilt function) – a rectangle that defines the dimensions of this GraphicsScene

drawForeground(painter, rect)

This is an inbuilt method of QGraphicsScene, that is overwritten by GraphicsScene to draw additional logic for intersection points between wires. This logic is drawn overtop of all other items within the GraphicsScene apart from Blocks. Intersection points are drawn as a circle (same fill color as the background) to create a separation between the wires, then redraws a vertical section of the wire overtop.

Parameters
  • painter (QPainter, automatically recognized and overwritten from this method) – a painter (paint brush) that paints the foreground of this GraphicsScene

  • rect (QRect, automatically recognized by the inbuilt function) – a rectangle that defines the dimensions of this GraphicsScene

mouseMoveEvent(event)

This is an inbuilt method of QGraphicsScene, that is overwritten by GraphicsScene to update the start-end points of where the wires are drawn to, as items are moved around within the GraphicsScene.

Parameters

event (QMouseEvent, automatically recognized by the inbuilt function) – a mouse movement event that has occurred with this GraphicsScene

setGrScene(width, height)

This method sets the dimensions of the GraphicsScene.

Parameters
  • width (int, required) – width of the GraphicsScene

  • height (int, required) – height of the GraphicsScene

updateMode(value)

This method updates the background mode for the GraphicsScene, depending on what option was chosen in the toolbar drop down menu next to grid mode.

Parameters

value (str, required) – the color mode of the background (“Light”, “Dark”, “Off”)

bdedit.interface_graphics_view module

class bdedit.interface_graphics_view.GraphicsView(grScene, parent=None)

Bases: PyQt5.QtWidgets.QGraphicsView

The GraphicsView Class extends the QGraphicsView Class from PyQt5, and handles most of the user interactions with the Interface, through press/scroll/click/scroll/key events. It also contains the logic for what Wire should be drawn and what Sockets it connects to. Here mouse click events are used to drag the wires from a start to a end socket, when click is dragged from a socket, the mode == MODE_WIRE_DRAG will be set to True and a Wire will follow the mouse until a end socket is set or mode == MODE_WIRE_DRAG is False and the Wire will be deleted.

__init__(grScene, parent=None)

This method creates an QGraphicsView instance and associates it to this GraphicsView instance.

Parameters
  • grScene (GraphicsScene, required) – the GraphicsScene to which this GraphicsView belongs to

  • parent (None, optional) – the parent widget this GraphicsView belongs to (should be None)

closeParamWindows()

This method will close the parameter window used for changing the user-editable block variables.

deleteSelected()

This method removes the selected Block or Wire from the scene.

dist_click_release(event)

This method checks how for the cursor has moved. This is be used when the Wire is dragged, to check that wire has been dragged away from the start socket, so that when it is released on a socket we know its not the start socket.

Parameters

event (QMouseEvent, automatically recognized by the inbuilt function) – a mouse release event that has occurred with this GraphicsView

Returns

  • True (if mouse has been released more than an defined distance from the start_socket)

  • False (if mouse has been released too close too the start_socket)

Return type

bool

edgeDragEnd(item)

This method is used for setting the end socket of the Wire. The place where the wire has been released will be checked, and if it is a GraphicSocket and is not the start socket then a Wire is completed.

Next some check will be made to see that inputs are not connected to inputs and outputs are not connected to outputs. Additionally, Block Sockets will be checked to prevent multiple Wires from connecting to a single input socket. No such restriction is placed on the output sockets. This same logic is applied to Connector Blocks.

If these conditions are met, the wire that was dragged will be deleted, and a new Wire will be created with the start socket from the block the wire drag started at, and the end socket being from the socket of the block the Wire was dragged to.

If the above-mentioned conditions not met, the wire is simply removed.

Parameters

item – should be the socket that has been clicked on (however could

be one of the following: GraphicsBlock, GraphicsSocket, GraphicsWireStep or NoneType) :type item: GraphicsSocket, required :return: False (if the the Wire has been successfully drawn between Blocks) :rtype: bool

edgeDragStart(item)

This method starts drawing a Wire between two Blocks. It will construct a new Wire and set the start socket to the socket that has been clicked on, and the end socket to None. The end socket will be set when either another socket is clicked, or the mouse button is released over another socket. If neither happen, the wire will be deleted.

Parameters

item (GraphicsSocket, required) – the socket that has been clicked on

flipBlockSockets()

This method flips the selected Block so that the input and output Sockets change sides.

getItemAtClick(event)

This method returns the object at the click location. It is used when checking what item within the GraphicsView has been clicked when starting to drag a wire.

Parameters

event (QMouseEvent, automatically recognized by the inbuilt function) – a mouse click event that has occurred with this GraphicsView

Returns

the item that has been clicked on (can be GraphicsBlock, GraphicsSocket, GraphicsWireStep, NoneType), required

Return type

GraphicsBlock, GraphicsSocket, GraphicsWireStep or NoneType

initUI()

This method initializes the GraphicsScene with additional settings to make things draw smoother

intersectionTest()

This method initiates the checking of all Wires within the Scene for intersection points where they overlap.

keyPressEvent(event)

This is an inbuilt method of QGraphicsView, that is overwritten by GraphicsView to detect, and assign actions to the following key presses.

  • DEL or BACKSPACE: removes selected item from the Scene

  • F: flips the sockets on a Block or Connector Block

  • CTRL + S: previously connected to saving the Scene

  • CTRL + L: previously connected to loading a Scene file

The saving and loading of a file using keys has since been disabled, as it used an old method for saving/loading JSON files which has since been overwritten in the Interface Class. However these key checks are still connected if future development should take place.

Parameters

event (QKeyPressEvent, automatically recognized by the inbuilt function) – key(s) press(es) that have been detected

leftMouseButtonPress(event)

This method handles the logic associate with the Left mouse button press. It will always run the getItemAtClick method to return the item that has been clicked on.

  • If a GraphicsSocket is pressed on, then a draggable Wire will be started.

  • If a GraphicWire is pressed, then the active draggable Wire will be ended (when the wire is draggable, clicking off at a Socket, will register the clicked item as a GraphicsWire).

Alternatively, the following logic is applied for selecting items.

  • If an empty space within the GraphicsView is pressed, a draggable net will appear, within which all items will be selected.

  • If left clicking while holding the SHIFT or CTRL key, this will incrementally select an item from within the GraphicsView. The items that are selectable are GraphicsBlock, GraphicsWire or GraphicsSocketBlock (which is the Connector Block).

Otherwise nothing is done with the left mouse press.

Parameters

event (QMousePressEvent, required) – a Left mouse button press

Returns

None to exit the method

Return type

NoneType

leftMouseButtonRelease(event)

This method handles the logic associate with the Left mouse button release. It will always run the getItemAtClick method to return the item that the mouse has been released from.

  • If a Wire was the item being dragged, it will check how far the Wire has moved, then an attempt to complete the Wire onto a Socket will be made. If no Socket is found, the Wire will be ended.

Alternatively, the following logic is applied for selecting items.

  • If an empty space within the GraphicsView is released, if a draggable net was active, all items within that net will be selected.

  • If left clicking while holding the SHIFT or CTRL key, this will incrementally select an item from within the GraphicsView. The items that are selectable are GraphicsBlock, GraphicsWire or GraphicsSocketBlock (which is the Connector Block).

Parameters

event (QMouseReleaseEvent, required) – a Left mouse button release

Returns

None to exit the method

Return type

NoneType

middleMouseButtonPress(event)

This method handles the logic associate with the Middle mouse button press (perhaps more intuitively understood as pressing the scroll wheel). When the scroll wheel is pressed, the mouse cursor will appear as a hand that pinches the GraphicsView, allowing the canvas to be dragged around.

Parameters

event (QMousePressEvent, required) – the detected middle mouse press event

middleMouseButtonRelease(event)

This method handles the logic associate with the Middle mouse button release (perhaps more intuitively understood as releasing the scroll wheel). When the scroll wheel is releasing, the mouse cursor will change back from appearing as a hand to the default mouse cursor (pointer arrow on Windows).

Parameters

event (QMouseReleaseEvent, required) – the detected middle mouse release event

mouseMoveEvent(event)

This is an inbuilt method of QGraphicsView, that is overwritten by GraphicsView to assign logic to detected mouse movement.

  • If the wire is in dragging mode, the position the wire is drawn to will be updated to the mouse cursor as it is moved around.

  • Additionally, the code to check for intersection amongst wires will be run, and subsequently, if any are found, they will be automatically marked within the GraphicsScene Class.

Parameters

event (QMouseMoveEvent, automatically recognized by the inbuilt function) – the detected mouse movement event

mousePressEvent(event)

This is an inbuilt method of QGraphicsView, that is overwritten by GraphicsView to detect, and direct the Left, Middle and Right mouse button presses to methods that handle their associated logic.

Additionally, when the Left mouse button is pressed anywhere in the GraphicsView, any currently ParamWindow that relates to an active Block within the Scene will be closed.

Parameters

event (QMousePressEvent, automatically recognized by the inbuilt function) – a mouse press event (Left, Middle or Right)

mouseReleaseEvent(event)

This is an inbuilt method of QGraphicsView, that is overwritten by GraphicsView to detect, and direct the Left, Middle and Right mouse button releases to methods that handle their associated logic.

Parameters

event (QMouseReleaseEvent, required) – a mouse release event (Left, Middle or Right)

rightMouseButtonPress(event)

This method handles the logic associate with the Right mouse button press. Currently no logic is linked to a right mouse press.

Parameters

event (QMousePressEvent, required) – the detected right mouse press event

Returns

the mouse press event is returned

Return type

QMousePressEvent

rightMouseButtonRelease(event)

This method handles the logic associate with the Right mouse button release. Currently no logic is linked to a right mouse release.

Parameters

event (QMousePressEvent, required) – the detected right mouse release event

Returns

the mouse release event is returned

Return type

QMouseReleaseEvent

wheelEvent(event)

This is an inbuilt method of QGraphicsView, that is overwritten by GraphicsView to assign logic to detected scroll wheel movement.

  • As the scroll wheel is moved up, this will make the zoom in on the work area of the GraphicsScene.

  • As the scroll wheel is moved down, this will make the zoom out of the work area of the GraphicsScene.

Parameters

event (QWheelEvent, automatically recognized by the inbuilt function) – the detected scroll wheel movement

bdedit.interface_scene module

class bdedit.interface_scene.Scene(resolution, window)

Bases: bdedit.interface_serialize.Serializable

The Scene Class extends the Serializable Class from BdEdit, and holds the information of all the Block and Wire instances that are within it. It also handles the storage of intersection points of the wires.

This class includes information about the:

  • blocks, a list containing all Block instances

  • wires, a list containing all Wire instances

  • intersection points, a list containing all intersection points between Wires

__init__(resolution, window)

This method initializes an instance of the Scene Class.

Parameters
  • resolution (PyQt5.QtCore.QRect(0, 0, screen_width, screen_height), required) – the desktop screen resolution of the user

  • window (QGridLayout, required) – the application’s layer manager

addBlock(block)

This method adds a Block to the Scene's list of blocks.

addWire(wire)

This method adds a Wire to the Scene's list of wires.

checkForDuplicates(name)

This method checks if the given name would be a duplicate of an existing block name.

Parameters

name (str, required) – the desired name for a Block

Returns

  • False (if given name is not a duplicate)

  • True (if given name is a duplicate)

Return type

bool

clear()

This method removes all blocks from the list of blocks within the Scene. This will subsequently remove any and all wires between these blocks.

deserialize(data, hashmap={})

This method is called to reconstruct a Scene and all its items when loading a saved JSON.

Parameters
  • data (OrderedDict, required) – a Dictionary of essential information for reconstructing a Scene

  • hashmap (Dict, required) – a Dictionary for directly mapping the essential scene variables to this instance of Scene, without having to individually map each variable

Returns

True when completed successfully

Return type

bool

displayMessage()

This method displays a ‘File saved successfully!’ pop-up message, providing the user with feedback that their was saved. This pop-up message will disappear on its own after 1 second.

getSceneHeight()

This method returns the current height of the Scene.

getSceneWidth()

This method returns the current width of the Scene.

initUI()

This method creates an GraphicsScene instance and associates it to this Scene instance. The GraphicsScene dictates how all items within the Scene are visually represented.

loadFromFile(filename)

This method loads the contents of a saved JSON file with the given filename into an instance of the Scene.

This method will call upon the self.deserialize() method which will subsequently call the self.deserialize() method within each item that should be reconstructed for the Scene (these being the Block, Wire and Socket).

Parameters

filename (str) – name of the file to load from

removeBlock(block)

This method removes a Block to the Scene's list of blocks.

removeWire(wire)

This method removes a Wire to the Scene's list of wires.

saveToFile(filename)

This method saves the contents of the Scene instance into a JSON file under the given filename. This method will call upon the self.serialize() method which will subsequently call the self.serialize() method within each item displayed in the Scene (these being the Block, Wire and Socket).

Parameters

filename (str, required) – name of the file to save into

serialize()

This method is called to create an ordered dictionary of all of this Scenes’ parameters - necessary for the reconstruction of this Scene - as key-value pairs. This dictionary is later used for writing into a JSON file.

Returns

an OrderedDict of [keys, values] pairs of all essential Scene parameters.

Return type

OrderedDict ([keys, values]*)

setSceneHeight(height)

This method sets the current height of the Scene, to the given height.

setSceneWidth(width)

This method sets the current width of the Scene, to the given width.

updateSceneDimensions()

This method sets the dimensions of the Scene to the currently set scene_width and scene_height.

bdedit.interface_serialize module

class bdedit.interface_serialize.Serializable

Bases: object

The Serializable class provides three essential methods for: ensuring uniqueness amongst necessary data (1), saving (2) and loading (3) of this data that is needed for the reconstruction of a Block Diagram.

__init__()

This method extracts the unique identification number of the class instance that calls this method, and stores it as a variable within that class instance.

deserialize(data, hashmap={})

This method is inherited and overwritten by all classes that have graphical components related to them (Scene, Block, Socket and Wire). This allows those classes to un-package (load) the data stored within the saved JSON file in order to reconstruct the respective class instance.

Parameters
  • data (OrderedDict, required) – a Dictionary of essential data for reconstructing a Block Diagram

  • hashmap – a Dictionary for of the same data, but used for simpler mapping

of variables to class instances :type hashmap: Dict, required

serialize()

This method is inherited and overwritten by all classes that have graphical components related to them (Scene, Block, Socket and Wire). This allows those classes to package (save) essential variables necessary for the later reconstruction of those class instances, into a JSON file.

Module contents

================================================ FILE: bdsim/bdedit/docs/genindex.html ================================================ Index — BdEdit documentation
  • »
  • Index

Index

_ | A | B | C | D | E | F | G | H | I | K | L | M | N | O | P | Q | R | S | T | U | W

_

A

B

C

D

E

F

G

H

I

K

L

M

N

O

P

Q

R

S

T

U

W

================================================ FILE: bdsim/bdedit/docs/index.html ================================================ Welcome to BdEdit’s documentation! — BdEdit documentation

Welcome to BdEdit’s documentation!

Indices and tables

================================================ FILE: bdsim/bdedit/docs/modules.html ================================================ bdedit — BdEdit documentation ================================================ FILE: bdsim/bdedit/docs/py-modindex.html ================================================ Python Module Index — BdEdit documentation ================================================ FILE: bdsim/bdedit/docs/search.html ================================================ Search — BdEdit documentation
  • »
  • Search

================================================ FILE: bdsim/bdedit/docs/searchindex.js ================================================ Search.setIndex({docnames:["bdedit","block_docs/block_discrete_blocks","block_docs/block_function_blocks","block_docs/block_hierarchy_blocks","block_docs/block_sink_blocks","block_docs/block_source_blocks","block_docs/block_transfer_blocks","block_docs/modules","index","modules"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["bdedit.rst","block_docs\\block_discrete_blocks.rst","block_docs\\block_function_blocks.rst","block_docs\\block_hierarchy_blocks.rst","block_docs\\block_sink_blocks.rst","block_docs\\block_source_blocks.rst","block_docs\\block_transfer_blocks.rst","block_docs\\modules.rst","index.rst","modules.rst"],objects:{"":{bdedit:[0,0,0,"-"]},"bdedit.Icons":{qCleanupResources:[0,1,1,""],qInitResources:[0,1,1,""]},"bdedit.block":{Block:[0,2,1,""],DiscreteBlock:[0,2,1,""],FunctionBlock:[0,2,1,""],INPORTBlock:[0,2,1,""],OUTPORTBlock:[0,2,1,""],SUBSYSTEMBlock:[0,2,1,""],SinkBlock:[0,2,1,""],SourceBlock:[0,2,1,""],TransferBlock:[0,2,1,""],TupleEncoder:[0,2,1,""],block:[0,1,1,""],blockname:[0,1,1,""]},"bdedit.block.Block":{__init__:[0,3,1,""],closeParamWindow:[0,3,1,""],deserialize:[0,3,1,""],getSocketPosition:[0,3,1,""],makeInputSockets:[0,3,1,""],makeOutputSockets:[0,3,1,""],pos:[0,4,1,""],remove:[0,3,1,""],removeSockets:[0,3,1,""],serialize:[0,3,1,""],setDefaultTitle:[0,3,1,""],setFocusOfBlocks:[0,3,1,""],setPos:[0,3,1,""],setTitle:[0,3,1,""],toggleParamWindow:[0,3,1,""],tuple_decoder:[0,3,1,""],updateConnectedEdges:[0,3,1,""],updateSocketPositions:[0,3,1,""],updateSocketSigns:[0,3,1,""]},"bdedit.block.DiscreteBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.FunctionBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.INPORTBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.OUTPORTBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.SUBSYSTEMBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.SinkBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.SourceBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.TransferBlock":{__init__:[0,3,1,""],numInputs:[0,3,1,""],numOutputs:[0,3,1,""]},"bdedit.block.TupleEncoder":{encode:[0,3,1,""]},"bdedit.block_graphics_block":{GraphicsBlock:[0,2,1,""],GraphicsSocketBlock:[0,2,1,""]},"bdedit.block_graphics_block.GraphicsBlock":{__init__:[0,3,1,""],boundingRect:[0,3,1,""],checkBlockHeight:[0,3,1,""],checkMode:[0,3,1,""],getTitle:[0,3,1,""],initTitle:[0,3,1,""],initUI:[0,3,1,""],mouseMoveEvent:[0,3,1,""],mousePressEvent:[0,3,1,""],paint:[0,3,1,""],setTitle:[0,3,1,""],titleLength:[0,3,1,""],updateMode:[0,3,1,""]},"bdedit.block_graphics_block.GraphicsSocketBlock":{__init__:[0,3,1,""],boundingRect:[0,3,1,""],initUI:[0,3,1,""],mouseMoveEvent:[0,3,1,""],mousePressEvent:[0,3,1,""],paint:[0,3,1,""]},"bdedit.block_graphics_socket":{GraphicsSocket:[0,2,1,""]},"bdedit.block_graphics_socket.GraphicsSocket":{__init__:[0,3,1,""],boundingRect:[0,3,1,""],getSignPath:[0,3,1,""],paint:[0,3,1,""],paintDivide:[0,3,1,""],paintMinus:[0,3,1,""],paintMultiply:[0,3,1,""],paintPlus:[0,3,1,""]},"bdedit.block_graphics_wire":{GraphicsWire:[0,2,1,""],GraphicsWireBezier:[0,2,1,""],GraphicsWireDirect:[0,2,1,""],GraphicsWireStep:[0,2,1,""]},"bdedit.block_graphics_wire.GraphicsWire":{__init__:[0,3,1,""],paint:[0,3,1,""],setDestination:[0,3,1,""],setDestinationOrientation:[0,3,1,""],setSource:[0,3,1,""],setSourceOrientation:[0,3,1,""],updateLineSegments:[0,3,1,""],updatePath:[0,3,1,""],updateWireCoordinates:[0,3,1,""]},"bdedit.block_graphics_wire.GraphicsWireBezier":{updatePath:[0,3,1,""]},"bdedit.block_graphics_wire.GraphicsWireDirect":{updatePath:[0,3,1,""]},"bdedit.block_graphics_wire.GraphicsWireStep":{updatePath:[0,3,1,""]},"bdedit.block_param_window":{ParamWindow:[0,2,1,""]},"bdedit.block_param_window.ParamWindow":{__init__:[0,3,1,""],buildParamWindow:[0,3,1,""],closeQMessageBox:[0,3,1,""],displayPopUpMessage:[0,3,1,""],getSafeValue:[0,3,1,""],initUI:[0,3,1,""],updateBlockParameters:[0,3,1,""]},"bdedit.block_socket":{Socket:[0,2,1,""]},"bdedit.block_socket.Socket":{__init__:[0,3,1,""],deserialize:[0,3,1,""],getSocketPosition:[0,3,1,""],hasEdge:[0,3,1,""],isInputSocket:[0,3,1,""],isOutputSocket:[0,3,1,""],removeAllWires:[0,3,1,""],removeSockets:[0,3,1,""],removeWire:[0,3,1,""],serialize:[0,3,1,""],setConnectedEdge:[0,3,1,""]},"bdedit.block_socket_block":{Connector:[0,2,1,""]},"bdedit.block_socket_block.Connector":{__init__:[0,3,1,""]},"bdedit.block_wire":{Wire:[0,2,1,""]},"bdedit.block_wire.Wire":{__init__:[0,3,1,""],checkIntersections:[0,3,1,""],deserialize:[0,3,1,""],end_socket:[0,4,1,""],remove:[0,3,1,""],remove_from_sockets:[0,3,1,""],serialize:[0,3,1,""],start_socket:[0,4,1,""],updatePositions:[0,3,1,""],wire_type:[0,4,1,""]},"bdedit.interface":{Interface:[0,2,1,""],importBlocks:[0,1,1,""]},"bdedit.interface.Interface":{__init__:[0,3,1,""],initUI:[0,3,1,""],loadFromFile:[0,3,1,""],on_click:[0,3,1,""],resizeEvent:[0,3,1,""],saveAsToFile:[0,3,1,""],saveToFile:[0,3,1,""],save_image:[0,3,1,""],toggleCanvasItems:[0,3,1,""],toggleDiscreteBlocks:[0,3,1,""],toggleFunctionBlocks:[0,3,1,""],toggleSinkBlocks:[0,3,1,""],toggleSourceBlocks:[0,3,1,""],toggleSubSystemBlocks:[0,3,1,""],toggleTransferBlocks:[0,3,1,""],updateColorMode:[0,3,1,""],updateSceneDimensions:[0,3,1,""]},"bdedit.interface_graphics_scene":{GraphicsScene:[0,2,1,""]},"bdedit.interface_graphics_scene.GraphicsScene":{__init__:[0,3,1,""],checkMode:[0,3,1,""],drawBackground:[0,3,1,""],drawForeground:[0,3,1,""],mouseMoveEvent:[0,3,1,""],setGrScene:[0,3,1,""],updateMode:[0,3,1,""]},"bdedit.interface_graphics_view":{GraphicsView:[0,2,1,""]},"bdedit.interface_graphics_view.GraphicsView":{__init__:[0,3,1,""],closeParamWindows:[0,3,1,""],deleteSelected:[0,3,1,""],dist_click_release:[0,3,1,""],edgeDragEnd:[0,3,1,""],edgeDragStart:[0,3,1,""],flipBlockSockets:[0,3,1,""],getItemAtClick:[0,3,1,""],initUI:[0,3,1,""],intersectionTest:[0,3,1,""],keyPressEvent:[0,3,1,""],leftMouseButtonPress:[0,3,1,""],leftMouseButtonRelease:[0,3,1,""],middleMouseButtonPress:[0,3,1,""],middleMouseButtonRelease:[0,3,1,""],mouseMoveEvent:[0,3,1,""],mousePressEvent:[0,3,1,""],mouseReleaseEvent:[0,3,1,""],rightMouseButtonPress:[0,3,1,""],rightMouseButtonRelease:[0,3,1,""],wheelEvent:[0,3,1,""]},"bdedit.interface_scene":{Scene:[0,2,1,""]},"bdedit.interface_scene.Scene":{__init__:[0,3,1,""],addBlock:[0,3,1,""],addWire:[0,3,1,""],checkForDuplicates:[0,3,1,""],clear:[0,3,1,""],deserialize:[0,3,1,""],displayMessage:[0,3,1,""],getSceneHeight:[0,3,1,""],getSceneWidth:[0,3,1,""],initUI:[0,3,1,""],loadFromFile:[0,3,1,""],removeBlock:[0,3,1,""],removeWire:[0,3,1,""],saveToFile:[0,3,1,""],serialize:[0,3,1,""],setSceneHeight:[0,3,1,""],setSceneWidth:[0,3,1,""],updateSceneDimensions:[0,3,1,""]},"bdedit.interface_serialize":{Serializable:[0,2,1,""]},"bdedit.interface_serialize.Serializable":{__init__:[0,3,1,""],deserialize:[0,3,1,""],serialize:[0,3,1,""]},bdedit:{"interface":[0,0,0,"-"],Icons:[0,0,0,"-"],block:[0,0,0,"-"],block_graphics_block:[0,0,0,"-"],block_graphics_socket:[0,0,0,"-"],block_graphics_wire:[0,0,0,"-"],block_param_window:[0,0,0,"-"],block_socket:[0,0,0,"-"],block_socket_block:[0,0,0,"-"],block_wire:[0,0,0,"-"],interface_graphics_scene:[0,0,0,"-"],interface_graphics_view:[0,0,0,"-"],interface_scene:[0,0,0,"-"],interface_serialize:[0,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","property","Python property"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:property"},terms:{"0":0,"1":0,"1000":0,"15721641":0,"2":0,"2160":0,"3":0,"3840":0,"4k":0,"5":0,"90":0,"boolean":0,"class":0,"default":0,"do":0,"enum":0,"final":0,"float":0,"function":0,"import":0,"int":0,"new":0,"return":0,"static":0,"true":0,"while":0,AND:0,And:0,As:0,For:0,If:0,It:0,No:0,Or:0,TO:0,That:0,The:0,Then:0,These:0,To:0,__class__:0,__init__:0,__name__:0,__tuple__:0,about:0,abov:0,access:0,accompani:0,act:0,action:0,activ:0,ad:0,adapt:0,add:0,addblock:0,addit:0,addition:0,addwir:0,affect:0,after:0,against:0,align:0,all:0,allow:0,allow_nan:0,along:0,alongsid:0,alreadi:0,also:0,altern:0,alwai:0,amongst:0,an:0,ani:0,anoth:0,anywher:0,apart:0,appear:0,append:0,appli:0,applic:0,appropri:0,ar:0,area:0,around:0,arrow:0,assign:0,associ:0,attempt:0,auto:0,automat:0,awai:0,back:0,background:0,backspac:0,bad:0,base:0,basic:0,bdsim:0,becaus:0,been:0,befor:0,begin:0,behav:0,behind:0,being:0,belong:0,below:0,bend:0,between:0,bezier:0,blob:0,block:9,block_class:0,block_discrete_block:7,block_function_block:7,block_graphics_block:9,block_graphics_socket:9,block_graphics_wir:9,block_hierarchy_block:7,block_param_window:9,block_sink_block:7,block_socket:9,block_socket_block:9,block_source_block:7,block_transfer_block:7,block_wir:9,blocknam:0,bodi:0,bool:0,border:0,both:0,boundingrect:0,box:0,browser:0,brush:0,build:0,buildparamwindow:0,built:0,button:0,calcul:0,call:0,can:0,canva:0,capit:0,caus:0,certain:0,chang:0,charact:0,check:0,check_circular:0,checkblockheight:0,checkfordupl:0,checkintersect:0,checkmod:0,child:0,choos:0,chosen:0,circl:0,cl:0,clear:0,click:0,close:0,closeparamwindow:0,closeqmessagebox:0,code:0,color:0,colour:0,com:0,combin:0,compat:0,complet:0,compon:0,comput:0,condit:0,connect:0,connector:0,construct:0,contain:0,content:9,control:0,convert:0,coordin:0,copi:0,could:0,creat:0,criteria:0,ctrl:0,cubic:0,current:0,cursor:0,curv:0,dark:0,data:0,decid:0,decod:0,decor:0,defin:0,degre:0,del:0,delet:0,deleteselect:0,depend:0,deselect:0,deseri:0,desir:0,desktop:0,destin:0,detect:0,determin:0,develop:0,diagram:0,dict:0,dictat:0,dictionari:0,differ:0,dimens:0,direct:0,directli:0,disabl:0,disappear:0,discret:0,discreteblock:0,displai:0,displaymessag:0,displaypopupmessag:0,dist_click_releas:0,distanc:0,divid:0,document:0,doe:0,doesn:0,done:0,down:0,drag:0,draggabl:0,draw:0,drawbackground:0,drawforeground:0,drawn:0,drop:0,duplic:0,e:0,each:0,edgedragend:0,edgedragstart:0,edit:0,either:0,elaps:0,element:0,empti:0,encod:0,end:0,end_socket:0,enforc:0,enough:0,ensur:0,ensure_ascii:0,enter:0,enumer:0,equal:0,error:0,es:0,essenti:0,even:0,evenli:0,event:0,ever:0,everi:0,exactli:0,exampl:0,exist:0,exit:0,expand:0,explan:0,extend:0,extract:0,f:0,fals:0,far:0,feedback:0,file:0,filenam:0,filepath:0,fill:0,find:0,first:0,fit:0,flag:0,flip:0,flipblocksocket:0,follow:0,foreground:0,format:0,found:0,freeli:0,from:0,front:0,functionblock:0,further:0,futur:0,g:0,gain:0,gener:0,get:0,getitematclick:0,getsafevalu:0,getsceneheight:0,getscenewidth:0,getsignpath:0,getsocketposit:0,gettitl:0,github:0,give:0,given:0,go:0,goe:0,good:0,grab:0,grai:0,grandchild:0,graphic:0,graphicsblock:0,graphicsocket:0,graphicsscen:0,graphicssocket:0,graphicssocketblock:0,graphicsview:0,graphicswir:0,graphicswirebezi:0,graphicswiredirect:0,graphicswirestep:0,graphicwir:0,grid:0,grscene:0,ha:0,hand:0,handl:0,happen:0,hasedg:0,hashmap:0,have:0,height:0,here:0,hide:0,hold:0,horizont:0,hous:0,how:0,howev:0,http:0,icon:9,idea:0,identif:0,imag:0,implement:0,importblock:0,inbuilt:0,includ:0,incompat:0,increment:0,indent:0,index:[0,8],individu:0,inf:0,inform:0,inherit:0,initi:0,inittitl:0,initui:0,inport:0,inportblock:0,input:0,inputsnum:0,inputvalu:0,insid:0,instanc:0,instead:0,intend:0,interact:0,interfac:9,interface_graphics_scen:9,interface_graphics_view:9,interface_scen:9,interface_seri:9,intern:0,intersect:0,intersectiontest:0,intuit:0,invalid:0,isinputsocket:0,isn:0,isoutputsocket:0,issu:0,item:0,its:0,itself:0,json:0,jsonencod:0,kei:0,keypressev:0,keyword:0,know:0,known:0,l:0,label:0,last:0,later:0,layer:0,layout:0,left:0,leftmousebuttonpress:0,leftmousebuttonreleas:0,length:0,librari:0,light:0,like:0,line:0,link:0,list:0,load:0,loadfromfil:0,local:0,locat:0,logic:0,look:0,loop:0,made:0,make:0,makeinputsocket:0,makeoutputsocket:0,manag:0,mani:0,manipul:0,map:0,mark:0,match:0,math:0,max:0,maximum:0,mean:0,meet:0,mention:0,menu:0,messag:0,messagetyp:0,met:0,method:0,middl:0,middlemousebuttonpress:0,middlemousebuttonreleas:0,mimick:0,min:0,mode:0,mode_wire_drag:0,modul:[7,8,9],more:0,most:0,mous:0,mousemoveev:0,mousepressev:0,mousereleaseev:0,movabl:0,move:0,movement:0,multi:0,multi_wir:0,multipl:0,must:0,n:0,name:0,neatli:0,necessari:0,need:0,neither:0,net:0,new_coordin:0,next:0,node:0,non:0,none:0,nonetyp:0,note:0,noth:0,notifi:0,now:0,number:0,numinput:0,numoutput:0,obj:0,object:0,occur:0,off:0,old:0,on_click:0,onc:0,one:0,onli:0,onto:0,open:0,option:0,orang:0,order:0,ordereddict:0,orient:0,other:0,otherwis:0,out:0,outlin:0,outport:0,outportblock:0,output:0,outputsnum:0,outsid:0,over:0,overlap:0,overrid:0,overtop:0,overwritten:0,own:0,packag:[8,9],page:8,paint:0,paintdivid:0,painter:0,paintminu:0,paintmultipli:0,paintplu:0,pair:0,param:0,paramet:0,paramwindow:0,parent:0,pass:0,past:0,path:0,pen:0,perhap:0,petercork:0,picture_nam:0,pinch:0,pixel:0,place:0,plain:0,pleas:0,po:0,point:0,pointer:0,pop:0,popul:0,posit:0,premul:0,press:0,prevent:0,previou:0,previous:0,prod:0,prompt:0,properti:0,propos:0,provid:0,purpos:0,py:0,pyqt5:0,qcleanupresourc:0,qfiledialog:0,qgraphicsitem:0,qgraphicspathitem:0,qgraphicsscen:0,qgraphicstextitem:0,qgraphicsview:0,qgridlayout:0,qinitresourc:0,qkeypressev:0,qmouseev:0,qmousemoveev:0,qmousepressev:0,qmousereleaseev:0,qpainter:0,qpainterpath:0,qrect:0,qrectf:0,qresizeev:0,qstyleoptiongraphicsitem:0,qtcore:0,qtwidget:0,qwheelev:0,qwidget:0,rang:0,re:0,reach:0,recogn:0,reconstruct:0,recreat:0,rect:0,rectangl:0,redirect:0,redraw:0,reduc:0,refer:0,referenc:0,reformat:0,regist:0,relat:0,releas:0,relev:0,remov:0,remove_from_socket:0,removeallwir:0,removeblock:0,removesocket:0,removewir:0,repeat:0,replac:0,repres:0,represent:0,requir:0,requiredopt:0,requiredtyp:0,resiz:0,resizeev:0,resolut:0,resourc:0,respect:0,respons:0,restrict:0,retriev:0,right:0,rightmousebuttonpress:0,rightmousebuttonreleas:0,round:0,rtype:0,run:0,s:0,safe:0,same:0,saniti:0,save:0,save_imag:0,saveastofil:0,savetofil:0,scene:0,scene_height:0,scene_width:0,screen:0,screen_height:0,screen_width:0,scroll:0,search:8,second:0,section:0,see:0,segment:0,select:0,self:0,send:0,sent:0,separ:0,serial:0,serializ:0,set:0,setconnectededg:0,setdefaulttitl:0,setdestin:0,setdestinationorient:0,setfocusofblock:0,setgrscen:0,setpo:0,setsceneheight:0,setscenewidth:0,setsourc:0,setsourceorient:0,settitl:0,shape:0,shift:0,shortest:0,should:0,shown:0,side:0,sign:0,signifi:0,simpler:0,simpli:0,sinc:0,sine:0,singl:0,sink:0,sinkblock:0,size:0,skipkei:0,slice:0,slight:0,smaller:0,smoother:0,snap:0,snapshot:0,so:0,socket:0,socket_typ:0,sockettyp:0,some:0,sort_kei:0,sound:0,sourc:0,sourceblock:0,space:0,specif:0,specifi:0,squar:0,stackoverflow:0,start:0,start_socket:0,step:0,still:0,storag:0,store:0,str:0,straight:0,string:0,strip:0,structur:0,style:0,subclass:0,submodul:9,subsequ:0,subsystem:0,subsystemblock:0,success:0,successfulli:0,sum:0,support:0,t:0,take:0,technic:0,term:0,text:0,than:0,thei:0,them:0,thi:0,thicker:0,thing:0,think:0,those:0,three:0,through:0,time:0,titl:0,titlelength:0,toggl:0,togglecanvasitem:0,togglediscreteblock:0,togglefunctionblock:0,toggleparamwindow:0,togglesinkblock:0,togglesourceblock:0,togglesubsystemblock:0,toggletransferblock:0,too:0,toolbar:0,top:0,transfer:0,transferblock:0,triangl:0,trigger:0,tupl:0,tuple_decod:0,tupleencod:0,turn:0,two:0,type:0,un:0,under:0,underneath:0,underscor:0,understood:0,union:0,uniqu:0,unnam:0,unsuccess:0,until:0,up:0,updat:0,updateblockparamet:0,updatecolormod:0,updateconnectededg:0,updatelineseg:0,updatemod:0,updatepath:0,updateposit:0,updatescenedimens:0,updatesocketposit:0,updatesocketsign:0,updatewirecoordin:0,upon:0,us:0,usabl:0,user:0,valu:0,vari:0,variabl:0,vertic:0,visibl:0,visual:0,wa:0,wai:0,wave:0,we:0,well:0,were:0,what:0,wheel:0,wheelev:0,when:0,whenev:0,where:0,whether:0,which:0,widget:0,width:0,window:0,wire:0,wire_typ:0,within:0,without:0,word:0,work:0,would:0,wrap:0,write:0,written:0,x:0,y:0,zoom:0},titles:["bdedit package","block_discrete_blocks module","block_function_blocks module","block_hierarchy_blocks module","block_sink_blocks module","block_source_blocks module","block_transfer_blocks module","Block_Classes","Welcome to BdEdit\u2019s documentation!","bdedit"],titleterms:{bdedit:[0,8,9],block:0,block_class:7,block_discrete_block:1,block_function_block:2,block_graphics_block:0,block_graphics_socket:0,block_graphics_wir:0,block_hierarchy_block:3,block_param_window:0,block_sink_block:4,block_socket:0,block_socket_block:0,block_source_block:5,block_transfer_block:6,block_wir:0,content:[0,8],document:8,icon:0,indic:8,interfac:0,interface_graphics_scen:0,interface_graphics_view:0,interface_scen:0,interface_seri:0,modul:[0,1,2,3,4,5,6],packag:0,s:8,submodul:0,tabl:8,welcom:8}}) ================================================ FILE: bdsim/bdedit/docstring.md ================================================ # Parameter types and defaults The docstrings of bdsim blocks are parsed to determine the types and default values of all parameters. This means that they need to conform to some simple guidelines. `bdedit` parses the docstrings of all discovered blocks at startup time, and will complain at runtime if it cannot parse a docstring. Input values are checked when the `Update parameters` button is clicked, and type or value violations are notified by a popup. ## Default values For assigning the default value for a parameter, expected format in param definition ``` :param myparam: ..., defaults to X ``` * Text up until `defaults to` is ignored, the rest is either a value that can be evaluated or a string * If the value cannot be parsed it defaults to `None` ### List * If the value that follows `defaults to` is in the form of a list, there must be no spaces between values in the list, e.g. [0,0,0] For example: ``` frequency, defaults to 1 the constant, defaults to None interpolation method, defaults to 'linear' denominator coefficients, defaults to [1,1] axis labels (xlabel, ylabel), defaults to ["X","Y"] Initial phase of signal in the range [0,1], defaults to 0 duty cycle for square wave in range [0,1], defaults to 0.5 extra keyword arguments passed to the function, defaults to {} pass in a reference to a dictionary instance, defaults to False extra positional arguments passed to the function, defaults to [] signs associated with input ports, accepted characters: + or -, defaults to '++' ``` ### One of a set For parameters where input can be one of certain keywords, expected format in param definition: ``` :param myparam: ... one of: 'option1', 'option2' [default], 'option3' ``` * Text up until `one of:` is ignored * After `one of:` every option is expected to be given as as string using **single quotes** * Options are separated by commas * The default value has `[default]` after the option string, but before the comma * There should be no more text after last option For example: ``` type of waveform to generate, one of: 'sine', 'square' [default], 'triangle' frequency unit, one of: 'rad/s', 'Hz' [default] ``` ### Range limit For parameters where input must be within a range, expected format in param definition: ``` :param myparam: ... range [min, max] ``` * Text up to `range` is ignored * The string after `range` bdedit is evaluated and the result is a list instance of length of 2 which is taken as the range, else an error message. For example: ``` duty cycle for square wave in range [0,1], defaults to 0.5 Initial phase of signal in the range [0,1], defaults to 0 ``` ### Character subset For parameters where input is a string that comprises a subset of characters: ``` :param myparam: ... accepted characters: X or Y or Z ``` * Text up to `accepted characters:` is ignored * Text after after `accepted characters:` is assumed to be a white-space separated list and the even elements are taken. The odd elements are the keyword `or`. * Values must be **not alphanumeric**, else ignored. * bdedit knows it has reached the end of the character options when there is no longer an `or` after a given value For example: ``` signs associated with input ports, accepted characters: + or -, defaults to '++' operations associated with input ports, accepted characters: * or /, defaults to '**' ``` ## Parameter types If for whatever reason a parameter type is not detected, bdedit will assign `str` as the default type. ### Array like If the keyword `array_like` is used in the type definition of a parameter. #### No size restriction ``` :type myparam: ... or array_like, ... :type myparam: ... array_like ..., ... :type myparam: ... array_like, ... ``` * bdedit uses regex to search for `array_like[^(]` (not size restricted) For example: ``` float or array_like, optional array_like, shape (N,) optional array_like, optional array_like ``` #### With size restriction ``` :type myparam: ... or array_like(N), ... :type myparam: ... array_like(N) ..., ... :type myparam: ... array_like(N), ... ``` * bdedit uses regex to search for `array_like([0-9]+)` (size restricted) * if a match, or multiple matches for `array_like` are found, each will be checked for size restrictions ???? * if size restrictions are found: parameter restricted to types: list or dict * else if no size restrictions are found: parameter restricted to types: list, dict, int or float For example: ``` str or array_like(2) or array_like(4) str or array_like(2), optional str or array_like(2) array_like(2) ``` ### sequence or string * If keyword `string` (instead of `str`) is found, the parameter will be granted types: list and str * If keyword `sequence` found, the parameter will be granted types: list For example: ``` callable or sequence of callables, optional sequence of strings bool or sequence sequence of time, value pairs ``` ### Python native types If any of the keywords `str`, `int`, `float`, `list`, `dict` are used are used in type definition of a parameter, bdedit will simply try to evaluate the given parameter value and test the resulting type. If keywords `callable`, `any` are used in type definition of a parameter, bdedit will consider the parameter of type `str`. The keyword `tuple` will be considered to be type `list` (JSON cannot distinguish `tuple` and `list`) ================================================ FILE: bdsim/bdedit/floating_label.py ================================================ # Library imports import json import copy from collections import OrderedDict # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # BdEdit imports from bdsim.bdedit.interface_serialize import Serializable from bdsim.bdedit.floating_label_graphics import GraphicsLabel DEBUG = False class Floating_Label(Serializable): def __init__(self, scene, window, mainwindow, label_text="text", pos=(0, 0)): super().__init__() self.scene = scene self.window = window self.interfaceManager = mainwindow self.label_text = label_text self.position = pos self.width = 0 self.height = 0 self.content = ContentWidget(self) self.grContent = GraphicsLabel(self) self.scene.addLabel(self) self.scene.grScene.addItem(self.grContent) self.scene.has_been_modified = True def setPos(self, x, y): self.grContent.setPos(x, y) def setFocusOfFloatingText(self): """ This method sends all ``FloatingLabel`` instances within the ``Scene`` to back and then sends the currently selected ``FloatingLabel`` instance to front. """ # Iterates through each floating label within the relevant list stored in # the Scene Class and sets the graphical component of each label to a zValue of -4. for floatinglabel in self.scene.floating_labels: floatinglabel.grContent.setZValue(-4) # Then sets the graphical component of the currently selected label to a # zValue of -3, which makes it display above all other labels on screen. self.grContent.setZValue(-3) # Todo - update comments to match floating label, not block def remove(self): # For each socket associated with this block, remove the connected wires if DEBUG: print("> Removing Floating Label", self) # Remove the graphical representation of this block from the scene # This will also remove the associated graphical representation of the # blocks' sockets. if DEBUG: print(" - removing grContent") self.scene.grScene.removeItem(self.grContent) self.grContent = None # Finally, call the removeBlock method from within the Scene, which # removes this block from the list of blocks stored in the Scene. if DEBUG: print(" - removing Floating Label from the scene") self.scene.removeLabel(self) if DEBUG: print(" - everything was done.") # ----------------------------------------------------------------------------- def serialize(self): font_info = self.content.text_edit.toHtml() fill_color = self.content.backgroundColor.getRgb() return OrderedDict( [ ("id", self.id), ("text", self.label_text), ("pos_x", self.grContent.scenePos().x()), ("pos_y", self.grContent.scenePos().y()), ("width", self.width), ("height", self.height), ("fill_color", fill_color), ("styling", font_info), ] ) # ----------------------------------------------------------------------------- def deserialize(self, data, hashmap={}): # The id of this floating label is set to whatever was stored as its id in the JSON file. self.id = data["id"] self.label_text = data["text"] self.width = data["width"] self.height = data["height"] # The position of the Floating Labels within the Scene, are set accordingly. self.setPos(data["pos_x"], data["pos_y"]) self.content.text_edit.document().setHtml(data["styling"]) try: if data["fill_color"]: # If fill color exists in json, convert rgba to QColor color = QColor( data["fill_color"][0], data["fill_color"][1], data["fill_color"][2], data["fill_color"][3], ) # Grab copy of text edit's color palette, to change its background palette = self.content.text_edit.viewport().palette() # Update the copy of background color self.content.backgroundColor = color palette.setColor( self.content.text_edit.viewport().backgroundRole(), color ) self.content.text_edit.viewport().setPalette(palette) # Update the text highlighting color color_for_highlighting = copy.copy(self.content.backgroundColor) color_for_highlighting.setAlpha(0) self.content.setTextHighlighting(color_for_highlighting) # Update color of rounded border drawn around text edit self.grContent.outline_brush = QBrush(self.content.backgroundColor) except KeyError: # Fill color is a new feature, so some models might not have fill color saved in the json # if this is the case, draw the labels with the default white color pass self.content.updateShape() return True class ContentWidget(QWidget): def __init__(self, label): super().__init__() self.floating_label = label self.text_edit = QTextEdit(self) self.defaultFont = "Arial" self.defaultFontSize = 14 self.defaultWeight = QFont.Normal self.defaultItalics = False self.defaultUnderline = False # self.defaultColor = QColor("#000000") self.defaultColor = QColor("#0000ff") self.defaultAlignment = Qt.AlignLeft self.defaultBackgroundColor = QColor(255, 255, 255) self.currentFontSize = copy.copy(self.defaultFontSize) self.backgroundColor = copy.copy(self.defaultBackgroundColor) self.padding = 8 self.setup() self.updateShape() self.wasEdited = False self.initUI() def setup(self): self.text_edit.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.text_edit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.text_edit.setFrameStyle(QFrame.NoFrame) self.text_edit.setTextInteractionFlags(Qt.NoTextInteraction) self.text_edit.setAutoFillBackground(True) self.setDefaultFormatting() self.text_edit.setPlainText(self.floating_label.label_text) self.text_edit.textChanged.connect(self.updateText) def initUI(self): self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.setLayout(self.layout) self.layout.addWidget(self.text_edit) def setTextHighlighting(self, highlight): p = self.text_edit.palette() p.setColor(QPalette.Highlight, highlight) self.text_edit.setPalette(p) def updateText(self): if self.text_edit.document().isModified(): current_text = self.text_edit.toPlainText() # If changes have been made to the labels current text, update it internally if current_text != self.floating_label.label_text: self.floating_label.label_text = current_text self.updateShape() self.wasEdited = True def updateShape(self): # Find the space occupied by text within the floating label font = self.text_edit.currentFont() fontmetrics = QFontMetrics(font) textSize = fontmetrics.size(0, self.text_edit.toPlainText()) w = textSize.width() + self.padding h = textSize.height() + self.padding # Resize the interactable area where the text is displayed self.text_edit.setMinimumSize(w, h) self.text_edit.setMaximumSize(w, h) self.text_edit.resize(w, h) # Resize max width of widget container for the text area self.setMaximumSize(w, h) # Update the dimensions of the floating label widget, so that the outline around it could be properly drawn self.floating_label.width = w self.floating_label.height = h def setDefaultFormatting(self): self.text_edit.setFont(QFont(self.defaultFont, self.defaultFontSize)) self.text_edit.setFontPointSize(self.defaultFontSize) self.text_edit.setFontWeight(self.defaultWeight) self.text_edit.setFontItalic(self.defaultItalics) self.text_edit.setFontUnderline(self.defaultUnderline) self.text_edit.setAlignment(self.defaultAlignment) self.text_edit.setTextColor(self.defaultColor) ================================================ FILE: bdsim/bdedit/floating_label_graphics.py ================================================ import copy # PyQt5 imports from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * class GraphicsLabel(QGraphicsItem): def __init__(self, label, parent=None): super().__init__(parent) self.floating_label = label self.content = self.floating_label.content # Label related outline settings self.edge_size = 6.0 self._selected_line_thickness = ( 5.0 # Thickness of the label outline on selection ) self._pen_selected = QPen( QColorConstants.Svg.orange, self._selected_line_thickness ) self.outline_brush = QBrush(QColorConstants.Svg.white) self.initUI() self.wasMoved = False self.lastPos = self.pos() def initUI(self): self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable) self.setAcceptHoverEvents(True) self.grText = QGraphicsProxyWidget(self) self.content.setGeometry( 0, 0, self.floating_label.width, self.floating_label.height ) self.grText.setWidget(self.content) self.setZValue(-3) def boundingRect(self): return QRectF( -self.edge_size, -self.edge_size, 2 * self.edge_size + self.floating_label.width, 2 * self.edge_size + self.floating_label.height, ).normalized() def paint(self, painter, style, widget=None): # Draw the outline of the box and fill in the background whitespace path = QPainterPath() path.addRoundedRect(self.boundingRect(), self.edge_size, self.edge_size) painter.setPen(Qt.NoPen if not self.isSelected() else self._pen_selected) painter.setBrush(QColor("#F0F0F0")) painter.drawPath(path.simplified()) painter.setBrush(self.outline_brush) painter.drawPath(path.simplified()) def setLabelUnfocus(self): self.floating_label.setFocusOfFloatingText() color_for_highlighting = copy.copy(self.floating_label.content.backgroundColor) color_for_highlighting.setAlpha(0) self.floating_label.content.setTextHighlighting(color_for_highlighting) self.floating_label.content.text_edit.setTextInteractionFlags( Qt.NoTextInteraction ) # If floating label has been edited, update the variable within the model, to then update the # title of the model, to indicate that there is unsaved progress if self.content.wasEdited: self.content.wasEdited = False self.floating_label.scene.has_been_modified = True self.floating_label.scene.history.storeHistory("Floating label edited") def setLabelFocus(self): self.floating_label.content.setTextHighlighting(QColor(225, 225, 225, 127)) self.floating_label.content.text_edit.setTextInteractionFlags( Qt.TextEditorInteraction ) def setLabelSizeBox(self): self.floating_label.interfaceManager.updateToolbarValues() def hoverEnterEvent(self, event): """ When a ``FloatingLabel`` is hovered over with the cursor, this method will display a tooltip with a description of how to change its color. :param event: mouse hover detected over floating label :type event: QGraphicsSceneHoverEvent """ self.setToolTip("Right click to change background color.") def mousePressEvent(self, event): def updateColor(chosen_color): if chosen_color.isValid(): # Grab copy of text edit's color palette, to change its background palette = self.floating_label.content.text_edit.viewport().palette() # Update the copy of background color self.floating_label.content.backgroundColor = chosen_color palette.setColor( self.floating_label.content.text_edit.viewport().backgroundRole(), chosen_color, ) self.floating_label.content.text_edit.viewport().setPalette(palette) # Update the text highlighting color color_for_highlighting = copy.copy( self.floating_label.content.backgroundColor ) color_for_highlighting.setAlpha(0) self.floating_label.content.setTextHighlighting(color_for_highlighting) # Update color of rounded border drawn around text edit self.outline_brush = QBrush(self.floating_label.content.backgroundColor) self.floating_label.scene.has_been_modified = True self.floating_label.scene.history.storeHistory( "Grouping box color changed" ) super().mousePressEvent(event) self.setLabelUnfocus() self.setLabelSizeBox() # If the right mouse button is pressed, bring up a QColorDialog if event.button() == Qt.RightButton: # Make a backup of the original background color in case user cancels color selection # current_palette = self.floating_label.content.text_edit.viewport().palette() current_color = copy.copy(self.floating_label.content.backgroundColor) # Open a color dialog window, and when the chosen color changes, call func updateColor # to update the background color of the floating label colorDialog = QColorDialog() colorDialog.setOption(QColorDialog.ShowAlphaChannel, on=True) colorDialog.currentColorChanged.connect( lambda checked: updateColor(colorDialog.currentColor()) ) # If user selects okay, this finalizes the color selection if colorDialog.exec_() == QDialog.Accepted: updateColor(colorDialog.selectedColor()) # Otherwise, if they exit out of the color picker, the original color will be reverted to. else: updateColor(current_color) def mouseDoubleClickEvent(self, event): super().mouseDoubleClickEvent(event) self.setLabelFocus() def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) # If floating label has been moved, update the variable within the model, to then update the # title of the model, to indicate that there is unsaved progress if self.wasMoved: self.wasMoved = False self.floating_label.scene.has_been_modified = True self.floating_label.scene.history.storeHistory("Floating label moved") ================================================ FILE: bdsim/bdedit/grouping_box.py ================================================ # Library imports import json import copy from collections import OrderedDict # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # BdEdit imports from bdsim.bdedit.interface_serialize import Serializable from bdsim.bdedit.grouping_box_graphics import GraphicsGBox DEBUG = False class Grouping_Box(Serializable): def __init__( self, scene, window, width=500, height=300, bg_color=(146, 187, 255), pos=(-200, -100), ): super().__init__() self.scene = scene self.window = window self.position = pos self.width = width self.height = height self.background_color = QColor(bg_color[0], bg_color[1], bg_color[2], 127) self.border_color = QColor(0, 0, 0, 255) self.grGBox = GraphicsGBox(self, 0, 0, self.width, self.height) self.grGBox.setPos(self.position[0], self.position[1]) self.scene.addGBox(self) self.scene.grScene.addItem(self.grGBox) self.scene.has_been_modified = True def setPos(self, x, y): self.grGBox.setPos(x, y) def setFocusOfGroupingBox(self): """ This method sends all ``Grouping Box`` instances within the ``Scene`` to back and then sends the currently selected ``Grouping Box`` instance to front. """ # Iterates through each Grouping Box within grouping_boxes list stored in \ # the Scene Class and sets the graphical component of each gbox to a zValue of -11. for groupBox in self.scene.grouping_boxes: groupBox.grGBox.setZValue(-11) # Then sets the graphical component of the currently selected gbox to a # zValue of -10, which makes it display above all other gboxes on screen, but behind # all other items drawn in the canvas. self.grGBox.setZValue(-10) # Todo - update comments to match floating label, not block def remove(self): if DEBUG: print("> Removing Grouping Box", self) # Remove the graphical representation of this grouping box from the scene. if DEBUG: print(" - removing grGBox") self.scene.grScene.removeItem(self.grGBox) self.grGBox = None # Finally, call the removeGBox method from within the Scene, which # removes this GBox from the list of grouping boxes stored in the Scene. if DEBUG: print(" - removing Grouping Box from the scene") self.scene.removeGBox(self) if DEBUG: print(" - everything was done.") # ----------------------------------------------------------------------------- def serialize(self): actual_pos = self.grGBox.mapToScene(self.grGBox.rect()) return OrderedDict( [ ("id", self.id), ("pos_x", actual_pos.boundingRect().x()), ("pos_y", actual_pos.boundingRect().y()), ("width", self.grGBox.rect().width()), ("height", self.grGBox.rect().height()), ("color", self.grGBox.bg_color.getRgb()[0:3]), ] ) # ----------------------------------------------------------------------------- def deserialize(self, data, hashmap={}): # The id of this Grouping Box is set to whatever was stored as its id in the JSON file. self.id = data["id"] return True pass ================================================ FILE: bdsim/bdedit/grouping_box_graphics.py ================================================ # PyQt5 imports import copy from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # Majority of this code has been adapted with heavy inspiration from: https://stackoverflow.com/a/34442054 class GraphicsGBox(QGraphicsRectItem): handleTopLeft = 1 handleTopMiddle = 2 handleTopRight = 3 handleMiddleLeft = 4 handleMiddleRight = 5 handleBottomLeft = 6 handleBottomMiddle = 7 handleBottomRight = 8 handleSize = +8.0 handleSpace = -4.0 handleCursors = { handleTopLeft: Qt.SizeFDiagCursor, handleTopMiddle: Qt.SizeVerCursor, handleTopRight: Qt.SizeBDiagCursor, handleMiddleLeft: Qt.SizeHorCursor, handleMiddleRight: Qt.SizeHorCursor, handleBottomLeft: Qt.SizeBDiagCursor, handleBottomMiddle: Qt.SizeVerCursor, handleBottomRight: Qt.SizeFDiagCursor, } # ----------------------------------------------------------------------------- def __init__(self, gbox, *args): super().__init__(*args) # Handles dictionary and variables to manage cursors self.handles = {} self.handleSelected = None self.mousePressPos = None self.mousePressRect = None # Inherit properties from parent grouping box self.grouping_box = gbox self.width = self.grouping_box.width self.height = self.grouping_box.height # Pen thickness and block-related spacings are defined self._line_thickness = 3.0 # Thickness of the block outline by default self._selected_line_thickness = ( 5.0 # Thickness of the block outline on selection ) # Colours for pens are defined self._pen_selected = QPen( QColorConstants.Svg.orange, self._selected_line_thickness, Qt.SolidLine ) self.bg_color = self.grouping_box.background_color self.br_color = self.grouping_box.border_color # Internal variable for catching fatal errors, and allowing user to save work before crashing self.FATAL_ERROR = False # Method called to further initialize necessary block settings self.initUI() self.wasMoved = False self.lastPos = self.pos() # ----------------------------------------------------------------------------- def initUI(self): """ This method sets flags to allow for this Block to be movable and selectable. """ self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setFlag(QGraphicsItem.ItemIsFocusable) self.setAcceptHoverEvents(True) self.updateHandlesPos() self.setZValue(-11) def handleAt(self, point): """ Returns the resize handle below the given point. """ for ( k, v, ) in self.handles.items(): if v.contains(point): return k return None def hoverMoveEvent(self, moveEvent): """ Executed when the mouse moves over the shape (NOT PRESSED). """ if self.isSelected(): handle = self.handleAt(moveEvent.pos()) cursor = Qt.ArrowCursor if handle is None else self.handleCursors[handle] self.setCursor(cursor) super().hoverMoveEvent(moveEvent) def hoverLeaveEvent(self, moveEvent): """ Executed when the mouse leaves the shape (NOT PRESSED). """ self.setCursor(Qt.ArrowCursor) super().hoverLeaveEvent(moveEvent) def mousePressEvent(self, mouseEvent): """ Executed when the mouse is pressed on the item. """ def updateColor(chosen_color): if chosen_color.isValid(): # Set alpha value of chosen color, to half transparency chosen_color.setAlpha(127) self.bg_color = chosen_color self.grouping_box.scene.has_been_modified = True self.grouping_box.scene.history.storeHistory( "Grouping box color changed" ) # Selected grouping box will be brought into focus, by sending others 1 layer back self.grouping_box.setFocusOfGroupingBox() # If a grouping box edge/corner is selected to increase its size, handle the logic for this self.handleSelected = self.handleAt(mouseEvent.pos()) if self.handleSelected: self.mousePressPos = mouseEvent.pos() self.mousePressRect = self.boundingRect() # If the right mouse button is pressed, bring up a QColorDialog if mouseEvent.button() == Qt.RightButton: # Make a backup of the original background color incase user cancel's color selection current_bg_color = copy.copy(self.bg_color) # Open a color dialog window, and when the chosen color changes, call func updateColor # to update the background color of the grouping box colorDialog = QColorDialog() colorDialog.currentColorChanged.connect( lambda checked: updateColor(colorDialog.currentColor()) ) # If user selects okay, this finalizes the color selection if colorDialog.exec_() == QDialog.Accepted: updateColor(colorDialog.selectedColor()) # Otherwise, if they exit out of the color picker, the original color will be reverted to. else: updateColor(current_bg_color) super().mousePressEvent(mouseEvent) def mouseMoveEvent(self, mouseEvent): """ Executed when the mouse is being moved over the item while being pressed. """ if self.handleSelected is not None: self.interactiveResize(mouseEvent.pos()) self.wasMoved = True else: super().mouseMoveEvent(mouseEvent) def mouseReleaseEvent(self, mouseEvent): """ Executed when the mouse is released from the item. """ super().mouseReleaseEvent(mouseEvent) self.handleSelected = None self.mousePressPos = None self.mousePressRect = None self.update() # If grouping box has been moved, update the variable within the model, to then update the # title of the model, to indicate that there is unsaved progress if self.wasMoved: self.wasMoved = False self.grouping_box.scene.has_been_modified = True self.grouping_box.scene.history.storeHistory( "Grouping box moved or resized" ) def boundingRect(self): """ Returns the bounding rect of the shape (including the resize handles). """ o = self.handleSize + self.handleSpace return self.rect().adjusted(-o, -o, o, o) def updateHandlesPos(self): """ Update current resize handles according to the shape size and position. """ s = self.handleSize b = self.boundingRect() self.handles[self.handleTopLeft] = QRectF(b.left(), b.top(), s, s) self.handles[self.handleTopMiddle] = QRectF( b.center().x() - s / 2, b.top(), s, s ) self.handles[self.handleTopRight] = QRectF(b.right() - s, b.top(), s, s) self.handles[self.handleMiddleLeft] = QRectF( b.left(), b.center().y() - s / 2, s, s ) self.handles[self.handleMiddleRight] = QRectF( b.right() - s, b.center().y() - s / 2, s, s ) self.handles[self.handleBottomLeft] = QRectF(b.left(), b.bottom() - s, s, s) self.handles[self.handleBottomMiddle] = QRectF( b.center().x() - s / 2, b.bottom() - s, s, s ) self.handles[self.handleBottomRight] = QRectF( b.right() - s, b.bottom() - s, s, s ) def interactiveResize(self, mousePos): """ Perform shape interactive resize. """ offset = self.handleSize + self.handleSpace boundingRect = self.boundingRect() rect = self.rect() diff = QPointF(0, 0) # Quantize resizing of grouping box spacing = 20 x_diff = mousePos.x() - self.mousePressPos.x() y_diff = mousePos.y() - self.mousePressPos.y() x = round(x_diff / spacing) * spacing y = round(y_diff / spacing) * spacing self.prepareGeometryChange() if self.handleSelected == self.handleTopLeft: fromX = self.mousePressRect.left() fromY = self.mousePressRect.top() toX = fromX + x toY = fromY + y diff.setX(toX - fromX) diff.setY(toY - fromY) boundingRect.setLeft(toX) boundingRect.setTop(toY) rect.setLeft(boundingRect.left() + offset) rect.setTop(boundingRect.top() + offset) elif self.handleSelected == self.handleTopMiddle: fromY = self.mousePressRect.top() toY = fromY + y diff.setY(toY - fromY) boundingRect.setTop(toY) rect.setTop(boundingRect.top() + offset) elif self.handleSelected == self.handleTopRight: fromX = self.mousePressRect.right() fromY = self.mousePressRect.top() toX = fromX + x toY = fromY + y diff.setX(toX - fromX) diff.setY(toY - fromY) boundingRect.setRight(toX) boundingRect.setTop(toY) rect.setRight(boundingRect.right() - offset) rect.setTop(boundingRect.top() + offset) elif self.handleSelected == self.handleMiddleLeft: fromX = self.mousePressRect.left() toX = fromX + x diff.setX(toX - fromX) boundingRect.setLeft(toX) rect.setLeft(boundingRect.left() + offset) elif self.handleSelected == self.handleMiddleRight: fromX = self.mousePressRect.right() toX = fromX + x diff.setX(toX - fromX) boundingRect.setRight(toX) rect.setRight(boundingRect.right() - offset) elif self.handleSelected == self.handleBottomLeft: fromX = self.mousePressRect.left() fromY = self.mousePressRect.bottom() toX = fromX + x toY = fromY + y diff.setX(toX - fromX) diff.setY(toY - fromY) boundingRect.setLeft(toX) boundingRect.setBottom(toY) rect.setLeft(boundingRect.left() + offset) rect.setBottom(boundingRect.bottom() - offset) elif self.handleSelected == self.handleBottomMiddle: fromY = self.mousePressRect.bottom() toY = fromY + y diff.setY(toY - fromY) boundingRect.setBottom(toY) rect.setBottom(boundingRect.bottom() - offset) elif self.handleSelected == self.handleBottomRight: fromX = self.mousePressRect.right() fromY = self.mousePressRect.bottom() toX = fromX + x toY = fromY + y diff.setX(toX - fromX) diff.setY(toY - fromY) boundingRect.setRight(toX) boundingRect.setBottom(toY) rect.setRight(boundingRect.right() - offset) rect.setBottom(boundingRect.bottom() - offset) # Finally, check if rectangle has been dragged inside out if rect.width() < spacing: if self.handleSelected in [ self.handleTopLeft, self.handleMiddleLeft, self.handleBottomLeft, ]: rect.setLeft(rect.right() - spacing) else: rect.setRight(rect.left() + spacing) if rect.height() < spacing: if self.handleSelected in [ self.handleTopLeft, self.handleTopMiddle, self.handleTopRight, ]: rect.setTop(rect.bottom() - spacing) else: rect.setBottom(rect.top() + spacing) self.setRect(rect) self.updateHandlesPos() def hoverEnterEvent(self, event): """ When a ``GraphicsGBox`` is hovered over with the cursor, this method will display a tooltip with a description of how to change its color. :param event: mouse hover detected over grouping box :type event: QGraphicsSceneHoverEvent """ self.setToolTip("Right click to change background color.") def shape(self): """ Returns the shape of this item as a QPainterPath in local coordinates. """ path = QPainterPath() path.addRect(self.rect()) if self.isSelected(): for shape in self.handles.values(): path.addEllipse(shape) return path def paint(self, painter, option, widget=None): """ Paint the node in the graphic view. """ painter.setBrush(QBrush(self.bg_color)) painter.setPen( QPen(self.br_color, 1.0, Qt.SolidLine) if not self.isSelected() else self._pen_selected ) # black default outline, thicker orange when selected painter.drawRect(self.rect()) if self.isSelected(): painter.setRenderHint(QPainter.Antialiasing) # painter.setBrush(QBrush(QColor("#FFFFA637"))) painter.setBrush(QBrush(QColorConstants.Svg.orange)) painter.setPen( QPen( QColorConstants.Svg.black, 1.0, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin, ) ) for handle, rect in self.handles.items(): painter.drawEllipse(rect) ================================================ FILE: bdsim/bdedit/interface.py ================================================ # Library imports import os import fnmatch # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # BdEdit imports from bdsim.bdedit.block import * from bdsim.bdedit.Icons import * from bdsim.bdedit.grouping_box import * from bdsim.bdedit.block_wire import Wire from bdsim.bdedit.block_importer import * from bdsim.bdedit.floating_label import * from bdsim.bdedit.block_main_block import * from bdsim.bdedit.interface_scene import Scene from bdsim.bdedit.block_connector_block import * from bdsim.bdedit.interface_graphics_view import GraphicsView # ============================================================================= # # Defining and setting global variables and methods # # ============================================================================= # Variable for enabling/disabling debug comments DEBUG = False # ============================================================================= # # Defining the Interface Class, which houses all the application appearance # logic, and connects all other classes together. # # ============================================================================= class Interface(QWidget): """ The ``Interface`` Class extends the ``QWidget`` Class from PyQt5, and houses the necessary methods for controlling how the application appears. It also connects the other Classes to allow for Block Diagrams to be constructed. This class includes information about the: - toolbar, its buttons and their connections - library Browser menu, its buttons and their connections - scene, its appearance (Background and Foreground) and its items (Blocks, Sockets, Wires) - application layout, in terms of where the toolbar, library Browser, scene and parameter window is displayed. Note, the toolbar, library Browser, scene and parameter window will be referred to as application components. """ # ----------------------------------------------------------------------------- def __init__(self, resolution, debug=False, parent=None): """ This method initializes an instance of the ``Interface``. :param resolution: the desktop screen resolution of the user :type resolution: PyQt5.QtCore.QRect(0, 0, screen_width, screen_height), required :param parent: the parent widget this interface belongs to (should be None) :type parent: None, optional """ super().__init__(parent) # The toolbar and library browser widgets are initialized # self.toolBar = QWidget() self.libraryBrowser = QWidget() # LibraryBrowserBox is wrapped by LibraryBrowser, as this allows the items within # the libraryBrowser to be scrollable self.libraryBrowserBox = QGroupBox() # # Set file extension for screenshots. PDF (default) PNG if specified from command line args # self.screenshot_extension_format = 'pdf' # The Scene interface is called to be initialized self.initUI(resolution, debug, parent) # ----------------------------------------------------------------------------- def initUI(self, resolution, debug, main_window): """ This method is responsible for controlling the size of the application window. It is also responsible for each application component, in terms of: - setting its dimensions ; - setting its position within the application layout; - creating its relevant buttons and connecting them to desired actions; - populating it with the created buttons; and - spacing out these buttons within the application component itself. :param resolution: the desktop screen resolution of the user :type resolution: PyQt5.QtCore.QRect(0, 0, screen_width, screen_height), required :return: a QWidget that is displayed within the application from bdEdit.py :rtype: QWidget """ # _______________________________ Initial Setup _______________________________ # Sets interface's (application window's) resolution. Several options available: # self.setGeometry(100, 100, resolution.width() - 200, resolution.height() - 200) # Interface will be same size as desktop screen, minus 100 pixels from all sides # self.setGeometry(resolution.width() / 4, resolution.height() / 4, resolution.width() / 2, resolution.height() / 2) # Interface will be half the size of the desktop screen, and centered in the screen # self.setGeometry(100, 100, resolution.width() / 2, resolution.height() / 2) # Interface will be half the size of the desktop screen, and displayed 100 pixels down and right from top left corner of the screen main_window.setGeometry( 100, 100, int(resolution.width() / 2), int(resolution.height() / 2) ) # Interface will be half the size of the desktop screen, and displayed 100 pixels down and right from top left corner of the screen # Layout manager is defined and set for the application layout. This will handle # the positioning of each application component. self.layout = QGridLayout() # Contains all the widgets self.layout.setContentsMargins(0, 0, 0, 0) # Removes the border self.setLayout(self.layout) # Different screen resolutions result in varying sizes of the library browser # and parameter window panels. To compensate for this, the preferred dimensions # of these panels as seen on the screen while being developed (2560 resolution # width) have been scaled to other screen sizes. self.layout.scale = int(2560 / resolution.width()) # An instance of the Scene class is created, providing it the resolution of the # desktop screen and the application layout manager. self.scene = Scene(resolution, self.layout, main_window) # Since the Scene itself is only a class and doesn't have a 'visual' representation, # it will create a 'grScene' variable, that will be responsible for handling everything # graphical related. This 'grScene' is fed into an instance of a GraphicsView, which # handles visual updates to the 'grScene' (e.g. updating the Scene when Blocks are # deleted, or when wires are moved around) self.canvasView = GraphicsView(self.scene.grScene, main_window, self) # Import all the blocks that bdedit can see, and sort the list in alphabetical order self.blockLibrary = import_blocks(self.scene, self.layout) self.sortBlockLibrary() # if debug: # print("\nfrom self.blockLibrary") # for group in self.blockLibrary: # for block_cls in group[1]: # for variables in block_cls[1].__dict__.items(): # if variables[0] in ["parameters"]: # print("('" + variables[0] + "',") # for param in variables[1]: # print(" ", param) # else: # print(variables) # print() # # print("\n----------------------------------\n") # ___________________________ Library Browser Setup ___________________________ # The library browser will be displayed along the left-hand side of the interface, # items should be displayed vertically within it, hence the vertical layout manager. self.libraryBrowser.layout = QVBoxLayout(self.libraryBrowser) # Adding icon for the tool above the library browser self.tool_logo = QLabel() self.tool_logo.setPixmap( QPixmap(":/Icons_Reference/Icons/bdsim_logo2.png").scaledToWidth( 230 * self.layout.scale ) ) # self.tool_logo.setPixmap(QPixmap(":/Icons_Reference/Icons/bdsim_logo2.png").scaledToHeight(40 * self.layout.scale)) self.libraryBrowser.layout.addWidget(self.tool_logo) self.libraryBrowser.layout.setAlignment( self.tool_logo, Qt.AlignmentFlag.AlignHCenter ) # The follow label is added to the library browser, naming it # self.libraryBrowser.layout.addWidget(QLabel('Library Browser')) # The library browser box makes the items inside of it scrollable, so all the relevant # buttons will be added into this widget. This widget will also have a vertical layout # manager. Then this widget will be wrapped by the library browser (will go inside it), # in order to position where it is displayed in the application window. self.libraryBrowserBox.layout = QVBoxLayout() # Remove whitespace between buttons within the library browser panel self.libraryBrowserBox.layout.setSpacing(2) # Make a button for canvas items. These items are like the auto-imported blocks, # but are created from definitions in bdedit, rather than from external libraries self.canvasItems_button = QPushButton(" + Canvas Items") self.canvasItems_button.setStyleSheet("QPushButton { text-align: left }") self.canvasItems_button.clicked.connect(self.toggleCanvasItems) self.canvas_items_hidden = True # A list is created to hold all the buttons that will create Blocks within the Scene self.list_of_scrollbar_buttons = [] # This set of blocks are definited internally within bdedit. Currently they include: # the connector block, the main block, and the text item. As they are definied by # bdedit, rather than having their definitions come from an external library, the # buttons for these items must also be manually created within bdedit. self.libraryBrowserBox.layout.addWidget(self.canvasItems_button) self.connector_block_button = QPushButton("Connector Block") self.main_block_button = QPushButton("Main Block") self.text_item_button = QPushButton("Text Item") self.grouping_box_button = QPushButton("Grouping Box") self.connector_block_button.setVisible(False) self.main_block_button.setVisible(False) self.text_item_button.setVisible(False) self.grouping_box_button.setVisible(False) # These buttons are then connected to creating their respective instances within the Scene self.connector_block_button.clicked.connect( lambda checked: Connector(self.scene, self.layout, "Connector Block") ) self.main_block_button.clicked.connect( lambda checked: Main(self.scene, self.layout) ) self.text_item_button.clicked.connect( lambda checked: Floating_Label(self.scene, self.layout, main_window) ) self.grouping_box_button.clicked.connect( lambda checked: Grouping_Box(self.scene, self.layout) ) # Set up secondary connection to these buttons, to update scene history when they are added to the scene self.connector_block_button.clicked.connect( lambda checked, desc="Added connector block to scene": self.scene.history.storeHistory( desc ) ) self.main_block_button.clicked.connect( lambda checked, desc="Added main block to scene": self.scene.history.storeHistory( desc ) ) self.text_item_button.clicked.connect( lambda checked, desc="Added text label to scene": self.scene.history.storeHistory( desc ) ) self.grouping_box_button.clicked.connect( lambda checked, desc="Added grouping box to scene": self.scene.history.storeHistory( desc ) ) # Adding the buttons to the library browser's layout manager self.libraryBrowserBox.layout.addWidget(self.text_item_button) self.libraryBrowserBox.layout.addWidget(self.main_block_button) self.libraryBrowserBox.layout.addWidget(self.grouping_box_button) self.libraryBrowserBox.layout.addWidget(self.connector_block_button) # This for loop goes through each block type (sink, source, function) that was auto # imported (and stored into self.blockLibrary at the Interface's initialization). for sub_class_group in self.blockLibrary: group_of_buttons = [] # Grab each group that blocks belong to, and create a library panel button for those groups cleaned_class_group = ( sub_class_group[0][:-1] if sub_class_group[0].endswith("s") else sub_class_group[0] ) group_button = QPushButton( " + " + cleaned_class_group.capitalize() + " Blocks" ) # Buttons' text alignment is set to be left-aligned group_button.setStyleSheet("QPushButton { text-align: left }") # The following variables control the '+,-' sign displayed alongside each # hide-able/expandable list section within the library browser. If below it is # defined as True, the button will be '+' when hidden and '-' when expanded. Setting to # False will reverse this logic. Setting it to False, WILL NOT make the list # sections display in expanded mode by default. This is controlled further down with setVisible. group_button.is_hidden = True group_button.clicked.connect(self.toggle_sub_buttons) group_of_buttons.append((group_button, cleaned_class_group.capitalize())) self.libraryBrowserBox.layout.addWidget(group_button) list_of_sub_buttons = [] # Go through each class block in the 2nd element of the group of blocks, and create buttons to spawn each # of those blocks into bdedit for class_block in sub_class_group[1]: # Make a button with the name of the block type button = QPushButton(class_block[0]) # Set the button to be invisible by default (for the list's to be hidden) button.setVisible(False) # Connect button to calling a new instance of the block type class button.clicked.connect( lambda checked, blockClass=class_block[1]: blockClass() ) button.clicked.connect( lambda checked, desc="Added imported block to scene": self.scene.history.storeHistory( desc ) ) # Add button to list of scrollbar buttons for reference of what buttons should be # affected when expanding/hiding the list sections list_of_sub_buttons.append((button, class_block[1])) # self.list_of_scrollbar_buttons.append((button, class_block[1])) # Add the button to the library browser box layout (the scrollable widget) self.libraryBrowserBox.layout.addWidget(button) group_of_buttons.append(list_of_sub_buttons) self.list_of_scrollbar_buttons.append(group_of_buttons) # All the button items are set to align to the top of the libraryBrowserBox self.libraryBrowserBox.layout.addStretch() self.libraryBrowserBox.layout.setAlignment(Qt.AlignTop) self.libraryBrowserBox.setLayout(self.libraryBrowserBox.layout) # A scroll area is defined and applied to the libraryBrowserBox. Its dimensions # are set to allow for resizing, however the minimum height of the scroll area # is set to 300 pixels. self.scroll = QScrollArea() self.scroll.setWidget(self.libraryBrowserBox) self.scroll.setWidgetResizable(True) self.scroll.setMinimumHeight(300) # The width of the libraryBrowser widget is set to 250 pixels, and it the scroll # area is added to the libraryBrowser widget and set to align to the top of this widget. self.libraryBrowser.setFixedWidth(250 * self.layout.scale) self.libraryBrowser.layout.addWidget(self.scroll) self.libraryBrowser.layout.setAlignment(Qt.AlignTop) self.libraryBrowser.setLayout(self.libraryBrowser.layout) # _________ Application components added to application layout manager ___________ # Refer to technical documentation for visualising how these components are # organised within the grid layout. # Each component is added to the grid layout, # * at an initial cell position within that grid, denoted by the first two ints # ** (which represent the y, then x coordinate of the cell), # * then is stretched by a number of cells, denoted by the last two ints # ** (which represent how many cells to stretch vertically, then how many cells to stretch horizontally) # For example, the canvas view is added to cell in the 0th row, and 1st column, # then stretched vertically by 10 row (to row 11), # and stretched horizontally by 9 columns (to column 10) self.layout.addWidget(self.libraryBrowser, 0, 0, 10, 1) self.layout.addWidget(self.canvasView, 0, 1, 10, 9) # ----------------------------------------------------------------------------- @pyqtSlot() def on_click(self, scene): """ This method creates a ``Wire`` instance from the ``Socket`` that was clicked on, to the mouse pointer. :param scene: the scene within which the ``Block`` and ``Socket`` are located :type scene: Scene, required """ # The start block (which has the start socket) and the end block (which has # the end socket) are grabbed. startBlock, endBlock, startSocket, endSocket = self.get_Input() # And a wire is made between those two points. When the wire is being dragged # from a Socket to another socket, the endSocket (and endBlock) are considered # as the location of the mouse pointer. Wire( scene, scene.blocks[startBlock].outputs[startSocket], scene.blocks[endBlock].inputs[endSocket], wire_type=3, ) # Todo - add doc comment for this # ----------------------------------------------------------------------------- def sortBlockLibrary(self): for sub_list in self.blockLibrary: # Sort the blocks within each sublist (functions, sources, sinks, etc) in alphabetical order sub_list[1].sort(key=lambda x: x[0]) # Then sort the groups in alphabetical order too self.blockLibrary.sort(key=lambda x: x[0]) # ----------------------------------------------------------------------------- def clickBox(self, state): """ This method is called when the toolbar checkbox for toggling the visiblity of the connector blocks is triggered. It toggles between the connector blocks being displayed and hidden. :param state: the state of the checkbox (clicked, unclicked) :type state: int """ if state == QtCore.Qt.Checked: # Set variable for hiding connector blocks to True self.scene.hide_connector_blocks = True else: # Set variable for hiding connector blocks to False self.scene.hide_connector_blocks = False # Todo - add main block into here # ----------------------------------------------------------------------------- def toggleCanvasItems(self): """ This method toggles hiding/expanding all Canvas related Items. Currently these include only the ``Connector Block``. """ # If the list section button is set to be hidden, the associated sign # will be displayed as '-', otherwise it will be displayed as '+' if expanded if self.canvas_items_hidden: self.canvasItems_button.setText(" - Canvas Items") else: self.canvasItems_button.setText(" + Canvas Items") # When toggling, the variable that represents the current hidden/expanded # state of the list section, is flipped to the opposite boolean value of itself # If True -> False, If False -> True self.canvas_items_hidden = not self.canvas_items_hidden # And the associated buttons are set to being visible/invisible depending # on that variable. self.connector_block_button.setVisible( not self.connector_block_button.isVisible() ) self.main_block_button.setVisible(not self.main_block_button.isVisible()) self.text_item_button.setVisible(not self.text_item_button.isVisible()) self.grouping_box_button.setVisible(not self.grouping_box_button.isVisible()) # Todo - update doc string comments # ----------------------------------------------------------------------------- def toggle_sub_buttons(self): # Go through list of buttons in the scroll bar for group in self.list_of_scrollbar_buttons: # Find the button group button that was pressed (functions, sink, source, etc) # group looks like - [[QPushButton_for_functions, "functions"], [all relevant buttons]] group_button = group[0][0] if group_button == self.sender(): # If the list section button is set to be hidden, the associated sign # will be displayed as '-', otherwise it will be displayed as '+' if expanded if group_button.is_hidden: group_button.setText(group_button.text().replace("+", "- ")) else: group_button.setText(group_button.text().replace("- ", "+")) # When toggling, the variable that represents the current hidden/expanded # state of the list section, is flipped to the opposite boolean value of itself # If True -> False, If False -> True group_button.is_hidden = not group_button.is_hidden # Go through all relevant buttons and hide/show them # group[1] looks like - [[QPushButton_for_block1, "block1"],[QPushButton_for_block2, "block2"], ...] for button_tuple in group[1]: button_tuple[0].setVisible(not button_tuple[0].isVisible()) # Code for debugging # for group in self.list_of_scrollbar_buttons: # print("group:", group[0][1], "\t", group[0][0]) # for item in group[1]: # print("\titem:", item[1], "\t", item[0]) # print("___________________________") # Todo, update documentation of this function # ----------------------------------------------------------------------------- def grab_screenshot_dimensions(self): def find_boundaries(L, T, R, B, left, top, right, btm): if L < left: left = L if T < top: top = T if R > right: right = R if B > btm: btm = B return [left, top, right, btm] # Define initial dimensions of screenshot (if no blocks in scene) top, btm, left, right = 0, 0, 0, 0 spacer = 50 # half a typical block's width # Go through each block in scene, to find the top/bottom/left/right-most blocks for block in self.scene.blocks: b_left = block.grBlock.pos().x() b_top = block.grBlock.pos().y() b_right = b_left + block.width b_btm = b_top + block.height [left, top, right, btm] = find_boundaries( b_left, b_top, b_right, b_btm, left, top, right, btm ) # if b_left < left: left = b_left # if b_top < top: top = b_top # if b_right > right: right = b_right # if b_btm > btm: btm = b_btm # Then go through each floating text item, grouping box, and wire segments for floating_text in self.scene.floating_labels: f_left = floating_text.grContent.pos().x() f_top = floating_text.grContent.pos().y() f_rect = floating_text.grContent.boundingRect() f_right = f_left + f_rect.width() f_btm = f_top + f_rect.height() [left, top, right, btm] = find_boundaries( f_left, f_top, f_right, f_btm, left, top, right, btm ) # print("floating label - rect, left, top, right, btm:", [f_rect, f_left, f_top, f_right, f_btm]) for gbox in self.scene.grouping_boxes: g_left = gbox.grGBox.pos().x() g_top = gbox.grGBox.pos().y() g_rect = gbox.grGBox.boundingRect() g_right = g_left + g_rect.width() g_btm = g_top + g_rect.height() [left, top, right, btm] = find_boundaries( g_left, g_top, g_right, g_btm, left, top, right, btm ) # print("grouping box - rect, left, top, right, btm:", [g_rect, g_left, g_top, g_right, g_btm]) for wire in self.scene.wires: w_rect = wire.grWire.boundingRect() w_left = w_rect.left() w_top = w_rect.top() w_right = w_left + w_rect.width() w_btm = w_top + w_rect.height() [left, top, right, btm] = find_boundaries( w_left, w_top, w_right, w_btm, left, top, right, btm ) # print("wire - rect, left, top, right, btm:", [w_rect, w_left, w_top, w_right, w_btm]) if DEBUG: print( "Left most:", left, " | Top most:", top, " | Right most:", right, " | Bottom most:", btm, ) # Return the rect (x,y, width, height) that all these blocks occupy width = (right + spacer) - (left - spacer) height = (btm + spacer) - (top - spacer) return [left - spacer, top - spacer, width, height] # ----------------------------------------------------------------------------- def save_image(self, picture_path, picture_name=None, picture_format=None): """ This method takes a filename and saves a snapshot of all the items within the ``Scene`` into it. Currently the resolution of this image is set to 4K resolution (3840 x 2160). :param picture_path: path where the given model is saved, and where the image will be saved :type picture_path: path, required :param picture_name: name of screenshot, same as model name if not given :type picture_name: str, optional """ print("Rendering image") # Creates an image, of defined resolution quality ratio = 3 output_image = QImage( int(self.scene.scene_width * ratio), int(self.scene.scene_height * ratio), QImage.Format_RGBA64, ) # Then a painter is initialized to that image painter = QPainter(output_image) painter.setRenderHint(QPainter.Antialiasing, True) # The canvas is rendered by the above-defined painter (into the image) self.scene.grScene.render(painter) painter.end() # Grab the dimensions of the space all blocks within the screen occupy [x, y, width, height] = self.grab_screenshot_dimensions() # ensure number of bytes in row (width*3) is a multiple of 4 width += (width * 3) % 4 # Scale the dimensions from above, to the image rect = QRect( int(output_image.width() / 2 + (x * ratio)), int(output_image.height() / 2 + (y * ratio)), int(width * ratio), int(height * ratio), ) # Crop the image to area of interest output_image = output_image.copy(rect) # by default this is in QImage.Format_RGBA64 = 26 format, convert # 8-bit RGB pixels # see https://doc.qt.io/qt-5/qimage.html output_image = output_image.convertToFormat(QImage.Format_RGB888) save_path = self.getScreenshotName(picture_path, picture_name, picture_format) # use PIL to do the PDF printing from PIL import Image bytes = output_image.bits().asstring(output_image.sizeInBytes()) img_PIL = Image.frombuffer( "RGB", (output_image.width(), output_image.height()), bytes, "raw", "RGB", 0, 1, ) img_PIL.save(save_path) # # And the image is saved under the given file name, as a PDF print("Screenshot saved --> ", save_path) # ----------------------------------------------------------------------------- def getScreenshotName(self, picture_path, picture_name, picture_format=None): """ """ # If picture_name is None, save screenshot under same name as model file. # Extract directory of model file and save screenshot in same place, suffixed with .pdf by default # If picture_format is given, this overrides the default file type. This can only happen if user selects file type when exporting image from menubar if picture_name is None: name_to_save = ( os.path.join( os.path.splitext(os.path.basename(picture_path))[0] + ".pdf" ) if picture_format is None else os.path.join( os.path.splitext(os.path.basename(picture_path))[0] + "." + picture_format ) ) # If picture name is given, use this name when saving screenshot else: name_to_save = ( os.path.join(picture_name) if picture_format is None else os.path.join(picture_name + "." + picture_format) ) # Find all other images in the current directory (files ending with .png) # as bdedit only saves images with .png extensions # HACK images_in_dir = [] dir_list = os.listdir(os.path.dirname(picture_path)) for img in fnmatch.filter(dir_list, "*.pdf"), fnmatch.filter(dir_list, "*.png"): images_in_dir.append(img) # Check if saving current model under the model name would create a duplicate no_duplicates = True for image in images_in_dir: if name_to_save == image: no_duplicates = False break # HACK # no_duplicates = True # If no duplicates are found, save screenshot under current model name if no_duplicates: return os.path.join(os.path.dirname(picture_path), name_to_save) else: print( "Handle not yet implemented. Saving screenshot would override existing screenshot with same name." ) # def getScreenshotName(self, picture_path, increment=None): # """ # This function takes a path of where the current model is saved, and searches # if there are any screenshots in this path with the same name as the model. # If a duplicate name is detected, the given picture_name is incremented # with a -N, where N is a unique integer. # # :param picture_path: path of model to extract name from # :type picture_path: path, required # :param increment: integer which makes filename unique. Incremented internally. # :type increment: int, optional # """ # # # Given the filepath where to save the picture, find the basename of the screenshot # if increment is None: # name_to_save = os.path.join(os.path.splitext(os.path.basename(picture_path))[0] + ".pdf") # else: # name_to_save = os.path.join(os.path.splitext(os.path.basename(picture_path))[0] + "-" + str(increment)) + ".pdf" # increment += 1 # # # Find all other images in the current directory (files ending with .png) # # as bdedit only saves images with .png extensions # # HACK # images_in_dir = [] # print('Picture path:', picture_path) # dir_list = os.listdir(os.path.dirname(picture_path)) # for img in fnmatch.filter(dir_list, "*.pdf"): # images_in_dir.append(img) # # # Check if saving current model under the model name would create a duplicate # no_duplicates = True # for image in images_in_dir: # if name_to_save == image: # no_duplicates = False # break # #HACK # # no_duplicates = True # # # If no duplicates are found, save screenshot under current model name # if no_duplicates: # return os.path.join(os.path.dirname(picture_path), name_to_save) # else: # if increment is None: return self.getScreenshotName(picture_path, 1) # else: return self.getScreenshotName(picture_path, increment) # ----------------------------------------------------------------------------- def updateSceneDimensions(self): """ This method updates the dimensions of the scene based on current window size (will change as the application window is resized). """ # The largest size the scene can be is: # the difference between the max zoom out level (zoomRange[max_zoom_out, max_zoom_in]) and default zoom # multiplied by the zoom out factor multiplier = ( abs(self.canvasView.zoomRange[0] - self.canvasView._default_zoom_level) * 0.8 ) # Only update if canvas dimensions have changed from what they were previously set to if ( self.width() * multiplier != self.scene.getSceneWidth() * multiplier or self.height() * multiplier != self.scene.getSceneHeight() * multiplier ): self.scene.setSceneWidth((self.width()) * multiplier) self.scene.setSceneHeight((self.height()) * multiplier) self.scene.updateSceneDimensions() # ----------------------------------------------------------------------------- # Update the canvas's dimension if its size has changed (if window has been resized) def resizeEvent(self, event): """ This is an inbuilt method of QWidget, that is overwritten by ``Interface`` to update the dimensions of the ``Scene`` when the application window has been resized. :param event: an interaction event that has occurred with this QWidget :type event: QResizeEvent, automatically recognized by the inbuilt function """ self.updateSceneDimensions() ================================================ FILE: bdsim/bdedit/interface_graphics_scene.py ================================================ # Library imports import math # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtSvg import * # BdEdit imports from bdsim.bdedit.Icons import * # ============================================================================= # # Defining the GraphicsScene Class, which mainly controls the background and # foreground of the canvas within the Scene. # # ============================================================================= class GraphicsScene(QGraphicsScene): """ The ``GraphicsScene`` Class extends the ``QGraphicsScene`` Class from PyQt5, and controls the basic appearance of the ``Scene`` it belongs to. The things it controls include its background, and foreground. """ # ----------------------------------------------------------------------------- def __init__(self, scene, parent=None): """ This method creates an ``QGraphicsScene`` instance and associates it to this ``GraphicsScene`` instance. The GraphicsScene dictates how all items within the Scene are visually represented. :param scene: the ``Scene`` to which this ``GraphicsScene`` belongs to :type scene: Scene, required :param parent: the parent widget this GraphicsScene belongs to (should be None) :type parent: None, optional """ super().__init__(parent) # The Scene this GraphicsScene belongs to, is assigned to an internal variable self.scene = scene # Grid settings are defined for the spacing of the smaller grid squares (20 px) # and how many smaller grid squares fit into a larger grid square (5) self.gridSize = 20 self.gridSquares = 5 # Set the default background mode of the Scene self.mode = False # Set the wire overlaps to not being detected by default self.enable_intersections = True self.mousePressed = False # Set the default background color for when no grid lines are drawn # Currently set to same color as the background for Light mode # self._default_background_color = QColor("#E0E0E0") # Alternatively could be set to a plain white background self._default_background_color = QColor("#FFFFFF") # Set the image used for separating wires at points of overlap self.overlap_image_renderer = QSvgRenderer( ":/Icons_Reference/Icons/overlap.svg" ) # ----------------------------------------------------------------------------- def setGrScene(self, width, height): """ This method sets the dimensions of the ``GraphicsScene``. :param width: width of the GraphicsScene :type width: int, required :param height: height of the GraphicsScene :type height: int, required """ # Set this way so that the (0,0) coordinate would be in the center # of the scene. self.setSceneRect(-width // 2, -height // 2, width, height) # ----------------------------------------------------------------------------- def updateBackgroundMode(self, bgcolor, grid): """Set the background color and grid status for the GraphicsScene. :param bgcolor: the background color :type bgcolor: str, required :param grid: grid drawn :type grid: bool, required """ # Set the mode of the background and update the GraphicsScene self.bgcolor = bgcolor self.grid = grid self.update() # ----------------------------------------------------------------------------- def checkMode(self): """ This method updates the colors used for painting the background of the ``GraphicsScene``. """ if self.bgcolor == "white": self._color_background = QColorConstants.Svg.white elif self.bgcolor == "grey": self._color_background = QColor("#E0E0E0") # Light gray else: raise ValueError(f"bad color {self.bgcolor}") if self.grid is True: # set color of major and minor grid lines self._color_light = QColorConstants.Svg.lightgray self._color_dark = QColorConstants.Svg.silver elif self.grid is False: # no grid, major and minor grid lines are background color self._color_light = self._color_background self._color_dark = self._color_background # if self.mode == False: # self._color_background = QColor("#E0E0E0") # Light gray # self._color_light = QColorConstants.Svg.lightgray # self._color_dark = QColorConstants.Svg.silver # elif self.mode == True: # self._color_background = self._default_background_color # Light gray # Set the line thickness of the smaller, then larger grid squares self._pen_light = QPen(self._color_light) self._pen_light.setWidth(1) self._pen_dark = QPen(self._color_dark) self._pen_dark.setWidth(2) # Set the background fill color self.setBackgroundBrush(self._color_background) def mousePressEvent(self, event): # If the mouse is pressed, an internal variable is set to True super().mousePressEvent(event) self.mousePressed = True def mouseReleaseEvent(self, event): # If mouse is released, the internal variable is set to False super().mouseReleaseEvent(event) self.mousePressed = False # ----------------------------------------------------------------------------- def mouseMoveEvent(self, event): """ This is an inbuilt method of QGraphicsScene, that is overwritten by ``GraphicsScene``. It handles the movement related logic for items selected within the ``GraphicsScene``. Currently, generic movement logic is applied to instances of the following classes: blocks, floating labels, and grouping boxes This genermic movement logic consists of the following being applied on mouse movement: - a detected mouse move event on selected item will enforce grid-snapping (making it move only in increments matching the size of the smaller grid squares in the background). - the selected item will be prevented from moving outside the maximum zoomed out border of the work area (the GraphicsScene). - additioanlly only applies if the selected item is a block: * the locations of its sockets, and that of other connected blocks, are updated as the block moves around. * Additionally, block movements can be relevant to wire routing when they are in custom routing mode, so this logic is checked as blocks are moved. * Finally, moving a block will affect where wires may overlap, so this logic is updated as the blocks are moved. - additionally only applies if the selected item is a floating text label: * floating labels have the ability of being moved closer to wires and blocks, so their grid-snapping is enforced to 5 pixels instead of the typical 20 pixels like for all other items. - finally, if an item's position within the ``GraphicsScene`` has visually been updated, an internal will reflect that the item has moved, to then indicate unsaved changes and record a new snapshop of the scene's history. :param event: a mouse movement event that has occurred with this GraphicsScene :type event: QMouseEvent, automatically recognized by the inbuilt function """ def borderRestriction(item, padding): if hasattr(item, "block"): item_width = item.width item_height = item.height item_title = item.title_height elif hasattr(item, "floating_label"): item_width = item.floating_label.width item_height = item.floating_label.height item_title = 0 elif hasattr(item, "grouping_box"): item_width = item.grouping_box.width item_height = item.grouping_box.height item_title = 0 # left if item.pos().x() < item.scene().sceneRect().x() + padding: item.setPos(item.scene().sceneRect().x() + padding, item.pos().y()) # top if item.pos().y() < item.scene().sceneRect().y() + padding: item.setPos(item.pos().x(), item.scene().sceneRect().y() + padding) # right if item.pos().x() > ( item.scene().sceneRect().x() + item.scene().sceneRect().width() - item_width - padding ): item.setPos( item.scene().sceneRect().x() + item.scene().sceneRect().width() - item_width - padding, item.pos().y(), ) # bottom if item.pos().y() > ( item.scene().sceneRect().y() + item.scene().sceneRect().height() - item_height - item_title - padding ): item.setPos( item.pos().x(), item.scene().sceneRect().y() + item.scene().sceneRect().height() - item_height - item_title - padding, ) super().mouseMoveEvent(event) if self.mousePressed: # For all moveable items which are selected, update their mouseMove related methods for item in self.selectedItems(): # Padding of 20 pixelsis used for how close the item can come up to the border of the scene padding = 20 # The x,y position of the mouse cursor is grabbed, and is restricted to update # every 20 pixels (the size of the smaller grid squares, as defined in GraphicsScene) x = round(item.pos().x() / padding) * padding y = round(item.pos().y() / padding) * padding pos = QPointF(x, y) # For blocks: if hasattr(item, "block"): # The position of this GraphicsBlock is set to the restricted position of the mouse cursor item.setPos(pos) borderRestriction(item, padding) # Update the connected wires of all Blocks that are affected by this Block being moved item.block.updateConnectedEdges() # Since block was moved, update the wires connected to its input & output # sockets to route the wires using the inbuilt hardcoded logic item.block.updateWireRoutingLogic() # If there are wires within the Scene if self.scene.wires: # Call the first wire in the Scene to check the intersections # Calling the first wire will still check intersection points # of all wires, however since that code is located within the # Wire class, this is how it's accessed. self.scene.wires[0].checkIntersections() # For floating text labels: elif hasattr(item, "floating_label"): padding = 5 x = round(item.pos().x() / padding) * padding y = round(item.pos().y() / padding) * padding pos = QPointF(x, y) # The position of this GraphicsFloatingLabel is set to the restricted position of the mouse cursor item.setPos(pos) borderRestriction(item, padding) # For grouping boxes: elif hasattr(item, "grouping_box"): # The position of this GraphicsGroupingBox is set to the restricted position of the mouse cursor item.setPos(pos) borderRestriction(item, padding) # If selected item is of any block type, floating text label or grouping box, and if ( hasattr(item, "block") or hasattr(item, "floating_label") or hasattr(item, "grouping_box") ): # If the above rounding has rounded to a new value, and the block has actually "moved" in the scene, updated variable to reflect that if pos != item.lastPos: item.lastPos = item.pos() item.wasMoved = True # ----------------------------------------------------------------------------- def drawForeground(self, painter, rect): """ This is an inbuilt method of QGraphicsScene, that is overwritten by ``GraphicsScene`` to draw additional logic for intersection points between wires. This logic is drawn overtop of all other items within the GraphicsScene apart from Blocks. Intersection points are drawn as a circle (same fill color as the background) to create a separation between the wires, then redraws a vertical section of the wire overtop. :param painter: a painter (paint brush) that paints the foreground of this GraphicsScene :type painter: QPainter, automatically recognized and overwritten from this method :param rect: a rectangle that defines the dimensions of this GraphicsScene :type rect: QRect, automatically recognized by the inbuilt function """ # Passes on the drawForeground so that this method wouldn't block any # foreground drawing logic in other classes super().drawForeground(painter, rect) # If user has enabled intersections detection if self.enable_intersections: # Check first if any wires are present in the scene if self.scene.wires: # If there are intersection points to draw if self.scene.intersection_list: # Check the current colour mode of the scene and set the pen to that colour self.checkMode() painter.setPen(Qt.NoPen) # painter.setBrush(QBrush(self._color_background)) # Paint each intersection point for intersection_point in self.scene.intersection_list: painter.setBrush(QBrush(self._color_background)) x = intersection_point[0] y = intersection_point[1] # Prepare an overlap rectangle to "white-out" the wires underneath rect = QRectF(x - 7, y - 7.6, 21, 15.2) painter.drawRect(rect) # painter.drawRect(x-7, y-6, 21, 12) # If there are grouping boxes present in the scene if self.scene.grouping_boxes: # Find which grouping boxes, if any, this rectangle overlaps for box in self.scene.grouping_boxes: # Grab QPath of the current location of this grouping box within the scene gbox_location = box.grGBox.mapToScene(box.grGBox.rect()) # Find the intersecting area between the overlapping rect and this grouping box, if there is one intersecting = self.findIntersectingAreaPoints( gbox_location.boundingRect(), rect ) # If an overlap was found, an intersecting rect will be provided, else False if intersecting: painter.setBrush(QBrush(box.grGBox.bg_color)) painter.drawRect(intersecting) # Paint an image over the intersection point self.overlap_image_renderer.render( painter, QRectF(x - 7.2, y - 8, 17, 16) ) # Else, if no wires in scene, clear intersection_list else: self.scene.intersection_list.clear() # ----------------------------------------------------------------------------- def drawBackground(self, painter, rect): """ This is an inbuilt method of QGraphicsScene, that is overwritten by ``GraphicsScene`` to draw a grid background, or a plain background, depending on what grid mode is chosen. This background is drawn behind all other items within the GraphicsScene. :param painter: a painter (paint brush) that paints the background of this GraphicsScene :type painter: QPainter, automatically recognized and overwritten from this method :param rect: a rectangle that defines the dimensions of this GraphicsScene :type rect: QRect, automatically recognized by the inbuilt function """ # Passes on the drawBackground so that this method wouldn't block any # background drawing logic in other classes super().drawBackground(painter, rect) # Check the grid_mode the user has chosen (Light by default) self.checkMode() # If the grid_mode is not "Off", we want to draw grid lines, # so continue with the following logic if self.mode == False: # Here we create our grid left = int(math.floor(rect.left())) right = int(math.ceil(rect.right())) top = int(math.floor(rect.top())) bottom = int(math.ceil(rect.bottom())) first_left = left - (left % self.gridSize) first_top = top - (top % self.gridSize) # Compute all lines to be drawn lines_light, lines_dark = [], [] for x in range(first_left, right, self.gridSize): if x % (self.gridSize * self.gridSquares) != 0: lines_light.append(QLine(x, top, x, bottom)) else: lines_dark.append(QLine(x, top, x, bottom)) for y in range(first_top, bottom, self.gridSize): if y % (self.gridSize * self.gridSquares) != 0: lines_light.append(QLine(left, y, right, y)) else: lines_dark.append(QLine(left, y, right, y)) # Draw lines for the smaller grid squares painter.setPen(self._pen_light) try: painter.drawLines(*lines_light) except TypeError: painter.drawLines(lines_light) # Draw lines for the larger grid squares painter.setPen(self._pen_dark) try: painter.drawLines(*lines_dark) except TypeError: painter.drawLines(lines_dark) # ----------------------------------------------------------------------------- def findIntersectingAreaPoints(self, rectA, rectB): """ This method takes in two QRectF's and finds the union area of overlap between these two rects, as well as the points which make up the overlapping rectangle. :param rectA: rectangle one (representing the grouping box) :type rectA: QRectF :param rectB: rectangle two (representing the wire overlap box) :type rectB: QRectF :return: a rectangle representing the intersection between the two given rectangles :rtype: QRectF """ x1 = max(rectA.left(), rectB.left()) y1 = max(rectA.top(), rectB.top()) x2 = min(rectA.right(), rectB.right()) y2 = min(rectA.bottom(), rectB.bottom()) # If the area of the intersecting rectangle is greater than 0 if (max(0, x2 - x1) * max(0, y2 - y1)) > 0: # Make a new QRectF representing the intersection return QRectF(x1, y1, (max(0, x2 - x1)), (max(0, y2 - y1))) else: # Otherwise return False return False ================================================ FILE: bdsim/bdedit/interface_graphics_view.py ================================================ # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import QGraphicsView # BdEdit imports from bdsim.bdedit.block import Block from bdsim.bdedit.block_graphics_wire import GraphicsWire from bdsim.bdedit.grouping_box_graphics import GraphicsGBox from bdsim.bdedit.block_graphics_socket import GraphicsSocket from bdsim.bdedit.floating_label_graphics import GraphicsLabel from bdsim.bdedit.block_graphics_block import GraphicsBlock, GraphicsConnectorBlock from bdsim.bdedit.block_wire import ( Wire, WIRE_TYPE_STEP, WIRE_TYPE_DIRECT, WIRE_TYPE_BEZIER, ) # ============================================================================= # # Defining and setting global variables # # ============================================================================= # Wire mode variables - used for determining what type of wire to draw MODE_NONE = 1 MODE_WIRE_DRAG = 2 EDGE_DRAG_LIM = 10 # Variable for enabling/disabling debug comments DEBUG = False # ============================================================================= # # Defining the GraphicsView Class, which handles most auto detected # press/scroll/click/scroll/key events, and assigns logic to those actions as # needed. # # ============================================================================= class GraphicsView(QGraphicsView): """ The ``GraphicsView`` Class extends the ``QGraphicsView`` Class from PyQt5, and handles most of the user interactions with the ``Interface``, through press/scroll/click/scroll/key events. It also contains the logic for what Wire should be drawn and what Sockets it connects to. Here mouse click events are used to drag the wires from a start to a end socket, when click is dragged from a socket, the mode == MODE_WIRE_DRAG will be set to True and a Wire will follow the mouse until a end socket is set or mode == MODE_WIRE_DRAG is False and the Wire will be deleted. """ # ----------------------------------------------------------------------------- def __init__(self, grScene, mainwindow, parent=None): """ This method creates an ``QGraphicsView`` instance and associates it to this ``GraphicsView`` instance. :param grScene: the ``GraphicsScene`` to which this ``GraphicsView`` belongs to :type grScene: GraphicsScene, required :param parent: the parent widget this GraphicsView belongs to (should be None) :type parent: None, optional """ super().__init__(parent) # The GraphicsScene this GraphicsView belongs to, is assigned to an internal variable self.grScene = grScene # The InterfaceManager class this application is running within. Only used to access # spinbox widget in toolbar, for displaying correct font size of selected floating text self.interfaceManager = mainwindow # The GraphicsScene is initialized with some settings to make things draw smoother self.initUI() # The GraphicsScene this GraphicsView belongs to is connected self.setScene(self.grScene) # The drawing mode of the wire is initially set to MODE_NONE self.mode = MODE_NONE # Definitions of zoom related variables # The there are 10 zoom levels, with level 7 being the default level # Levels 8-10 zoom in, while levels 0-6 zoom out self._default_zoom_level = 7 self.zoom = self._default_zoom_level self.zoomStep = 1 self.zoomRange = [0, 10] # ----------------------------------------------------------------------------- def initUI(self): """ This method initializes the GraphicsScene with additional settings to make things draw smoother """ self.setRenderHints( QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform ) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.setDragMode(QGraphicsView.RubberBandDrag) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) # ----------------------------------------------------------------------------- def closeParamWindows(self): """ This method will close the parameter window used for changing the user-editable block variables. """ # If there are Blocks within the Scene if len(self.grScene.scene.blocks) != 0: # Iterate through all the Blocks for block in self.grScene.scene.blocks: # And if that Block has a ParamWindow, close it if block.parameterWindow is not None: block.parameterWindow.setVisible(False) block._param_visible = False # ----------------------------------------------------------------------------- def deleteSelected(self): """ This method removes the selected Block or Wire from the scene. """ # If any items are selected within the scene if self.grScene.selectedItems(): # For each selected item within the GraphicsScene for item in self.grScene.selectedItems(): # If the item is a Wire, remove it if isinstance(item, GraphicsWire): item.wire.remove() self.grScene.scene.has_been_modified = True self.grScene.scene.history.storeHistory("Deleted selected wire") # Or if the item is a Block or Connector Block, remove it elif isinstance(item, GraphicsBlock) or isinstance( item, GraphicsConnectorBlock ): item.block.remove() self.grScene.scene.has_been_modified = True self.grScene.scene.history.storeHistory("Deleted selected block") # Or if the item is a Floating_Label, remove it elif isinstance(item, GraphicsLabel): item.floating_label.remove() self.interfaceManager.updateToolbarValues() self.grScene.scene.has_been_modified = True self.grScene.scene.history.storeHistory( "Deleted selected floating label" ) # Or if item is a Grouping Box, remove it elif isinstance(item, GraphicsGBox): item.grouping_box.remove() self.grScene.scene.has_been_modified = True self.grScene.scene.history.storeHistory( "Deleted selected grouping box" ) # ----------------------------------------------------------------------------- def flipBlockSockets(self): """ This method flips the selected Block so that the input and output Sockets change sides. """ # For each selected item within the GraphicsScene for item in self.grScene.selectedItems(): # If the item is a Block or Connector Block, flip its sockets if isinstance(item, GraphicsBlock) or isinstance( item, GraphicsConnectorBlock ): item.block.updateSocketPositions() item.block.updateWireRoutingLogic() item.block.flipped = not (item.block.flipped) self.grScene.scene.has_been_modified = True self.grScene.scene.history.storeHistory("Block Flipped") # ----------------------------------------------------------------------------- def dist_click_release(self, event): """ This method checks how for the cursor has moved. This is be used when the Wire is dragged, to check that wire has been dragged away from the start socket, so that when it is released on a socket we know its not the start socket. :param event: a mouse release event that has occurred with this GraphicsView :type event: QMouseEvent, automatically recognized by the inbuilt function :return: - True (if mouse has been released more than an defined distance from the start_socket) - False (if mouse has been released too close too the start_socket) :rtype: bool """ # Measures that the cursor has moved a reasonable distance click_release_poss = self.mapToScene(event.pos()) mouseMoved = click_release_poss - self.last_click_poss edgeThreshSqr = EDGE_DRAG_LIM * EDGE_DRAG_LIM return ( mouseMoved.x() * mouseMoved.x() + mouseMoved.y() * mouseMoved.y() ) > edgeThreshSqr # ----------------------------------------------------------------------------- def getItemAtClick(self, event): """ This method returns the object at the click location. It is used when checking what item within the GraphicsView has been clicked when starting to drag a wire. :param event: a mouse click event that has occurred with this GraphicsView :type event: QMouseEvent, automatically recognized by the inbuilt function :return: the item that has been clicked on (can be ``GraphicsBlock``, ``GraphicsSocket``, ``GraphicsWireStep``, ``NoneType``), required :rtype: GraphicsBlock, GraphicsSocket, GraphicsWireStep or NoneType """ pos = event.pos() obj = self.itemAt(pos) return obj # ----------------------------------------------------------------------------- def intersectionTest(self): """ This method initiates the checking of all Wires within the Scene for intersection points where they overlap. """ # If there are wires within the Scene if self.grScene.scene.wires: # print("Graphics view - intersection test") # for i, wire in enumerate(self.grScene.scene.wires): # print("Wire " + str(i) + " Coordinates: ", wire.wire_coordinates) # print() # Call the first wire in the Scene to check the intersections # Calling the first wire will still check intersection points # of all wires, however since that code is located within the # Wire class, this is how it's accessed. self.grScene.scene.wires[0].checkIntersections() # ----------------------------------------------------------------------------- def edgeDragStart(self, item): """ This method starts drawing a Wire between two Blocks. It will construct a new ``Wire`` and set the start socket to the socket that has been clicked on, and the end socket to None. The end socket will be set when either another socket is clicked, or the mouse button is released over another socket. If neither happen, the wire will be deleted. :param item: the socket that has been clicked on :type item: GraphicsSocket, required """ # If in DEBUG mode, the follow code will print the start and end # sockets that have been recognized, as being relevant to this wire. if DEBUG: print("socket is input socket:", item.socket.isInputSocket()) if DEBUG: print("socket is output socket:", item.socket.isOutputSocket()) # The start socket is extracted from the provided item self.drag_start_socket = item.socket # A step wire is made from the start socket, to nothing self.drag_wire = Wire(self.grScene.scene, item.socket, None, WIRE_TYPE_STEP) # If in DEBUG mode, the following code will print the wire that has # just been created if DEBUG: print("View::wireDragStart ~ dragwire:", self.drag_wire) # ----------------------------------------------------------------------------- def edgeDragEnd(self, item): """ This method is used for setting the end socket of the Wire. The place where the wire has been released will be checked, and if it is a ``GraphicSocket`` and is not the start socket then a Wire is completed. Next some check will be made to see that inputs are not connected to inputs and outputs are not connected to outputs. Additionally, Block Sockets will be checked to prevent multiple Wires from connecting to a single input socket. No such restriction is placed on the output sockets. This same logic is applied to Connector Blocks. If these conditions are met, the wire that was dragged will be deleted, and a new Wire will be created with the start socket from the block the wire drag started at, and the end socket being from the socket of the block the Wire was dragged to. If the above-mentioned conditions not met, the wire is simply removed. :param item: should be the socket that has been clicked on (however could be one of the following: ``GraphicsBlock``, ``GraphicsSocket``, ``GraphicsWireStep`` or ``NoneType``) :type item: GraphicsSocket, required :return: False (if the the Wire has been successfully drawn between Blocks) :rtype: bool """ # The dragging mode of the wire is initially set to being None self.mode = MODE_NONE if DEBUG: print("View::edgeDragEnd ~ End dragging edge") # The previous wire (drag_wire) is removed self.drag_wire.remove() self.drag_wire = None # If the clicked item is a GraphicsSocket if type(item) is GraphicsSocket: # And the clicked socket is not the same socket the original wire started from if item.socket != self.drag_start_socket: # If we released dragging on a socket (other then the beginning socket) # We want to keep all the wires coming from target socket if not item.socket.is_multi_wire: item.socket.removeAllEdges() # We want to keep all the wires coming from start socket if not self.drag_start_socket.is_multi_wire: self.drag_start_socket.removeAllWires() # If the block is a socket block, check the start socket of the # wire that ends in the socket block before connecting an end block, # so that 2 outputs or 2 inputs are not connected through the # Socket block if self.drag_start_socket.socket_type == 3: if len(self.drag_start_socket.wires) > 0: if ( self.drag_start_socket.wires[0].start_socket.socket_type != item.socket.socket_type ): if item.socket.socket_type == 1: if len(item.socket.wires) == 0: new_wire = Wire( self.grScene.scene, self.drag_start_socket, item.socket, WIRE_TYPE_STEP, ) else: new_wire = Wire( self.grScene.scene, self.drag_start_socket, item.socket, WIRE_TYPE_STEP, ) # Socket block can have multi outputs only and not multi inputs elif item.socket.socket_type == 3: if len(item.socket.wires) > 0: i = len(self.drag_start_socket.wires) if i >= 1: self.drag_start_socket.wires[i - 1].remove() else: if item.socket.socket_type == 1: if len(item.socket.wires) == 0: new_wire = Wire( self.grScene.scene, self.drag_start_socket, item.socket, WIRE_TYPE_STEP, ) else: new_wire = Wire( self.grScene.scene, self.drag_start_socket, item.socket, WIRE_TYPE_STEP, ) # Cannot connect a input to a input or a output to a output # Input sockets can not have multiple wires # Wire can only be drawn if start and end sockets are different (input to output, or output to input) elif self.drag_start_socket.socket_type != item.socket.socket_type: # Additional logic to ensure the input socket (either at start or end of the wire) only has a single # wire coming into it if item.socket.socket_type == 1: if len(item.socket.wires) == 0: new_wire = Wire( self.grScene.scene, self.drag_start_socket, item.socket, WIRE_TYPE_STEP, ) elif self.drag_start_socket.socket_type == 1: if len(self.drag_start_socket.wires) == 0: new_wire = Wire( self.grScene.scene, self.drag_start_socket, item.socket, WIRE_TYPE_STEP, ) # Otherwise draw a wire between the two sockets else: new_wire = Wire( self.grScene.scene, self.drag_start_socket, item.socket, WIRE_TYPE_STEP, ) self.grScene.scene.has_been_modified = True self.grScene.scene.history.storeHistory("Created new wire by dragging") if DEBUG: print("created wire") if DEBUG: print("View::edgeDragEnd ~ everything done.") # Call for the intersection code to be run # print("edge drag end - before true") self.intersectionTest() return True return False # ----------------------------------------------------------------------------- def keyPressEvent(self, event): """ This is an inbuilt method of QGraphicsView, that is overwritten by ``GraphicsView`` to detect, and assign actions to the following key presses. - DEL or BACKSPACE: removes selected item from the Scene - F: flips the sockets on a Block or Connector Block - I: toggles intersection detection amongst wires (Off by default) - CTRL + S: previously connected to saving the Scene - CTRL + L: previously connected to loading a Scene file The saving and loading of a file using keys has since been disabled, as it used an old method for saving/loading JSON files which has since been overwritten in the Interface Class. However these key checks are still connected if future development should take place. :param event: key(s) press(es) that have been detected :type event: QKeyPressEvent, automatically recognized by the inbuilt function """ # if event.key() == Qt.Key_1: # print("HISTORY: len(%d)" % len(self.grScene.scene.history.history_stack), # " -- current_step", self.grScene.scene.history.history_current_step) # ix = 0 # for item in self.grScene.scene.history.history_stack: # print("#", ix, "--", item['desc']) # ix += 1 # elif event.key() == Qt.Key_2: # for item in self.grScene.selectedItems(): # if hasattr(item, 'wire'): # print("Wire coordinates:", item.wire.wire_coordinates) # elif event.key() == Qt.Key_3: # hist_stack_item = self.grScene.scene.history.history_stack[self.grScene.scene.history.history_current_step] # print("history stack current step:", self.grScene.scene.history.history_current_step) # for item in hist_stack_item['snapshot'].items(): # if item[0] == 'blocks': # print(item[0]) # for i, block in enumerate(item[1]): # print("\n\tnew block %d" % i) # for block_items in block.items(): # if block_items[0] in ['inputs', 'outputs']: # for k, socket in enumerate(block_items[1]): # print("\n\t\tnew socket %d" % k) # for socket_items in socket.items(): # print("\t\t", socket_items) # else: # print("\t", block_items) # elif item[0] == 'wires': # print(item[0]) # for j, wire in enumerate(item[1]): # print("\n\tnew wire %d" % j) # for wire_items in wire.items(): # print("\t", wire_items) # else: # print(item) # else: # super().keyPressEvent(event) super().keyPressEvent(event) # ----------------------------------------------------------------------------- def mousePressEvent(self, event): """ This is an inbuilt method of QGraphicsView, that is overwritten by ``GraphicsView`` to detect, and direct the Left, Middle and Right mouse button presses to methods that handle their associated logic. Additionally, when the Left mouse button is pressed anywhere in the ``GraphicsView``, any currently ``ParamWindow`` that relates to an active ``Block`` within the ``Scene`` will be closed. :param event: a mouse press event (Left, Middle or Right) :type event: QMousePressEvent, automatically recognized by the inbuilt function """ if event.button() == Qt.MiddleButton: self.middleMouseButtonPress(event) elif event.button() == Qt.LeftButton: self.leftMouseButtonPress(event) self.closeParamWindows() elif event.button() == Qt.RightButton: self.closeParamWindows() self.rightMouseButtonPress(event) else: super().mousePressEvent(event) # ----------------------------------------------------------------------------- def mouseReleaseEvent(self, event): """ This is an inbuilt method of QGraphicsView, that is overwritten by ``GraphicsView`` to detect, and direct the Left, Middle and Right mouse button releases to methods that handle their associated logic. :param event: a mouse release event (Left, Middle or Right) :type event: QMouseReleaseEvent, required """ if event.button() == Qt.MiddleButton: self.middleMouseButtonRelease(event) elif event.button() == Qt.LeftButton: self.leftMouseButtonRelease(event) elif event.button() == Qt.RightButton: self.rightMouseButtonRelease(event) else: super().mouseReleaseEvent(event) # ----------------------------------------------------------------------------- def leftMouseButtonPress(self, event): """ This method handles the logic associate with the Left mouse button press. It will always run the getItemAtClick method to return the item that has been clicked on. - If a GraphicsSocket is pressed on, then a draggable Wire will be started. - If a GraphicWire is pressed, then the active draggable Wire will be ended (when the wire is draggable, clicking off at a Socket, will register the clicked item as a GraphicsWire). Alternatively, the following logic is applied for selecting items. - If an empty space within the GraphicsView is pressed, a draggable net will appear, within which all items will be selected. - If left clicking while holding the SHIFT or CTRL key, this will incrementally select an item from within the GraphicsView. The items that are selectable are ``GraphicsBlock``, ``GraphicsWire`` or ``GraphicsSocketBlock`` (which is the Connector Block). Otherwise nothing is done with the left mouse press. :param event: a Left mouse button press :type event: QMousePressEvent, required :return: None to exit the method :rtype: NoneType """ # Item that is clicked on is grabbed item = self.getItemAtClick(event) self.last_click_poss = self.mapToScene(event.pos()) # if isinstance(item, GraphicsBlock) or isinstance(item, GraphicsWire) or isinstance(item, GraphicsConnectorBlock) or item is None: if ( isinstance(item, (GraphicsBlock, GraphicsWire, GraphicsConnectorBlock)) or item is None ): if self.grScene.scene.floating_labels: # If floating labels are present and an open space is clicked, bring cursor out of focus from the labels for label in self.grScene.scene.floating_labels: cursor = label.content.text_edit.textCursor() cursor.clearSelection() label.content.text_edit.setTextCursor(cursor) label.grContent.setLabelUnfocus() if event.modifiers() & Qt.ShiftModifier: event.ignore() fakeEvent = QMouseEvent( QEvent.MouseButtonPress, event.localPos(), event.screenPos(), Qt.LeftButton, event.buttons() | Qt.LeftButton, event.modifiers() | Qt.ControlModifier, ) super().mousePressEvent(fakeEvent) return if type(item) is GraphicsSocket: if self.mode == MODE_NONE: self.mode = MODE_WIRE_DRAG self.edgeDragStart(item) return if self.mode == MODE_WIRE_DRAG: res = self.edgeDragEnd(item) # Call for the intersection code to be run # print("left mouse press - wire drag - socket") self.intersectionTest() if res: return if issubclass(item.__class__, GraphicsWire): if self.mode == MODE_WIRE_DRAG: res = self.edgeDragEnd(item) # Call for the intersection code to be run # print("left mouse press - wire drag - wire") self.intersectionTest() if res: return super().mousePressEvent(event) # ----------------------------------------------------------------------------- def leftMouseButtonRelease(self, event): """ This method handles the logic associate with the Left mouse button release. It will always run the getItemAtClick method to return the item that the mouse has been released from. - If a Wire was the item being dragged, it will check how far the Wire has moved, then an attempt to complete the Wire onto a Socket will be made. If no Socket is found, the Wire will be ended. Alternatively, the following logic is applied for selecting items. - If an empty space within the GraphicsView is released, if a draggable net was active, all items within that net will be selected. - If left clicking while holding the SHIFT or CTRL key, this will incrementally select an item from within the GraphicsView. The items that are selectable are ``GraphicsBlock``, ``GraphicsWire`` or ``GraphicsSocketBlock`` (which is the Connector Block). :param event: a Left mouse button release :type event: QMouseReleaseEvent, required :return: None to exit the method :rtype: NoneType """ # Get item which we clicked item = self.getItemAtClick(event) # if isinstance(item, GraphicsBlock) or isinstance(item, GraphicsWire) or isinstance(item, GraphicsConnectorBlock) or item is None: if ( isinstance(item, (GraphicsBlock, GraphicsWire, GraphicsConnectorBlock)) or item is None ): if self.grScene.scene.floating_labels: # Update font size box to display correct font size value of selected floating text self.interfaceManager.updateToolbarValues() if event.modifiers() & Qt.ShiftModifier: event.ignore() fakeEvent = QMouseEvent( event.type(), event.localPos(), event.screenPos(), Qt.LeftButton, Qt.NoButton, event.modifiers() | Qt.ControlModifier, ) super().mouseReleaseEvent(fakeEvent) return if self.mode == MODE_WIRE_DRAG: if self.dist_click_release(event): res = self.edgeDragEnd(item) # print("left mouse release - wire drag") self.intersectionTest() if res: return super().mouseReleaseEvent(event) # ----------------------------------------------------------------------------- def rightMouseButtonPress(self, event): """ This method handles the logic associate with the Right mouse button press. Currently no logic is linked to a right mouse press. :param event: the detected right mouse press event :type event: QMousePressEvent, required :return: the mouse press event is returned :rtype: QMousePressEvent """ return super().mousePressEvent(event) # ----------------------------------------------------------------------------- def rightMouseButtonRelease(self, event): """ This method handles the logic associate with the Right mouse button release. Currently no logic is linked to a right mouse release. :param event: the detected right mouse release event :type event: QMousePressEvent, required :return: the mouse release event is returned :rtype: QMouseReleaseEvent """ return super().mouseReleaseEvent(event) # ----------------------------------------------------------------------------- def middleMouseButtonPress(self, event): """ This method handles the logic associate with the Middle mouse button press (perhaps more intuitively understood as pressing the scroll wheel). When the scroll wheel is pressed, the mouse cursor will appear as a hand that pinches the GraphicsView, allowing the canvas to be dragged around. :param event: the detected middle mouse press event :type event: QMousePressEvent, required """ releaseEvent = QMouseEvent( QEvent.MouseButtonRelease, event.localPos(), event.screenPos(), Qt.LeftButton, Qt.NoButton, event.modifiers(), ) super().mouseReleaseEvent(releaseEvent) self.setDragMode(QGraphicsView.ScrollHandDrag) fakeEvent = QMouseEvent( event.type(), event.localPos(), event.screenPos(), Qt.LeftButton, event.buttons() | Qt.LeftButton, event.modifiers(), ) super().mousePressEvent(fakeEvent) # ----------------------------------------------------------------------------- def middleMouseButtonRelease(self, event): """ This method handles the logic associate with the Middle mouse button release (perhaps more intuitively understood as releasing the scroll wheel). When the scroll wheel is releasing, the mouse cursor will change back from appearing as a hand to the default mouse cursor (pointer arrow on Windows). :param event: the detected middle mouse release event :type event: QMouseReleaseEvent, required """ fakeEvent = QMouseEvent( event.type(), event.localPos(), event.screenPos(), Qt.LeftButton, event.buttons() & ~Qt.LeftButton, event.modifiers(), ) super().mouseReleaseEvent(fakeEvent) self.setDragMode(QGraphicsView.NoDrag) # ----------------------------------------------------------------------------- def wheelEvent(self, event): """ This is an inbuilt method of QGraphicsView, that is overwritten by ``GraphicsView`` to assign logic to detected scroll wheel movement. - As the scroll wheel is moved up, this will make the zoom in on the work area of the ``GraphicsScene``. - As the scroll wheel is moved down, this will make the zoom out of the work area of the ``GraphicsScene``. :param event: the detected scroll wheel movement :type event: QWheelEvent, automatically recognized by the inbuilt function """ # If scroll wheel vertical motion is detected to being upward if event.angleDelta().y() > 0: # Set the zoom factor to 1.25, and incrementally increase the zoom step zoomFactor = 1.25 self.zoom += self.zoomStep # Else the scroll wheel is moved downwards else: # Set the zoom factor to 1/1.25, and incrementally decrease the zoom step zoomFactor = 0.8 self.zoom -= self.zoomStep # If the current zoom is within the allowable zoom levels (0 to 10) # Scale the Scene (in the x and y) by the above-set zoomFactor if self.zoomRange[0] - 1 <= self.zoom <= self.zoomRange[1]: self.scale(zoomFactor, zoomFactor) # Otherwise if the current zoom is below the lowest allowable zoom level (0) # Force the zoom level to the lowest allowable level elif self.zoom < self.zoomRange[0] - 1: self.zoom = self.zoomRange[0] - 1 # Otherwise if the current zoom is above the highest allowable zoom level (10) # Force the zoom level to the highest allowable level elif self.zoom > self.zoomRange[1]: self.zoom = self.zoomRange[1] # ----------------------------------------------------------------------------- def mouseMoveEvent(self, event): """ This is an inbuilt method of QGraphicsView, that is overwritten by ``GraphicsView`` to assign logic to detected mouse movement. - If the wire is in dragging mode, the position the wire is drawn to will be updated to the mouse cursor as it is moved around. - Additionally, the code to check for intersection amongst wires will be run, and subsequently, if any are found, they will be automatically marked within the ``GraphicsScene`` Class. :param event: the detected mouse movement event :type event: QMouseMoveEvent, automatically recognized by the inbuilt function """ super().mouseMoveEvent(event) try: # If the wire is in dragging mode if self.mode == MODE_WIRE_DRAG: # Grab the on-screen position of the mouse cursor pos = self.mapToScene(event.pos()) # Set the point that the wire draws to, as the current x,y position of the mouse cursor self.drag_wire.grWire.setDestination(pos.x(), pos.y()) # Call for the wire to be redrawn/updated accordingly self.drag_wire.grWire.update() except AttributeError: self.mode = MODE_NONE ================================================ FILE: bdsim/bdedit/interface_manager.py ================================================ # Library imports import os import json import subprocess import datetime from sys import platform from pathlib import Path # PyQt5 imports from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * # BdEdit imports from bdsim.bdedit.Icons import * from bdsim.bdedit.interface import Interface # Todo - update documentation for this new class, handles any edits/saves/undo/redo within interface. # Also handles the run related functionality now # ============================================================================= # # Defining the Interface Manager Class, # # ============================================================================= class InterfaceWindow(QMainWindow): def __init__(self, resolution, debug=False): super().__init__() # The name of the current model is initially set to None, this is then # overwritten when the model is saved self.filename = None self.bgmode = 0 # default background/grid mode self.initUI(resolution, debug) def initUI(self, resolution, debug): # create node editor widget self.interface = Interface(resolution, debug, self) self.interface.scene.addHasBeenModifiedListener(self.updateApplicationName) self.setCentralWidget(self.interface) self.runButtonParameters = { "SimTime": 10.0, "Graphics": True, "Animation": True, "Verbose": False, "Progress": True, "Debug": "", } self.toolbar = QToolBar() self.fontSizeBox = QSpinBox() self.simTimeBox = QLineEdit() self.floatValidator = QDoubleValidator() # Create the toolbar action items and the toolbar itself self.createActions() self.createToolbar() # set window properties # self.setWindowIcon(QIcon(":/Icons_Reference/Icons/bdsim_icon.png")) self.updateApplicationName() self.show() def createActions(self): # Creates basic actions related to saving/loading files self.actNew = QAction( QIcon(":/Icons_Reference/Icons/new_file.png"), "&New", self, shortcut="Ctrl+N", toolTip="Create new model.", triggered=self.newFile, ) self.actOpen = QAction( QIcon(":/Icons_Reference/Icons/open_folder.png"), "&Open", self, shortcut="Ctrl+O", toolTip="Open model.", triggered=self.loadFromFile, ) self.actSave = QAction( QIcon(":/Icons_Reference/Icons/save.png"), "&Save", self, shortcut="Ctrl+S", toolTip="Save model.", triggered=self.saveToFile, ) self.actSaveAs = QAction( QIcon(":/Icons_Reference/Icons/save_as.png"), "&Save As", self, shortcut="Ctrl+Shift+S", toolTip="Save model as.", triggered=self.saveAsToFile, ) self.actExit = QAction( QIcon(":/Icons_Reference/Icons/quit.png"), "&Quit", self, shortcut="Ctrl+Q", toolTip="Quit bdedit.", triggered=self.close, ) # Actions related to editing files (undo/redo) self.actUndo = QAction( QIcon(":/Icons_Reference/Icons/undo.png"), "&Undo", self, shortcut="Ctrl+Z", toolTip="Undo last action.", triggered=self.editUndo, ) self.actRedo = QAction( QIcon(":/Icons_Reference/Icons/redo.png"), "&Redo", self, shortcut="Ctrl+Shift+Z", toolTip="Redo last action.", triggered=self.editRedo, ) self.actDelete = QAction( QIcon(":/Icons_Reference/Icons/remove.png"), "&Delete", self, toolTip="Delete selected items.", triggered=self.editDelete, ) self.actDelete.setShortcuts({QKeySequence("Delete"), QKeySequence("Backspace")}) # Miscellaneous actions self.actFlipBlocks = QAction( "Flip Blocks", self, shortcut="F", toolTip="Flip selected blocks.", triggered=self.miscFlip, ) self.actScreenshot = QAction( "Screenshot", self, shortcut="P", toolTip="Take and save a screenshot of your diagram.", triggered=lambda checked: self.miscScreenshot(None), ) self.actWireOverlaps = QAction( "Toggle Wire Overlaps", self, shortcut="I", toolTip="Toggle markers where wires overlap.", triggered=self.miscEnableOverlaps, checkable=True, ) self.actHideConnectors = QAction( "Toggle Connectors", self, shortcut="H", toolTip="Toggle visibilitiy of connector blocks (hidden/visible).", triggered=self.miscHideConnectors, checkable=True, ) self.actDisableBackground = QAction( "Disable Background", self, shortcut="T", toolTip="Toggle background mode (grey with grid / white without grid).", triggered=self.miscToggleBackground, checkable=True, ) # Actions related to model simulation self.actRunButton = QAction( QIcon(":/Icons_Reference/Icons/run.png"), "Run", self, shortcut="R", toolTip="Run Button (R)

Simulate your block diagram model.

", triggered=self.runButton, ) self.actAbortButton = QAction( QIcon(":/Icons_Reference/Icons/abort.png"), "Abort", self, shortcut="Q", toolTip="Abort Button (Q)

Abort simulation of your block diagram model.

", triggered=self.abortButton, ) self.actSimTime = self.simTimeBox.addAction( QIcon(":/Icons_Reference/Icons/simTime.png"), self.simTimeBox.LeadingPosition, ) self.actSimTime.setToolTip( "Simulation Time

Description to be added

" ) self.simTimeBox.setText(str(self.runButtonParameters["SimTime"])) self.simTimeBox.setMinimumWidth(55) self.simTimeBox.setMaximumWidth(75) self.simTimeBox.setValidator(self.floatValidator) self.simTimeBox.editingFinished.connect(self.updateSimTime) # Actions related to formatting floating text labels self.actAlignLeft = QAction( QIcon(":/Icons_Reference/Icons/left_align.png"), "Left", self, shortcut="Ctrl+Shift+L", toolTip="Left Align (Ctrl+Shift+L)

Left align your selected floating text.

", triggered=lambda: self.textAlignment("AlignLeft"), checkable=True, ) self.actAlignCenter = QAction( QIcon(":/Icons_Reference/Icons/center_align.png"), "Center", self, shortcut="Ctrl+Shift+C", toolTip="Center (Ctrl+Shift+C)

Center your selected floating text.

", triggered=lambda: self.textAlignment("AlignCenter"), checkable=True, ) self.actAlignRight = QAction( QIcon(":/Icons_Reference/Icons/right_align.png"), "Right", self, shortcut="Ctrl+Shift+R", toolTip="Right Align (Ctrl+Shift+R)

Right align your selected floating text.

", triggered=lambda: self.textAlignment("AlignRight"), checkable=True, ) self.actBoldText = QAction( QIcon(":/Icons_Reference/Icons/bold.png"), "&Bold", self, shortcut="Ctrl+B", toolTip="Bold (Ctrl+B)

Toggle bold on selected floating text.

", triggered=self.textBold, checkable=True, ) self.actUnderLineText = QAction( QIcon(":/Icons_Reference/Icons/underline.png"), "&Underline", self, shortcut="Ctrl+U", toolTip="Underline (Ctrl+U)

Toggle underline on selected floating text.

", triggered=self.textUnderline, checkable=True, ) self.actItalicText = QAction( QIcon(":/Icons_Reference/Icons/italic.png"), "&Italicize", self, shortcut="Ctrl+I", toolTip="Italic (Ctrl+I)

Toggle italics on selected floating text.

", triggered=self.textItalicize, checkable=True, ) self.actFontType = QAction( "Font", self, shortcut="Ctrl+Shift+F", toolTip="Font (Ctrl+Shift+F)

Choose a font style for floating text.

", triggered=self.textFontStyle, ) self.fontSizeBox.setValue(14) self.fontSizeBox.valueChanged.connect(self.textFontSize) self.actTextColor = QAction( QIcon(":/Icons_Reference/Icons/color_picker.png"), "Text Color", self, toolTip="Font Color

Change the color of your text.

", triggered=self.textColor, ) self.actRemoveFormat = QAction( QIcon(":/Icons_Reference/Icons/clear_format.png"), "Clear Format", self, toolTip="Clear Text Formatting

Removes all formatting from selected floating text.

", triggered=self.removeFormat, ) self.actRunBtnOp1 = QAction( "Graphics", self, toolTip="Toggle Graphics

Description to be added

", triggered=lambda checked: self.setRunBtnOptions("Graphics"), checkable=True, ) self.actRunBtnOp2 = QAction( "Animation", self, toolTip="Toggle Animation

Description to be added

", triggered=lambda checked: self.setRunBtnOptions("Animation"), checkable=True, ) self.actRunBtnOp3 = QAction( "Verbose", self, toolTip="Toggle Verbose

Description to be added

", triggered=lambda checked: self.setRunBtnOptions("Verbose"), checkable=True, ) self.actRunBtnOp4 = QAction( "Progress", self, toolTip="Toggle Progress

Description to be added

", triggered=lambda checked: self.setRunBtnOptions("Progress"), checkable=True, ) self.actRunBtnOp5 = QAction( "Debug", self, toolTip="Debug String

Description to be added

", triggered=lambda checked: self.setRunBtnOptions("Debug"), ) self.actRunBtnOp6 = QAction( "Simulation Time", self, toolTip="Simulation Time

Description to be added

", triggered=lambda checked: self.setRunBtnOptions("SimTime"), ) self.helpButton = QAction( QIcon(":/Icons_Reference/Icons/help.png"), "Help", self, toolTip="Help

Open BdEdit documentation.

", triggered=self.displayHelpURL, ) def createToolbar(self): self.createFileMenu() self.createEditMenu() self.createToolsMenu() self.createRunButtonParameters() self.createToolbarItems() self.createHelpItem() def createFileMenu(self): # self._file_menubar = QMenuBar() if platform == 'darwin' else self.menuBar() # self.fileMenu = QMenu('File') # self.fileMenu.setToolTipsVisible(True) # self.fileMenu.addAction(self.actNew) # self.fileMenu.addSeparator() # self.fileMenu.addAction(self.actOpen) # self.fileMenu.addAction(self.actSave) # self.fileMenu.addAction(self.actSaveAs) # self.fileMenu.addSeparator() # self.fileMenu.addAction(self.actExit) # self._file_menubar.addMenu(self.fileMenu) # # self._file_menubar.setNativeMenuBar(False) menubar = self.menuBar() self.fileMenu = menubar.addMenu("File") self.fileMenu.setToolTipsVisible(True) self.fileMenu.addAction(self.actNew) self.fileMenu.addAction(self.actOpen) self.fileMenu.addSeparator() self.fileMenu.addAction(self.actSave) self.fileMenu.addAction(self.actSaveAs) exportMenu = QMenu("Export As", self) exportMenu.setIcon(QIcon(":/Icons_Reference/Icons/export_as.png")) exportPDF = QAction( "PDF", self, toolTip="Export model as a pdf.", triggered=lambda checked: self.exportAsToFile("pdf"), ) exportPNG = QAction( "PNG", self, toolTip="Export model as a png.", triggered=lambda checked: self.exportAsToFile("png"), ) exportMenu.addAction(exportPDF) exportMenu.addAction(exportPNG) self.fileMenu.addMenu(exportMenu) self.fileMenu.addSeparator() self.fileMenu.addAction(self.actExit) def createEditMenu(self): # self._edit_menubar.setNativeMenuBar(False) # self._edit_menubar = QMenuBar() if platform == 'darwin' else self.menuBar() # self.editMenu = QMenu('Edit') # self.editMenu.setToolTipsVisible(True) # self.editMenu.addAction(self.actUndo) # self.editMenu.addAction(self.actRedo) # self.editMenu.addSeparator() # self.editMenu.addAction(self.actDelete) # self._edit_menubar.addMenu(self.editMenu) # # self._edit_menubar.setNativeMenuBar(False) menubar = self.menuBar() self.editMenu = menubar.addMenu("Edit") self.editMenu.setToolTipsVisible(True) self.editMenu.addAction(self.actUndo) self.editMenu.addAction(self.actRedo) self.editMenu.addSeparator() self.editMenu.addAction(self.actDelete) def createToolsMenu(self): # self._tools_menubar = QMenuBar() if platform == 'darwin' else self.menuBar() # self.toolsMenu = QMenu('Tools') # self.toolsMenu.setToolTipsVisible(True) # self.toolsMenu.addAction(self.actFlipBlocks) # self.toolsMenu.addAction(self.actScreenshot) # self.toolsMenu.addSeparator() # self.toolsMenu.addAction(self.actWireOverlaps) # self.toolsMenu.addAction(self.actHideConnectors) # self.toolsMenu.addAction(self.actDisableBackground) # self.toolsMenu.addSeparator() # self.toolsMenu.addAction(self.actDelete) # self._tools_menubar.addMenu(self.toolsMenu) # # self._tools_menubar.setNativeMenuBar(False) menubar = self.menuBar() self.toolsMenu = menubar.addMenu("Tools") self.toolsMenu.setToolTipsVisible(True) self.toolsMenu.addAction(self.actFlipBlocks) self.toolsMenu.addAction(self.actScreenshot) self.toolsMenu.addSeparator() self.toolsMenu.addAction(self.actWireOverlaps) self.toolsMenu.addAction(self.actHideConnectors) self.toolsMenu.addAction(self.actDisableBackground) # self.toolsMenu.addSeparator() # self.toolsMenu.addAction(self.actDelete) def createRunButtonParameters(self): # self._params_menubar = QMenuBar() if platform == 'darwin' else self.menuBar() # self.runMenu = QMenu('Simulation') # self.runMenu.setToolTipsVisible(True) # self.runMenu.addAction(self.actRunBtnOp6) # self.runMenu.addSeparator() # self.runMenu.addAction(self.actRunBtnOp1) # self.runMenu.addAction(self.actRunBtnOp2) # self.runMenu.addAction(self.actRunBtnOp3) # self.runMenu.addAction(self.actRunBtnOp4) # self.runMenu.addSeparator() # self.runMenu.addAction(self.actRunBtnOp5) # self._params_menubar.addMenu(self.runMenu) menubar = self.menuBar() self.runMenu = menubar.addMenu("Simulation") self.runMenu.setToolTipsVisible(True) self.runMenu.addAction(self.actRunBtnOp6) self.runMenu.addSeparator() self.runMenu.addAction(self.actRunBtnOp1) self.runMenu.addAction(self.actRunBtnOp2) self.runMenu.addAction(self.actRunBtnOp3) self.runMenu.addAction(self.actRunBtnOp4) self.runMenu.addSeparator() self.runMenu.addAction(self.actRunBtnOp5) def createHelpItem(self): # self._help_menubar = QMenuBar() if platform == 'darwin' else self.menuBar() # self.helpBar = QMenu('Help') # self.helpBar.setToolTipsVisible(True) # self.helpBar.addAction(self.helpButton) # self._help_menubar.addMenu(self.helpBar) # # self._help_menubar.setNativeMenuBar(False) menubar = self.menuBar() self.helpBar = menubar.addMenu("Help") self.helpBar.setToolTipsVisible(True) self.helpBar.addAction(self.helpButton) def createToolbarItems(self): self.toolbar = self.addToolBar("ToolbarItems") self.toolbar.addAction(self.actRunButton) self.toolbar.addAction(self.actAbortButton) self.toolbar.addWidget(self.simTimeBox) self.toolbar.addSeparator() self.toolbar.addAction(self.actAlignLeft) self.toolbar.addAction(self.actAlignCenter) self.toolbar.addAction(self.actAlignRight) self.toolbar.addSeparator() self.toolbar.addAction(self.actBoldText) self.toolbar.addAction(self.actUnderLineText) self.toolbar.addAction(self.actItalicText) self.toolbar.addSeparator() self.toolbar.addAction(self.actFontType) self.toolbar.addWidget(self.fontSizeBox) self.toolbar.addAction(self.actTextColor) self.toolbar.addAction(self.actRemoveFormat) self.toolbar.addSeparator() # ----------------------------------------------------------------------------- def updateApplicationName(self): name = "bdedit - " if self.filename is None: name += "untitled.bd" else: name += os.path.basename(self.filename) if self.centralWidget().scene.has_been_modified: name += "*" self.setWindowTitle(name) def closeEvent(self, event): if self.exitingWithoutSave(): event.accept() else: event.ignore() def isModified(self): return self.centralWidget().scene.has_been_modified def exitingWithoutSave(self): if not self.isModified(): return True msg_prompt = QMessageBox.warning( self, "Exiting without saving work.", "The document has been modified.\nDo you want to save your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, ) if msg_prompt == QMessageBox.Save: return self.saveToFile() elif msg_prompt == QMessageBox.Cancel: return False return True # ----------------------------------------------------------------------------- def setRunBtnOptions(self, value): if value not in ["Debug", "SimTime"]: self.runButtonParameters[value] = not (self.runButtonParameters[value]) elif value == "Debug": arbitrary_string, done = QInputDialog.getText( self, "Input Dialog", "Enter a debug string:" ) if done: self.runButtonParameters[value] = arbitrary_string elif value == "SimTime": sim_time, done = QInputDialog.getText( self, "Input Dialog", "Enter simulation time (sec):", QLineEdit.Normal, str(self.runButtonParameters[value]), ) if done: try: # If simulation time is positive integer, update value if float(sim_time) > 0: self.runButtonParameters[value] = float(sim_time) self.simTimeBox.setText(str(self.runButtonParameters[value])) self.interface.scene.sim_time = float(sim_time) # Else return feedback else: print( "Incompatible simulation time given. Expected a positive non-zero float or integer." ) self.setRunBtnOptions(value) # If value is not an integer, return feedback except ValueError as e: print( "Incompatible simulation time given. Expected a positive non-zero float or integer." ) self.setRunBtnOptions(value) else: # Leave simulation time value unchanged. pass print(self.runButtonParameters) def displayHelpURL(self): QDesktopServices.openUrl( QtCore.QUrl( "https://github.com/petercorke/bdsim/blob/master/bdsim/bdedit/README.md" ) ) # ----------------------------------------------------------------------------- def runButton(self): self.saveToFile() main_block_found = False # Go through blocks within scene, if a main block exists, extract the file_name from the main block for block in self.centralWidget().scene.blocks: if block.block_type in ["Main", "MAIN"]: main_block_found = True main_file_name = block.parameters[0][2] break # Convert the GUI simulation options to command line args args = [] for key, value in self.runButtonParameters.items(): arg = key.lower() if isinstance(value, bool): if not arg: arg = "no-" + arg args.append("--" + arg) else: if isinstance(value, str): if len(value) > 0: value = '"' + value + '"' args.append(f"--{arg}={value}") print(args) print(self.args) if main_block_found: # Check if given file_name from the main block, contains a file extension bdfile = Path(self.filename) mainfile = Path(main_file_name) if not mainfile.is_absolute(): mainfile = bdfile.resolve().with_name(mainfile.name) mainfile = mainfile.with_suffix(".py") # file_name, extension = os.path.splitext(main_file_name) # if not extension: # main_file_name = os.path.join(main_file_name + ".py") model_name = os.path.basename(self.filename) if not mainfile.is_file(): print(f"Main block detected: file {main_file_name} could not be opened") return command = ["python"] if self.args.pdb: command.extend(["-m", "pdb"]) command.extend([str(mainfile), str(bdfile)]) command.extend(args) else: model_name = os.path.basename(self.filename) command = ["bdrun", model_name] command.extend(args) print("\n" + "#" * 100) print(f"{datetime.datetime.now()}:: {' '.join(command)}") try: subprocess.Popen(command, shell=False) except (ValueError, OSError): print(f"failed to spawn subprocess") # ----------------------------------------------------------------------------- def abortButton(self): # Added function for handling what the abort button does when pressed. print( "Abort button pressed. Functionality yet to be implemented. Function in 'interface_manager' under 'runButton' function" ) pass # ----------------------------------------------------------------------------- def updateSimTime(self): # This function is called when the Simulation Time value has been changed in the toolbar text widget. sim_time = self.simTimeBox.text() try: # If simulation time is positive integer, update value if float(sim_time) > 0: self.runButtonParameters["SimTime"] = float(sim_time) self.simTimeBox.setText(str(self.runButtonParameters["SimTime"])) self.interface.scene.sim_time = float(sim_time) # Else return feedback else: print( "Incompatible simulation time given. Expected a positive non-zero float or integer." ) # If value is not an integer, return feedback except ValueError as e: print( "Incompatible simulation time given. Expected a positive non-zero float or integer." ) print(self.runButtonParameters) # ----------------------------------------------------------------------------- def newFile(self): if self.exitingWithoutSave(): # Clear scene and all its elements. Reset simulation time parameters self.centralWidget().scene.clear() self.runButtonParameters = { "SimTime": 10.0, "Graphics": True, "Animation": True, "Verbose": False, "Progress": True, "Debug": "", } self.simTimeBox.setText(str(self.runButtonParameters["SimTime"])) self.interface.scene.sim_time = self.runButtonParameters["SimTime"] # Reset filename and update GUI to display default file name self.filename = None self.updateApplicationName() # Reset history stack self.centralWidget().scene.history.clear() self.centralWidget().scene.history.storeInitialHistoryStamp() # ----------------------------------------------------------------------------- def loadFromFilePath(self, filepath): """ This method is only used when loading a file from the command line. It will check if the file at the given path exists, and if so, will load its contents. """ if self.exitingWithoutSave(): # Check if file at given path exists, if so, run the deserializing method if os.path.isfile(filepath): self.centralWidget().scene.loadFromFile(filepath) self.filename = filepath self.updateApplicationName() self.centralWidget().scene.history.clear() self.centralWidget().scene.history.storeInitialHistoryStamp() # ----------------------------------------------------------------------------- def loadFromFile(self): """ This method opens a QFileDialog window, prompting the user to select a file to load from. """ if self.exitingWithoutSave(): # The filename of the selected file is grabbed fname, filter = QFileDialog.getOpenFileName(self) if fname == "": return # And the method for deserializing from a file is called, feeding in the # extracted filename from above if os.path.isfile(fname): self.centralWidget().scene.loadFromFile(fname) self.filename = fname self.updateApplicationName() # Update SimTime in runButtonParameters in case it was set in model self.runButtonParameters["SimTime"] = self.interface.scene.sim_time self.simTimeBox.setText(str(self.runButtonParameters["SimTime"])) # ----------------------------------------------------------------------------- def saveToFile(self): """ This method calls the method from within the ``Scene`` to save a copy of the current Scene, with all its items under a file with the current filename. If this is the first time a user is saving their file, they will be prompted to name the file and to choose where it will be saved. """ if self.filename is None: return self.saveAsToFile() self.centralWidget().scene.saveToFile(self.filename) self.updateApplicationName() return True # ----------------------------------------------------------------------------- def saveAsToFile(self): """ This method opens a QFileDialog window, prompting the user to enter a name under which the current file will be saved. This file will automatically be given a .json file type. """ # The allowable file types are defined below file_types = "bdedit files(*.bd);;JSON files (*.json)" fname, _ = QFileDialog.getSaveFileName(self, "untitled.bd", filter=file_types) # The filename is extracted from the QFileDialog if fname == "": return False # The filename of the scene is stored as a variable inside the Interface, and # the self.saveToFile method is called (which will call the self.scene.saveToFile # method from within the Scene, which will serialize the contents of the Scene # into a JSON file with the provided file name). self.filename = fname self.saveToFile() return True # ----------------------------------------------------------------------------- def exportAsToFile(self, fileType): self.miscScreenshot(fileType) # ----------------------------------------------------------------------------- def editUndo(self): self.interface.scene.history.undo() def editRedo(self): self.interface.scene.history.redo() def editDelete(self): if self.interface: self.interface.canvasView.deleteSelected() self.interface.canvasView.intersectionTest() # ----------------------------------------------------------------------------- def miscFlip(self): if self.interface: self.interface.canvasView.intersectionTest() self.interface.canvasView.flipBlockSockets() def miscEnableOverlaps(self): if self.interface: self.interface.scene.grScene.enable_intersections = ( not self.interface.scene.grScene.enable_intersections ) def miscScreenshot(self, fileType): if self.interface: if self.filename is None: print( "Please save your model before taking a screenshot, then try again." ) self.saveToFile() else: self.interface.save_image( self.filename, picture_name=None, picture_format=fileType ) def miscHideConnectors(self): if self.interface: if self.actHideConnectors.isChecked(): # Set variable for hiding connector blocks to True self.interface.scene.hide_connector_blocks = True else: # Set variable for hiding connector blocks to False self.interface.scene.hide_connector_blocks = False def miscToggleBackground(self): """ This method is called to cycle through various background and grid options. """ # possible modes modes = [("grey", True), ("white", True), ("white", False)] self.bgmode = (self.bgmode + 1) % len(modes) # update current mode mode = modes[self.bgmode] self.interface.scene.grScene.updateBackgroundMode(*mode) # # For each block within the Scene, the mode of their outline is also updated # for eachBlock in self.interface.scene.blocks: # # If the block has a mode (Connector Blocks do not) # if not (eachBlock.block_type == "CONNECTOR" or eachBlock.block_type == "Connector"): # # eachBlock.grBlock.updateBackgroundMode(self.actDisableBackground.isChecked()) # eachBlock.grBlock.updateBackgroundMode(mode[0], mode[1]) # ----------------------------------------------------------------------------- def textAlignment(self, alignment): if self.interface.scene.floating_labels: # Make a map of alignment text to actual Qt alignments map = { "AlignLeft": Qt.AlignLeft, "AlignCenter": Qt.AlignCenter, "AlignRight": Qt.AlignRight, } # Iterate through each floating label item and if the label is selected, # then set the alignment of its contents for label in self.interface.scene.floating_labels: if self.checkSelection(label): label.content.text_edit.setAlignment(map[alignment]) self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label changed alignment" ) self.updateToolbarValues() def textBold(self): if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): if self.actBoldText.isChecked(): label.content.text_edit.setFontWeight(QFont.Bold) else: label.content.text_edit.setFontWeight(QFont.Normal) label.content.updateShape() self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label changed boldness" ) def textUnderline(self): if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): if self.actUnderLineText.isChecked(): label.content.text_edit.setFontUnderline(True) else: label.content.text_edit.setFontUnderline(False) label.content.updateShape() self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label changed underline" ) def textItalicize(self): if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): if self.actItalicText.isChecked(): label.content.text_edit.setFontItalic(True) else: label.content.text_edit.setFontItalic(False) label.content.updateShape() self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label changed italics" ) def textFontStyle(self): (font, ok) = QFontDialog.getFont() # print("ok, font name, font size:", [ok, font.family(), font.styleName(), font.pointSize()]) if ok: if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): label.content.text_edit.setFont(font) label.content.text_edit.setFontWeight(font.weight()) label.content.currentFontSize = font.pointSize() label.content.updateText() label.grContent.setLabelSizeBox() label.content.updateShape() self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label changed font style" ) def textFontSize(self): if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): value = self.fontSizeBox.value() label.content.text_edit.setFontPointSize(value) label.content.currentFontSize = value label.content.updateShape() self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label changed font size" ) def textColor(self): color = QColorDialog.getColor(options=QColorDialog.ShowAlphaChannel) if color.isValid(): if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): label.content.text_edit.setTextColor(color) self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label changed font color" ) # self.updateToolbarValues() # Enable this if you ever make the font color icon update # Clears all format on selected floating labels, reverting to default format def removeFormat(self): if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): label.content.setDefaultFormatting() label.content.updateText() self.interface.scene.has_been_modified = True self.interface.scene.history.storeHistory( "Floating label cleared formatting" ) self.updateToolbarValues() # This function checks if the current label is selected def checkSelection(self, label): if label.grContent.isSelected(): label.content.text_edit.selectAll() return True return False # This function contains the logic for when to select/unselect toolbar items def updateToolbarValues(self): selected_labels = [] any_bold = False any_italics = False any_underlined = False if self.interface.scene.floating_labels: for label in self.interface.scene.floating_labels: if self.checkSelection(label): selected_labels.append(label) # If any label is bold, or italicized, or underlined, # when mutliple labels are selected the respective icon will be selected if label.content.text_edit.fontWeight() > 50: any_bold = True if label.content.text_edit.fontItalic(): any_italics = True if label.content.text_edit.fontUnderline(): any_underlined = True if len(selected_labels) == 0: # If no labels are selected, unselects all toolbar items (alignment, bold, italics, underline) self.fontSizeBox.setValue(14) self.actBoldText.setChecked(False) self.actItalicText.setChecked(False) self.actUnderLineText.setChecked(False) self.unselectAlignmentIcons() elif len(selected_labels) == 1: # If only one label is selected, sets the correct values of all toolbar items based on the # label's value (alignment, bold, italics, underline) our_label = selected_labels[0].content self.fontSizeBox.setValue(our_label.currentFontSize) self.actBoldText.setChecked(our_label.text_edit.fontWeight() > 50) self.actItalicText.setChecked(our_label.text_edit.fontItalic()) self.actUnderLineText.setChecked(our_label.text_edit.fontUnderline()) self.actAlignLeft.setChecked( our_label.text_edit.alignment() == Qt.AlignLeft ) self.actAlignCenter.setChecked( our_label.text_edit.alignment() == Qt.AlignCenter ) self.actAlignRight.setChecked( our_label.text_edit.alignment() == Qt.AlignRight ) else: # If multiple labels are selected, font size box and alignment options are cleared # but bold, italics, underline are selected/unselected depending on if any of the selected # labels are bold, italicizied or underlined. self.fontSizeBox.clear() self.actBoldText.setChecked(any_bold) self.actItalicText.setChecked(any_italics) self.actUnderLineText.setChecked(any_underlined) self.unselectAlignmentIcons() def unselectAlignmentIcons(self): self.actAlignLeft.setChecked(False) self.actAlignCenter.setChecked(False) self.actAlignRight.setChecked(False) ================================================ FILE: bdsim/bdedit/interface_scene.py ================================================ # Library imports import json import time import getpass from PIL import ImageFont from collections import OrderedDict # PyQt5 imports from PyQt5.QtCore import Qt from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QMessageBox, QWidget, QVBoxLayout # BdEdit imports from bdsim.bdedit.block import * from bdsim.bdedit.Icons import * from bdsim.bdedit.block_wire import Wire from bdsim.bdedit.block_main_block import Main from bdsim.bdedit.grouping_box import Grouping_Box from bdsim.bdedit.floating_label import Floating_Label from bdsim.bdedit.block_connector_block import Connector from bdsim.bdedit.interface_serialize import Serializable from bdsim.bdedit.interface_scene_history import SceneHistory from bdsim.bdedit.interface_graphics_scene import GraphicsScene # ============================================================================= # # Defining the Scene Class, which holds information of all the Blocks and # Wires that exist within it. # # ============================================================================= class Scene(Serializable): """ The ``Scene`` Class extends the ``Serializable`` Class from BdEdit, and holds the information of all the ``Block`` and ``Wire`` instances that are within it. It also handles the storage of intersection points of the wires. This class includes information about the: - blocks, a list containing all ``Block`` instances - wires, a list containing all ``Wire`` instances - floating labels, a list containing all the ``Floating Label`` instances - grouping boxes, a list containing all the ``Grouping Box`` instances - intersection points, a list containing all intersection points between Wires """ # ----------------------------------------------------------------------------- def __init__(self, resolution, window, main_window): """ This method initializes an instance of the ``Scene`` Class. :param resolution: the desktop screen resolution of the user :type resolution: PyQt5.QtCore.QRect(0, 0, screen_width, screen_height), required :param window: the application's layer manager :type window: QGridLayout, required """ super().__init__() # The application's layer manager and interface manager are assigned to internal variables self.window = window self.main_window = main_window # Empty lists for the blocks, wires and intersection points are initialized self.blocks = [] self.wires = [] self.floating_labels = [] self.grouping_boxes = [] self.intersection_list = [] # Set default font size of block names, to be used when they are spawned self.block_name_fontsize = 12 # Variables to listen for modifications with in the scene self._has_been_modified = False # Initialize listener self._has_been_modified_listeners = [] # Variable for toggling between connector blocks being visible or not # False by default self.hide_connector_blocks = False # The scened dimensions are initially set to the width/height of the desktop # screen, but later adjusted with self.updateSceneDimensions() self.scene_width = resolution.width() self.scene_height = resolution.height() self.initUI() self.determineFont() self.history = SceneHistory(self) self.history.storeHistory("Blank canvas loaded") # Todo - add doc for this method # ----------------------------------------------------------------------------- @property def has_been_modified(self): return self._has_been_modified # Todo - add doc for this method # ----------------------------------------------------------------------------- @has_been_modified.setter def has_been_modified(self, value): if not self._has_been_modified and value: self._has_been_modified = value # call all registered listeners for callback in self._has_been_modified_listeners: callback() self._has_been_modified = value # ----------------------------------------------------------------------------- def initUI(self): """ This method creates an ``GraphicsScene`` instance and associates it to this ``Scene`` instance. The GraphicsScene dictates how all items within the Scene are visually represented. """ # A GraphicsScene is created and mapped to this Scene, # and the scene dimensions are updated self.grScene = GraphicsScene(self) self.updateSceneDimensions() # Create variables to record when and by whom this diagram was created self.creation_time = int(time.time()) self.created_by = getpass.getuser() # Copy variable of simulation time from interface manager for saving # Initialized to 10.0, overwritten when changed in GUI or if loaded from model self.sim_time = 10.0 # ----------------------------------------------------------------------------- def determineFont(self): """ This method sets the truetype font with which socket labels should be drawn. Originally, this property was set within the sockets themselves, but when they are constantly drawn/redrawn (i.e. when undo/redo is spammed) and as a result, multiple attemps are made to access the truetype font, Windows issues an OSError crashing the program... So since this font only needs to be defined on startup, the font is accessed whenever the scene is created. """ try: self._system_font = ImageFont.truetype("arial.ttf", 14) except OSError: self._system_font = ImageFont.load_default() # ----------------------------------------------------------------------------- def addHasBeenModifiedListener(self, callback: "function"): self._has_been_modified_listeners.append(callback) # ----------------------------------------------------------------------------- def addBlock(self, block): """ This method adds a ``Block`` to the ``Scene's`` list of blocks. """ self.blocks.append(block) # ----------------------------------------------------------------------------- def addWire(self, wire): """ This method adds a ``Wire`` to the ``Scene's`` list of wires. """ self.wires.append(wire) # ----------------------------------------------------------------------------- def addLabel(self, label): """ This method adds a ``Floating Label`` to the ``Scene's`` list of labels. """ self.floating_labels.append(label) # ----------------------------------------------------------------------------- def addGBox(self, GBox): """ This method adds a ``Grouping Box`` to the ``Scene's`` list of grouping boxes. """ self.grouping_boxes.append(GBox) # ----------------------------------------------------------------------------- def removeBlock(self, block): """ This method removes a ``Block`` to the ``Scene's`` list of blocks. """ self.blocks.remove(block) # ----------------------------------------------------------------------------- def removeWire(self, wire): """ This method removes a ``Wire`` to the ``Scene's`` list of wires. """ self.wires.remove(wire) # ----------------------------------------------------------------------------- def removeLabel(self, label): """ This method removes a ``Floating Label`` to the ``Scene's`` list of labels. """ self.floating_labels.remove(label) # ----------------------------------------------------------------------------- def removeGBox(self, GBox): """ This method removes a ``Grouping Box`` to the ``Scene's`` list of grouping boxes. """ self.grouping_boxes.remove(GBox) # ----------------------------------------------------------------------------- def getSceneWidth(self): """ This method returns the current width of the ``Scene``. """ return self.scene_width # ----------------------------------------------------------------------------- def getSceneHeight(self): """ This method returns the current height of the ``Scene``. """ return self.scene_height # ----------------------------------------------------------------------------- def setSceneWidth(self, width): """ This method sets the current width of the ``Scene``, to the given width. """ self.scene_width = width # ----------------------------------------------------------------------------- def setSceneHeight(self, height): """ This method sets the current height of the ``Scene``, to the given height. """ self.scene_height = height # ----------------------------------------------------------------------------- def updateSceneDimensions(self): """ This method sets the dimensions of the ``Scene`` to the currently set scene_width and scene_height. """ self.grScene.setGrScene(self.scene_width, self.scene_height) # ----------------------------------------------------------------------------- def getView(self): """ This method returns the associated ``GraphicsView`` for this ``Scene``. :return: ``GraphicsView`` associated to this ``Scene`` :rtype: ``QGraphicsView`` """ self.grScene.views()[0] # ----------------------------------------------------------------------------- def clear(self): """ This method removes all blocks and floating text labels from the list of blocks and list of floating labels respectively, within the ``Scene``. This will subsequently remove any and all wires between these blocks. """ # Removes the first block from the self.blocks array, until the array is empty while len(self.blocks) > 0: self.blocks[0].parameterWindow.setVisible(False) self.blocks[0].remove() # Removes the first label from self.floating_labels array, until it is empty while len(self.floating_labels) > 0: self.floating_labels[0].remove() # Removes the first GBox from self.grouping_boxes array, until it is empty while len(self.grouping_boxes) > 0: self.grouping_boxes[0].remove() self.has_been_modified = False # ----------------------------------------------------------------------------- def checkForDuplicates(self, name): """ This method checks if the given name would be a duplicate of an existing block name. :param name: the desired name for a ``Block`` :type name: str, required :return: - False (if given name is not a duplicate) - True (if given name is a duplicate) :rtype: bool """ # Duplicate found is initialized to False duplicate = False # For each block within the list of blocks for block in self.blocks: # If the given name matches the title of a block if name == block.title: # Change the duplicate found variable to True, and end the search duplicate = True break # Return the duplicate variable return duplicate # ----------------------------------------------------------------------------- def saveToFile(self, filename): """ This method saves the contents of the ``Scene`` instance into a JSON file under the given filename. This method will call upon the self.serialize() method which will subsequently call the self.serialize() method within each item displayed in the ``Scene`` (these being the ``Block``, ``Wire`` and ``Socket``). :param filename: name of the file to save into :type filename: str, required """ with open(filename, "w") as file: file.write(json.dumps(self.serialize(), indent=4)) self.has_been_modified = False # ----------------------------------------------------------------------------- def loadFromFile(self, filename): """ This method loads the contents of a saved JSON file with the given filename into an instance of the ``Scene``. This method will call upon the self.deserialize() method which will subsequently call the self.deserialize() method within each item that should be reconstructed for the ``Scene`` (these being the ``Block``, ``Wire`` and ``Socket``). :param filename: name of the file to load from :type filename: str """ with open(filename, "r") as file: raw_data = file.read() data = json.loads(raw_data) self.deserialize(data, self.window) self.has_been_modified = False # ----------------------------------------------------------------------------- def serialize(self): """ This method is called to create an ordered dictionary of all of this Scenes' parameters - necessary for the reconstruction of this Scene - as key-value pairs. This dictionary is later used for writing into a JSON file. :return: an ``OrderedDict`` of [keys, values] pairs of all essential ``Scene`` parameters. :rtype: ``OrderedDict`` ([keys, values]*) """ # The blocks and wires associated with this scene, have their own parameters # that are required for their reconstruction, so the serialize method within the # Block and Wire classes are called to package this information respectively, # also into an OrderedDict. These ordered dictionaries are then stored in a temporary # blocks/wires variable and are returned as part of the OrderedDict of this Scene. blocks, wires, labels, gboxes = [], [], [], [] for block in self.blocks: blocks.append(block.serialize()) # # If parameter window still opened for any block, close it # if block.parameterWindow: # if block.parameterWindow.isVisible(): # block.closeParamWindow() for wire in self.wires: wires.append(wire.serialize()) for label in self.floating_labels: labels.append(label.serialize()) for gbox in self.grouping_boxes: gboxes.append(gbox.serialize()) return OrderedDict( [ ("id", self.id), ("created_by", self.created_by), ("creation_time", self.creation_time), ("simulation_time", self.sim_time), ("scene_width", self.scene_width), ("scene_height", self.scene_height), ("blocks", blocks), ("wires", wires), ("labels", labels), ("grouping_boxes", gboxes), ] ) # ----------------------------------------------------------------------------- def deserialize(self, data, hashmap={}): """ This method is called to reconstruct a ``Scene`` and all its items when loading a saved JSON. :param data: a Dictionary of essential information for reconstructing a ``Scene`` :type data: OrderedDict, required :param hashmap: a Dictionary for directly mapping the essential scene variables to this instance of ``Scene``, without having to individually map each variable :type hashmap: Dict, required :return: True when completed successfully :rtype: bool """ # The current scene is cleared if anything inside it exists self.clear() # Extract data of who created this diagram and when try: self.creation_time = data["creation_time"] self.created_by = data["created_by"] # If that information isn't stored in the json file, set it to current user and time except: self.creation_time = int(time.time()) self.created_by = getpass.getuser() # If sim_time parameter exists in model, load it try: self.sim_time = data["simulation_time"] # Otherwise, ignore as model will be updated on save except: pass hashmap = {} # All the blocks which were saved, are re-created from the JSON file # For each block from the saved blocks for block_data in data["blocks"]: block_type = block_data["block_type"] # If a block is one that is manually defined by bdedit (such as the connector # or main blocks, or the text item), they must manaully be re-created. if block_type == "CONNECTOR" or block_type == "Connector": Connector(self, self.window).deserialize(block_data, hashmap) elif block_type == "MAIN" or block_type == "Main": Main(self, self.window).deserialize(block_data, hashmap) # Otherwise if it is any other block (will be an auto-imported block) else: # For each block class within the blocklist for block_class in blocklist: # Re-create an instance of a block class that matches a name of one of the blocks # from the blocklist. There will always be a match, as the block_type is determined # by the blockname(block_class) in the first place, and this will never change. # The block_class.__name__ supports older files which would of used self.__class__.__name__ # to define the block type if ( block_type == blockname(block_class) or block_type == block_class.__name__ ): block_class().deserialize(block_data, hashmap) break # Next recreate all the wires that were saved for wire_data in data["wires"]: Wire(self).deserialize(wire_data, hashmap) # Lastly, if it exists, add the floating text labels that were saved try: if data["labels"]: # If the data for the labels is not null, then create the labels for label_data in data["labels"]: if label_data is not None: Floating_Label(self, self.window, self.main_window).deserialize( label_data, hashmap ) except KeyError: # If model data doesn't contain 'labels' then none were saved, so don't create any. pass # Finally, if it exists, add the grouping boxes that were saved try: if data["grouping_boxes"]: # If the data for the gboxes is not null, then create the grouping boxes for gbox_data in data["grouping_boxes"]: if gbox_data is not None: # Ensure essnetial Grouping Box info exists; if it does not, we cannot fully reconstruct Grouping Box, so ignore if ( gbox_data["width"] and gbox_data["height"] and gbox_data["color"] ): if (gbox_data["pos_x"] is not None) and ( gbox_data["pos_y"] is not None ): pos = (gbox_data["pos_x"], gbox_data["pos_y"]) Grouping_Box( self, self.window, gbox_data["width"], gbox_data["height"], gbox_data["color"], pos, ).deserialize(gbox_data, hashmap) else: Grouping_Box( self, self.window, gbox_data["width"], gbox_data["height"], gbox_data["color"], ).deserialize(gbox_data, hashmap) except KeyError: # If model data doesn't contain 'grouping_boxes' then none were saved, so don't create any. pass return True ================================================ FILE: bdsim/bdedit/interface_scene_history.py ================================================ # Library imports import sys import traceback # BdEdit imports from bdsim.bdedit.block_graphics_wire import GraphicsWire DEBUG = False DEBUG_SELECTION = False class SceneHistory: def __init__(self, scene): self.scene = scene self.clear() self.history_limit = 32 # listeners self._history_modified_listeners = [] self._history_stored_listeners = [] self._history_restored_listeners = [] # Internal variable for catching fatal errors, and allowing user to save work before crashing self.FATAL_ERROR = False def clear(self): self.history_stack = [] self.history_current_step = -1 def storeInitialHistoryStamp(self): # After model has been loaded, add current deserialized state as the first stamp in the history stack self.storeHistory("Initial History Stamp") def addHistoryModifiedListener(self, callback: "function"): self._history_modified_listeners.append(callback) def addHistoryStoredListener(self, callback: "function"): self._history_stored_listeners.append(callback) def addHistoryRestoredListener(self, callback: "function"): self._history_restored_listeners.append(callback) def undo(self): if DEBUG: print("UNDO") # Only able to undo steps if current pointer is not on first item of the history stack if self.history_current_step > 0: self.history_current_step -= 1 self.restroreHistory() self.scene.has_been_modified = True def redo(self): if DEBUG: print("REDO") # Only able to redo steps if current pointer is not on last item of the history stack if self.history_current_step + 1 < len(self.history_stack): self.history_current_step += 1 self.restroreHistory() self.scene.has_been_modified = True def restroreHistory(self): if DEBUG: print( "Resorting history ... current_step: @%d" % self.history_current_step, "(%d)" % len(self.history_stack), ) self.restoreHistoryStamp(self.history_stack[self.history_current_step]) for callback in self._history_modified_listeners: callback() for callback in self._history_restored_listeners: callback() def storeHistory(self, desc, setModified: bool = False): if setModified: self.scene.has_been_modified = True if DEBUG: print( "Storing history", '"%s"' % desc, "... current_step: @%d" % self.history_current_step, "(%d)" % len(self.history_stack), ) # If pointer (history_current_step), is not at end of history_stack and a change is made, # then we must remove everything ahead of the pointer, as we've overwritten previous changes if self.history_current_step + 1 < len(self.history_stack): # Remove everything thats currently in the history stack ahead of the pointer self.history_stack = self.history_stack[0 : self.history_current_step + 1] # If history stack is larger than our history limit if self.history_current_step + 1 >= self.history_limit: # Remove first item in history stack and add new item to end of list self.history_stack = self.history_stack[1:] self.history_current_step -= 1 hs = self.createHistoryStamp(desc) self.history_stack.append(hs) self.history_current_step += 1 if DEBUG: print(" -- setting step to:", self.history_current_step) for callback in self._history_modified_listeners: callback() for callback in self._history_stored_listeners: callback() def captureCurrentSelection(self): sel_obj = { "blocks": [], "wires": [], "floating_labels": [], "grouping_boxes": [], } for item in self.scene.grScene.selectedItems(): if hasattr(item, "block"): sel_obj["blocks"].append(item.block.id) elif hasattr(item, "wire"): sel_obj["wires"].append(item.wire.id) elif hasattr(item, "floating_label"): sel_obj["floating_labels"].append(item.floating_label.id) elif hasattr(item, "grouping_box"): sel_obj["grouping_boxes"].append(item.grouping_box.id) return sel_obj def createHistoryStamp(self, desc): history_stamp = { "desc": desc, "snapshot": self.scene.serialize(), "selection": self.captureCurrentSelection(), } return history_stamp def restoreHistoryStamp(self, history_stamp): if DEBUG: print("RHS: ", history_stamp["desc"]) try: self.scene.deserialize(history_stamp["snapshot"]) # restore selection # first clear selection on all wires, then restore selection on wires from history_stamp for wire in self.scene.wires: wire.grWire.setSelected(False) for wire_id in history_stamp["selection"]["wires"]: for wire in self.scene.wires: if wire.id == wire_id: wire.grWire.setSelected(True) break # first clear selection on all blocks, then restore selection on blocks from history_stamp for block in self.scene.blocks: block.grBlock.setSelected(False) for block_id in history_stamp["selection"]["blocks"]: for block in self.scene.blocks: if block.id == block_id: block.grBlock.setSelected(True) break # first clear selection on all floating labels, then restore selection on floating labels from history_stamp for label in self.scene.floating_labels: label.grContent.setSelected(False) for label_id in history_stamp["selection"]["floating_labels"]: for label in self.scene.floating_labels: if label.id == label_id: label.grContent.setSelected(True) break # first clear selection on all grouping boxes, then restore selection on grouping boxes from history_stamp for gbox in self.scene.grouping_boxes: gbox.grGBox.setSelected(False) for gbox_id in history_stamp["selection"]["grouping_boxes"]: for gbox in self.scene.grouping_boxes: if gbox.id == gbox_id: gbox.grGBox.setSelected(True) break except Exception as e: if self.FATAL_ERROR == False: print( "-------------------------------------------------------------------------" ) print( "Caught fatal exception while trying to undo/redo. " "\nThis may have caused unsaved changes to become corrupted, apologies. " "\nPlease note the error and report it to Daniel." ) print( "-------------------------------------------------------------------------" ) traceback.print_exc(file=sys.stderr) self.FATAL_ERROR = True ================================================ FILE: bdsim/bdedit/interface_serialize.py ================================================ class Serializable: """ The ``Serializable`` class provides three essential methods for: ensuring uniqueness amongst necessary data (1), saving (2) and loading (3) of this data that is needed for the reconstruction of a Block Diagram. """ def __init__(self): """ This method extracts the unique identification number of the class instance that calls this method, and stores it as a variable within that class instance. """ self.id = id(self) def serialize(self): """ This method is inherited and overwritten by all classes that have graphical components related to them (``Scene``, ``Block``, ``Socket`` and ``Wire``). This allows those classes to package (save) essential variables necessary for the later reconstruction of those class instances, into a JSON file. """ raise NotImplemented() def deserialize(self, data, hashmap={}): """ This method is inherited and overwritten by all classes that have graphical components related to them (``Scene``, ``Block``, ``Socket`` and ``Wire``). This allows those classes to un-package (load) the data stored within the saved JSON file in order to reconstruct the respective class instance. :param data: a Dictionary of essential data for reconstructing a Block Diagram :type data: OrderedDict, required :param hashmap: a Dictionary for of the same data, but used for simpler mapping of variables to class instances :type hashmap: Dict, required """ raise NotImplemented() ================================================ FILE: bdsim/bdrun.py ================================================ import json import sys import traceback from bdsim import BDSim from colored import fg, attr # available for use in bdedit expressions import numpy as np import math from math import pi try: from spatialmath import SE3, SE2 except: pass def bdload(bd, filename, globalvars={}, verbose=False, **kwargs): """ Load a block diagram model :param bd: block diagram to load into :type bd: BlockDiagram instance :param filename: name of JSON file to load from :type filename: str or Path :param globalvars: global variables for evaluating expressions, defaults to {} :type globalvars: dict, optional :param verbose: print parameters of all blocks as they are instantiated, defaults to False :type verbose: bool, optional :raises RuntimeError: unable to load the file :raises ValueError: unable to load the file :return: the loaded block diagram :rtype: BlockDiagram instance Block diagrams are saved as JSON files. A number of errors can arise at this stage: * a parameter starting with "=" cannot be evaluated * the block throws an error when instantiated, incorrect parameter values * unconnected input port If the JSON file contains a parameter of the form ``"=expression"`` then it is evaluated using ``eval`` with the global name space given by ``globalvars``. This means that you can embed lambda expressions that use functions/classes defined in your module if ``globalargs`` is set to ``globals()``. """ # load the JSON file with open(filename, "r") as f: model = json.load(f) # result is a dict with elements: blocks, wires # load the blocks and build mappings # blocks and wires have unique ids. # block input and output ports have an associated socket id # each wire is specified by the socket ids of its start and end output_dict = {} # block output id -> Plug connector_dict = {} # connector block: input socket -> output socket wire_dict = {} # wire: start socket t-> end socket block_dict = {} # block: block id -> Block instance namespace = {**globals(), **globalvars} # create a dictionary of all blocks for block in model["blocks"]: # Connector block, create a dict that maps end port id to start port id if block["block_type"] == "CONNECTOR": start = block["inputs"][0]["id"] end = block["outputs"][0]["id"] connector_dict[end] = start elif block["block_type"] == "MAIN": continue # nothing to be done else: # regular bdsim Block try: block_init = bd.__dict__[block["block_type"]] # block class except KeyError: print(fg("red")) print(f"block [{block['block_type']}] not loaded, check BDSIMPATH") print(attr(0)) params = dict(block["parameters"]) # block params as a dict if verbose: print(f"[{block['title']}]:") # process the parameters default_params = [] for key, value in params.items(): if verbose: print(f" {key}: ", end="") newvalue = None if isinstance(value, str): # either an "any" type or an assignment if value[0] == "=": # assignment try: newvalue = eval(value[1:], namespace) except (ValueError, TypeError, NameError, SyntaxError): print(fg("red")) print( f"bdload: error resolving parameter {key}: {value} for" f" block [{block['title']}]" ) traceback.print_exc(limit=-1, file=sys.stderr) print(attr(0)) raise RuntimeError( f"cannot instantiate block [{block['title']}] - bad" " parameters?" ) else: # assume it's an "any" type, attempt to evaluate it try: newvalue = eval(value, namespace) except (NameError, SyntaxError): pass else: newvalue = value if newvalue is None: # default_params.append(key) if verbose: print(f" {value} default") else: params[key] = newvalue if verbose: print(f" {value} -> {newvalue}") # for key in default_params: # del params[key] # instantiate the block try: if "blockargs" in params: blockargs = params["blockargs"] del params["blockargs"] else: blockargs = {} blockargs = blockargs or {} newblock = block_init( name=block["title"], **params, **blockargs ) # instantiate the block except ( ValueError, TypeError, NameError, SyntaxError, AssertionError, AttributeError, ): print(fg("red")) print(f"bdload: error instantiating block [{block['title']}]") args = ", ".join( [f"{arg[0]} = {arg[1]}" for arg in block["parameters"]] ) print(f" {block['block_type']}({args})") print(attr(0)) raise RuntimeError( f"cannot instantiate block [{block['title']}] - bad parameters?" ) block_dict[block["id"]] = newblock # add to mapping for output in block["outputs"]: # each output id is mapped to the output Plug output_dict[output["id"]] = newblock[output["index"]] # create a dictionary of all wires: map end id -> start id # end id is associated with a block input port (socket) # this maps to a unique output port for wire in model["wires"]: start = wire["start_socket"] end = wire["end_socket"] wire_dict[end] = start # do the wiring for block in model["blocks"]: if block["block_type"] == "CONNECTOR": continue # only process real blocks id = block["id"] for input in block["inputs"]: # for every input port in_id = input["id"] # get the socket id if in_id not in wire_dict: raise ValueError( f"bdload: error block [{block['title']}] has unconnected input port" ) # if input has a wire attached (should have!) start_id = wire_dict[in_id] while start_id in connector_dict: start_id = wire_dict[ connector_dict[start_id] ] # other side of the connector # start_id now refers to a bdsim block output end = block_dict[id][input["index"]] # create an output Plug start = output_dict[start_id] # get Plug it goes to if verbose: print(start, " --> ", end) bd.connect(start, end) return bd def bdrun(filename=None, globals={}, **kwargs): if filename is None: if len(sys.argv) > 1: filename = sys.argv[1] else: print("Usage:\n bdrun file.bd ") return sim = BDSim(**kwargs) # create simulator bd = sim.blockdiagram() # create diagram bd = bdload(bd, filename=filename, globalvars=globals, **kwargs) bd.compile() bd.report() out = sim.run(bd) # simulate print("bdrun exiting") if __name__ == "__main__": bdrun() ================================================ FILE: bdsim/bin/stubgen.py ================================================ #!/usr/bin/env python3 import bdsim import inspect sim = bdsim.BDSim() filename = "blockdiagram.pyi" with open(filename, "w") as f: print("writing stubs --> ", filename) print("""from spatialmath.base.types import * import numpy as np import math class BlockDiagram:""", file=f) for block, info in sim._blocklibrary.items(): meth = info["class"] sig = inspect.signature(meth.__init__) print("\n", file=f) print(f" # {info['module']}.{info['classname']}", file=f) print(f" def {block}{str(sig)}:", file=f) print(' """', end="", file=f) print(meth.__init__.__doc__, end="", file=f) print('\n """\n ...', file=f) ================================================ FILE: bdsim/blockdiagram.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Mon May 18 21:43:18 2020 @author: corkep """ import os from pathlib import Path import sys import importlib import inspect import traceback from collections import Counter, namedtuple from copy import deepcopy import numpy as np from colored import fg, attr import warnings from ansitable import ANSITable, Column from bdsim.components import * # from stubs import BlockDiagramMixin # ------------------------------------------------------------------------- # # class BlockDiagram(BlockDiagramMixin): class BlockDiagram: r""" Block diagram class. This object is the parent of all blocks and wires in the system. :ivar wirelist: all wires in the diagram :vartype wirelist: list of Wire instances :ivar blocklist: all blocks in the diagram :vartype blocklist: list of Block subclass instances :ivar x: state vector :vartype x: np.ndarray :ivar compiled: diagram has successfully compiled :vartype compiled: bool :ivar blockcounter: unique counter for each block type :vartype blockcounter: collections.Counter :ivar blockdict: index of all blocks by category :vartype blockdict: dict of lists :ivar name: name of this diagram :vartype name: str This object: * holds all the blocks and wires that comprise the system * manages continuous- and discrete-time state vector for the whole system, splitting it across blocks as required * evaluates the entire diagram as a function to compute :meth:`\dot{x} = f(x, t)` """ def __init__(self, name="main", **kwargs): self.wirelist = [] # list of all wires self.blocklist = [] # list of all blocks self.clocklist = [] # list of all clock sources self.compiled = False # network has been compiled self.blockcounter = Counter() self.name = name self.nstates = 0 self.ndstates = 0 self._issubsystem = False self.blocknames = {} self.options = None self.n_auto_sum = 0 self.n_auto_prod = 0 self.n_auto_const = 0 self.n_auto_gain = 0 self.n_auto_pow = 0 def __getitem__(self, id): print(id) if isinstance(id, str): return self.blocknames[id] else: for b in self.blocklist: if b.id == id: return b raise ValueError(f"block {id} not found") def __len__(self): return len(self.blocklist) def __deepcopy__(self, memo): # deep copy a block diagram # retain references (don't copy) to blocks and the runtime cls = self.__class__ result = cls.__new__(cls) memo[id(self)] = result for k, v in self.__dict__.items(): if type(v).__name__ == "method": # it's a block factory method setattr(result, k, v) elif k == "runtime": # it's a reference to the runtime setattr(result, k, v) else: # otherwise, do a deepcopy setattr(result, k, deepcopy(v, memo)) return result @property def issubsystem(self): return self._issubsystem def clock(self, *args, **kwargs): clock = Clock(*args, **kwargs) clock.bd = self self.clocklist.append(clock) return clock def add_block(self, block): if block.name in self.blocknames: raise ValueError("block {} already added".format(block.name)) block.id = len(self.blocklist) if block.name is None: i = self.blockcounter[block.type] self.blockcounter[block.type] += 1 block.name = "{:s}.{:d}".format(block.type, i) block.bd = self self.blocklist.append(block) # add to the list of available blocks if block in self.blocknames: raise Warning(f"block name {block} is not unique") self.blocknames[block.name] = block def add_wire(self, wire, name=None): wire.id = len(self.wirelist) wire.name = name # just add wire to the list, gets instantiated at compile time # when add_output_wire and add_input_wire are called on the blocks return self.wirelist.append(wire) def __str__(self): return "BlockDiagram: {:s}".format(self.name) def __repr__(self): return str(self) + " with {:d} blocks and {:d} wires".format( len(self.blocklist), len(self.wirelist) ) # for block in self.blocklist: # s += str(block) + "\n" # s += "\n" # for wire in self.wirelist: # s += str(wire) + "\n" # return s.lstrip("\n") def ls(self): for k, v in self.blockdict.items(): print("{:12s}: ".format(k), ", ".join(v)) def connect(self, start, *ends, name=None): """ TODO: s.connect(out[3], in1[2], in2[3]) # one to many block[1] = SigGen() # use setitem block[1] = SumJunction(block2[3], block3[4]) * Gain(value=2) """ # start.type = 'start' # ensure all blocks are in the blocklist for x in [start, *ends]: if isinstance(x, Block): if x.bd is None: self.add_block(x) elif isinstance(x, Plug): if x.block.bd is None: self.add_block(x.block) for end in ends: if isinstance(start, Block): if isinstance(end, Block): # connect(X, Y) # wires from all outport to all inports assert start.nout == end.nin, ( "can only connect blocks where number of input and output ports" " match" ) for i in range(start.nout): wire = Wire(StartPlug(start, i), EndPlug(end, i), name) self.add_wire(wire) elif isinstance(end, Plug) and not end.isslice: # connect(X, Y[i]) assert ( start.nout == 1 ), "can only connect single output block to a port" end.type = "end" wire = Wire(StartPlug(start, 0), end, name) self.add_wire(wire) elif isinstance(end, Plug) and end.isslice: # connect(X, Y[m:n]) assert start.nout == end.width, ( "can only connect single output block to an input port slice of" " width 1" ) end.type = "end" for i in range(start.nout): wire = Wire(StartPlug(start, i), end[i], name) self.add_wire(wire) else: raise ValueError("bad end type") elif isinstance(start, Plug) and not start.isslice: if isinstance(end, Block): # connect(X[i], Y) # wires from all outport to all inports assert ( end.nin == 1 ), "can only connect a port to a block with single input port" wire = Wire(start, EndPlug(end, 0), name) self.add_wire(wire) elif isinstance(end, Plug) and not end.isslice: # connect(X[i], Y[i]) end.type = "end" wire = Wire(start, end, name) self.add_wire(wire) elif isinstance(end, Plug) and end.isslice: # connect(X[i], Y[m:n]) assert ( end.width == 1 ), "can only connect output port to an input port slice of width 1" end.type = "end" wire = Wire(start, end[0], name) self.add_wire(wire) else: raise ValueError("bad end type") elif isinstance(start, Plug) and start.isslice: if isinstance(end, Block): # connect(X[i:j], Y) assert start.width == end.nin, ( "can only connect output slice to a block with matching number" " of input ports" ) for i in range(end.nin): wire = Wire(start[i], EndPlug(end, i), name) self.add_wire(wire) elif isinstance(end, Plug) and not end.isslice: # connect(X[i:j], Y[m]) assert ( start.width == 1 ), "can only connect output slice of width 1 to a port" wire = Wire(start[0], end, name) self.add_wire(wire) if isinstance(end, Plug) and end.isslice: # connect(X[i:j], Y[m:n]) assert ( start.width == end.width ), "can only connect port slices of same width" for i in range(start.width): wire = Wire(start[i], end[i], name) self.add_wire(wire) else: raise ValueError("bad end type") else: raise ValueError("bad start type") # ---------------------------------------------------------------------- # def compile( self, subsystem=False, doimport=True, evaluate=True, report=False, verbose=True ): """ Compile the block diagram :param subsystem: importing a subsystems, defaults to False :type subsystem: bool, optional :param doimport: import subsystems, defaults to True :type doimport: bool, optional :raises RuntimeError: various block diagram errors :return: Compile status :rtype: bool Performs a number of operations: - Check sanity of block parameters - Recursively clone and import subsystems - Check for loops without dynamics - Check for inputs driven by more than one wire - Check for unconnected inputs and outputs - Link all output ports to outgoing wires - Link all input ports to incoming wires - Evaluate all blocks in the network """ # name the elements self.nblocks = len(self.blocklist) self.nwires = len(self.wirelist) error = False self.nstates = 0 self.ndstates = 0 self.statenames = [] self.dstatenames = [] self.blocknames = {} if not subsystem and verbose: print("\nCompiling:") # process all subsystem imports # ssblocks = [b for b in self.blocklist if b.type == 'subsystem'] # for b in ssblocks: # print(' importing subsystem', b.name) # if b.ssvar is not None: # print('-- Wiring in subsystem', b, 'from module local variable ', b.ssvar) self.blocklist, self.wirelist = self._subsystem_import( self, None, verbose=verbose ) # check that wires all point to valid blocks for w in self.wirelist: if w.start.block not in self.blocklist: raise RuntimeError( f"wire {w} starts at unreferenced block {w.start.block}" ) if w.end.block not in self.blocklist: raise RuntimeError(f"wire {w} ends at unreferenced block {w.end.block}") # run block specific checks for b in self.blocklist: try: b.check() except: raise RuntimeError("block failed check " + str(b)) # build a dictionary of all block names self.blocknames = {b.name: b for b in self.blocklist} # visit all stateful blocks for b in self.blocklist: if b.blockclass == "transfer": self.nstates += b.nstates if b._state_names is not None: assert ( len(b._state_names) == b.nstates ), "number of state names not consistent with number of states" self.statenames.extend(b._state_names) else: # create default state names self.statenames.extend( [b.name + "x" + str(i) for i in range(0, b.nstates)] ) if b.blockclass == "clocked": self.ndstates += b.ndstates if b._state_names is not None: assert ( len(b._state_names) == b.nstates ), "number of state names not consistent with number of states" self.dstatenames.extend(b._state_names) else: # create default state names self.statenames.extend( [b.name + "X" + str(i) for i in range(0, b.nstates)] ) # initialize lists of input and output ports for b in self.blocklist: b.output_wires = [[] for i in range(0, b.nout)] b.input_wires = [None for i in range(0, b.nin)] b.sources = [None for i in range(0, b.nin)] # used to build execution plan # TODO: might overlap with sources b._parents = [None for i in range(0, b.nin)] # connect the source and destination blocks to each wire for w in self.wirelist: try: w.start.block.add_output_wire(w) w.end.block.add_input_wire(w) w.end.block._parents[w.end.port] = w.start.block except: print(fg("red")) print("error connecting wire ", w.fullname + ": ", sys.exc_info()[1]) print(attr(0)) error = True # check connections every block for b in self.blocklist: # check all inputs are connected for port, connection in enumerate(b.input_wires): if connection is None: print( " ERROR: [{:s}] input {:d} is not connected".format( str(b), port ) ) error = True # check all outputs are connected for port, connections in enumerate(b.output_wires): if len(connections) == 0: print( " INFORMATION: [{:s}] output {:d} is not connected".format( str(b), port ) ) if b._inport_names is not None: assert ( len(b._inport_names) == b.nin ), "incorrect number of input names given: " + str(b) if b._outport_names is not None: assert ( len(b._outport_names) == b.nout ), "incorrect number of output names given: " + str(b) if b._state_names is not None: assert ( len(b._state_names) == b.nstates ), "incorrect number of state names given: " + str(b) # check for cycles of function blocks def _DFS(path): start = path[0] tail = path[-1] for outgoing in tail.output_wires: # for every port on this block for w in outgoing: dest = w.end.block if dest == start: print( " ERROR: cycle found: ", " - ".join([str(x) for x in path + [dest]]), ) return True if dest.blockclass == "function": return _DFS(path + [dest]) # recurse return False for b in self.blocklist: if b.blockclass == "function": # do depth first search looking for a cycle if _DFS([b]): error = True if error: if not subsystem: raise RuntimeError("could not compile system") # create the execution plan/schedule self.schedule_generate() ## evaluate the network once to check out wire types x = self.getstate0() for clock in self.clocklist: clock._x = clock.getstate0() if report: self.report() self.schedule_report() if not subsystem and evaluate: # run all the blocks for one step try: self.schedule_evaluate(x, 0.0, sinks=False) except RuntimeError as err: print("\nFrom compile: unrecoverable error in value propagation:", err) traceback.print_exc(file=sys.stderr) error = True if error: # show report if there was an error if not report: self.report() if not subsystem: raise RuntimeError("could not compile system") else: self.compiled = True return self.compiled def _subsystem_import(self, bd, sspath, verbose=False): blocks = [] wires = bd.wirelist for b in bd.blocklist: # rename the block to include subsystem path if sspath is not None: b.name = sspath + "/" + b.name if b.type == "subsystem": # deal with a subsystem # - recurse to import it # - add its blocks and wires to the set if verbose: print("instantiating subsystem ", b.name) ssb, ssw = self._subsystem_import(b.subsystem, b.name) blocks.extend(ssb) wires.extend(ssw) # INPORT/OUTPORT blocks now become simple pass throughs # same number of inputs and outputs b.inport.nin = b.inport.nout b.outport.nout = b.outport.nin # modify the wiring, keep the INPORT/OUTPORT blocks but lose # the SUBSYSTEM blocks for w in bd.wirelist: # for all wires at this level, find those that connect # to the subsystem and tweak them if w.start.block == b: # SS output w.start.block = b.outport if w.end.block == b: # SS input w.end.block = b.inport else: # not a subsystem, just add the block to the list blocks.append(b) # systematically renumber all blocks and wires for i, b in enumerate(blocks): b.id = i for i, w in enumerate(wires): w.id = i return blocks, wires # ---------------------------------------------------------------------- # def schedule_evaluate(self, x, t, checkfinite=True, sinks=True, simstate=None): """ Evaluate all blocks in the network :param x: state :type x: ndarray :param t: current time :type t: float :param checkfinite: check for Inf or Nan values in block outputs :type checkfinite: bool :param sinks: evaluate sink blocks, defaults to Trye :type sinks: bool, optional :param simstate: simulation state :return: state derivative :rtype: numpy.ndarray Performs the following steps: 1. Partition the state vector ``x`` to all stateful blocks 2. Execute the blocks in the order given by the ``plan``. The block outputs are "sent" to their connected inputs. Sink blocks are not executed here, but after completion their inputs will all be valid. """ # TODO: don't copy outputs to inputs of next block, have inputs # pull the value from connected inputs self.runtime.DEBUG("state", ">>>>>>>>> t={}, x={} >>>>>>>>>>>>>>>>", t, x) # reset all the blocks ready for the evalation self.reset() # split the state vector to stateful blocks for b in self.blocklist: if b.blockclass == "transfer": x = b.setstate(x) # split the discrete state vector to clocked blocks for clock in self.clocklist: clock.setstate() self.runtime.DEBUG("propagate", "t={:.3f}", t) for sequence, group in enumerate(self.plan): # self.runtime.DEBUG('propagate', '---- sequence = ', sequence) for b in group: # ask the block for output, check for errors try: if sequence == 0: # blocks called at step 0 have no inputs out = b.output(t, None, b._x) else: out = b.output(t, b.inputs, b._x) except Exception as err: # output method failed, report it print(fg("red")) print( "--Error at t={:f} when computing output of [{:s}::{:s}]".format( t, b.type, str(b) ) ) print() # print(' {}'.format(err)) traceback.print_exc(file=sys.stderr) print() for i, input in enumerate(b.inputs): print(f"Input[{i}] = {input}") if b.nstates > 0: print(f"Block state x = {b._x}") print(attr(0)) raise RuntimeError from None self.runtime.DEBUG("propagate", "block {:s}: output = {}", b, out) # check that output is a list of correct length if not isinstance(out, (tuple, list)): raise AssertionError( f"block {b} output {b} must be a list: {type(out)}" ) if len(out) != b.nout: raise AssertionError( f"block {b} output {b} has incorrect length: {len(out)} instead" f" of {b.nout}" ) # TODO check output validity once at the startq # check it has no nan or inf values if ( checkfinite and isinstance(out, (int, float, np.ndarray)) and not np.isfinite(out).any() ): raise RuntimeError(f"block {b} output contains NaN") # # send block outputs to all downstream connected blocks # for (port, outwires) in enumerate(b.outports): # every port # value = out[port] # for w in outwires: # every wire # self.DEBUG('propagate', ' [{}] = {} --> {}[{}]', port, value, w.end.block.name, w.end.port) # # send value to wire # w.send(value) # # TODO send return status no longer needed # # TODO use common error handler in all cases above b.output_values = out if sinks: for b in self.blocklist: if isinstance(b, SinkBlock): b.step(t, b.inputs) # gather the derivative YD = self.deriv(t) self.runtime.DEBUG("deriv", YD) return YD def schedule_generate(self): """ Create execution plan The plan is saved in the attribute ``plan`` and is a list ``[L0, L1, ... LN]`` where each ``Li`` is a list of blocks. The blocks in the lists are executed sequentially, ie. all the blocks in ``L0`` then all the blocks in ``L1`` etc. The plan ensures that the inputs of all blocks in ``Li`` have been previously computed. .. note:: - The plan is essentially a dataflow graph. - The blocks in list ``Li`` could potentially be executed in parallel. - Constant blocks and stateful blocks are all executed in ``L0`` - The block attribute ``_sequence`` is ``i`` and indicates its execution order :seealso: :func:`schedule_report`, :func:`schedule_dotfile` """ plan = [] group = [] for b in self.blocklist: b._sequence = None if b.blockclass in ("source", "transfer", "clocked"): b._sequence = 0 group.append(b) plan.append(group) sequence = len(plan) while True: group = [] for b in self.blocklist: if b._sequence is not None: continue # already has a sequence assigned if all( [ p._sequence < sequence if p._sequence is not None else False for p in b._parents ] ): group.append(b) for b in group.copy(): b._sequence = sequence if b.blockclass in ("sink", "graphics"): group.remove(b) if len(group) == 0: break plan.append(group) sequence += 1 self.plan = plan def schedule_dotfile(self, filename): """ Write a GraphViz dot file representing the execution schedule :param file: Name of file to write to :type file: str The file can be processed using neato or dot:: % dot -Tpng -o out.png dotfile.dot Display execution plan as a dataflow graph. :seealso: :func:`schedule_plan`, :func:`schedule_print` """ if isinstance(filename, str): file = open(filename, "w") else: file = filename header = r"""digraph G { graph [splines=ortho, rankdir=LR, splines=spline] node [shape=box] """ file.write(header) for sequence, group in enumerate(self.plan): # for each execution group, place the blocks in a subgraph file.write("\tsubgraph step{:d} {{\n".format(sequence)) file.write("\t\trank=same;\n") for b in group: file.write('\t\t"{:s}"\n'.format(b.name)) file.write("\t}\n\n") # connect them to their parents, except if a transfer block for b in self.blocklist: if not b.blockclass == "transfer": for p in b._parents: file.write('\t"{:s}" -> "{:s}"\n'.format(p.name, b.name)) file.write("}\n") # ---------------------------------------------------------------------- # def _debugger(self, simstate=None, integrator=None): if simstate.t_stop is not None and simstate.t < simstate.t_stop: return def print_output(b, t, inports, x): out = b.output(t, inports, x) if len(out) == 1: print(f"{b.name} = {out[0]}") else: print(f"{b.name}:") for i, o in enumerate(out): print(f" [{i}] = {o}") np.set_printoptions(precision=6, linewidth=120) simstate.t_stop = None if not hasattr(self, "debug_watch"): self.debug_watch = None print("\n") if self.debug_watch is not None: t = simstate.t for b in self.debug_watch: print_output(b, t, b.inputs, b._x) while True: try: t = simstate.t cmd = input(f"(bdsim, t={t:.6f}) ") if len(cmd) == 0: continue if cmd[0] == "p": # print variables if len(cmd) > 1: id = int(cmd[1:]) b = self.blocklist[id] print_output(b, t, b.inputs, b._x) else: for b in self.blocklist: if b.nout > 0: print_output(b, t, b.inputs, b._x) elif cmd[0] == "i": print( f"status={integrator.status}, dt={integrator.step_size:.4g}, nfev={integrator.nfev}" ) elif cmd[0] == "s": # step break elif cmd[0] == "c": # continue self.debug_stop = False self.t_stop = None break elif cmd[0] == "t": self.t_stop = float(cmd[1:]) break elif cmd[0] == "q": sys.exit(1) elif cmd[0] == "r": self.report() elif cmd[0] == "w": if len(cmd) == 1: # clear the watch list print(self.debug_watch) self.debug_watch = None else: self.debug_watch = [ self.blocklist[int(s.strip())] for s in cmd[2:].split(" ") ] elif cmd == "pdb": import pdb pdb.runeval('print("type exit to leave Pdb")') elif cmd[0] in "h?": print("p print all outputs") print("pI print block id I output") print("i print integrator status") print("s single step") print("c continue") print("tT stop at or after time T") print("r print block and wires") print("pdb enter PDB debugger") print("w id watch list, display at every step") print("q quit") except (IndexError, ValueError, TypeError): print("??") pass # ---------------------------------------------------------------------- # def report_summary(self, sortby="name", **kwargs): """ Print a summary of block diagram. :param sortby: sort rows by specified block attribute: "name" [default] or "type" :type sortby: str, optional :param style: table style, one of: ansi (default), markdown, latex :type style: str Print a table with 4 columns: 1. Block name, sorted in alphabetical order 2. The input port (if not a source block) 3. The block driving this port (if not a source block) 4. The type of value driving this port (if not a source block) If the block is an event source, add a ``@`` suffix. """ table = ANSITable( Column("block", headalign="^", colalign="<"), Column("type", headalign="^", colalign="<"), Column("inport", headalign="^", colalign="<"), Column("source", headalign="^", colalign="<"), Column("source type", headalign="^", colalign="<"), border="thin", ) if sortby == "name": sortfunc = lambda x: x.name elif sortby == "type": sortfunc = lambda x: x.type first = True legend = None for b in sorted(self.blocklist, key=sortfunc): name = str(b) if isinstance(b, EventSource): name += "@" legend = "Note: @ = event source" # add a divider before each subsequent row if not first: table.rule() else: first = False # print the details if len(b.sources) > 0: # non source block, list all its inputs, one per row inputs = b.inputs for port, source in enumerate(b.sources): # every port value = inputs[port] typ = type(value).__name__ if isinstance(value, np.ndarray): typ += "{:s}.{:s}".format(str(value.shape), str(value.dtype)) src_name = source.block.name if source.block.nout > 1: src_name += f"[{source.port}]" if port == 0: # first row for this block table.row(name, b.type, port, src_name, typ) else: # subsequent rows table.row("", "", port, src_name, typ) else: # source block, just list the name table.row(name, b.type, "", "", "") table.print(**kwargs) if legend: print(legend + "\n") def report(self, **kwargs): warnings.warn("use reports_lists() method instead", DeprecationWarning) self.report_lists(**kwargs) def report_lists(self, **kwargs): """ Print a tabular report about the block diagram. :param kwargs: options passed to :meth:`ansitable.ANSITable.print` Print the important lists in pretty format. * block list, all blocks * wire list, all wires * clock list, all discrete time clocks """ # print all the blocks print("\nBlocks::\n") table = ANSITable( Column("id"), Column("name"), Column("nin"), Column("nout"), Column("nstate"), Column("ndstate"), Column("type", headalign="^", colalign="<"), border="thin", ) for b in self.blocklist: table.row(b.id, str(b), b.nin, b.nout, b.nstates, b.ndstates, b.type) table.print(**kwargs) # print all the wires print("\nWires::\n") table = ANSITable( Column("id"), Column("from", headalign="^"), Column("to", headalign="^"), Column("description", headalign="^", colalign="<"), Column("type", headalign="^", colalign="<"), border="thin", ) for w in self.wirelist: start = "{:d}[{:d}]".format(w.start.block.id, w.start.port) end = "{:d}[{:d}]".format(w.end.block.id, w.end.port) try: value = w.end.block.inputs[w.end.port] typ = type(value).__name__ if isinstance(value, np.ndarray): typ += "{:s}.{:s}".format(str(value.shape), str(value.dtype)) except: typ = "??" table.row(w.id, start, end, w.fullname, typ) table.print(**kwargs) if len(self.clocklist) > 0: # print all the clocked blocks print("\nClocked blocks::\n") table = ANSITable( Column("id"), Column("block"), Column("clock"), Column("period"), Column("offset"), border="thin", ) for b in self.blocklist: if b.blockclass == "clocked": c = b.clock table.row(b.id, str(b), c.name, c.T, c.offset) table.print(**kwargs) if not self.compiled: print("** System has not been compiled, or had a compile time error") def report_schedule(self, **kwargs): """ Display execution schedule in tabular form :param kwargs: options passed to :meth:`ansitable.ANSITable.print` :seealso: :func:`schedule_plan`, :func:`schedule_dotfile` """ table = ANSITable( Column("Step"), Column("Blocks", colalign="<", headalign="^"), border="thin", ) for sequence, group in enumerate(self.plan): table.row(sequence, ", ".join([str(b) for b in group])) table.print(**kwargs) # ---------------------------------------------------------------------- # def _error_handler(self, where, block): # called from except clause import traceback import types t, v, tb = sys.exc_info() # get the exception print(fg("red")) # red text # print the traceback print(f"[{block.type} block: {block.name}.{where}]: exception {t.__name__}") print(f" {v}\n") traceback.print_tb(tb) # print all block inputs print() for i in range(block.nin): input = block.inputs[i] print( f"input {i} from" f" {block.sources[i].block.name} [{input.__class__.__name__}]" ) print(" ", input) print(attr(0)) # default text # traceback = err[2] # back_frame = traceback.tb_frame.f_back # back_tb = types.TracebackType(tb_next=None, # tb_frame=back_frame, # tb_lasti=back_frame.f_lasti, # tb_lineno=back_frame.f_lineno) # raise RuntimeError('Fatal failure').with_traceback(back_tb) raise RuntimeError("Fatal failure") from None def getstate0(self): # get the state from each stateful block x0 = np.array([]) for b in self.blocklist: try: if b.blockclass == "transfer": x0 = np.r_[x0, b.getstate0()] # print('x0', x0) except: self._error_handler("getstate0", b) return x0 def reset(self): """ Reset conditions within every active block. Most importantly, all inputs are marked as unknown. Invokes the `reset` method on all blocks. """ for b in self.blocklist: try: b.reset() except: self._error_handler("reset", b) def step(self, t): """ Step all blocks :param t: simulation time, defaults to None :type t: float :param inports: block input port values :type inports: list Tell all blocks to take action on new inputs by invoking their ``step`` method and passing the ``state`` object. Used to save results to a figure or file. Called at the end of every integration interval. .. note:: - if ``graphics`` is False, Graphics blocks are not called """ # TODO could be done by output method, even if no outputs for b in self.blocklist: try: if isinstance(b, SinkBlock): b.step(t, b.inputs) except: self._error_handler("step", b) def deriv(self, t): """ Harvest derivatives from all blocks. :param t: simulation time, defaults to None :type t: float :param simstate: simulation state, defaults to None :type simstate: SimState, optional """ YD = np.array([]) for b in self.blocklist: if b.blockclass == "transfer": try: yd = b.deriv(t, b.inputs, b._x) if not isinstance(yd, np.ndarray): raise AssertionError(f"deriv: block {b} did not return ndarray") if yd.ndim != 1 or yd.shape[0] != b.nstates: raise AssertionError( f"deriv: block {b} returns wrong shape {yd.shape}, should" f" be ({b.nstates},)" ) YD = np.r_[YD, yd] except: self._error_handler("deriv", b) return YD def start(self, simstate=None): """ Start all blocks :param simstate: simulation state, defaults to None :type simstate: SimState, optional Inform all blocks that BlockDiagram execution is about to start by invoking their ``start`` method and passing the ``state`` object. Used to open files, create figures etc. .. note:: if ``graphics`` is False, Graphics blocks are not called """ for c in self.clocklist: try: c.start(simstate) except: self._error_handler("start_clocked", c) # safe wrapper for block starting, does error handling for b in self.blocklist: # print('starting block', b) try: b.start(simstate) except: self._error_handler("start", b) def initialstate(self): for b in self.blocklist: if b.blockclass in ("transfer", "clocked"): b._x = b._x0 def done(self, block=False): """ Finishup all blocks :param state: simulation state, defaults to None :type state: SimState, optional :param graphics: graphics enabled, defaults to False :type graphics: bool, optional Inform all blocks that BlockDiagram execution is complete by invoking their ``done`` method and passing options. Used to close files, display figures etc. .. note:: if ``graphics`` is False, Graphics blocks are not called """ for b in self.blocklist: try: b.done(block=block) except: self._error_handler("done", b) def dotfile(self, filename, shapes=None): """ Write a GraphViz dot file representing the network. :param file: Name of file to write to, or file handle :type file: str, file handle :param shapes: block shapes :type shapes: dict Create a GraphViz format file for procesing by ``dot``. The graph is: * directed graph, drawn left to right * source blocks are in the first column * sink and graphics blocks are in the last column * ``SUM`` and ``PROD`` blocks have the sign or operation of their input wires labeled. The file can be processed using ``dot``:: % dot -Tpng -o out.png dotfile.dot .. image:: ../../figs/eg1.png :width: 600 :alt: Block diagram represented as a mathematical graph .. note:: By default all blocks have the default shape, with source blocks shown as a rectangle ("record"), and sink/graphics blocks as a rounded rectangle ("Mrecord"). This can be overriden by provide a dictionary ``shapes`` that maps block class (sink, source, graphics, function, transfer) to the names of GraphViz shapes. :seealso: :meth:`showgraph` """ if shapes is None: shapes = dict(source="record", sink="Mrecord", graphics="Mrecord") if isinstance(filename, str): file = open(filename, "w") else: file = filename header = r"""digraph G { rankdir = "LR" """ file.write(header) # add the blocks for b in self.blocklist: options = [] if b.blockclass in shapes: options.append("shape={:s}".format(shapes[b.blockclass])) if b.blockclass == "source": options.append('rank="source"') if b.blockclass in ("sink", "graphics"): options.append('rank="sink"') if b.pos is not None: options.append('pos="{:g},{:g}!"'.format(b.pos[0], b.pos[1])) # options.append( # 'xlabel=<
{:s}>'.format( # b.type # ) # ) if len(options) > 0: file.write('\t"{:s}" [{:s}]\n'.format(b.name, ", ".join(options))) file.write("\n") # add the wires for w in self.wirelist: options = [] # options.append('xlabel="{:s}"'.format(w.name)) if w.end.block.type == "sum": options.append( 'headlabel="{:s} "'.format(w.end.block.signs[w.end.port]) ) options.append("labeldistance=1.5") if w.end.block.type == "prod": options.append('headlabel="{:s} "'.format(w.end.block.ops[w.end.port])) options.append("labeldistance=1.5") file.write( '\t"{:s}" -> "{:s}" [{:s}]\n'.format( w.start.block.name, w.end.block.name, ", ".join(options) ) ) file.write("}\n") def showgraph(self): """ Display diagram as a graph in browser tab :seealso: :meth:`dotfile` """ # Lazy import try: import tempfile import subprocess import webbrowser except ModuleNotFoundError: return # create the temporary dotfile dotfile = tempfile.TemporaryFile(mode="w") self.dotfile(dotfile) # rewind the dot file, create PDF file in the filesystem, run dot dotfile.seek(0) pdffile = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) subprocess.run("dot -Tpdf", shell=True, stdin=dotfile, stdout=pdffile) # open the PDF file in browser (hopefully portable), then cleanup webbrowser.open(f"file://{pdffile.name}") def blockvalues(self, t=None, simstate=None): for b in self.blocklist: print("Block {:s}:".format(b.name)) print(" inputs: ", b.inputs) print(" outputs: ", b.output(t, b.inputs, b._x)) if __name__ == "__main__": # pragma: no cover import bdsim bd = bdsim.BlockDiagram() # define the blocks demand = bd.STEP(T=1, pos=(0, 0), name="demand") sum = bd.SUM("+-", pos=(1, 0)) gain = bd.GAIN(10, pos=(1.5, 0)) plant = bd.LTI_SISO(0.5, [2, 1], name="plant", pos=(3, 0)) # scope = bd.SCOPE(pos=(4,0), styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}) scope = bd.SCOPE(nin=2, styles=["k", "r--"], pos=(4, 0)) # connect the blocks bd.connect(demand, sum[0], scope[1]) bd.connect(plant, sum[1]) bd.connect(sum, gain) bd.connect(gain, plant) bd.connect(plant, scope[0]) bd.compile() # check the diagram bd.report() # list all blocks and wires bd.run(5, debug=True) # from pathlib import Path # exec(open(Path(__file__).parent.absolute() / "test_blockdiagram.py").read()) ================================================ FILE: bdsim/blockdiagram.pyi ================================================ from spatialmath.base.types import * import numpy as np import math class BlockDiagram: # bdsim.blocks.functions.Sum def SUM(self, signs: str = '++', mode: str = None, **blockargs): """ :param signs: signs associated with input ports, accepted characters: + or -, defaults to "++" :type signs: str, optional :param mode: controls addition mode, per element, string comprises ``r`` or ``c`` or ``C`` or ``L``, defaults to None :type mode: str, optional :param blockargs: |BlockOptions| :type blockargs: dict ``mode`` controls how elements of the input vectors are added/subtracted. Elements which are angles must be treated specially, and this is indicated by the corresponding characters in ``mode``. The string's length must equal the width of the input vectors. The characters of the string can be: ============== ============================================ mode character purpose ============== ============================================ r real number, don't wrap (default) c angle on circle, wrap to [-π, π) C angle on circle, wrap to [0, 2π) L colatitude angle, wrap to [0, π] ============== ============================================ For example if ``mode="rc"`` then a 2-element array would have its second element wrapped to the range [-π, π). """ ... # bdsim.blocks.functions.Prod def PROD(self, ops: str = '**', matrix: bool = False, **blockargs): """ :param ops: operations associated with input ports, accepted characters: * or /, defaults to '**' :type ops: str, optional :param inputs: Optional incoming connections :type inputs: Block or Plug :param matrix: Arguments are matrices, defaults to False :type matrix: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.functions.Gain def GAIN(self, K: Union[int, float, numpy.ndarray] = 1, premul: bool = False, **blockargs): """ :param K: The gain value, defaults to 1 :type K: scalar, array_like :param premul: premultiply by constant, default is postmultiply, defaults to False :type premul: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.functions.Pow def POW(self, p: Union[int, float] = 1, matrix: bool = False, **blockargs): """ :param p: The exponent value, defaults to 1 :type p: scalar :param matrix: premultiply by constant, default is postmultiply, defaults to False :type matrix: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.functions.Clip def CLIP(self, min: Union[numpy.ndarray, int, float, list, tuple] = -inf, max: Union[numpy.ndarray, int, float, list, tuple] = inf, **blockargs): """ :param min: Minimum value, defaults to -math.inf :type min: scalar or array_like, optional :param max: Maximum value, defaults to math.inf :type max: float or array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.functions.Function def FUNCTION(self, func: Callable = None, nin: int = 1, nout: int = 1, persistent: bool = False, fargs: list = None, fkwargs: dict = None, **blockargs): """ :param func: function or lambda, or list thereof, defaults to None :type func: callable or sequence of callables, optional :param nin: number of inputs, defaults to 1 :type nin: int, optional :param nout: number of outputs, defaults to 1 :type nout: int, optional :param persistent: pass in a reference to a dictionary instance to hold persistent state, defaults to False :type persistent: bool, optional :param fargs: extra positional arguments passed to the function, defaults to [] :type fargs: list, optional :param fkwargs: extra keyword arguments passed to the function, defaults to {} :type fkwargs: dict, optional :param blockargs: |BlockOptions| :type blockargs: dict, optional """ ... # bdsim.blocks.functions.Interpolate def INTERPOLATE(self, x: Union[list, tuple, numpy.ndarray] = None, y: Union[list, tuple, numpy.ndarray] = None, xy: numpy.ndarray = None, time: bool = False, kind: str = 'linear', **blockargs): """ :param x: x-values of function, defaults to None :type x: array_like, shape (N,) optional :param y: y-values of function, defaults to None :type y: array_like, optional :param xy: combined x- and y-values of function, defaults to None :type xy: array_like, optional :param time: x new is simulation time, defaults to False :type time: bool, optional :param kind: interpolation method, defaults to 'linear' :type kind: str, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sources.Constant def CONSTANT(self, value=0, **blockargs): """ :param value: the constant, defaults to 0 :type value: any, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sources.Time def TIME(self, value=None, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sources.WaveForm def WAVEFORM(self, wave='square', freq=1, unit='Hz', phase=0, amplitude=1, offset=0, min=None, max=None, duty=0.5, **blockargs): """ :param wave: type of waveform to generate, one of: 'sine', 'square' [default], 'triangle' :type wave: str, optional :param freq: frequency, defaults to 1 :type freq: float, optional :param unit: frequency unit, one of: 'rad/s', 'Hz' [default] :type unit: str, optional :param amplitude: amplitude, defaults to 1 :type amplitude: float, optional :param offset: signal offset, defaults to 0 :type offset: float, optional :param phase: Initial phase of signal in the range [0,1], defaults to 0 :type phase: float, optional :param min: minimum value, defaults to None :type min: float, optional :param max: maximum value, defaults to None :type max: float, optional :param duty: duty cycle for square wave in range [0,1], defaults to 0.5 :type duty: float, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sources.Piecewise def PIECEWISE(self, *args, seq=None, **blockargs): """ :param seq: sequence of time, value pairs :type seq: list of 2-element iterables :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sources.Step def STEP(self, T=1, off=0, on=1, **blockargs): """ :param T: time of step, defaults to 1 :type T: float, optional :param off: initial value, defaults to 0 :type off: float, optional :param on: final value, defaults to 1 :type on: float, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sources.Ramp def RAMP(self, T=1, off=0, slope=1, **blockargs): """ :param T: time of ramp start, defaults to 1 :type T: float, optional :param off: initial value, defaults to 0 :type off: float, optional :param slope: gradient of slope, defaults to 1 :type slope: float, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sinks.Print def PRINT(self, fmt=None, file=None, **blockargs): """ :param fmt: Format string, defaults to None :type fmt: str, optional :param file: file to write data to, defaults to None :type file: file object, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A PRINT block :rtype: Print instance """ ... # bdsim.blocks.sinks.Stop def STOP(self, func=None, **blockargs): """ :param func: evaluate stop condition, defaults to None :type func: callable, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sinks.Null def NULL(self, nin=1, **blockargs): """ :param nin: number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.sinks.Watch def WATCH(self, **blockargs): """ :param nin: number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.transfers.Integrator def INTEGRATOR(self, x0=0, gain=1.0, min=None, max=None, enable=None, **blockargs): """ :param x0: Initial state, defaults to 0 :type x0: array_like, optional :param gain: gain or scaling factor, defaults to 1 :type gain: float :param min: Minimum value of state, defaults to None :type min: float or array_like, optional :param max: Maximum value of state, defaults to None :type max: float or array_like, optional :param enable: enable or disable integration :type enable: callable :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.transfers.PoseIntegrator def POSEINTEGRATOR(self, x0=None, **blockargs): """ :param x0: Initial pose, defaults to null :type x0: SE3, Twist3, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.transfers.LTI_SS def LTI_SS(self, A=None, B=None, C=None, x0=None, **blockargs): """ :param N: numerator coefficients, defaults to 1 :type N: array_like, optional :param D: denominator coefficients, defaults to [1,1] :type D: array_like, optional :param x0: initial states, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.transfers.LTI_SISO def LTI_SISO(self, N=1, D=[1, 1], x0=None, **blockargs): """ :param N: numerator coefficients, defaults to 1 :type N: array_like, optional :param D: denominator coefficients, defaults to [1,1] :type D: array_like, optional :param x0: initial states, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: LTI_SISO block :rtype: ``LTI_SISO`` instance """ ... # bdsim.blocks.connections.SubSystem def SUBSYSTEM(self, subsys, nin=1, nout=1, **blockargs): """ :param subsys: Subsystem as either a filename or a ``BlockDiagram`` instance :type subsys: str or BlockDiagram :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param nout: Number of output ports, defaults to 1 :type nout: int, optional :param blockargs: |BlockOptions| :type blockargs: dict :raises ImportError: DESCRIPTION :raises ValueError: DESCRIPTION """ ... # bdsim.blocks.transfers.Deriv def DERIV(self, alpha, x0=0, y0=None, **blockargs): """ :param alpha: filter pole in units of rad/s :type alpha: float :param x0: initial states, defaults to 0 :type x0: array_like, optional :param y0: inital outputs :type y0: array_like :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.transfers.PID def PID(self, type: str = 'PID', P: float = 0.0, D: float = 0.0, I: float = 0.0, D_pole=1, I_limit=None, I_band=0, **blockargs): """ :param type: the controller type, defaults to "PID" :type type: str, optional :param P: proportional gain, defaults to 0 :type P: float :param D: derivative gain, defaults to 0 :type D: float :param I: integral gain, defaults to 0 :type I: float :param D_pole: filter pole for derivative estimate, defaults to 1 rad/s :type D_pole: float :param I_limit: integral limit :type I_limit: float or 2-tuple :param I_band: band within which integral action is active :type I_band: float :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.discrete.ZOH def ZOH(self, clock, x0=0, **blockargs): """ :param clock: clock source :type clock: Clock :param x0: Initial value of the hold, defaults to 0 :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.discrete.DIntegrator def DINTEGRATOR(self, clock, x0=0, gain=1.0, min=None, max=None, **blockargs): """ :param clock: clock source :type clock: Clock :param x0: Initial state, defaults to 0 :type x0: array_like, optional :param gain: gain or scaling factor, defaults to 1 :type gain: float :param min: Minimum value of state, defaults to None :type min: float or array_like, optional :param max: Maximum value of state, defaults to None :type max: float or array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.discrete.DPoseIntegrator def DPOSEINTEGRATOR(self, clock, x0=None, **blockargs): """ :param clock: clock source :type clock: Clock :param x0: Initial pose, defaults to null :type x0: SE3, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Inverse def INVERSE(self, pinv=False, **blockargs): """ :param pinv: force pseudo inverse, defaults to False :type pinv: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Transpose def TRANSPOSE(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Norm def NORM(self, ord=None, axis=None, **blockargs): """ :param axis: specifies the axis along which to compute the vector norms, defaults to None. :type axis: int, optional :param ord: Order of the norm, default to None. :type ord: int or str :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Flatten def FLATTEN(self, order='C', **blockargs): """ :param order: flattening order, either "C" or "F", defaults to "C" :type order: str :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Slice2 def SLICE2(self, rows=None, cols=None, **blockargs): """ :param rows: row selection, defaults to None :type rows: tuple(3) or list :param cols: column selection, defaults to None :type cols: tuple(3) or list :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Slice1 def SLICE1(self, index, **blockargs): """ :param index: slice, defaults to None :type index: tuple(3) :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Det def DET(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.linalg.Cond def COND(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.displays.Scope def SCOPE(self, nin=1, vector=None, styles=None, stairs=False, scale='auto', labels=None, grid=True, watch=False, title=None, loc='best', **blockargs): """ :param nin: number of inputs, defaults to 1 or if given, the length of style vector :type nin: int, optional :param vector: vector signal on single input port, defaults to None :type vector: int or list, optional :param styles: styles for each line to be plotted :type styles: str or dict, list of strings or dicts; one per line, optional :param stairs: force staircase style plot for all lines, defaults to False :type stairs: bool, optional :param scale: fixed y-axis scale or defaults to 'auto' :type scale: str or array_like(2) :param labels: vertical axis labels :type labels: sequence of strings :param grid: draw a grid, defaults to True. Can be boolean or a tuple of options for grid() :type grid: bool or sequence :param watch: add these signals to the watchlist, defaults to False :type watch: bool, optional :param title: title of plot :type title: str :param loc: location of legend, see :meth:`matplotlib.pyplot.legend`, defaults to "best" :type loc: str :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.displays.ScopeXY def SCOPEXY(self, style=None, scale='auto', aspect='equal', labels=['X', 'Y'], init=None, nin=2, **blockargs): """ :param style: line style, defaults to None :type style: optional str or dict :param scale: fixed y-axis scale or defaults to 'auto' :type scale: str or array_like(2) or array_like(4) :param labels: axis labels (xlabel, ylabel), defaults to ["X","Y"] :type labels: 2-element tuple or list :param init: function to initialize the graphics, defaults to None :type init: callable :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.displays.ScopeXY1 def SCOPEXY1(self, indices=[0, 1], **blockargs): """ :param indices: indices of elements to select from block input vector, defaults to [0,1] :type indices: array_like(2) :param style: line style :type style: optional str or dict :param scale: fixed y-axis scale or defaults to 'auto' :type scale: str or array_like(2) or array_like(4) :param labels: axis labels (xlabel, ylabel) :type labels: 2-element tuple or list :param init: function to initialize the graphics, defaults to None :type init: callable :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.connections.Item def ITEM(self, item, **blockargs): """ :param item: name of dictionary item :type item: str :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.connections.Dict def DICT(self, keys, **blockargs): """ :param keys: list of dictionary keys :type keys: list :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.connections.Mux def MUX(self, nin=1, **blockargs): """ :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.connections.DeMux def DEMUX(self, nout=1, **blockargs): """ :param nout: number of outputs, defaults to 1 :type nout: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.connections.Index def INDEX(self, index=[], **blockargs): """ Index an iterable signal. :param index: elements of input array, defaults to [] :type index: list, slice or str, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.connections.InPort def INPORT(self, nout=1, **blockargs): """ :param nout: Number of output ports, defaults to 1 :type nout: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.connections.OutPort def OUTPORT(self, nin=1, **blockargs): """ :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.spatial.Pose_postmul def POSE_POSTMUL(self, pose=None, **blockargs): """ :param pose: pose to apply :type pose: SO2, SE2, SO3 or SE3 :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.spatial.Pose_premul def POSE_PREMUL(self, pose=None, **blockargs): """ :param pose: pose to apply :type pose: SO2, SE2, SO3 or SE3 :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.spatial.Transform_vector def TRANSFORM_VECTOR(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.spatial.Pose_inverse def POSE_INVERSE(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # bdsim.blocks.io.DOUT def DOUT(self, pin=0, **blockargs): """ Constant value. :param value: the constant, defaults to 0 :type value: any, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a CONSTANT block :rtype: Constant instance This block has only one output port, but the value can be any Python type, for example float, list or Numpy ndarray. """ ... # roboticstoolbox.blocks.arm.FKine def FKINE(self, robot=None, args={}, **blockargs): """ :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param robot: Robot model, defaults to None :type robot: Robot subclass, optional :param args: Options for fkine, defaults to {} :type args: dict, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.IKine def IKINE(self, robot=None, q0=None, useprevious=True, ik=None, args={}, seed=None, **blockargs): """ :param robot: Robot model, defaults to None :type robot: Robot subclass, optional :param q0: Initial joint angles, defaults to None :type q0: array_like(n), optional :param useprevious: Use previous IK solution as q0, defaults to True :type useprevious: bool, optional :param ik: Specify an IK function, defaults to "LM" :type ik: str :param args: Options passed to IK function :type args: dict :param seed: random seed for solution :type seed: int :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.Jacobian def JACOBIAN(self, robot, frame='0', representation=None, inverse=False, pinv=False, damping=None, transpose=False, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param frame: Frame to compute Jacobian for, one of: "0" [default], "e" :type frame: str, optional :param representation: representation for analytical Jacobian :type representation: str, optional :param inverse: output inverse of Jacobian, defaults to False :type inverse: bool, optional :param pinv: output pseudo-inverse of Jacobian, defaults to False :type pinv: bool, optional :param damping: damping term for inverse, defaults to None :type damping: float or array_like(N) :param transpose: output transpose of Jacobian, defaults to False :type transpose: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict If an inverse is requested and ``damping`` is not None it is added to the diagonal of the Jacobian prior to the inversion. If a scalar is provided it is added to each element of the diagonal, otherwise an N-vector is assumed. .. note:: - Only one of ``inverse`` or ``pinv`` can be True - ``inverse`` or ``pinv`` can be used in conjunction with ``transpose`` - ``inverse`` requires that the Jacobian is square - If ``inverse`` is True and the Jacobian is singular a runtime error will occur. """ ... # roboticstoolbox.blocks.arm.ArmPlot def ARMPLOT(self, robot=None, q0=None, backend=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param q0: initial joint angles, defaults to None :type q0: ndarray(N) :param backend: RTB backend name, defaults to 'pyplot' :type backend: str, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.JTraj def JTRAJ(self, q0, qf, qd0=None, qdf=None, T=None, **blockargs): """ :param q0: initial joint coordinate :type q0: array_like(n) :param qf: final joint coordinate :type qf: array_like(n) :param T: time vector or number of steps, defaults to None :type T: array_like or int, optional :param qd0: initial velocity, defaults to None :type qd0: array_like(n), optional :param qdf: final velocity, defaults to None :type qdf: array_like(n), optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.CTraj def CTRAJ(self, T1, T2, T, trapezoidal=True, **blockargs): """ :param T1: initial pose :type T1: SE3 :param T2: final pose :type T2: SE3 :param T: motion time :type T: float :param trapezoidal: Use LSPB motion profile along the path :type trapezoidal: bool :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.CirclePath def CIRCLEPATH(self, radius=1, centre=(0, 0, 0), pose=None, frequency=1, unit='rps', phase=0, **blockargs): """ :param radius: radius of circle, defaults to 1 :type radius: float :param centre: center of circle, defaults to [0,0,0] :type centre: array_like(3) :param pose: SE3 pose of output, defaults to None :type pose: SE3 :param frequency: rotational frequency, defaults to 1 :type frequency: float :param unit: unit for frequency, one of: 'rps' [default], 'rad' :type unit: str :param phase: phase :type phase: float :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.Trapezoidal def TRAPEZOIDAL(self, q0, qf, V=None, T=None, **blockargs): """ Compute a joint-space trajectory :param q0: initial joint coordinate :type q0: float :param qf: final joint coordinate :type qf: float :param T: maximum time, defaults to None :type T: float, optional :param blockargs: |BlockOptions| :type blockargs: dict If ``T`` is given the value ``qf`` is reached at this time. This can be less or greater than the simulation time. """ ... # roboticstoolbox.blocks.arm.Traj def TRAJ(self, y0=0, yf=1, T=None, time=False, traj='trapezoidal', **blockargs): """ :param y0: initial value, defaults to 0 :type y0: array_like(m), optional :param yf: final value, defaults to 1 :type yf: array_like(m), optional :param T: maximum time, defaults to None :type T: float, optional :param time: x is simulation time, defaults to False :type time: bool, optional :param traj: trajectory type, one of: 'trapezoidal' [default], 'quintic' :type traj: str, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.IDyn def IDYN(self, robot, gravity=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param gravity: gravitational acceleration :type gravity: float :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.Gravload def GRAVLOAD(self, robot, gravity=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param gravity: gravitational acceleration :type gravity: float :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.Gravload_X def GRAVLOAD_X(self, robot, representation='rpy/xyz', gravity=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param representation: task-space representation, defaults to "rpy/xyz" :type representation: str :param gravity: gravitational acceleration :type gravity: float :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.Inertia def INERTIA(self, robot, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.Inertia_X def INERTIA_X(self, robot, representation='rpy/xyz', pinv=False, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param representation: task-space representation, defaults to "rpy/xyz" :type representation: str :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.FDyn def FDYN(self, robot, q0=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param q0: Initial joint configuration :type q0: array_like(n) :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.arm.FDyn_X def FDYN_X(self, robot, q0=None, gravcomp=False, velcomp=False, representation='rpy/xyz', **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param q0: Initial joint configuration :type q0: array_like(n) :param gravcomp: perform gravity compensation :type gravcomp: bool :param velcomp: perform velocity term compensation :type velcomp: bool :param representation: task-space representation, defaults to "rpy/xyz" :type representation: str :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.mobile.Bicycle def BICYCLE(self, L=1, speed_max=inf, accel_max=inf, steer_max=1.413716694115407, x0=None, **blockargs): """ :param L: Wheelbase, defaults to 1 :type L: float, optional :param speed_max: Velocity limit, defaults to math.inf :type speed_max: float, optional :param accel_max: maximum acceleration, defaults to math.inf :type accel_max: float, optional :param steer_max: maximum steered wheel angle, defaults to math.pi*0.45 :type steer_max: float, optional :param x0: Initial state, defaults to [0,0,0] :type x0: array_like(3), optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.mobile.Unicycle def UNICYCLE(self, w=1, speed_max=inf, accel_max=inf, steer_max=inf, x0=None, **blockargs): """ :param w: vehicle width, defaults to 1 :type w: float, optional :param speed_max: Velocity limit, defaults to math.inf :type speed_max: float, optional :param accel_max: maximum acceleration, defaults to math.inf :type accel_max: float, optional :param steer_max: maximum turn rate :math:`\omega`, defaults to math.inf :type steer_max: float, optional :param x0: Inital state, defaults to [0,0,0] :type x0: array_like(3), optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.mobile.DiffSteer def DIFFSTEER(self, w=1, R=1, speed_max=inf, accel_max=inf, steer_max=None, a=0, x0=None, **blockargs): """ :param w: vehicle width, defaults to 1 :type w: float, optional :param R: Wheel radius, defaults to 1 :type R: float, optional :param speed_max: Velocity limit, defaults to 1 :type speed_max: float, optional :param accel_max: maximum acceleration, defaults to math.inf :type accel_max: float, optional :param steer_max: maximum steering rate, defaults to 1 :type steer_max: float, optional :param x0: Inital state, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.mobile.VehiclePlot def VEHICLEPLOT(self, animation=None, path=None, labels=['X', 'Y'], square=True, init=None, scale='auto', polyargs={}, **blockargs): """ :param animation: Graphical animation of vehicle, defaults to None :type animation: VehicleAnimation subclass, optional :param path: linestyle to plot path taken by vehicle, defaults to None :type path: str or dict, optional :param labels: axis labels (xlabel, ylabel), defaults to ["X","Y"] :type labels: array_like(2) or list :param square: Set aspect ratio to 1, defaults to True :type square: bool, optional :param init: function to initialize graphics, defaults to None :type init: callable, optional :param scale: scale of plot, defaults to "auto" :type scale: list or str, optional :param polyargs: arguments passed to :meth:`Animation.Polygon` :type polyargs: dict :param blockargs: |BlockOptions| :type blockargs: dict .. note:: - The ``init`` function is called after the axes are initialized and can be used to draw application specific detail on the plot. In the example below, this is the dot and star. - A dynamic trail, showing path to date can be animated if the option ``path`` is set to a linestyle. """ ... # roboticstoolbox.blocks.uav.MultiRotor def MULTIROTOR(self, model, groundcheck=True, speedcheck=True, x0=None, **blockargs): """ Create a multi-rotor dynamic model block. :param model: A dictionary of vehicle geometric and inertial properties :type model: dict :param groundcheck: Prevent vehicle moving below ground :math:`z>0`, defaults to True :type groundcheck: bool :param speedcheck: Check for non-positive rotor speed, defaults to True :type speedcheck: bool :param x0: Initial state, defaults to None :type x0: array_like(6) or array_like(12), optional :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.uav.MultiRotorMixer def MULTIROTORMIXER(self, model=None, wmax=1000, wmin=5, **blockargs): """ :param model: A dictionary of vehicle geometric and inertial properties :type model: dict :param maxw: maximum rotor speed in rad/s, defaults to 1000 :type maxw: float :param minw: minimum rotor speed in rad/s, defaults to 5 :type minw: float :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.uav.MultiRotorPlot def MULTIROTORPLOT(self, model, scale=[-2, 2, -2, 2, 10], flapscale=1, projection='ortho', **blockargs): """ :param model: A dictionary of vehicle geometric and inertial properties :type model: dict :param scale: dimensions of workspace: xmin, xmax, ymin, ymax, zmin, zmax, defaults to [-2,2,-2,2,10] :type scale: array_like, optional :param flapscale: exagerate flapping angle by this factor, defaults to 1 :type flapscale: float :param projection: 3D projection, one of: 'ortho' [default], 'perspective' :type projection: str :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.spatial.Tr2Delta def TR2DELTA(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.spatial.Delta2Tr def DELTA2TR(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # roboticstoolbox.blocks.spatial.Point2Tr def POINT2TR(self, T=None, **blockargs): """ :param T: the transform :type T: SE3 :param blockargs: |BlockOptions| :type blockargs: dict If ``T`` is None then it defaults to the identity matrix. """ ... # roboticstoolbox.blocks.spatial.TR2T def TR2T(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ ... # machinevisiontoolbox.blocks.camera.Camera def CAMERA(self, camera=None, args={}, **blockargs): """ :param camera: Camera model, defaults to None :type camera: Camera subclass, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a CAMERA block :rtype: Camera instance Camera projection model. **Block ports** :input pose: Camera pose as an SE3 object. :input P: world points as ndarray(3,N) :output p: image plane points as ndarray(2,N) """ ... # machinevisiontoolbox.blocks.camera.Visjac_p def VISJAC_P(self, camera, depth=1, depthest=False, **blockargs): """ :param camera: Camera model, defaults to None :type camera: Camera subclass, optional :param depth: Point depth :type depth: float or ndarray :param depthest: Use depth estimation, defaults to True :type depthest: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a VISJAC_P block :rtype: Visjac_p instance If the Jacobian """ ... # machinevisiontoolbox.blocks.camera.EstPose_p def ESTPOSE_P(self, camera, P, frame='world', method='iterative', **blockargs): """ :param camera: Camera model, defaults to None :type camera: Camera subclass, optional :param P: World point coordinates :type P: ndarray(2,N) :param frame: return pose of points with respect to reference frame which is one of: 'world' [default] or 'camera' :type frame: str, optional :param method: pose estimation algorithm one of: 'iterative' [default], 'epnp', 'p3p', 'ap3p', 'ippe', 'ippe-square' :type method: str, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a ESTPOSE_P block :rtype: EstPose_p instance """ ... # machinevisiontoolbox.blocks.camera.ImagePlane def IMAGEPLANE(self, camera, style=None, labels=None, grid=True, retain=False, watch=False, init=None, **blockargs): """ Create a block that plots image plane coordinates. :param camera: a camera model :type camera: Camera instance :param style: styles for each point to be plotted :type style: str or dict, list of strings or dicts; one per line, optional :param grid: draw a grid, defaults to True. Can be boolean or a tuple of options for grid() :type grid: bool or sequence :param retain: keep previous image plane points, defaults to False :type retain: bool, optional :param watch: add these signals to the watchlist, defaults to False :type watch: bool, optional :param init: function to initialize the graphics, defaults to None :type init: callable, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: An IMAGEPLANE block :rtype: ImagePlane instance Create a block that plots points on a camera object's virtual image plane. Examples:: SCOPE() SCOPE(nin=2) SCOPE(nin=2, scale=[-1,2]) SCOPE(styles='k--') SCOPE(styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}]) SCOPE(styles=['k', 'r--']) .. figure:: ../../figs/Figure_1.png :width: 500px :alt: example of generated graphic Example of scope display. """ ... ================================================ FILE: bdsim/blocks/IO/Firmata.py ================================================ """ Define real-time i/o blocks for use in block diagrams. These are blocks that: - have inputs or outputs - have no state variables - are a subclass of ``SourceBlock`` or ``SinkBlock`` """ # The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance. from bdsim.components import SinkBlock, SourceBlock import time import sys class FirmataIO: board = None port = "/dev/cu.usbmodem1441401" def __init__(self): from pyfirmata import Arduino, util if FirmataIO.board is None: print(f"connecting to Arduino/firmata node on {self.port}...", end="") sys.stdout.flush() FirmataIO.board = Arduino(self.port) print(" done") # start a background thread to read inputs iterator = util.Iterator(FirmataIO.board) iterator.start() time.sleep(0.25) # allow time for the iterator thread to start def pin(self, name): return FirmataIO.board.get_pin(name) class AnalogIn(SourceBlock): nin = 0 nout = 1 def __init__(self, pin=None, scale=1.0, offset=0.0, **blockargs): super().__init__(**blockargs) self.board = FirmataIO() self.pin = self.board.pin(f"a:{pin}:i") self.scale = scale self.offset = offset # deal with random None values at startup while self.pin.read() == None: time.sleep(0.1) def output(self, t, inports, x): return [self.scale * self.pin.read() + self.offset] class AnalogOut(SinkBlock): nin = 1 nout = 0 def __init__(self, pin=None, scale=1.0, offset=0.0, **blockargs): super().__init__(**blockargs) self.board = FirmataIO() self.pin = self.board.pin(f"d:{pin}:p") # PWM output self.scale = scale self.offset = offset def step(self, t, inports): self.pin.write(self.scale * inports[0] + self.offset) class DigitalIn(FirmataIO, SourceBlock): nin = 0 nout = 1 def __init__(self, pin=None, bool=False, **blockargs): super().__init__(**blockargs) self.pin = self.board.get_pin(f"d:{pin}:i") def output(self, t, inports, x): if self.bool: return [self.pin.read()] else: return [self.pin.read() > 0] class DigitalOut(FirmataIO, SinkBlock): nin = 1 nout = 0 def __init__(self, pin=None, **blockargs): super().__init__(**blockargs) self.pin = self.board.get_pin(f"d:{pin}:o") def step(self, t, inports): self.pin.write(inports[0] > 0) ================================================ FILE: bdsim/blocks/IO/README.md ================================================ This folder contains "drivers" for particular i/o hardware configurations. Each file contains a set of class definitions which are imported in the normal Python fashion, not dynamically as are most bdsim blocks. | File | Purpose | |------|---------| |Firmata.py | analog and digital i/o for Arduino + Firmata | Notes: - For [Firmata](https://github.com/firmata/protocol) you need to load a Firmata sketch onto the Arduino. - For now the port and the baudrate (57600) are hardwired. Perhaps the i/o system is initialized by a separate function, or options passed through BDRealTime. - There are myriad Firmata variants, but StandardFirmata is provided as a built-in example with the Arduino IDE and does digital and analog i/o. It runs fine on a Uno. - ConfigurableFirmata has more device options but needs to be installed, and its default baud rate is 115200. It does not include quadrature encoders :( - The Firmata interface is [pyfirmata](https://github.com/tino/pyFirmata), old but quite solid and efficient. ================================================ FILE: bdsim/blocks/Icons/README.md ================================================ # Icon files Icons are 250x250 greyscale PNG images with black paint and a transparent background, and used only by `bdedit`. Icons are black "ink" with a transparent background. Icon names are all lower-case versions of the CamelCase class name, eg. the class `Gain` has the icon file named `gain.png`. ![gain.png](https://github.com/petercorke/bdsim/raw/master/bdsim/blocks/Icons/gain.png) Icons are drawn to be interpretted with inputs on the left and outputs on the right. Icons can be flipped so that inputs are on the right, and if it makes sense for an alternative icon in that situation its name has the `_flipped` suffix. For the case of the `Gain` block that would be `gain_flipped.png` ![gain_flipped.png](https://github.com/petercorke/bdsim/raw/master/bdsim/blocks/Icons/gain_flipped.png) The bulk of icons are defined in this folder, but blocks can be imported from folders in other toolboxes. In that case, the `Icons` folder in those folders contains the corresponding icon files. # Creating icons from LaTeX Icons are created by the script `bdtex2icon` which is run like this from the `Icons` folder ``` bdtex2icon -r 300 -o sum.png -t '\sum' ``` where the `-o` option specifies the name of the output file and the `-t` option is the LaTeX string. The `-r` option is the resolution or scale of the icon. If it is too big or too small it will suggest a better value for resolution. Choose a value just under the recommended value, check that that `bdtex2icon` is happy with the value and inspect the icon file. Add the line to the file `icons.sh` which is a shell script that builds all icon files. **Note that you must have a working LaTeX installation that includes `pdflatex`, `pdfcrop` and `gs`** # Drawing icons by hand See [details here](https://github.com/petercorke/bdsim/blob/master/bdsim/bdedit/docstring.md). ================================================ FILE: bdsim/blocks/Icons/icons.sh ================================================ #! /bin/bash bdtex2icon -r 300 -o sum.png -t '\sum' bdtex2icon -r 450 -o prod.png -t '\Pi' bdtex2icon -r 200 -o norm.png -t '\| \cdot \|' bdtex2icon -r 250 -o det.png -t '| \cdot |' bdtex2icon -r 110 -o cond.png -t '\mathrm{cond}(\cdot)' bdtex2icon -r 150 -o fkine.png -t '\vec{T}\!\left(\mat{q}\right)' bdtex2icon -r 150 -o ikine.png -t '\mat{q}\!\left(\vec{T}\right)' bdtex2icon -r 150 -o jacobian.png -t '\mat{J}\!\left(\vec{q}\right)' bdtex2icon -r 300 -o time.png -t 't' bdtex2icon -r 100 -o point2tr.png -t '\begin{array}{c|c} \mat{1} & \vec{t}\\ \hline 0 & 1 \end{array}' bdtex2icon -r 120 -o tr2delta.png -t '\vec{\Delta}\!\left(\mat{T}\right)' bdtex2icon -r 120 -o delta2tr.png -t '\mat{T}\!\left(\vec{\Delta}\right)' bdtex2icon -r 140 -o dposeintegrator.png -t '\sum_0^T \vec{\nu}' bdtex2icon -r 150 -o dict.png -t '\mathbf{\{\cdots\}}' bdtex2icon -r 150 -o index.png -t '\mathbf{[}k\mathrm{]}' bdtex2icon -r 150 -o item.png -t '\mathbf{\{\}[}k\mathrm{]}' bdtex2icon -r 180 -o transpose.png -t '\mat{A}^{\!\top}' bdtex2icon -r 180 -o inverse.png -t '\mat{A}^{\!-\!1}' bdtex2icon -r 250 -o integrator.png -t '\frac{1}{s}' bdtex2icon -r 220 -o dintegrator.png -t '\frac{z}{z-1}' bdtex2icon -r 150 -o lti_siso.png -t '\frac{N(s)}{D(s)}' bdtex2icon -r 90 -o lti_ss.png -t '\begin{array}{c|c} A & B\\ \hline C & D \end{array}' bdtex2icon -r 90 -o tr2t.png -t '\begin{array}{c|c} \mat{R} & \vec{t}\\ \hline 0 & 1 \end{array}' bdtex2icon -r 100 -o gravload.png -t '\vec{g}\!\left(\vec{q}\right)' bdtex2icon -r 100 -o coriolis.png -t '\mat{C}\left(\vec{q}, \dvec{q}\right)' bdtex2icon -r 100 -o inertia.png -t '\mat{M}\!\left(\vec{q}\right)' bdtex2icon -r 100 -o fdyn.png -t '\ddvec{q}\left(\vec{q}, \vec{\tau}\right)' bdtex2icon -r 100 -o fdynx.png -t '\ddvec{x}\left(\vec{q}, \vec{w}\right)' bdtex2icon -r 80 -o idyn.png -t '\vec{\tau}\!\left(\vec{q}, \dvec{q}, \ddvec{q}\right)' bdtex2icon -r 80 -o idynx.png -t '\vec{w}\!\left(\vec{q}, \dvec{q}, \ddvec{x}\right)' bdtex2icon -r 180 -o pose_postmul.png -t '\mathbf{\oplus\\!\pose[x]_y}' bdtex2icon -r 180 -o pose_premul.png -t '\mathbf{\pose[x]_y\\!\oplus}' bdtex2icon -r 170 -o pose_inverse.png -t '\mathbf{\ominus}' bdtex2icon -r 150 -o transform_vector.png -t '\mathbf{\pose[x]_y}\\!\sbullet\\!\vec{p}' bdtex2icon -r 140 -o poseintegerator.png -t "\int\! \nu\, dt" bdtex2icon -r 192 -o pid.png -t '\mbox{PID}' bdtex2icon -r 190 -o deriv.png -t 's' bdtex2icon -r 300 -o deriv.png -t 's' ================================================ FILE: bdsim/blocks/README.md ================================================ This folder holds the core bdsim block definitions. Each block is a class with a CamelCase name. The class definitions are grouped by block class | File | Purpose | |------|---------| |connections.py | signal grouping, subsystems etc. | |discrete.py | discrete transfer blocks | |displays.py | graphical sink blocks | |functions.py | function blocks without state | |linalg.py | linear algebra blocks | |sinks.py | signal sink blocks | |sources.py | signal source blocks | |transfers.py | transfer function blocks | |tex2icon.py | convert LaTeX string to an icon image file | |Icons | folder of icon images | ================================================ FILE: bdsim/blocks/__init__.py ================================================ from .functions import * from .sources import * from .sinks import * from .transfers import * from .discrete import * from .linalg import * from .displays import * from .connections import * from .spatial import * url = "https://petercorke.github.io/bdsim/" + __package__ ================================================ FILE: bdsim/blocks/connections.py ================================================ """ Connection blocks are in two categories: 1. Signal manipulation: - have inputs and outputs - have no state variables - are a subclass of ``FunctionBlock`` |rarr| ``Block`` 2. Subsystem support - have inputs or outputs - have no state variables - are a subclass of ``SubsysytemBlock`` |rarr| ``Block`` """ # The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance. import importlib.util import numpy as np import copy import bdsim from bdsim.components import SubsystemBlock, SourceBlock, SinkBlock, FunctionBlock # ------------------------------------------------------------------------ # class Item(FunctionBlock): """ :blockname:`ITEM` Select item from a dictionary signal. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - dict - ``D`` * - Output - 0 - any - ``D[i]`` For a dictionary type input signal, select one item as the output signal. For example:: item = bd.ITEM("xd") selects the ``xd`` item from the dictionary signal input to the block. This is somewhat like a demultiplexer :class:`DeMux` but allows for named heterogeneous data. A dictionary signal can serve a similar purpose to a "bus" in Simulink(R). :seealso: :class:`Dict` """ nin = 1 nout = 1 def __init__(self, item, **blockargs): """ :param item: name of dictionary item :type item: str :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.item = item def output(self, t, inports, x): input = inports[0] # TODO, handle inputs that are vectors themselves assert isinstance(input, dict), "Input signal must be a dict" assert self.item in input, "Item is not in input dict" return [input[self.item]] class Dict(FunctionBlock): """ :blockname:`DICT` Create a dictionary signal. :inputs: N :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - any - :math:`x_i` * - Output - 0 - dict - ``{key: x[i] for i, key in enumerate(keys)}`` Inputs are assigned to a dictionary signal, using the corresponding names from ``keys``. For example:: dd = bd.DICT(["x", "xd", "xdd"]) expects three inputs and assigns them to dictionary items ``x``, ``xd``, ``xdd`` of the output dictionary respectively. This is somewhat like a multiplexer :class:`Mux` but allows for named heterogeneous data. A dictionary signal can serve a similar purpose to a "bus" in Simulink(R). :seealso: :class:`Item` :class:`Mux` """ nin = 1 nout = 1 def __init__(self, keys, **blockargs): """ :param keys: list of dictionary keys :type keys: list :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.keys = keys def output(self, t, inports, x): return {key: inports[i] for i, key in enumerate(self.keys)} # ------------------------------------------------------------------------ # class Mux(FunctionBlock): """ :blockname:`MUX` Multiplex signals. :inputs: N :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - float, ndarray - :math:`x_i` * - Output - 0 - ndarray - :math:`[x_0 \ldots x_{N-1}]` This block takes a number of scalar or 1D-array signals and concatenates them into a single 1-D array signal. For example:: mux = bd.MUX(2) :seealso: :class:`Demux` :class:`Dict` """ # TODO could be generalized to creating a list of non numeric data nin = -1 nout = 1 def __init__(self, nin=1, **blockargs): """ :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(nin=nin, **blockargs) def output(self, t, inports, x): # TODO, handle inputs that are vectors themselves out = [] for input in inports: if isinstance(input, (int, float, bool)): out.append(input) elif isinstance(input, np.ndarray): out.extend(input.flatten().tolist()) return [np.array(out)] # ------------------------------------------------------------------------ # class DeMux(FunctionBlock): """ :blockname:`DEMUX` Demultiplex signals. :inputs: 1 :outputs: N :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - iterable - :math:`x` * - Output - i - any - :math:`x_i` This block has a single input port and ``nout`` output ports. The input signal is an iterable whose ``nout`` elements are routed element-wise to individual scalar output ports. If the input is a 1D Numpy array, then each output port is an element of that array. :seealso: :class:`Mux` """ nin = 1 nout = -1 def __init__(self, nout=1, **blockargs): """ :param nout: number of outputs, defaults to 1 :type nout: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(nout=nout, **blockargs) def output(self, t, inports, x): input = inports[0] # TODO, handle inputs that are vectors themselves assert ( len(input) == self.nout ), "Input width not equal to number of output ports" return list(input) # ------------------------------------------------------------------------ # class Index(FunctionBlock): """ :blockname:`INDEX` :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - iterable - :math:`x` * - Output - j - iterable - :math:`x_i` The specified element(s) of the input iterable (list, string, etc.) are output. The index can be an integer, sequence of integers, a Python slice object, or a string with Python slice notation, eg. ``"::-1"``. :seealso: :class:`Slice1` :class:`Slice2` """ nin = 1 nout = 1 def __init__(self, index=[], **blockargs): """ Index an iterable signal. :param index: elements of input array, defaults to [] :type index: list, slice or str, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) if isinstance(index, str): args = [None if a == "" else int(a) for a in index.split(":")] self.index = slice(*args) self.index = index def output(self, t, inports, x): input = inports[0] if len(self.index) == 1: return [input[self.index[0]]] elif isinstance(input, np.ndarray): return [np.array([input[i] for i in self.index])] else: return [[input[i] for i in self.index]] # ------------------------------------------------------------------------ # class SubSystem(SubsystemBlock): """ :blockname:`SUBSYSTEM` Instantiate a subsystem. :inputs: N :outputs: M :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - any - :math:`x_i` * - Output - j - any - :math:`y_j` This block represents a subsystem in a block diagram. The definition of the subsystem can be: - the name of a module which is imported and must contain only only ``BlockDiagram`` instance, or - a ``BlockDiagram`` instance The referenced block diagram must contain one or both of: - one ``InPort`` block, which has outputs but no inputs. These outputs are connected to the inputs to the enclosing ``SubSystem`` block. - one ``OutPort`` block, which has inputs but no outputs. These inputs are connected to the outputs to the enclosing ``SubSystem`` block. .. note:: - The referenced block diagram is treated like a macro and copied into the parent block diagram at compile time. The ``SubSystem``, ``InPort`` and ``OutPort`` blocks are eliminated, that is, all hierarchical structure is lost. - The same subsystem can be used multiple times, its blocks and wires will be cloned. Subsystems can also include subsystems. - The number of input and output ports is not specified, they are computed from the number of ports on the ``InPort`` and ``OutPort`` blocks within the subsystem. """ nin = -1 nout = -1 def __init__(self, subsys, nin=1, nout=1, **blockargs): """ :param subsys: Subsystem as either a filename or a ``BlockDiagram`` instance :type subsys: str or BlockDiagram :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param nout: Number of output ports, defaults to 1 :type nout: int, optional :param blockargs: |BlockOptions| :type blockargs: dict :raises ImportError: DESCRIPTION :raises ValueError: DESCRIPTION """ super().__init__(**blockargs) if isinstance(subsys, str): # attempt to import the file try: module = importlib.import_module(subsys, package=".") except SyntaxError: print("-- syntax error in block definiton: " + subsys) except ModuleNotFoundError: print("-- module not found ", subsys) # get all the bdsim.BlockDiagram instances simvars = [ name for name, ref in module.__dict__.items() if isinstance(ref, bdsim.BlockDiagram) ] if len(simvars) == 0: raise ImportError("no bdsim.Simulation instances in imported module") elif len(simvars) > 1: raise ImportError( "multiple bdsim.Simulation instances in imported module" + str(simvars) ) subsys = module.__dict__[simvars[0]] self.ssvar = simvars[0] elif isinstance(subsys, bdsim.BlockDiagram): # use an in-memory diagram self.ssvar = None else: raise ValueError("argument must be filename or BlockDiagram instance") # check if valid input and output ports ninp = 0 noutp = 0 for b in subsys.blocklist: if b.type == "inport": ninp += 1 elif b.type == "outport": noutp += 1 if ninp > 1: raise ValueError("subsystem cannot have more than one INPORT block") if noutp > 1: raise ValueError("subsystem cannot have more than one OUTPORT block") if ninp + noutp == 0: raise ValueError("subsystem cannot have zero INPORT or OUTPORT blocks") # it's valid, make a deep copy self.subsystem = copy.deepcopy(subsys) # get references to the input and output port blocks self.inport = None self.outport = None for b in self.subsystem.blocklist: if b.type == "inport": self.inport = b elif b.type == "outport": self.outport = b self.ssname = subsys.name self.nin = ninp self.nout = noutp # ------------------------------------------------------------------------ # class InPort(SubsystemBlock): """ :blockname:`INPORT` Input ports for a subsystem. :inputs: 0 :outputs: N :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Output - j - any - :math:`y_j` This block connects a subsystem to a parent block diagram. Inputs to the parent-level ``SubSystem`` block appear as the outputs of this block. .. note:: Only one ``INPORT`` block can appear in a block diagram but it can have multiple ports. This is different to Simulink(R) which would require multiple single-port input blocks. """ nin = 0 nout = -1 def __init__(self, nout=1, **blockargs): """ :param nout: Number of output ports, defaults to 1 :type nout: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(nout=nout, **blockargs) def output(self, t, inports, x): # signal feed through return inports # ------------------------------------------------------------------------ # class OutPort(SubsystemBlock): """ :blockname:`OUTPORT` Output ports for a subsystem. :inputs: N :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - any - :math:`x_i` This block connects a subsystem to a parent block diagram. The inputs of this block become the outputs of the parent-level ``SubSystem`` block. .. note:: Only one ``OUTPORT`` block can appear in a block diagram but it can have multiple ports. This is different to Simulink(R) which would require multiple single-port output blocks. """ nin = -1 nout = 0 def __init__(self, nin=1, **blockargs): """ :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(nin=nin, **blockargs) def output(self, t, inports, x): # signal feed through return inports if __name__ == "__main__": # pragma: no cover from pathlib import Path exec( open( Path(__file__).parent.parent.parent / "tests" / "test_connections.py" ).read() ) ================================================ FILE: bdsim/blocks/discrete.py ================================================ """ Transfer blocks: - have inputs and outputs - have discrete-time state variables that are sampled/updated at the times specified by the associated clock - are a subclass of ``TransferBlock`` |rarr| ``Block`` """ import numpy as np import math from math import sin, cos, atan2, sqrt, pi import matplotlib.pyplot as plt import inspect from spatialmath import Twist3, SE3 import spatialmath.base as smb from bdsim.components import ClockedBlock # ------------------------------------------------------------------------ class ZOH(ClockedBlock): """ :blockname:`ZOH` Zero-order hold. :inputs: 1 :outputs: 1 :states: N .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float, ndarray - :math:`x` * - Output - 0 - float, ndarray - :math:`y` Output is the input at the previous clock time $y_{k} = x_{k-1}. The state can be a scalar or a vector, this is given by the type of ``x0``. .. note:: If input is not a scalar, ``x0`` must have the shape of the input signal. """ nin = 1 nout = 1 def __init__(self, clock, x0=0, **blockargs): """ :param clock: clock source :type clock: Clock :param x0: Initial value of the hold, defaults to 0 :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ self.type = "sampler" super().__init__(nin=1, nout=1, clock=clock, **blockargs) x0 = smb.getvector(x0) self._x0 = x0 self.ndstates = len(x0) # print('nstates', self.nstates) def output(self, t, inports, x): # print('* output, x is ', self._x) return [x] def next(self, t, inports, x): u = smb.getvector(inports[0]) return u # must be an ndarray # ------------------------------------------------------------------------ class DIntegrator(ClockedBlock): """ :blockname:`DINTEGRATOR` Discrete-time integrator. :inputs: 1 :outputs: 1 :states: N .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float, ndarray - :math:`x` * - Output - 0 - float, ndarray - :math:`y` Create a discrete-time integrator block. Output is the time integral of the input. The state can be a scalar or a vector, this is given by the type of ``x0``. The minimum and maximum values can be: - a scalar, in which case the same value applies to every element of the state vector, or - a vector, of the same shape as ``x0`` that applies elementwise to the state. """ nin = 1 nout = 1 def __init__(self, clock, x0=0, gain=1.0, min=None, max=None, **blockargs): """ :param clock: clock source :type clock: Clock :param x0: Initial state, defaults to 0 :type x0: array_like, optional :param gain: gain or scaling factor, defaults to 1 :type gain: float :param min: Minimum value of state, defaults to None :type min: float or array_like, optional :param max: Maximum value of state, defaults to None :type max: float or array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(clock=clock, **blockargs) if isinstance(x0, (int, float)): x0 = np.r_[x0] elif isinstance(x0, np.ndarray): if x0.ndim > 1: raise ValueError("state must be a 1D vector") else: x0 = smb.getvector(x0) self.ndstates = x0.shape[0] if min is not None: min = smb.getvector(min, self.ndstates) if max is not None: max = smb.getvector(max, self.ndstates) self._x0 = x0 self.min = min self.max = max self.gain = gain def output(self, t, u, x): return [x] def next(self, t, u, x): xnext = x + self.gain * self.clock.T * np.array(u[0]) if self.min is not None or self.max is not None: xnext = np.clip(xnext, self.min, self.max) return xnext class DPoseIntegrator(ClockedBlock): """ :blockname:`DPOSEINTEGRATOR` Discrete-time spatial velocity integrator. :inputs: 1 :outputs: 1 :states: 6 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray(6,) - :math:`x` * - Output - 0 - SE3 - :math:`y` This block integrates spatial velocity over time. The block input is a spatial velocity as a 6-vector :math:`(v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)` and the output is pose as an ``SE3`` instance. .. note:: State is a velocity twist. """ nin = 1 nout = 1 inlabels = ("ν",) outlabels = ("ξ",) def __init__(self, clock, x0=None, **blockargs): r""" :param clock: clock source :type clock: Clock :param x0: Initial pose, defaults to null :type x0: SE3, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(clock=clock, **blockargs) if x0 is None: x0 = Twist3() elif isinstance(x0, SE3): x0 = Twist3(x0).A elif isinstance(x0, Twist3): x0 = x0.A elif isvector(x0, 6): x0 = getvector(x0, 6) self.ndstates = 6 self._x0 = x0 print("nstates", self.nstates, x0) def output(self, t, u, x): return [Twist3(x).SE3()] def next(self, t, u, x): T_delta = SE3.Delta(u[0] * self.clock.T) pose = Twist3(x).SE3() * T_delta return Twist3(pose).A # ------------------------------------------------------------------------ # # @block # class LTI_SS(TransferBlock): # """ # :blockname:`LTI_SS` # .. table:: # :align: left # +------------+---------+---------+ # | inputs | outputs | states | # +------------+---------+---------+ # | 1 | 01 | nc | # +------------+---------+---------+ # | float, | float, | | # | A(nb,) | A(nc,) | | # +------------+---------+---------+ # """ # def __init__(self, *inputs, A=None, B=None, C=None, x0=None, verbose=False, **blockargs): # r""" # :param ``*inputs``: Optional incoming connections # :type ``*inputs``: Block or Plug # :param N: numerator coefficients, defaults to 1 # :type N: array_like, optional # :param D: denominator coefficients, defaults to [1, 1] # :type D: array_like, optional # :param x0: initial states, defaults to zero # :type x0: array_like, optional # :param ``**blockargs``: |BlockOptions| # :return: A SCOPE block # :rtype: LTI_SISO instance # Create a state-space LTI block. # Describes the dynamics of a single-input single-output (SISO) linear # time invariant (LTI) system described by numerator and denominator # polynomial coefficients. # Coefficients are given in the order from highest order to zeroth # order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``. # Only proper transfer functions, where order of numerator is less # than denominator are allowed. # The order of the states in ``x0`` is consistent with controller canonical # form. # Examples:: # LTI_SISO(N=[1,2], D=[2, 3, -4]) # is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`. # """ # #print('in SS constructor') # self.type = 'LTI SS' # assert A.shape[0] == A.shape[1], 'A must be square' # n = A.shape[0] # if len(B.shape) == 1: # nin = 1 # B = B.reshape((n, 1)) # else: # nin = B.shape[1] # assert B.shape[0] == n, 'B must have same number of rows as A' # if len(C.shape) == 1: # nout = 1 # assert C.shape[0] == n, 'C must have same number of columns as A' # C = C.reshape((1, n)) # else: # nout = C.shape[0] # assert C.shape[1] == n, 'C must have same number of columns as A' # super().__init__(nin=nin, nout=nout, inputs=inputs, **blockargs) # self.A = A # self.B = B # self.C = C # self.nstates = A.shape[0] # if x0 is None: # self._x0 = np.zeros((self.nstates,)) # else: # self._x0 = x0 # def output(self, t=None): # return list(self.C @ self._x) # def deriv(self): # return self.A @ self._x + self.B @ np.array(self.inputs) # # ------------------------------------------------------------------------ # # @block # class LTI_SISO(LTI_SS): # """ # :blockname:`LTI_SISO` # .. table:: # :align: left # +------------+---------+---------+ # | inputs | outputs | states | # +------------+---------+---------+ # | 1 | 1 | n | # +------------+---------+---------+ # | float | float | | # +------------+---------+---------+ # """ # def __init__(self, N=1, D=[1, 1], *inputs, x0=None, verbose=False, **blockargs): # r""" # :param N: numerator coefficients, defaults to 1 # :type N: array_like, optional # :param D: denominator coefficients, defaults to [1, 1] # :type D: array_like, optional # :param ``*inputs``: Optional incoming connections # :type ``*inputs``: Block or Plug # :param x0: initial states, defaults to zero # :type x0: array_like, optional # :param ``**blockargs``: |BlockOptions| # :return: A SCOPE block # :rtype: LTI_SISO instance # Create a SISO LTI block. # Describes the dynamics of a single-input single-output (SISO) linear # time invariant (LTI) system described by numerator and denominator # polynomial coefficients. # Coefficients are given in the order from highest order to zeroth # order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``. # Only proper transfer functions, where order of numerator is less # than denominator are allowed. # The order of the states in ``x0`` is consistent with controller canonical # form. # Examples:: # LTI_SISO(N=[1, 2], D=[2, 3, -4]) # is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`. # """ # #print('in SISO constscutor') # if not isinstance(N, list): # N = [N] # if not isinstance(D, list): # D = [D] # self.N = N # self.D = N # n = len(D) - 1 # nn = len(N) # if x0 is None: # x0 = np.zeros((n,)) # assert nn <= n, 'direct pass through is not supported' # # convert to numpy arrays # N = np.r_[np.zeros((len(D) - len(N),)), np.array(N)] # D = np.array(D) # # normalize the coefficients to obtain # # # # b_0 s^n + b_1 s^(n-1) + ... + b_n # # --------------------------------- # # a_0 s^n + a_1 s^(n-1) + ....+ a_n # # normalize so leading coefficient of denominator is one # D0 = D[0] # D = D / D0 # N = N / D0 # A = np.eye(len(D) - 1, k=1) # control canonic (companion matrix) form # A[-1, :] = -D[1:] # B = np.zeros((n, 1)) # B[-1] = 1 # C = (N[1:] - N[0] * D[1:]).reshape((1, n)) # if verbose: # print('A=', A) # print('B=', B) # print('C=', C) # super().__init__(A=A, B=B, C=C, x0=x0, **blockargs) # self.type = 'LTI' if __name__ == "__main__": # pragma: no cover from pathlib import Path exec( open(Path(__file__).parent.parent.parent / "tests" / "test_discrete.py").read() ) ================================================ FILE: bdsim/blocks/displays.py ================================================ """ Sink blocks: - have inputs but no outputs - have no state variables - are a subclass of ``SinkBlock`` |rarr| ``Block`` - that perform graphics are a subclass of ``GraphicsBlock`` |rarr| ``SinkBlock`` |rarr| ``Block`` """ import numpy as np from math import pi, sqrt, sin, cos, atan2 import matplotlib.pyplot as plt from matplotlib.pyplot import Polygon import spatialmath.base as sm from bdsim.components import SinkBlock from bdsim.graphics import GraphicsBlock # ------------------------------------------------------------------------ # class Scope(GraphicsBlock): r""" :blockname:`SCOPE` Plot input signals against time. :inputs: N :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - float - :math:`x_i` is the i'th line Create a scope block that plots multiple signals against time. For each line plotted we can specify the: * line style as a heterogeneous list of: * Matplotlib `fmt` string comprising a color and line style, eg. ``"k"`` or ``"r:"`` * a dict of Matplotlib line style options for `Line2D `_ , eg. ``{"color": "k", "linewidth": 3, "alpha": 0.5)`` * line label, used in the legend and vertical axis. This can include math mode notation or unicode characters. The vertical scale factor defaults to auto-scaling but can be fixed by providing a 2-tuple ``[ymin, ymax]``. All lines are plotted against the same vertical scale. .. figure:: ../../figs/Figure_1.png :width: 500px :alt: example of generated graphic Example of scope display. **Scalar input ports against time** The number of lines to plot will be inferred from: * the length of the ``labels`` list if specified * the length of the ``styles`` list if specified * ``nin`` if specified, it defaults to 1 These numbers must be consistent. Examples:: bd.SCOPE() # a scope with 1 input port bd.SCOPE(nin=3) # a scope with 3 input ports bd.SCOPE(styles=["k", "r--"]) # a scope with 2 input ports bd.SCOPE(labels=["x", r"$\gamma$"]) # a scope with 2 input ports bd.SCOPE(styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}]) **Single input port with NumPy array** The port is fed with a 1D-array, and ``vector`` is an: * int, this is the expected width of the array, all its elements will be plotted * a list of ints, interpretted as indices of the elements to plot. Examples:: bd.SCOPE(vector=[0,1,2]) # display elements 0, 1, 2 of array on port 0 bd.SCOPE(vector=[0,1], styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}]) .. note:: * If the vector is of width 3, by default the inputs are plotted as red, green and blue lines. * If the vector is of width 6, by default the first three inputs are plotted as solid red, green and blue lines and the last three inputs are plotted as dashed red, green and blue lines. """ nin = -1 nout = 0 def __init__( self, nin=1, vector=None, styles=None, stairs=False, scale="auto", labels=None, grid=True, watch=False, title=None, loc="best", **blockargs, ): """ :param nin: number of inputs, defaults to 1 or if given, the length of style vector :type nin: int, optional :param vector: vector signal on single input port, defaults to None :type vector: int or list, optional :param styles: styles for each line to be plotted :type styles: str or dict, list of strings or dicts; one per line, optional :param stairs: force staircase style plot for all lines, defaults to False :type stairs: bool, optional :param scale: fixed y-axis scale or defaults to 'auto' :type scale: str or array_like(2) :param labels: vertical axis labels :type labels: sequence of strings :param grid: draw a grid, defaults to True. Can be boolean or a tuple of options for grid() :type grid: bool or sequence :param watch: add these signals to the watchlist, defaults to False :type watch: bool, optional :param title: title of plot :type title: str :param loc: location of legend, see :meth:`matplotlib.pyplot.legend`, defaults to "best" :type loc: str :param blockargs: |BlockOptions| :type blockargs: dict """ def listify(s): # guarantee that result is a list if isinstance(s, str): return [s] elif isinstance(s, (list, tuple)): return s else: raise ValueError("unknown argument to listify") # number of lines plotted (nplots) is inferred from the number of labels # or linestyles nplots = None if vector is not None: # vector argument is given # block has single input which is an array # vector is int, width of vector # vector is a list of ints, select those inputs from the input vector if nin != 1: raise ValueError("if vector is given, nin must be 1") if isinstance(vector, int): nplots = vector elif isinstance(vector, list): nplots = len(vector) else: raise ValueError("vector must be an int or list of indices") if styles is not None: self.styles = listify(styles) if nplots is None: nplots = len(self.styles) else: assert nplots == len(self.styles), "need one style per plot" else: self.styles = None if labels is not None: self.labels = listify(labels) if nplots is None: nplots = len(self.labels) else: assert nplots == len(self.labels), "need one label per plot" else: self.labels = None if nplots is None: # nplots has not been determined from styles or labels, so use nin nplots = nin elif nin == 1 and vector is None: # nplots is different to the default nin value, override it nin = nplots self.nplots = nplots self.vector = vector super().__init__(nin=nin, **blockargs) self.xlabel = "Time (s)" self.grid = grid self.stairs = stairs self.line = [None] * nplots self.scale = scale self.watch = watch self.title = title self.loc = loc # TODO, wire width # inherit names from wires, block needs to be able to introspect def start(self, simstate): super().start(simstate) if not self._enabled: return # init the arrays that hold the data self.tdata = np.array([]) self.ydata = [ np.array([]), ] * self.nplots # create the figures self.fig = self.create_figure(simstate) self.ax = self.fig.add_subplot(111) # get labels if not provided if self.labels is None: if self.vector is None: self.labels = [self.sourcename(i) for i in range(self.nin)] else: self.labels = [str(i) for i in range(self.vector)] if self.styles is None: if self.vector == 3: self.styles = ["r", "g", "b"] elif self.vector == 6: self.styles = ["r", "g", "b", "r--", "g--", "b--"] if self.styles is None: self.styles = [None] * self.nplots # create empty lines with defined styles for i in range(0, self.nplots): args = [] kwargs = {} style = self.styles[i] if isinstance(style, dict): kwargs = style elif isinstance(style, str): args = [style] if self.stairs: kwargs["drawstyle"] = "steps" # force steppy plot (self.line[i],) = self.ax.plot( self.tdata, self.ydata[i], *args, label=self.styles[i], linewidth=2, **kwargs, ) # label the axes if self.labels is not None: self.ax.set_ylabel(",".join(self.labels)) self.ax.set_xlabel(self.xlabel) if self.title is not None: name = self.title else: name = self.name_tex self.ax.set_title(name) # grid control if self.grid is True: self.ax.grid(self.grid) elif isinstance(self.grid, (list, tuple)): self.ax.grid(True, *self.grid) # set limits self.ax.set_xlim(0, simstate.T) if self.scale != "auto": self.ax.set_ylim(*self.scale) if self.labels is not None: def fix_underscore(s): if s[0] == "_": return "-" + s[1:] else: return s self.ax.legend( [fix_underscore(label) for label in self.labels], loc=self.loc ) if self.watch: for wire in self.input_wires: plug = wire.start # start plug for input wire # append to the watchlist, bdsim.run() will do the rest simstate.watchlist.append(plug) simstate.watchnamelist.append(str(plug)) plt.draw() plt.show(block=False) def step(self, t, inports): if not self._enabled: return # inputs are set self.tdata = np.append(self.tdata, t) if self.vector is None: # take data from multiple inputs as a list data = inports if len(data) != self.nplots: raise RuntimeError( "number of signals to plot doesnt match init parameters" ) else: # single input with vector data data = self.inputs[0] if isinstance(self.vector, list): data = data[self.vector] # append new data to the set for i, y in enumerate(data): self.ydata[i] = np.append(self.ydata[i], y) # plot the data for i in range(0, self.nplots): self.line[i].set_data(self.tdata, self.ydata[i]) if self.scale == "auto": self.ax.relim() self.ax.autoscale_view(scalex=False, scaley=True) super().step(t, inports) # ------------------------------------------------------------------------ # class ScopeXY(GraphicsBlock): """ :blockname:`SCOPEXY` Plot X against Y. :inputs: 2 :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float - :math:`x` * - Input - 1 - float - :math:`y` Create an XY scope where input :math:`y` (vertical axis) is plotted against :math:`x` (horizontal axis). Line style is one of: * Matplotlib `fmt` string comprising a color and line style, eg. ``"k"`` or ``"r:"`` * a dict of Matplotlib line style options for `Line2D `_ , eg. ``dict(color="k", linewidth=3, alpha=0.5)`` The scale factor defaults to auto-scaling but can be fixed by providing either: - a 2-tuple ``[min, max]`` which is used for the x- and y-axes - a 4-tuple ``[xmin, xmax, ymin, ymax]`` """ nin = 2 nout = 0 def __init__( self, style=None, scale="auto", aspect="equal", labels=["X", "Y"], init=None, nin=2, **blockargs, ): """ :param style: line style, defaults to None :type style: optional str or dict :param scale: fixed y-axis scale or defaults to 'auto' :type scale: str or array_like(2) or array_like(4) :param labels: axis labels (xlabel, ylabel), defaults to ["X","Y"] :type labels: 2-element tuple or list :param init: function to initialize the graphics, defaults to None :type init: callable :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.xdata = [] self.ydata = [] if init is not None: assert callable(init), "graphics init function must be callable" self.init = init self.styles = style if scale != "auto": scale = sm.expand_dims(scale, 2) self.scale = scale self.aspect = aspect self.labels = labels self.inport_names(("x", "y")) def start(self, simstate): super().start(simstate) if not self._enabled: return # create the plot super().reset() self.fig = self.create_figure(simstate) self.ax = self.fig.gca() args = [] blockargs = {} style = self.styles if isinstance(style, dict): blockargs = style elif isinstance(style, str): args = [style] (self.line,) = self.ax.plot(self.xdata, self.ydata, *args) self.ax.grid(True) self.ax.set_xlabel(self.labels[0]) self.ax.set_ylabel(self.labels[1]) self.ax.set_title(self.name) if not (isinstance(self.scale, str) and self.scale == "auto"): self.ax.set_xlim(*self.scale[0:2]) self.ax.set_ylim(*self.scale[2:4]) self.ax.set_aspect(self.aspect) if self.init is not None: self.init(self.ax) plt.draw() plt.show(block=False) def step(self, t, inports): if not self._enabled: return self._step(inports[0], inports[1], t) def _step(self, x, y, t): self.xdata.append(x) self.ydata.append(y) plt.figure(self.fig.number) self.line.set_data(self.xdata, self.ydata) if self.bd.runtime.options.animation: self.fig.canvas.flush_events() if isinstance(self.scale, str) and self.scale == "auto": self.ax.relim() self.ax.autoscale_view() super().step(t, None) # def done(self, block=False, **blockargs): # if self.bd.runtime.options.graphics: # plt.show(block=block) # super().done() class ScopeXY1(ScopeXY): """ :blockname:`SCOPEXY1` Plot X[0] against X[1]. :inputs: 1 :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`x` Create an XY scope where input :math:`x_j` (vertical axis) is plotted against :math:`x_i` (horizontal axis). This block has one vector input and the elements to be plotted are given by a 2-element iterable :math:`(i, j)`. Line style is one of: * Matplotlib `fmt` string comprising a color and line style, eg. ``"k"`` or ``"r:"`` * a dict of Matplotlib line style options for `Line2D `_ , eg. ``dict(color="k", linewidth=3, alpha=0.5)`` The scale factor defaults to auto-scaling but can be fixed by providing either: - a 2-tuple ``[min, max]`` which is used for the x- and y-axes - a 4-tuple ``[xmin, xmax, ymin, ymax]`` """ nin = 1 nout = 0 def __init__(self, indices=[0, 1], **blockargs): """ :param indices: indices of elements to select from block input vector, defaults to [0,1] :type indices: array_like(2) :param style: line style :type style: optional str or dict :param scale: fixed y-axis scale or defaults to 'auto' :type scale: str or array_like(2) or array_like(4) :param labels: axis labels (xlabel, ylabel) :type labels: 2-element tuple or list :param init: function to initialize the graphics, defaults to None :type init: callable :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.inport_names(("xy",)) if len(indices) != 2: raise ValueError("indices must have 2 elements") self.indices = [int(x) for x in indices] def step(self, t, inports): if not self._enabled: return # inputs are set x = inports[0][self.indices[0]] y = inports[0][self.indices[1]] super()._step(x, y, t) # ------------------------------------------------------------------------ # if __name__ == "__main__": # pragma: no cover from pathlib import Path exec( open(Path(__file__).parent.parent.parent / "tests" / "test_displays.py").read() ) ================================================ FILE: bdsim/blocks/functions.py ================================================ """ Function blocks: - have inputs and outputs - have no state variables - are a subclass of ``FunctionBlock`` |rarr| ``Block`` """ # The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance. import numpy as np from scipy import linalg import scipy.interpolate import math import inspect import spatialmath.base as smb from typing import Any, Union, Callable ArrayLike = Union[np.ndarray, int, float, list, tuple] from bdsim.components import FunctionBlock # TODO: # transform 3D points class Sum(FunctionBlock): """ :blockname:`SUM` Summing junction. :inputs: N :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - int, float, ndarray - :math:`x_i` * - Output - 0 - int, float, ndarray - :math:`\sum_i \pm x_i` Add or subtract input signals according to the ``signs`` string. The number of input ports is the length of this string. For example:: sum = bd.SUM('+-+') is a 3-input summing junction which computes ``in[0] - in[1] + in[2]``. :note: The signals must be compatible for addition, and if some are arrays they must be broadcastable. """ nin = -1 nout = 1 _modefuncs = { "r": lambda x: x, "c": smb.wrap_mpi_pi, "C": smb.wrap_0_2pi, "L": smb.wrap_0_pi, "l": smb.wrap_0_pi, } def __init__(self, signs: str = "++", mode: str = None, **blockargs): """ :param signs: signs associated with input ports, accepted characters: + or -, defaults to "++" :type signs: str, optional :param mode: controls addition mode, per element, string comprises ``r`` or ``c`` or ``C`` or ``L``, defaults to None :type mode: str, optional :param blockargs: |BlockOptions| :type blockargs: dict ``mode`` controls how elements of the input vectors are added/subtracted. Elements which are angles must be treated specially, and this is indicated by the corresponding characters in ``mode``. The string's length must equal the width of the input vectors. The characters of the string can be: ============== ============================================ mode character purpose ============== ============================================ r real number, don't wrap (default) c angle on circle, wrap to [-π, π) C angle on circle, wrap to [0, 2π) L colatitude angle, wrap to [0, π] ============== ============================================ For example if ``mode="rc"`` then a 2-element array would have its second element wrapped to the range [-π, π). """ super().__init__(nin=len(signs), **blockargs) assert isinstance(signs, str), "first argument must be signs string" assert all([x in "+-" for x in signs]), "invalid sign" self.signs = signs self.mode = mode def output(self, t, inports, x): for i, input in enumerate(inports): # code makes no assumption about types of inputs # NOTE: use sum = sum =/- input rather than sum +/-= input since # these are references if self.signs[i] == "-": if i == 0: sum = -input else: sum = sum - input else: if i == 0: sum = input else: sum = sum + input if self.mode is not None: if isinstance(sum, np.ndarray): # sum is an array if sum.ndim == 1: if len(self.mode) != len(sum): raise ValueError("length of mode string doesn't match") sum = np.array( [self._modefuncs[m](x) for (m, x) in zip(self.mode, sum)] ) elif sum.ndim == 2: if len(self.mode) != sum.shape[0]: raise ValueError( "length of mode string doesn't match number of rows" ) out = [] for col in sum.T: out.append( [self._modefuncs[m](x) for (m, x) in zip(self.mode, col)] ) sum = np.array(out).T else: raise ValueError("expecting 1D or 2D array") else: # sum is a scalar sum = self._modefuncs[self.mode[0]](sum) return [sum] # ------------------------------------------------------------------------ # class Prod(FunctionBlock): r""" :blockname:`PROD` Product junction. :inputs: N :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - int, float, ndarray - :math:`x_i` * - Output - 0 - int, float, ndarray - :math:`\prod_i \{x_i | \frac{1}{x_i}\}` Multiply or divide input signals according to the ``ops`` string. The number of input ports is the length of this string. For example:: prod = PROD('*/*') is a 3-input product junction which computes ``in[0] / in[1] * in[2]``. :note: By default the ``*`` and ``/`` operators are used which perform element-wise operations. :note: The option ``matrix`` will instead use ``@`` and ``@ np.linalg.inv()``. The shapes of matrices must conform. A matrix on a ``/`` input must be square and non-singular. Matrices are multiplied in ascending port order. """ nin = -1 nout = 1 def __init__(self, ops: str = "**", matrix: bool = False, **blockargs): """ :param ops: operations associated with input ports, accepted characters: * or /, defaults to '**' :type ops: str, optional :param inputs: Optional incoming connections :type inputs: Block or Plug :param matrix: Arguments are matrices, defaults to False :type matrix: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(nin=len(ops), **blockargs) assert isinstance(ops, str), "first argument must be signs string" assert all([x in "*/" for x in ops]), "invalid op" self.ops = ops self.matrix = matrix def output(self, t, inports, x): for i, input in enumerate(inports): if i == 0: if self.ops[i] == "*": prod = input else: if self.matrix: prod = np.linalg.inv(input) prod = 1.0 / input else: if self.ops[i] == "*": if self.matrix: prod = prod @ input else: prod = prod * input else: if self.matrix: prod = prod @ np.linalg.inv(input) else: prod = prod / input return [prod] # ------------------------------------------------------------------------ # class Gain(FunctionBlock): """ :blockname:`GAIN` Gain block. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - int, float, ndarray - :math:`x` * - Output - 0 - int, float, ndarray - :math:`K x` Either or both the input and gain can be Numpy arrays and Numpy will compute the appropriate product :math:`u K`. If :math:`u` and ``K`` are both NumPy arrays the ``@`` operator is used and :math:`u` is postmultiplied by the gain. To premultiply by the gain, to compute :math:`K u` use the ``premul`` option. For example:: gain = bd.GAIN(2.5) """ nin = 1 nout = 1 def __init__( self, K: Union[int, float, np.ndarray] = 1, premul: bool = False, **blockargs ): """ :param K: The gain value, defaults to 1 :type K: scalar, array_like :param premul: premultiply by constant, default is postmultiply, defaults to False :type premul: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.K = K self.premul = premul self.add_param("K") def output(self, t, inports, x): input = inports[0] if isinstance(input, np.ndarray) and isinstance(self.K, np.ndarray): # array x array case if self.premul: # premultiply by gain return [self.K @ input] else: # postmultiply by gain return [input @ self.K] else: return [input * self.K] # ------------------------------------------------------------------------ # class Pow(FunctionBlock): """ :blockname:`POW` Power block. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - int, float, ndarray - :math:`x` * - Output - 0 - int, float, ndarray - :math:`x^p` If the input is a Numpy array the result depends on ``matrix``. If ``matrix`` is False the block performs an elementwise exponentiation and the result is a Numpy array of the same size. If ``matrix`` is True and the input is a square matrix and ``p`` is an integer then matrixwise exponentiation is performedand using repeated matrix multiplication and matrix inversion. If ``p`` is not an integer it uses SciPy to compute the power using an eigenvalue decomposition. For example:: pow = bd.POW(2) :seealso: :func:`numpy.linalg.matrix_power` :func:`scipy.linalg.fractional_matrix_power` """ nin = 1 nout = 1 def __init__(self, p: Union[int, float] = 1, matrix: bool = False, **blockargs): """ :param p: The exponent value, defaults to 1 :type p: scalar :param matrix: premultiply by constant, default is postmultiply, defaults to False :type matrix: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.p = p self.matrix = matrix self.add_param("p") def output(self, t, inports, x): input = inports[0] if isinstance(input, np.ndarray): # input is an array if self.matrix: # matrixwise exponentiation if isinstance(self.p, int): # integer case return [np.linalg.matrix_power(input, self.p)] else: # general fractional case return [linalg.fractional_matrix_power(input, self.p)] else: # elementwise exponentiation return [input**self.p] else: return [input**self.p] # ------------------------------------------------------------------------ # class Clip(FunctionBlock): r""" :blockname:`CLIP` Signal clipping. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - int, float, ndarray - :math:`x` * - Output - 0 - int, float, ndarray - :math:`\min(\max(x,a), b)` The input signal is clipped to the range from ``minimum`` to ``maximum`` inclusive. The signal can be a 1D-array in which case each element is clipped. The minimum and maximum values can be: - a scalar, in which case the same value applies to every element of the input vector , or - a 1D-array, of the same shape as the input vector that applies elementwise to the input vector. For example:: clip = bd.CLIP(-1, 1) """ nin = 1 nout = 1 def __init__( self, min: ArrayLike = -math.inf, max: ArrayLike = math.inf, **blockargs ): """ :param min: Minimum value, defaults to -math.inf :type min: scalar or array_like, optional :param max: Maximum value, defaults to math.inf :type max: float or array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.min = min self.max = max def output(self, t, inports, x): input = inports[0] if isinstance(input, np.ndarray): out = np.clip(input, self.min, self.max) else: out = min(self.max, max(input, self.min)) return [out] # ------------------------------------------------------------------------ # # TODO can have multiple outputs: pass in a tuple of functions, return a tuple class Function(FunctionBlock): r""" :blockname:`FUNCTION` Python function. :inputs: N :outputs: M :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - any - :math:`x_i` * - Output - j - any - :math:`f_j(x_0, \ldots, x_{N-1})` Inputs to the block are passed as separate arguments to the function. Programmatic ositional or keyword arguments can also be passed to the function. A block with one output port that sums its two input ports is:: func = bd.FUNCTION(lambda u1, u2: u1+u2, nin=2) A block with a function that takes two inputs and has two additional arguments:: def myfun(u1, u2, param1, param2): pass FUNCTION(myfun, nin=2, args=(p1,p2)) If we need access to persistent (static) data, to keep some state:: def myfun(u1, u2, param1, param2, state): pass func = bd.FUNCTION(myfun, nin=2, args=(p1,p2), persistent=True) where a dictionary is passed in as the last argument and which is kept from call to call. A block with a function that takes two inputs and additional keyword arguments:: def myfun(u1, u2, param1=1, param2=2, param3=3, param4=4): pass func = bd.FUNCTION(myfun, nin=2, kwargs=dict(param2=7, param3=8)) A block with two inputs and two outputs, the outputs are defined by two lambda functions with the same inputs:: FUNCTION( [ lambda x, y: x_t, lambda x, y: x* y]) A block with two inputs and two outputs, the outputs are defined by a single function which returns a list:: def myfun(u1, u2): return [ u1+u2, u1*u2 ] func = bd.FUNCTION( myfun, nin=2, nout=2) """ nin = -1 nout = -1 def __init__( self, func: Callable = None, nin: int = 1, nout: int = 1, persistent: bool = False, fargs: list = None, fkwargs: dict = None, **blockargs, ): """ :param func: function or lambda, or list thereof, defaults to None :type func: callable or sequence of callables, optional :param nin: number of inputs, defaults to 1 :type nin: int, optional :param nout: number of outputs, defaults to 1 :type nout: int, optional :param persistent: pass in a reference to a dictionary instance to hold persistent state, defaults to False :type persistent: bool, optional :param fargs: extra positional arguments passed to the function, defaults to [] :type fargs: list, optional :param fkwargs: extra keyword arguments passed to the function, defaults to {} :type fkwargs: dict, optional :param blockargs: |BlockOptions| :type blockargs: dict, optional """ if func is None: raise ValueError("function is not defined") super().__init__(nin=nin, nout=nout, **blockargs) if fargs is None: fargs = list() if fkwargs is None: fkwargs = dict() # TODO, don't know why this happens if len(fargs) > 0 and fargs[0] == {}: fargs = [] if isinstance(func, (list, tuple)): for f in func: assert callable(f), "Function must be a callable" if fkwargs is None: # we can check the number of arguments n = len(inspect.signature(func).parameters) if persistent: n -= 1 # discount dict if used if nin + len(fargs) != n: raise ValueError( f"argument count mismatch: function has {n} args," f" dict={dict}, nin={nin}" ) elif callable(func): if len(fkwargs) == 0: # we can check the number of arguments n = len(inspect.signature(func).parameters) if persistent: n -= 1 # discount dict if used if nin + len(fargs) != n: raise ValueError( f"argument count mismatch: function has {n} args, dict={dict}," f" nin={nin}" ) # self.nout = nout self.func = func if persistent: self.userdata = dict() fargs += (self.userdata,) else: self.userdata = None self.args = fargs self.kwargs = fkwargs def start(self, simstate): super().start(simstate) if self.userdata is not None: self.userdata.clear() print("clearing user data") def output(self, t, inports, x): if callable(self.func): # single function try: val = self.func(*inports, *self.args, **self.kwargs) except TypeError: raise RuntimeError( "Function invocation failed, check number of arguments" ) from None if isinstance(val, (list, tuple)): if len(val) != self.nout: raise RuntimeError( "Function returns wrong number of arguments: " + str(self) ) return val else: if self.nout != 1: raise RuntimeError( "Function returns wrong number of arguments: " + str(self) ) return [val] else: # list of functions out = [] for f in self.func: try: val = f(*inports, *self.args, **self.kwargs) except TypeError: raise RuntimeError( "Function invocation failed, check number of arguments" ) from None out.append(val) return out # ------------------------------------------------------------------------ # class Interpolate(FunctionBlock): """ :blockname:`INTERPOLATE` Interpolate signal. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - int, float - :math:`x` * - Output - 0 - float - :math:`f(x)` Interpolate a scalar function of a scalar. A simple triangle function with domain [0,10] and range [0,1] can be defined by:: interp = bd.INTERPOLATE(x=(0,5,10), y=(0,1,0)) We might also express this as a list of 2D-coordinates:: interp = bd.INTERPOLATE(xy=[(0,0), (5,1), (10,0)]) .. plot:: import matplotlib.pyplot as plt plt.plot([0, 5, 10], [0, 1, 0], lw=2) plt.grid(True) plt.xlabel("in[0]") plt.ylabel("out[0]") The data can also be expressed as Numpy arrays. If that is the case, the interpolation function can be vector valued. ``x`` has a shape of (N,1) and ``y`` has a shape of (N,M). Alternatively ``xy`` has a shape of (N,M+1) and the first column is the x-data. :note: if ``time=True``. In this case the block has no input ports and is a ``Source`` not a ``Function`` block. :seealso: :func:`scipy.interpolate.interp1d` """ nin = -1 nout = 1 def __init__( self, x: Union[list, tuple, np.ndarray] = None, y: Union[list, tuple, np.ndarray] = None, xy: np.ndarray = None, time: bool = False, kind: str = "linear", **blockargs, ): """ :param x: x-values of function, defaults to None :type x: array_like, shape (N,) optional :param y: y-values of function, defaults to None :type y: array_like, optional :param xy: combined x- and y-values of function, defaults to None :type xy: array_like, optional :param time: x new is simulation time, defaults to False :type time: bool, optional :param kind: interpolation method, defaults to 'linear' :type kind: str, optional :param blockargs: |BlockOptions| :type blockargs: dict """ self.time = time if time: nin = 0 self.blockclass = "source" else: nin = 1 super().__init__(nin=nin, **blockargs) if xy is None: # process separate x and y vectors x = np.array(x) y = np.array(y) assert x.shape[0] == y.shape[0], "x and y data must be same length" else: # process mixed xy data if isinstance(xy, (list, tuple)): x = [_[0] for _ in xy] y = [_[1] for _ in xy] # x = np.array(x).T # y = np.array(y).T print(x, y) elif isinstance(xy, np.ndarray): x = xy[:, 0] y = xy[:, 1:] self.f = scipy.interpolate.interp1d(x=x, y=y, kind=kind, axis=0) self.x = x def start(self, simstate, **blockargs): super().start(simstate) if simstate is not None: if self.time: assert self.x[0] >= 0, "interpolation not defined for t<0" if self.x[-1] is np.inf: self.x[-1] = simstate.T assert self.x[-1] >= simstate.T, "interpolation not defined for t>T" def output(self, t, inports, x): if self.time: xnew = t else: xnew = inports[0] return [self.f(xnew)] if __name__ == "__main__": # pragma: no cover from pathlib import Path exec( open(Path(__file__).parent.parent.parent / "tests" / "test_functions.py").read() ) ================================================ FILE: bdsim/blocks/linalg.py ================================================ """ Linear algebra blocks: - have inputs and outputs - have no state variables - are a subclass of ``FunctionBlock`` |rarr| ``Block`` """ # The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance. import numpy as np import math from bdsim.components import FunctionBlock class Inverse(FunctionBlock): r""" :blockname:`INVERSE` Matrix inverse. :inputs: 1 :outputs: 2 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`\mathbf{A}` * - Output - 0 - ndarray - :math:`\mathbf{A}^{-1}` * - Output - 1 - float - :math:`\mbox{cond}(\mathbf{A})` Compute inverse of the 2D-array input signal. If the matrix is square the inverse is computed unless the ``pinv`` flag is True. For a non-square matrix the pseudo-inverse is used. The condition number is output on the second port. :seealso: `numpy.linalg.inv `_, `numpy.linalg.pinv `_, `numpy.linalg.cond `_ """ nin = 1 nout = 2 onames = ("inv", "cond") def __init__(self, pinv=False, **blockargs): """ :param pinv: force pseudo inverse, defaults to False :type pinv: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "inverse" self.pinv = pinv def output(self, t, inports, x): mat = inports[0] if isinstance(mat, np.ndarray): if mat.shape[0] != mat.shape[1]: pinv = True else: pinv = self.pinv if pinv: out = np.linalg.pinv(mat) else: try: out = np.linalg.inv(mat) except np.linalg.LinAlgError: raise RuntimeError("matrix is singular") return [out, np.linalg.cond(mat)] elif hasattr(mat, "inv"): # ask the object to invert itself return [mat.inv(), None] else: raise RuntimeError("object cannot be inverted") # ------------------------------------------------------------------------ # class Transpose(FunctionBlock): r""" :blockname:`TRANSPOSE` Matrix transpose. :inputs: 1 :outputs: 2 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`\mathbf{A}` * - Output - 0 - ndarray - :math:`\mathbf{A}^{\top}` Compute transpose of the 2D-array input signal. .. note:: - An input 1D-array of shape (N,) is turned into a 2D-array column vector with shape (N,1). - An input 2D-array column vector of shape (N,1) becomes a 2D-array row vector with shape (1,N). :seealso: `numpy.linalg.transpose `_ """ nin = 1 nout = 1 def __init__(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "transpose" def output(self, t, inports, x): mat = inports[0] if mat.ndim == 1: out = mat.reshape((mat.shape[0], 1)) else: out = mat.T return [out] # ------------------------------------------------------------------------ # class Norm(FunctionBlock): r""" :blockname:`NORM` Array norm. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`\mathbf{A}` * - Output - 0 - ndarray - :math:`\|\mathbf{A}\|` Computes the specified norm for a 1D- or 2D-array. :seealso: `numpy.linalg.norm `_ """ nin = 1 nout = 1 def __init__(self, ord=None, axis=None, **blockargs): """ :param axis: specifies the axis along which to compute the vector norms, defaults to None. :type axis: int, optional :param ord: Order of the norm, default to None. :type ord: int or str :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "norm" self.args = dict(ord=ord, axis=axis) def output(self, t, inports, x): vec = inports[0] out = np.linalg.norm(vec, **self.args) return [out] # ------------------------------------------------------------------------ # class Flatten(FunctionBlock): r""" :blockname:`FLATTEN` Flatten a multi-dimensional array. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`\mathbf{A}` * - Output - 0 - ndarray - :math:`\mbox{vec}(\mathbf{A})` Flattens the incoming array in either row major ('C') or column major ('F') order. :seealso: `numpy.flatten `_ """ nin = 1 nout = 1 def __init__(self, order="C", **blockargs): """ :param order: flattening order, either "C" or "F", defaults to "C" :type order: str :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "flatten" self.order = order def output(self, t, inports, x): vec = inports[0] out = vec.flatten(self.order) return [out] # ------------------------------------------------------------------------ # class Slice2(FunctionBlock): r""" :blockname:`SLICE2` Slice out subarray of 2D-array. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`\mathbf{A}` * - Output - 0 - ndarray - :math:`\mathbf{A}_{i\ldots j, m\ldots n}` Compute a 2D slice of input 2D array. If ``rows`` or ``cols`` is ``None`` it means all rows or columns respectively. If ``rows`` or ``cols`` is a list, perform NumPy fancy indexing, returning the specified rows or columns Example:: slice = bd.SLICE2(rows=[2,3]) # return rows 2 and 3, all columns slice = bd.SLICE2(cols=[4,1]) # return columns 4 and 1, all rows slice = bd.SLICE2(rows=[2,3], cols=[4,1]) # return elements [2,4] and [3,1] as a 1D array If a single row or column is selected, the result will be a 1D array If ``rows`` or ``cols`` is a tuple, it must have three elements. It describes a Python slice ``(start, stop, step)`` where any element can be ``None`` * ``start=None`` means start at first element * ``stop=None`` means finish at last element * ``step=None`` means step by one ``rows=None`` is equivalent to ``rows=(None, None, None)``. Example:: slice = bd.SLICE2(rows=(None,None,2)) # return every second row slice = bd.SLICE2(cols=(None,None,-1)) # reverse the columns The list and tuple notation can be mixed, for example, one for rows and one for columns. :seealso: :class:`Slice1` :class:`Index` """ nin = 1 nout = 1 def __init__(self, rows=None, cols=None, **blockargs): """ :param rows: row selection, defaults to None :type rows: tuple(3) or list :param cols: column selection, defaults to None :type cols: tuple(3) or list :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "slice2" if rows is None: self.rows = slice(None, None, None) elif isinstance(rows, list): self.rows = rows elif isinstance(rows, tuple) and len(rows) == 3: self.rows = slice(*rows) else: raise ValueError("bad rows specifier") if cols is None: self.cols = slice(None, None, None) elif isinstance(cols, list): self.cols = cols elif isinstance(cols, tuple) and len(cols) == 3: self.cols = slice(*cols) else: raise ValueError("bad columns specifier") def output(self, t, inports, x): array = inports[0] if array.ndim != 2: raise RuntimeError("Slice2 block expecting 2d array") return [array[self.rows, self.cols]] # ------------------------------------------------------------------------ # class Slice1(FunctionBlock): r""" :blockname:`SLICE1` Slice out subarray of 1D-array. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`v` * - Output - 0 - ndarray - :math:`v_{i\dots j}` Compute a 1D slice of input 1D array. If ``index`` is ``None`` it means all elements. If ``index`` is a list, perform NumPy fancy indexing, returning the specified elements Example:: slice = bd.SLICE1(index=[2,3]) # return elements 2 and 3 as a 1D array slice = bd.SLICE1(index=[2]) # return element 2 as a 1D array slice = bd.SLICE1(index=2) # return element 2 as a NumPy scalar If ``index`` is a tuple, it must have three elements. It describes a Python slice ``(start, stop, step)`` where any element can be ``None`` * ``start=None`` means start at first element * ``stop=None`` means finish at last element * ``step=None`` means step by one ``rows=None`` is equivalent to ``rows=(None, None, None)``. Example:: slice = bd.SLICE1(index=(None,None,2)) # return every second element slice = bd.SLICE1(index=(None,None,-1)) # reverse the elements :seealso: :class:`Slice1` """ nin = 1 nout = 1 def __init__(self, index, **blockargs): """ :param index: slice, defaults to None :type index: tuple(3) :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "slice1" if index is None: self.index = slice(None, None, None) elif isinstance(index, list): self.index = index elif isinstance(index, tuple) and len(index) == 3: self.index = slice(*index) else: raise ValueError("bad index specifier") def output(self, t, inports, x): array = inports[0] if array.ndim != 1: raise RuntimeError("Slice1 block expecting 1d array") return [array[self.index]] # ------------------------------------------------------------------------ # class Det(FunctionBlock): r""" :blockname:`DET` Matrix determinant. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`\mathbf{A}` * - Output - 0 - ndarray - :math:`\mbox{det}(\mathbf{A})` Compute the matrix determinant. :seealso: `numpy.linalg.det `_ """ nin = 1 nout = 1 def __init__(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "det" def output(self, t, inports, x): mat = inports[0] out = np.linalg.det(mat) return [out] # ------------------------------------------------------------------------ # class Cond(FunctionBlock): r""" :blockname:`COND` Matrix condition number. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray - :math:`\mathbf{A}` * - Output - 0 - ndarray - :math:`\mbox{cond}(\mathbf{A})` :seealso: `numpy.linalg.cond `_ """ nin = 1 nout = 1 def __init__(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "cond" def output(self, t, inports, x): mat = inports[0] out = np.linalg.cond(mat) return [out] if __name__ == "__main__": # pragma: no cover from pathlib import Path exec(open(Path(__file__).parent.parent.parent / "tests" / "test_linalg.py").read()) ================================================ FILE: bdsim/blocks/sinks.py ================================================ """ Sink blocks: - have inputs but no outputs - have no state variables - are a subclass of ``SinkBlock`` |rarr| ``Block`` - that perform graphics are a subclass of ``GraphicsBlock`` |rarr| ``SinkBlock`` |rarr| ``Block`` """ import numpy as np from math import pi, sqrt, sin, cos, atan2 import matplotlib.pyplot as plt from matplotlib.pyplot import Polygon from bdsim.components import SinkBlock # ------------------------------------------------------------------------ # class Print(SinkBlock): """ :blockname:`PRINT` Print signal. :inputs: 1 :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - any - :math:`x` Creates a console print block which displays the value of the input signal at each simulation time step. The display format is like:: PRINT(print.0 @ t=0.100) [-1.0 0.2] and includes the block name, time, and the formatted value. The numerical formatting of the signal is controlled by ``fmt``: - if not provided, ``str()`` is used to format the signal - if provided: - a scalar is formatted by the ``fmt.format()`` - a NumPy array is formatted by ``fmt.format()`` applied to every element Examples:: bd.PRINT(name="X") # block name appears in the printed text bd.PRINT(fmt="{:.1f}") # print with explicit format .. note:: - By default writes to stdout - The output is cleaner if progress bar printing is disabled using the ``-p`` command line option. """ nin = 1 nout = 0 def __init__(self, fmt=None, file=None, **blockargs): """ :param fmt: Format string, defaults to None :type fmt: str, optional :param file: file to write data to, defaults to None :type file: file object, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A PRINT block :rtype: Print instance """ super().__init__(**blockargs) self.format = fmt self.file = file # TODO format can be a string or function def step(self, t, inports): prefix = "{:12s}".format("PRINT({:s} (t={:.3f})".format(self.name, t)) value = inports[0] if self.format is None: # no format string if hasattr(value, "strline"): print(prefix, value.strline(), file=self.file) else: print(prefix, str(value), file=self.file) else: # format string provided if isinstance(value, (int, float)): print(prefix, self.format.format(value), file=self.file) elif isinstance(value, np.ndarray): with np.printoptions( formatter={"all": lambda x: self.format.format(x)} ): print(prefix, value, file=self.file) else: print(prefix, str(value), file=self.file) # ------------------------------------------------------------------------ # class Stop(SinkBlock): """ :blockname:`STOP` Conditionally stop simulation. :inputs: 1 :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - any - :math:`x` Conditionally stop the simulation if the input :math:`x` is: - bool type and True - numeric type and > 0 If ``func`` is provided, then it is applied to the block input and if it returns True the simulation is stopped. """ nin = 1 nout = 0 def __init__(self, func=None, **blockargs): """ :param func: evaluate stop condition, defaults to None :type func: callable, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) if func is not None and not callable(func): raise TypeError("argument must be a callable") self.stopfunc = func def start(self, simstate): self._simstate = simstate def step(self, t, inports): value = inports[0] if self.stopfunc is not None: value = self.stopfunc(value) stop = False if isinstance(value, bool): stop = value else: try: stop = value > 0 except: raise RuntimeError("bad input type to stop block") # we signal stop condition by setting simstate.stop to the block calling # the stop if stop: self._simstate.stop = self # ------------------------------------------------------------------------ # class Null(SinkBlock): """ :blockname:`NULL` Discard signal. :inputs: N :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - any - :math:`x_i` Create a sink block with arbitrary number of input ports that discards all data, like ``/dev/null``. Useful for testing. .. note:: ``bdsim`` issues a warning for unconnected outputs but execution can continue. """ nin = -1 nout = 0 def __init__(self, nin=1, **blockargs): """ :param nin: number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(nin=nin, **blockargs) # ------------------------------------------------------------------------ # class Watch(SinkBlock): """ :blockname:`WATCH` Watch a signal. :inputs: N :outputs: 0 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - i - any - :math:`x_i` Causes the output ports connected to this block's input ports :math:`x_i` to be logged during the simulation run. Equivalent to adding it as the ``watch=`` argument to ``bdsim.run``. For example:: step = bd.STEP(5) ramp = bd.RAMP() watch = bd.WATCH(2) # watch 2 ports watch[0] = step watch[1] = ramp :seealso: :method:`BDSim.run` """ nin = 1 nout = 0 def __init__(self, **blockargs): """ :param nin: number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) def start(self, simstate): # called at start of simulation, add this block to the watchlist plug = self.sources[0] # start plug for input wire # append to the watchlist, bdsim.run() will do the rest simstate.watchlist.append(plug) simstate.watchnamelist.append(str(plug)) if __name__ == "__main__": # pragma: no cover from pathlib import Path exec(open(Path(__file__).parent.parent.parent / "tests" / "test_sinks.py").read()) ================================================ FILE: bdsim/blocks/sources.py ================================================ """ Source blocks: - have outputs but no inputs - have no state variables - are a subclass of ``SourceBlock`` |rarr| ``Block`` """ import numpy as np import math from bdsim.components import SourceBlock, EventSource # ------------------------------------------------------------------------ # class Constant(SourceBlock): """ :blockname:`CONSTANT` Constant value. :inputs: 0 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Output - 0 - any - constant ``value`` The output value is a constant and can be any Python type, for example float, list or Numpy ndarray. """ nin = 0 nout = 1 def __init__(self, value=0, **blockargs): """ :param value: the constant, defaults to 0 :type value: any, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) if isinstance(value, (tuple, list)): value = np.array(value) self.value = value self.add_param("value") def output(self, t, inports, x): return [self.value] # ------------------------------------------------------------------------ # class Time(SourceBlock): """ :blockname:`TIME` Simulation time. :inputs: 0 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Output - 0 - float - :math:`t` Outputs the current simulation time. For example:: time = bd.TIME() """ nin = 0 nout = 1 def __init__(self, value=None, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) def output(self, t, inports, x): return [t] # ------------------------------------------------------------------------ # class WaveForm(SourceBlock, EventSource): """ :blockname:`WAVEFORM` Waveform generator. :inputs: 0 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Output - 0 - float - :math:`y(t)` A general waveform generator. For example:: wave = bd.WAVEFORM(wave='sine', freq=2) # 2Hz sine wave varying from -1 to 1 wave = bd.WAVEFORM(wave='square', freq=2, unit='rad/s') # 2rad/s square wave varying from -1 to 1 The minimum and maximum values of the waveform are given by default in terms of amplitude and offset. The signals are symmetric about the offset value. For example:: wave = bd.WAVEFORM(wave='sine') # varies between -1 and +1 wave = bd.WAVEFORM(wave='sine', amplitude=2) # varies between -2 and +2 wave = bd.WAVEFORM(wave='sine', offset=1) # varies between 0 and +2 wave = bd.WAVEFORM(wave='sine', amplitude=2, offset=1) # varies between -1 and +3 Alternatively we can specify the minimum and maximum values which override amplitude and offset:: wave = bd.WAVEFORM(wave='triangle', min=0, max=5) # varies between 0 and +5 At time 0 the sine and triangle wave are zero and increasing, and the square wave has its first rise. We can specify a phase shift with a number in the range [0,1] where 1 corresponds to one cycle. .. note:: For discontinuous signals (square, triangle) the block declares events for every discontinuity. :seealso: :meth:`declare_events` """ nin = 0 nout = 1 def __init__( self, wave="square", freq=1, unit="Hz", phase=0, amplitude=1, offset=0, min=None, max=None, duty=0.5, **blockargs, ): """ :param wave: type of waveform to generate, one of: 'sine', 'square' [default], 'triangle' :type wave: str, optional :param freq: frequency, defaults to 1 :type freq: float, optional :param unit: frequency unit, one of: 'rad/s', 'Hz' [default] :type unit: str, optional :param amplitude: amplitude, defaults to 1 :type amplitude: float, optional :param offset: signal offset, defaults to 0 :type offset: float, optional :param phase: Initial phase of signal in the range [0,1], defaults to 0 :type phase: float, optional :param min: minimum value, defaults to None :type min: float, optional :param max: maximum value, defaults to None :type max: float, optional :param duty: duty cycle for square wave in range [0,1], defaults to 0.5 :type duty: float, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) assert 0 < duty < 1, "duty must be in range [0,1]" if wave in ("square", "triangle", "sine"): self.wave = wave else: raise ValueError("bad waveform") if unit == "Hz": self.freq = freq elif unit == "rad/s": self.freq = freq / (2 * math.pi) else: raise ValueError("bad unit") if 0 <= phase <= 1: self.phase = phase else: raise ValueError("phase out of range") if max is not None and min is not None: amplitude = (max - min) / 2 offset = (max + min) / 2 self.min = min self.mablock = max if 0 <= duty <= 1: self.duty = duty else: raise ValueError("duty out of range") self.amplitude = amplitude self.offset = offset def start(self, simstate): super().start(simstate) if self.wave == "square": t1 = self.phase / self.freq t2 = (self.duty + self.phase) / self.freq elif self.wave == "triangle": t1 = (0.25 + self.phase) / self.freq t2 = (0.75 + self.phase) / self.freq else: return # t1 < t2 T = 1.0 / self.freq if simstate is not None: while t1 < simstate.T: simstate.declare_event(self, t1) simstate.declare_event(self, t2) t1 += T t2 += T def output(self, t, inports, x): T = 1.0 / self.freq phase = (t * self.freq - self.phase) % 1.0 # define all signals in the range -1 to 1 if self.wave == "square": if phase < self.duty: out = 1 else: out = -1 elif self.wave == "triangle": if phase < 0.25: out = phase * 4 elif phase < 0.75: out = 1 - 4 * (phase - 0.25) else: out = -1 + 4 * (phase - 0.75) elif self.wave == "sine": out = math.sin(phase * 2 * math.pi) else: raise ValueError("bad option for signal") out = out * self.amplitude + self.offset # print('waveform = ', out) return [out] # ------------------------------------------------------------------------ # class Piecewise(SourceBlock, EventSource): """ :blockname:`PIECEWISE` Piecewise constant signal. :inputs: 0 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Output - 0 - float - :math:`y(t)` Generate a signal that is a piecewise constant function of time. This is described as a series of 2-tuples (time, value). The output value is taken from the active tuple, that is, the latest one in the list whose time is no greater than simulation time. The tuples can be provided in two different ways. Firstly, a form convenient for Python programming:: steering = bd.PIECEWISE((0,0), (2, 0.5), (3,0), (4,-0.5), (5,0)) Secondly, in a form that can be used from ``bdsim`` where we explicitly pass in a list in a way that can be represented in a JSON file:: steering = bd.PIECEWISE(seq=[(0,0), (3, 0.5), (4,0), (5,-0.5), (6,0)]) .. plot:: import matplotlib.pyplot as plt plt.plot([0, 2, 2, 3, 3, 4, 4, 5, 5, 5.2], [0, 0, 0.5, 0.5, 0, 0, -0.5, -0.5, 0, 0], lw=2) plt.grid(True) .. note:: - The tuples must be ordered by monotonically increasing time. - There is no default initial value, the list should contain a tuple with time zero otherwise the output will be undefined. - The 2-tuples can .. note:: The block declares an event for the start of each segment. :seealso: :meth:`declare_events` """ nin = 0 nout = 1 def __init__(self, *args, seq=None, **blockargs): """ :param seq: sequence of time, value pairs :type seq: list of 2-element iterables :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) if len(args) > 0: seq = args self.t = [x[0] for x in seq] self.y = [x[1] for x in seq] def start(self, simstate): super().start(simstate) if simstate is not None: for t in self.t: simstate.declare_event(self, t) def output(self, t, inports, x): i = sum([1 if t >= _t else 0 for _t in self.t]) - 1 out = self.y[i] # print(out) return [out] # ------------------------------------------------------------------------ # class Step(SourceBlock, EventSource): """ :blockname:`STEP` Step signal. :inputs: 0 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Output - 0 - float - :math:`y(t)` Generate a step signal that transitions from the value ``off`` to ``on`` when time equals ``T``. Example: step = bd.STEP(2, off=-1, on=1) .. plot:: import matplotlib.pyplot as plt plt.plot([0, 2, 2, 5], [-1, -1, 1, 1], lw=2) plt.grid(True) .. note:: The block declares an event for the step time. :seealso: :meth:`declare_events` """ nin = 0 nout = 1 def __init__(self, T=1, off=0, on=1, **blockargs): """ :param T: time of step, defaults to 1 :type T: float, optional :param off: initial value, defaults to 0 :type off: float, optional :param on: final value, defaults to 1 :type on: float, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.T = T self.off = off self.on = on def start(self, simstate): simstate.declare_event(self, self.T) def output(self, t, inports, x): if t >= self.T: out = self.on else: out = self.off # print(out) return [out] # ------------------------------------------------------------------------ # class Ramp(SourceBlock, EventSource): """ :blockname:`RAMP` Ramp signal. :inputs: 0 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Output - 0 - float - :math:`y(t)` Generate a signal that starts increasing from the value ``off`` when time equals ``T`` linearly with time, with a gradient of ``slope``. Example: step = bd.RAMP(2, off=-1, slope=2/3, T=2) .. plot:: import matplotlib.pyplot as plt plt.plot([0, 2, 5], [-1, -1, 5], lw=2) plt.grid(True) .. note:: The block declares an event for the ramp start time. :seealso: :meth:`declare_event` """ nin = 0 nout = 1 def __init__(self, T=1, off=0, slope=1, **blockargs): """ :param T: time of ramp start, defaults to 1 :type T: float, optional :param off: initial value, defaults to 0 :type off: float, optional :param slope: gradient of slope, defaults to 1 :type slope: float, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.T = T self.off = off self.slope = slope def start(self, simstate): simstate.declare_event(self, self.T) def output(self, t, inports, x): if t >= self.T: out = self.off + self.slope * (t - self.T) else: out = self.off # print(out) return [out] if __name__ == "__main__": # pragma: no cover from pathlib import Path exec(open(Path(__file__).parent.parent.parent / "tests" / "test_sources.py").read()) ================================================ FILE: bdsim/blocks/spatial.py ================================================ try: from spatialmath import SE2, SE3, SO2, SO3 sm = True except: sm = False from bdsim.components import FunctionBlock if sm: class Pose_postmul(FunctionBlock): r""" :blockname:`POSE_POSTMUL` Post multiply pose. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - SEn, SOn - :math:`\xi` * - Output - 0 - SEn, SOn - :math:`\xi \oplus \xi_c` Postmultiply the input pose by a constant pose. .. note:: Pose objects must be of the same type. :seealso: :class:`Pose_premul` """ nin = 1 nout = 1 def __init__(self, pose=None, **blockargs): """ :param pose: pose to apply :type pose: SO2, SE2, SO3 or SE3 :param blockargs: |BlockOptions| :type blockargs: dict """ if not isinstance(pose, (SO2, SO3, SE2, SE3)): raise ValueError("pose must be SO2, SE2, SO3 or SE3") super().__init__(**blockargs) self.pose = pose def output(self, t, inports, x): input = inports[0] return [input * self.pose] # ------------------------------------------------------------------------ # class Pose_premul(FunctionBlock): r""" :blockname:`POSE_PREMUL` Pre multiply pose. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - SEn, SOn - :math:`\xi` * - Output - 0 - SEn, SOn - :math:`\xi_c \oplus \xi` Premultiply the input pose by a constant pose. .. note:: Pose objects must be of the same type. :seealso: :class:`Pose_postmul` """ nin = 1 nout = 1 def __init__(self, pose=None, **blockargs): """ :param pose: pose to apply :type pose: SO2, SE2, SO3 or SE3 :param blockargs: |BlockOptions| :type blockargs: dict """ if not isinstance(pose, (SO2, SO3, SE2, SE3)): raise ValueError("pose must be SO2, SE2, SO3 or SE3") super().__init__(**blockargs) self.pose = pose def output(self, t, inports, x): input = inports[0] return [self.pose * input] # ------------------------------------------------------------------------ # class Transform_vector(FunctionBlock): r""" :blockname:`TRANSFORM_VECTOR` Transform a vector. :inputs: 2 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - SEn, SOn - :math:`\xi` * - Input - 1 - ndarray - :math:`v`, Euclidean 2D or 3D * - Output - 0 - ndarray - :math:`\xi \bullet v` Linearly transform the input vector by the input pose. """ nin = 2 nout = 1 def __init__(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(nin=2, **blockargs) def output(self, t, inports, x): pose = inports[0] if not isinstance(pose, (SO2, SO3, SE2, SE3)): raise ValueError("pose must be SO2, SE2, SO3 or SE3") return [pose * inports[1]] # ------------------------------------------------------------------------ # class Pose_inverse(FunctionBlock): r""" :blockname:`POSE_INVERSE` Pose inverse. :inputs: 1 :outputs: 1 :states: 0 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - SEn, SOn - :math:`\xi` * - Output - 0 - SEn, SOn - :math:`\ominus \xi` Invert the pose on the input port. """ nin = 1 nout = 1 def __init__(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) def output(self, t, inports, x): input = inports[0] return [input.inv()] # ------------------------------------------------------------------------ # if __name__ == "__main__": # pragma: no cover from pathlib import Path exec(open(Path(__file__).parent.parent.parent / "tests" / "test_spatial.py").read()) ================================================ FILE: bdsim/blocks/transfers.py ================================================ """ Transfer blocks: - have inputs and outputs - have state variables - are a subclass of ``TransferBlock`` |rarr| ``Block`` """ import numpy as np import scipy.signal import math from math import sin, cos, atan2, sqrt, pi import matplotlib.pyplot as plt from spatialmath import base from bdsim.components import TransferBlock, SubsystemBlock class Integrator(TransferBlock): r""" :blockname:`INTEGRATOR` Continuous-time integrator. :inputs: 1 :outputs: 1 :states: N .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float, ndarray - :math:`x` * - Output - 0 - any - :math:`y` Output is the time integral of the input :math:`y(t) = \int_0^T x(t) dt`. The state can be a scalar or a vector. The initial state, and type, is given by ``x0``. The shape of the input signal must match ``x0``. The minimum and maximum values can be: - a scalar, in which case the same value applies to every element of the state vector, or - a vector, of the same shape as ``x0`` that applies elementwise to the state. .. note:: The minimum and maximum prevent integration outside the limits, but assume that the initial state is within the limits. Integration can be controlled by an ``enable`` function:: enable(t, u, x): bool where the arguments are current time, a list of inputs to the block and the state as an ndarray. If the function returns False then the integrator's output is set to zero. .. todo:: Make enable an optional input to the block, or the input for a subclassed variant of the block. :seealso: :class:`Deriv` """ nin = 1 nout = 1 def __init__(self, x0=0, gain=1.0, min=None, max=None, enable=None, **blockargs): """ :param x0: Initial state, defaults to 0 :type x0: array_like, optional :param gain: gain or scaling factor, defaults to 1 :type gain: float :param min: Minimum value of state, defaults to None :type min: float or array_like, optional :param max: Maximum value of state, defaults to None :type max: float or array_like, optional :param enable: enable or disable integration :type enable: callable :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) if isinstance(x0, (int, float)): x0 = np.r_[x0] elif isinstance(x0, np.ndarray): if x0.ndim > 1: raise ValueError("state must be a 1D vector") else: x0 = base.getvector(x0) self.nstates = x0.shape[0] if min is not None: min = base.getvector(min, self.nstates) if max is not None: max = base.getvector(max, self.nstates) self._x0 = x0 self.min = min self.max = max self.gain = gain self._x0 = x0 self.min = min self.max = max self.enable = enable if enable is not None and not callable(enable): raise ValueError("enable must be callable") # print("nstates", self.nstates) def output(self, t, u, x): return [self.gain * x] def deriv(self, t, u, x): xd = base.getvector(u[0]) if self.enable is not None and not self.enable(t, u, x): # if enable function returns False then integrator output is jammed at zero self._x = np.zeros(x.shape) return np.zeros(x.shape) if self.min is not None: xd[x < self.min] = 0 if self.max is not None: xd[x > self.max] = 0 return self.gain * xd class PoseIntegrator(TransferBlock): r""" :blockname:`POSEINTEGRATOR` Continuous-time pose integrator :inputs: 1 :outputs: 1 :states: 6 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - ndarray(6,) - :math:`x` * - Output - 0 - SE3 - :math:`y` This block integrates spatial velocity over time. The block input is a spatial velocity as a 6-vector :math:`(v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)` and the output is pose as an ``SE3`` instance. .. note:: The state vector is a velocity twist. """ nin = 1 nout = 1 def __init__(self, x0=None, **blockargs): r""" :param x0: Initial pose, defaults to null :type x0: SE3, Twist3, optional :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) if x0 is None: x0 = np.zeros((6,)) self.nstates = len(x0) self._x0 = x0 def output(self, t, u, x): return [Twist3(x).SE3(1)] def deriv(self, t, u, x): return u[0] # ------------------------------------------------------------------------ # class LTI_SS(TransferBlock): r""" :blockname:`LTI_SS` Continuous-time state-space LTI dynamics :inputs: 1 :outputs: 1 :states: N .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float, ndarray - :math:`u` * - Output - 0 - float, ndarray - :math:`y` Implements the dynamics of a multi-input multi-output (MIMO) linear time invariant (LTI) system described in statespace form. The dynamics are given by .. math:: \dot{x} &= A x + B u y &= C x The order of the states in ``x0`` is consistent with controller canonical form. A direct passthrough component, typically :math:`D`, is not allowed in order to avoid algebraic loops. Examples:: lti = bd.LTI_SS(A=-2, B=1, C=-1) is the system :math:`\dot{x}=-2x+u, y=-x`. """ nin = 1 nout = 1 def __init__(self, A=None, B=None, C=None, x0=None, **blockargs): r""" :param N: numerator coefficients, defaults to 1 :type N: array_like, optional :param D: denominator coefficients, defaults to [1,1] :type D: array_like, optional :param x0: initial states, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict """ # print('in SS constructor') assert A.shape[0] == A.shape[1], "A must be square" n = A.shape[0] if len(B.shape) == 1: nin = 1 B = B.reshape((n, 1)) else: nin = B.shape[1] assert B.shape[0] == n, "B must have same number of rows as A" if len(C.shape) == 1: nout = 1 assert C.shape[0] == n, "C must have same number of columns as A" C = C.reshape((1, n)) else: nout = C.shape[0] assert C.shape[1] == n, "C must have same number of columns as A" super().__init__(**blockargs) self.A = A self.B = B self.C = C self.nstates = A.shape[0] if x0 is None: self._x0 = np.zeros((self.nstates,)) else: self._x0 = x0 def output(self, t, u, x): return list(self.C @ x) def deriv(self, t, u, x): # Reshape u and x to (N,1), i.e. column vectors, so # no problems with broadcasting between A@x and B@u x = x.reshape(-1, 1) u = np.array(u).reshape(-1, 1) xd = self.A @ x + self.B @ u return xd.flatten() # ------------------------------------------------------------------------ # class LTI_SISO(LTI_SS): r""" :blockname:`LTI_SISO` Continuous-time SISO LTI dynamics. :inputs: 1 :outputs: 1 :states: N .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float - :math:`u` * - Output - 0 - float - :math:`y` Implements the dynamics of a single-input single-output (SISO) linear time invariant (LTI) system described by numerator and denominator polynomial coefficients. The dynamics are given by .. math:: \frac{Y(s)}{U(s)} = \frac{N(s)}{D(s)} Coefficients are given in the order from highest order to zeroth order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``. Only proper transfer functions, where order of numerator is less than denominator are allowed. The order of the states in ``x0`` is consistent with controller canonical form. Examples:: lti = bd.LTI_SISO(N=[1, 2], D=[2, 3, -4]) is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`. """ nin = 1 nout = 1 def __init__(self, N=1, D=[1, 1], x0=None, **blockargs): r""" :param N: numerator coefficients, defaults to 1 :type N: array_like, optional :param D: denominator coefficients, defaults to [1,1] :type D: array_like, optional :param x0: initial states, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: LTI_SISO block :rtype: ``LTI_SISO`` instance """ # print('in SISO constscutor') if not isinstance(N, list): N = [N] if not isinstance(D, list): D = [D] self.N = N self.D = N n = len(D) - 1 nn = len(N) if x0 is None: x0 = np.zeros((n,)) assert nn <= n, "direct pass through is not supported" # convert to numpy arrays # N = np.r_[np.zeros((len(D) - len(N),)), np.array(N)] N = np.array(N) D = np.array(D) # normalize the coefficients to obtain # # b_0 s^n + b_1 s^(n-1) + ... + b_n # --------------------------------- # a_0 s^n + a_1 s^(n-1) + ....+ a_n # normalize so leading coefficient of denominator is one # D0 = D[0] # D = D / D0 # N = N / D0 # A = np.eye(len(D) - 1, k=1) # control canonic (companion matrix) form # A[-1, :] = -D[1:] # B = np.zeros((n, 1)) # B[-1] = 1 # C = (N[1:] - N[0] * D[1:]).reshape((1, n)) A, B, C, D = scipy.signal.tf2ss(N, D) self.num = N self.den = D if len(np.flatnonzero(D)) > 0: raise ValueError("D matrix is not zero") super().__init__(A=A, B=B, C=C, x0=x0, **blockargs) if self.verbose: print("A=", A) print("B=", B) print("C=", C) def change_param(self, param, newvalue): if param == "num": self.num = newvalue elif param == "den": self.den = newvalue self.A, self.B, self.C, self.D = scipy.signal.tf2ss(self.num, self.den) self.add_param("num", change_param) self.add_param("den", change_param) # ------------------------------------------------------------------------ # from bdsim.blocks.connections import SubSystem class Deriv(SubsystemBlock): r""" :blockname:`DERIV` Continuous-time derivative. :inputs: 1 :outputs: 1 :states: N .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float - :math:`x` * - Output - 0 - float - :math:`y` Implements the dynamics of a derivative filter, but to be causal it has a single pole given by ``alpha``. The dynamics is .. math:: \frac{s}{\frac{s}{\alpha} + 1} It is implemented as a subsystem with an integrator and a feedback loop. The initial state of the integrator is given by ``x0``. If the initial output of the derivative block is known it can be provided as ``y0`` which is related to ``x0`` by :math:`x_0 = - \alpha y_0`. :seealso: :class:`Integrator` """ nin = 1 nout = 1 def __init__(self, alpha, x0=0, y0=None, **blockargs): r""" :param alpha: filter pole in units of rad/s :type alpha: float :param x0: initial states, defaults to 0 :type x0: array_like, optional :param y0: inital outputs :type y0: array_like :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "subsystem" bd = self.bd.runtime.blockdiagram() if y0 is not None: x0 = -y0 * alpha integrator = bd.INTEGRATOR(x0=x0) inp = bd.INPORT(1) outp = bd.OUTPORT(1) sum = bd.SUM("+-") gain = bd.GAIN(1.0 / alpha) bd.connect(inp, sum[0]) bd.connect(sum[0], gain) bd.connect(gain, outp, integrator) bd.connect(integrator, sum[1]) # get references to the input and output port blocks self.inport = inp self.outport = outp self.subsystem = bd self.ssname = "derivative" # ------------------------------------------------------------------------ # class PID(SubsystemBlock): r""" :blockname:`PID` Continuous-time PID control. :inputs: 2 :outputs: 1 :states: 2 .. list-table:: :header-rows: 1 * - Port type - Port number - Types - Description * - Input - 0 - float - :math:`x`, plant output * - Input - 1 - float - :math:`x^*`, demanded output * - Output - 0 - any - :math:`u`, control to plant Implements the dynamics of a PID controller: .. math:: e &= x^* - x u &= Pe + D \frac{d}{dt} e + I \int e dt If the I or D terms are not required the ``type`` can be specified as ``"PD"`` or ``"PI"`` in which case the respective gain terms ``I`` and ``D`` will be ignored. To reduce noise the derivative is computed by the DERIV block which implements a first-order system .. math:: \frac{s}{s/a + 1} where the pole :math:`a=` ``D_filt`` can be positioned appropriately. If ``I_limit`` is provided it specifies the limits of the integrator state, before multiplication by ``I``. If ``I_limit`` is: * a scalar :math:`a` the integrator state is clipped to the interval :math:`[-a, a]` * a 2-tuple :math:`(a,b)` the integrator state is clipped to the interval :math:`[a, b]` If ``I_band`` is provided the integrator is reset to zero whenever the error :math:`e` is outside the band given by ``I_band`` which is: * a scalar :math:`s` the band is the interval :math:`[-s, s]` * a 2-tuple :math:`(a,b)` the band is the interval :math:`[a, b]` Examples:: pid = bd.PID(P=3, D=2, I=1) ..note:: The result is a subsystem which will be expanded into 12 blocks for the PID case, fewer for the PI or PD cases. :seealso: :class:`Deriv` """ nin = 2 nout = 1 def __init__( self, type: str = "PID", P: float = 0.0, D: float = 0.0, I: float = 0.0, D_pole=1, I_limit=None, I_band=None, **blockargs, ): r""" :param type: the controller type, defaults to "PID" :type type: str, optional :param P: proportional gain, defaults to 0 :type P: float :param D: derivative gain, defaults to 0 :type D: float :param I: integral gain, defaults to 0 :type I: float :param D_pole: filter pole for derivative estimate, defaults to 1 rad/s :type D_pole: float :param I_limit: integral limit :type I_limit: float or 2-tuple :param I_band: band within which integral action is active :type I_band: float :param blockargs: |BlockOptions| :type blockargs: dict """ super().__init__(**blockargs) self.type = "subsystem" subsystem = self.bd.runtime.blockdiagram() bd = blockargs["bd"] blockargs.pop("bd") Pblock = subsystem.GAIN(P) # proportional gain block if "I" in type: # if the I term is required, create the block if I_limit is None: min = -np.inf max = np.inf elif isinstance(I_limit, float): min = -I_limit max = I_limit elif isinstance(I_limit, tuple) and len(I_limit) == 2: min, max = I_limit if I_band is not None: def ifunc(t, u, x): return abs(u[0]) < I_band else: ifunc = None # integrator block Iblock = subsystem.INTEGRATOR(min=min, max=max, gain=I, enable=ifunc) if "D" in type: # if the D term is required, create the blocks Dblock = subsystem.DERIV(alpha=D_pole) # derivative block Dgain = subsystem.GAIN(D) # derivative gain subsystem.connect(Dblock, Dgain) error_sum = subsystem.SUM("-+", name="errsum") # error summing junction inp = subsystem.INPORT(2) # PID block inputs outp = subsystem.OUTPORT(1) # PID block output # for each case sum the various terms if type == "PID": out_sum = subsystem.SUM("+++", name="outsum") subsystem.connect(error_sum, Pblock, Dblock, Iblock) subsystem.connect(Pblock, out_sum[0]) subsystem.connect(Iblock, out_sum[1]) subsystem.connect(Dgain, out_sum[2]) elif type == "PI": out_sum = subsystem.SUM("++", name="outsum") subsystem.connect(error_sum, Pblock, Iblock) subsystem.connect(Pblock, out_sum[0]) subsystem.connect(Iblock, out_sum[1]) elif type == "PD": out_sum = subsystem.SUM("++", name="outsum") subsystem.connect(error_sum, Pblock, Dblock) subsystem.connect(Pblock, out_sum[0]) subsystem.connect(Dgain, out_sum[1]) subsystem.connect(inp, error_sum) subsystem.connect(out_sum, outp) subsystem.report() # super().__init__(subsystem, name=name, bd=bd) # get references to the input and output port blocks self.inport = inp self.outport = outp self.subsystem = subsystem self.ssname = "PID" if __name__ == "__main__": from bdsim import BDSim sim = BDSim(hold=False) bd = sim.blockdiagram() deriv = bd.DERIV(alpha=0.1, verbose=True) c = bd.WAVEFORM(wave="sine", freq=1) s = bd.SCOPE(2) bd.connect(c, deriv, s[0]) bd.connect(deriv, s[1]) bd.compile() bd.report_summary() out = sim.run(bd, 10, dt=0.02) import matplotlib.pyplot as plt plt.plot(out.t, out.x) sim.done(bd, block=True) # sim = BDSim() # bd = sim.blockdiagram() # pid = bd.PID(P=2, D=0.01, verbose=True) # c = bd.CONSTANT(1) # s = bd.SCOPE() # bd.connect(c, pid) # bd.connect(pid, s) # bd.compile(report=True) # bd.report() # from pathlib import Path # exec( # open(Path(__file__).parent.parent.parent / "tests" / "test_transfers.py").read() # ) ================================================ FILE: bdsim/blocks/vision.py ================================================ """ Define vision processing blocks for use in block diagrams. These are blocks that: - have inputs or outputs - have no state variables - are a subclass of ``SourceBlock``, ``SinkBlock`` or ``FunctionBlock`` """ # The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance. # camera # display # threshold # blobs # colorseg # surf mesh (XYZ) display # STL display ================================================ FILE: bdsim/components.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Components of the simulation system, namely blocks, wires and plugs. """ import types import math from re import S import numpy as np import matplotlib.pyplot as plt from matplotlib import animation from collections import UserDict # decorator for debugging implicit block creation with operator overloading def oodebug(func): def wrapper(*args, **kwargs): ret = func(*args, **kwargs) # print(f"{func.__qualname__}{args} --> {ret}") return ret return wrapper class BDStruct: """ A simple data container object that allows items to be added by attribute or by index. For example:: >>> d = BDStruct('thing') >>> d.foo = 1 >>> d.foo 1 >>> d["foo"] ] >>> d["bar"] = 2 >>> d.bar >>> d bar = 2 (int) foo = 1 (int) """ def __init__(self, name="BDStruct2", **kwargs): self._name = name for key, value in kwargs.items(): # self.__dict__[key] = value setattr(self, key, value) def add(self, name, value): # self.__dict__[name] = value setattr(self, name, value) def __repr__(self): return str(self) def __len__(self): return len([k for k in self.__dict__.keys() if not k.startswith("_")]) def __getitem__(self, key): return getattr(self, key) def __setitem__(self, key, value): setattr(self, key, value) def __str__(self): """ Display struct as a string :return: struct in indented string format :rtype: str The struct is rendered with one line per element, and substructures are indented. """ rows = [] if len(self) == 0: return "" maxwidth = max([len(key) for key in self.__dict__.keys()]) # if self.name is not None: # rows.append(self.name + '::') for k, v in sorted(self.__dict__.items(), key=lambda x: x[0]): if k.startswith("_"): continue if isinstance(v, BDStruct): rows.append("{:s}.{:s}::".format(k.ljust(maxwidth), v._name)) rows.append( "\n".join( [" " * (maxwidth + 3) + line for line in str(v).split("\n")] ) ) elif isinstance(v, str): rows.append( '{:s} = "{:s}" ({:s})'.format( k.ljust(maxwidth), str(v), type(v).__name__ ) ) elif isinstance(v, np.ndarray): rows.append( "{:s} = ndarray:{:s} {:s}".format( k.ljust(maxwidth), v.dtype.type.__name__, str(v.shape) ) ) else: rows.append( "{:s} = {:s} ({:s})".format( k.ljust(maxwidth), str(v), type(v).__name__ ) ) return "\n".join(rows) def dump(self, outfile): import pickle with open(outfile, "wb") as f: pickle.dump(self, f) class OptionsBase: """A struct like object for option handling Maintains an internal dict to keep options and their values. Some of these values, names in the ``_priority`` list are read-only and cannot be changed. Values can be read/written as attributes, or the ``set`` method can take a sequence of ``option=value`` arguments. """ def __init__(self, readonly={}, args={}): self._readonly = list(readonly) self._dict = {**args, **readonly} def items(self): return self._dict.items() def __getattr__(self, name): try: if name.startswith("_"): return self.__dict__[name] else: return self.__dict__["_dict"][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): if name.startswith("_"): self.__dict__[name] = value else: dict = self.__dict__["_dict"] if name not in self._readonly: dict[name] = value self.__dict__["_dict"] = self.sanity(dict) def set(self, **changes): changes = self.sanity(changes) dict = self._dict for name, value in changes.items(): if name not in self._readonly: dict[name] = value elif dict[name] != value: print( f"attempt to programmatically set option {name}={value} is" f" overriden by command line option {name}={dict[name]}, ignored" ) self._dict = dict def sanity(self, options): return options def __str__(self): dict = self._dict maxwidth = max([len(option) for option in dict.keys()]) options = sorted(dict.keys()) return "\n".join( [f"{option.ljust(maxwidth)}: {dict[option]}" for option in options] ) def __repr__(self): return str(self) class Wire: """ Create a wire. :param start: Plug at the start of a wire, defaults to None :type start: Plug, optional :param end: Plug at the end of a wire, defaults to None :type end: Plug, optional :param name: Name of wire, defaults to None :type name: str, optional :return: A wire object :rtype: Wire A Wire object connects two block ports. A Wire has a reference to the start and end ports. A wire records all the connections defined by the user. At compile time wires are used to build inter-block references. Between two blocks, a wire can connect one or more ports, ie. it can connect a set of output ports on one block to a same sized set of input ports on another block. """ def __init__(self, start=None, end=None, name=None): self.name = name self.id = None self.start = start self.end = end self.value = None self.type = None self.name = None @property def info(self): """ Interactive display of wire properties. Displays all attributes of the wire for debugging purposes. """ print("wire:") for k, v in self.__dict__.items(): print(" {:8s}{:s}".format(k + ":", str(v))) def __repr__(self): """ Display wire with name and connection details. :return: Long-form wire description :rtype: str String format:: wire.5: d2goal[0] --> Kv[0] """ return str(self) + ": " + self.fullname @property def fullname(self): """ Display wire connection details. :return: Wire name :rtype: str String format:: d2goal[0] --> Kv[0] """ return "{:s}[{:d}] --> {:s}[{:d}]".format( str(self.start.block), self.start.port, str(self.end.block), self.end.port ) def __str__(self): """ Display wire name. :return: Wire name :rtype: str String format:: wire.5 """ s = "wire." if self.name is not None: s += self.name elif self.id is not None: s += str(self.id) else: s += "??" return s # ------------------------------------------------------------------------- # class Plug: """ Create a plug. :param block: The block being plugged into :type block: Block :param port: The port on the block, defaults to 0 :type port: int, optional :param type: 'start' or 'end', defaults to None :type type: str, optional :return: Plug object :rtype: Plug Plugs are the interface between a wire and block and have information about port number and wire end. Plugs are on the end of each wire, and connect a Wire to a specific port on a Block. The ``type`` argument indicates if the ``Plug`` is at: - the start of a wire, ie. the port is an output port - the end of a wire, ie. the port is an input port A plug can specify a set of ports on a block. """ __array_ufunc__ = None # allow block operators with NumPy values def __init__(self, block, port=0, type=None): self.block = block self.port = port self.type = type # start def __str__(self): """ Display plug details. :return: Plug description :rtype: str String format:: bicycle.0[1] """ return str(self.block) + "[" + str(self.port) + "]" def __repr__(self): """ Display plug details. :return: Plug description :rtype: str String format:: bicycle.0[1] """ return "Plug/" + self.type + ":" + str(self) @property def isslice(self): """ Test if port number is a slice. :return: Whether the port is a slice :rtype: bool Returns ``True`` if the port is a slice, eg. ``[0:3]``, and ``False`` for a simple index, eg. ``[2]``. """ return isinstance(self.port, slice) @property def portlist(self): """ Return port numbers. :return: Port numbers :rtype: iterable of int If the port is a simple index, eg. ``[2]`` returns [2]. If the port is a slice, eg. ``[0:3]``, returns [0, 1, 2]. For the case ``[2:]`` the upper bound is the maximum number of input or output ports of the block. """ if isinstance(self.port, int): # easy case, this plug is a single wire return [self.port] elif isinstance(self.port, slice): # this plug is a bunch of wires start = self.port.start or 0 step = self.port.step or 1 if self.port.stop is None: if self.type == "start": stop = self.block.nout else: stop = self.block.nin else: stop = self.port.stop return range(start, stop, step) else: return ValueError("bad plug index") def __getitem__(self, i): return self.__class__(self.block, self.portlist[i]) @property def width(self): """ Return number of ports connected. :return: Number of ports :rtype: int If the port is a simple index, eg. ``[2]`` returns 1. If the port is a slice, eg. ``[0:3]``, returns 3. """ return len(self.portlist) @oodebug def __rshift__(left, right): """ Overloaded >> operator for implicit wiring. :param left: A plug to be wired from :type left: Plug :param right: A block or plug to be wired to :type right: Block or Plug :return: ``right`` :rtype: Block or Plug Implements implicit wiring, where the left-hand operator is a Plug, for example:: a = bike[2] >> bd.GAIN(3) will connect port 2 of ``bike`` to the input of the GAIN block. Note that:: a = bike[2] >> func[1] will connect port 2 of ``bike`` to port 1 of ``func``, and port 1 of ``func`` will be assigned to ``a``. To specify a different outport port on ``func`` we need to use parentheses:: a = (bike[2] >> func[1])[0] which will connect port 2 of ``bike`` to port 1 of ``func``, and port 0 of ``func`` will be assigned to ``a``. :seealso: Block.__mul__ """ # called for the cases: # block * block # block * plug s = left.block.bd # assert isinstance(right, Block), 'arguments to * must be blocks not ports (for now)' w = s.connect(left, right) # add a wire # print('plug * ' + str(w)) return right @oodebug def __add__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (plug) to be added :type self: Plug :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] + Y result = X[i] + Y[j] result = X[i] + C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("++")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. :seealso: :meth:`Plug.__radd__` :meth:`Block.__add__` """ if isinstance(other, (int, float, np.ndarray)): # plug + constant, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM("++", inputs=(self, other)) @oodebug def __radd__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (plug) to be added :type self: Plug :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the right operand is a ``Plug`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X + Y[j] result = X[i] + Y[j] result = C + Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("++") block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: The inputs to the summing junction are reversed: right then left operand. :seealso: :meth:`Plug.__add__` :meth:`Block.__radd__` """ if isinstance(other, (int, float, np.ndarray)): # constant + plug, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM("++", inputs=(other, self)) @oodebug def __sub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (plug) to be added (minuend) :type self: Plug :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] - Y result = X[i] - Y[j] result = X[i] - C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: * The ``mode`` is None, regular addition :seealso: :meth:`Plug.__rsub__` :meth:`Block.__sub__` """ if isinstance(other, (int, float, np.ndarray)): # plug - constant, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM("+-", inputs=(self, other)) @oodebug def __rsub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (plug) to be added (minuend) :type self: Plug :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X - Y[j] result = X[i] - Y[j] result = C - Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: The inputs to the summing junction are reversed: right then left operand. :seealso: :meth:`Plug.__sub__` :meth:`Block.__rsub__` """ # TODO deal with other cases as per above if isinstance(other, (int, float, np.ndarray)): # constant - plug, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM("+-", inputs=(other, self)) @oodebug def __neg__(self): """ Overloaded unary minus operator for implicit block creation. :param self: A signal (plug) to be negated :type self: Plug :return: GAIN block :rtype: Block subclass This method is implicitly invoked by the - operator for unary minus when the operand is a ``Plug``:: result = -X[i] where ``X`` is a block. Create a ``GAIN(-1)`` block named ``_gain.N`` whose input is the operand. :seealso: :meth:`Block.__neg__` """ return self.block.bd.GAIN(-1, inputs=[self]) @oodebug def __pow__(self, p): """ Overloaded unary power operator for implicit block creation. :param self: A signal (plug) to be exponentiated :type self: Plug :return: POW block :rtype: Block subclass This method is implicitly invoked by the ** operator for unary power when the operand is a ``Block``:: result = X**3 where ``X`` is a block. Creates a ``POW(3)`` block named ``_pow.N`` whose input is the operand. :seealso: :meth:`Plug.__pow__` """ return self.block.bd.POW(p, inputs=[self]) @oodebug def __mul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (plug) to be multiplied :type self: Plug :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] * Y result = X[i] * Y[j] result = X[i] * C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__rmul__` :meth:`Block.__mul__` """ if isinstance(other, (int, float, np.ndarray)): # plug * constant, create a GAIN block return self.block._autogain(other, inputs=[self]) else: # value * value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 return self.block.bd.PROD( "**", matrix=True, name=name, inputs=[self, other] ) @oodebug def __rmul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (plug) to be multiplied :type self: Plug :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the right operand is a ``Plug`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X * Y[j] result = X[i] * Y[j] result = C * Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__mul__` :meth:`Block.__rmul__` """ if isinstance(other, (int, float, np.ndarray)): # constant * plug, create a CONSTANT block matrix = isinstance(other, np.ndarray) return self.block._autogain(other, premul=matrix, inputs=[self]) @oodebug def __truediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (plug) to be multiplied (dividend) :type self: Plug :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the / operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] / Y result = X[i] / Y[j] result = X[i] / C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(1/C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__rtruediv__` :meth:`Block.__truediv__` """ if isinstance(other, (int, float, np.ndarray)): # plug / constant , create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.PROD("*/", inputs=(self, other)) @oodebug def __rtruediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (plug) to be multiplied (dividend) :type self: Plug :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD block :rtype: Block subclass This method is implicitly invoked by the / operator when the right operand is a ``Plug`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X / Y[j] result = X[i] / Y[j] result = C / Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("*/")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, a new CONSTANT block named ``_const.N`` is also created. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__truediv__` :meth:`Block.__rtruediv__` """ if isinstance(other, (int, float, np.ndarray)): # constant / plug, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.PROD("*/", inputs=(other, self)) class StartPlug(Plug): def __init__(self, *args, **kwargs): super().__init__(*args, type="start", **kwargs) class EndPlug(Plug): def __init__(self, *args, **kwargs): super().__init__(*args, type="end", **kwargs) # ------------------------------------------------------------------------- # clocklist = [] class Clock: def __init__(self, arg, unit="s", offset=0, name=None): global clocklist if unit == "s": self.T = arg elif unit == "ms": self.T = arg / 1000 elif unit == "Hz": self.T = 1 / arg else: raise ValueError("unknown clock unit", unit) self.offset = offset self.blocklist = [] self.x = [] # discrete state vector numpy.ndarray self.t = [] self.tick = 0 self.timer = None if name is None: self.name = "clock." + str(len(clocklist)) else: self.name = name clocklist.append(self) # events happen at time t = kT + offset def add_block(self, block): self.blocklist.append(block) def __repr__(self): return str(self) def __str__(self): s = f"{self.name}: T={self.T} sec" if self.offset != 0: s += f", offset={self.offset}" s += f", clocking {len(self.blocklist)} blocks" return s def getstate0(self): # get the state from each stateful block on this clock x0 = np.array([]) for b in self.blocklist: x0 = np.r_[x0, b.getstate0()] # print('x0', x0) return x0 def getstate(self, t): x = np.array([]) for b in self.blocklist: # update dstate xb = b.next(t, b.inputs, b._x) x = np.r_[x, xb.flatten()] return x def setstate(self): x = self._x for b in self.blocklist: x = b.setstate(x) # send it to blocks def start(self, simstate=None): self.i = 1 simstate.declare_event(self, self.time(self.i)) self.i += 1 def next_event(self, simstate=None): simstate.declare_event(self, self.time(self.i)) self.i += 1 def time(self, i): # return (math.floor((t - self.offset) / self.T) + 1) * self.T + self.offset # k = int((t - self.offset) / self.T + 0.5) return i * self.T + self.offset def savestate(self, t): # save clock state at time t self.t.append(t) self.x.append(self.getstate(t)) # ------------------------------------------------------------------------- # class Block: varinputs = False varoutputs = False __array_ufunc__ = None # allow block operators with NumPy values def __new__(cls, *args, bd=None, **kwargs): """ Construct a new Block object. :param cls: The class to construct :type cls: class type :param *args: positional args passed to constructor :type *args: list :param **kwargs: keyword args passed to constructor :type **kwargs: dict :return: new Block instance :rtype: Block instance """ # print('Block __new__', args,bd, kwargs) block = super(Block, cls).__new__(cls) # create a new instance # we overload setattr, so need to know whether it is being passed a port # name. Add this attribute now to allow proper operation. block.__dict__["portnames"] = [] # must be first, see __setattr__ block.nstates = 0 block.ndstates = 0 block._sequence = None block._x = None # state vector return block _latex_remove = str.maketrans({"$": "", "\\": "", "{": "", "}": "", "^": ""}) def __init__( self, name=None, nin=None, nout=None, inputs=None, type=None, inames=None, onames=None, snames=None, pos=None, bd=None, blockclass=None, verbose=False, **kwargs, ): """ Construct a new block object. :param name: Name of the block, defaults to None :type name: str, optional :param nin: Number of inputs, defaults to None :type nin: int, optional :param nout: Number of outputs, defaults to None :type nout: int, optional :param inputs: Optional incoming connections :type inputs: Block, Plug or list of Block or Plug :param inames: Names of input ports, defaults to None :type inames: list of str, optional :param onames: Names of output ports, defaults to None :type onames: list of str, optional :param snames: Names of states, defaults to None :type snames: list of str, optional :param pos: Position of block on the canvas, defaults to None :type pos: 2-element tuple or list, optional :param bd: Parent block diagram, defaults to None :type bd: BlockDiagram, optional :param verbose: enable diagnostic prints, defaults to False :type verbose: bool, optional :param kwargs: Unused arguments :type kwargs: dict :return: A Block superclass :rtype: Block A block object is the superclass of all blocks in the simulation environment. This is the top-level initializer, and handles most options passed to the superclass initializer for each block in the library. """ # print('Block constructor, bd = ', bd) if name is not None: self.name_tex = name self.name = self._fixname(name) else: self.name_tex = None self.name = None self.bd = bd self.pos = pos self.id = None self.out = [] self.inputs = None self.updated = False self.shape = "block" # for box self._inport_names = None self._outport_names = None self._state_names = None self.initd = True self._clocked = False self._graphics = False self._parameters = {} self.verbose = verbose if nin is not None: self.nin = nin if nout is not None: self.nout = nout if blockclass is not None: self.blockclass = blockclass if type is None: self.type = self.__class__.__name__.lower() if bd is not None: bd.add_block(self) if inames is not None: self.inport_names(inames) if onames is not None: self.outport_names(onames) if snames is not None: self.state_names(snames) if isinstance(inputs, Block): inputs = (inputs,) if inputs is not None and len(inputs) > 0: # assert len(inputs) == self.nin, 'Number of input connections must match number of inputs' for i, input in enumerate(inputs): self.bd.connect(input, Plug(self, port=i)) if len(kwargs) > 0: print("WARNING: unused arguments", kwargs.keys()) def add_param(self, param, handler=None): if handler == None: def handler(self, name, newvalue): setattr(self, name, newvalue) self.__dict__["_parameters"][param] = handler def set_param(self, name, newvalue): print(f"setting parameter {name} of block {self.name} to {newvalue}") self._parameters[name](self, name, newvalue) @property def info(self): """ Interactive display of block properties. Displays all attributes of the block for debugging purposes. """ print("block: " + type(self).__name__) for k, v in self.__dict__.items(): if k != "sim": print(" {:11s}{:s}".format(k + ":", str(v))) @property def isclocked(self): """ Test if block is clocked :return: True if block is clocked :rtype: bool True if block is clocked, False if it is continuous time. """ return self._clocked @property def isgraphics(self): """ Test if block does graphics :return: True if block does graphics :rtype: bool """ return self._graphics # for use in unit testing # TODO: should redo this, eliminate the monkey patch # TODO: make T_step(), dummpy out the state object def T_output(self, *inputs, t=0.0, x=None): """ Evaluate a block for unit testing. :param *inputs: Input port values :param t: Simulation time, defaults to 0.0 :type t: float, optional :param x: state vector :type x: ndarray :return: Block output port values :rtype: list The output ports of the block are evaluated for a given simulation time and set of input port values. Input ports are assigned to consecutive inputs, output port values are a list. Mostly used for making concise unit tests. .. warning:: the instance is monkey patched, not useable in a block diagram subsequently. """ # check inputs and assign to attribute assert len(inputs) == self.nin, "wrong number of inputs provided" # evaluate the block out = self.output(t, inputs, x) # sanity check the output assert isinstance(out, list), "result must be a list" assert len(out) == self.nout, "result list is wrong length" return out def T_deriv(self, *inputs, t=0.0, x=None): """ Evaluate a block for unit testing. :param inputs: input port values :type inputs: list :param t: Simulation time, defaults to 0.0 :type t: float, optional :param x: state vector :type x: ndarray :return: Block derivative value :rtype: ndarray The derivative of the block is evaluated for a given set of input port values. Input port values are treated as lists. Mostly used for making concise unit tests. .. warning:: the instance is monkey patched, not useable in a block diagram subsequently. """ # check inputs and assign to attribute assert len(inputs) == self.nin, "wrong number of inputs provided" if x is not None: assert len(x) == self.nstates, "passed state is wrong length" # evaluate the block out = self.deriv(t, inputs, x) # sanity check the output assert isinstance(out, np.ndarray), "result must be an ndarray" assert out.shape == (self.nstates,), "result array is wrong length" return out def T_next(self, *inputs, t=0.0, x=None): """ Evaluate a block for unit testing. :param inputs: input port values :type inputs: list :param t: Simulation time, defaults to 0.0 :type t: float, optional :param x: state vector :type x: ndarray :return: Block next state value :rtype: ndarray The next value of a discrete time block is evaluated for a given set of input port values. Input port values are treated as lists. Mostly used for making concise unit tests. """ # check inputs and assign to attribute assert len(inputs) == self.nin, "wrong number of inputs provided" if x is not None: assert len(x) == self.ndstates, "passed state is wrong length" # evaluate the block out = self.next(t, inputs, x) # sanity check the output assert isinstance(out, np.ndarray), "next state must be an ndarray" assert out.shape == (self.ndstates,), "next state array is wrong length" return out def T_step(self, *inputs, t=0.0): """ Step a block for unit testing. :param inputs: input port values :type inputs: list :param t: Simulation time, defaults to 0.0 :type t: float, optional Step the block for a given set of input port values. Input port values are treated as lists. Mostly used for making concise unit tests. """ # check inputs and assign to attribute assert len(inputs) == self.nin, "wrong number of inputs provided" # step the block self.step(t, inputs) def T_start(self, simstate=None): from bdsim.run_sim import BDSimState, Options if simstate is None: class RunTime: def DEBUG(*args): pass class BlockDiagram: pass self.bd = BlockDiagram() self.bd.runtime = RunTime() self.bd.runtime.options = Options() simstate = BDSimState() simstate.options = self.bd.runtime.options simstate.t = 0.0 # step the block self.start(simstate) return simstate def _output(self, *inputs, t=0.0, x=None): return self.T_output(*inputs, t=t, x=x) def _step(self, *inputs, t=0.0): return self.T_step(*inputs, t=t) # def input(self, port): # """ # Get input to block on specified port # :param port: port number # :type port: int # :return: value applied to specified input port # :rtype: any # Return the value of the input applied to the input port numbered # ``port``. The type depends on the source port connected to this input. # .. note:: When a block's ``output`` method is evaluated the resulting list is # saved as an attribute of that block. The ``input`` method traces back # along the wire connected to the input port to obtain a reference to the # output value held by the predecessor block. # .. note:: For unit testing purposes, it the block is simply an instance # of the class, then setting its attribute ``T_inputs`` to a list # provides the input values to the block. # :seealso: :meth:`inputs` # """ # try: # p = self.sources[port] # get plug for source block output # return p.block.output_values[p.port] @property def inputs(self): """ Get block inputs as a list :return: list of block inputs :rtype: list Returns a list of values corresponding to the input ports of the block. The types of the elements are dictated by the blocks connected to the input ports. .. note:: When a block's ``output`` method is evaluated the resulting list is saved as an attribute of that block. The ``inputs`` method uses the ``sources`` attribute which has references to the output values held by the predecessor block. :seealso: :meth:`input` """ values = [] for port in range(self.nin): plug = self.sources[port] # get plug for source block output values.append(plug.block.output_values[plug.port]) return values def __getitem__(self, port): """ Convert a block slice reference to a plug. :param port: Port number :type port: int :return: A port plug :rtype: Plug Invoked whenever a block is referenced as a slice, for example:: c = bd.CONSTANT(1) bd.connect(x, c[0]) bd.connect(c[0], x) In both cases ``c[0]`` is converted to a ``Plug`` by this method. """ # block[i] is a plug object # print('getitem called', self, port) return Plug(self, port) def __setitem__(self, port, src): """ Convert a LHS block slice reference to a wire. :param port: Port number :type port: int :param src: the RHS :type src: Block or Plug Used to create a wired connection by assignment, for example:: X[0] = Y where ``X`` and ``Y`` are blocks. This method is implicitly invoked and creates a wire from ``Y`` to input port 0 of ``X``. .. note:: The square brackets on the left-hand-side is critical, and ``X = Y`` will simply overwrite the reference to ``X``. """ # b[port] = src # src --> b[port] # print('connecting', src, self, port) self.bd.connect(src, self[port]) def __setattr__(self, name, value): """ Convert a LHS block name reference to a wire. :param name: Port name :type port: str :param value: the RHS :type value: Block or Plug Used to create a wired connection by assignment, for example:: c = bd.CONSTANT(1, inames=['u']) c.u = x Ths method is invoked to create a wire from ``x`` to port 'u' of the constant block ``c``. Notes: - this overloaded method handles all instances of ``setattr`` and implements normal functionality as well, only creating a wire if ``name`` is a known port name. """ # b[port] = src # src --> b[port] # gets called for regular attribute settings, as well as for wiring if name in self.portnames: # we're doing wiring # print('in __setattr___', self, name, value) self.bd.connect(value, getattr(self, name)) else: # regular case, add attribute to the instance's dictionary self.__dict__[name] = value @oodebug def __rshift__(left, right): """ Operator for implicit wiring. :param left: A block to be wired from :type left: Block :param right: A block or plugto be wired to :type right: Block or Plug :return: ``right`` :rtype: Block or Plug Implements implicit wiring, for example:: a = bd.CONSTANT(1) >> bd.GAIN(2) will connect the output of the CONSTANT block to the input of the GAIN block. The result will be GAIN block, whose output in this case will be assigned to ``a``. Note that:: a = bd.CONSTANT(1) >> func[1] will connect port 0 of CONSTANT to port 1 of ``func``, and port 1 of ``func`` will be assigned to ``a``. To specify a different outport port on ``func`` we need to use parentheses:: a = (bd.CONSTANT(1) >> func[1])[0] which will connect port 0 of CONSTANT ` to port 1 of ``func``, and port 0 of ``func`` will be assigned to ``a``. :seealso: Plug.__rshift__ """ # called for the cases: # block * block # block * plug s = left.bd # assert isinstance(right, Block), 'arguments to * must be blocks not ports (for now)' w = s.connect(left, right) # add a wire # print('block * ' + str(w)) return right # make connection, return a plug def _autoconstant(self, value): if isinstance(value, (int, float, str)): name = "_const.{:d}({})".format(self.bd.n_auto_const, value) else: name = "_const.{:d}<{}>".format(self.bd.n_auto_const, type(value).__name__) self.bd.n_auto_const += 1 return self.bd.CONSTANT(value, name=name) def _autogain(self, value, **kwargs): if isinstance(value, (int, float, str)): name = "_gain.{:d}({})".format(self.bd.n_auto_gain, value) else: name = "_gain.{:d}<{}>".format(self.bd.n_auto_gain, type(value).__name__) self.bd.n_auto_gain += 1 return self.bd.GAIN(value, name=name, **kwargs) def _autopow(self, value, **kwargs): name = "_pow.{:d}({})".format(self.bd.n_auto_pow, value) self.bd.n_auto_pow += 1 return self.bd.POW(value, name=name, **kwargs) @oodebug def __add__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (block) to be added :type self: Block :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X + Y result = X + Y[j] result = X + C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("++") block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: * The inputs to the summing junction are reversed: right then left operand. * The ``mode`` is None, regular addition :seealso: :meth:`Block.__radd__` :meth:`Plug.__add__` """ # value + value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # block + constant, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM("++", inputs=(self, other), name=name) @oodebug def __radd__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (block) to be added :type self: Block :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X + Y[j] result = X[i] + Y[j] result = C + Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("++") block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: * The inputs to the summing junction are reversed: right then left operand. * The ``mode`` is None, regular addition :seealso: :meth:`Block.__add__` :meth:`Plug.__radd__` """ # value + value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # constant + block, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM("++", inputs=(other, self), name=name) @oodebug def __sub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (block) to be added (minuend) :type self: Block :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X - Y result = X - Y[j] result = X - C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. :seealso: :meth:`Block.__rsub__` :meth:`Plug.__sub__` """ # value - value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # block - constant, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM("+-", inputs=(self, other), name=name) @oodebug def __rsub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (block) to be added (minuend) :type self: Block :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X - Y result = X[i] - Y result = C - Y where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: * The inputs to the summing junction are reversed: right then left operand. * The ``mode`` is None, regular addition :seealso: :meth:`Block.__sub__` :meth:`Plug.__rsub__` """ # value - value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # constant - block, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM("+-", inputs=(other, self), name=name) @oodebug def __neg__(self): """ Overloaded unary minus operator for implicit block creation. :param self: A signal (block) to be negated :type self: Block :return: GAIN block :rtype: Block subclass This method is implicitly invoked by the - operator for unary minus when the operand is a ``Block``:: result = -X where ``X`` is a block. Creates a ``GAIN(-1)`` block named ``_gain.N`` whose input is the operand. :seealso: :meth:`Plug.__neg__` """ return self._autogain(-1.0, inputs=[self]) @oodebug def __pow__(self, p): """ Overloaded unary power operator for implicit block creation. :param self: A signal (block) to be negated :type self: Block :return: POW block :rtype: Block subclass This method is implicitly invoked by the ** operator for unary power when the operand is a ``Block``:: result = X**3 where ``X`` is a block. Creates a ``POW(3)`` block named ``_pow.N`` whose input is the operand. :seealso: :meth:`Plug.__pow__` """ return self._autopow(p, inputs=[self]) @oodebug def __mul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (block) to be multiplied :type self: Block :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X * Y result = X * Y[j] result = X * C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__rmul__` :meth:`Plug.__mul__` """ matrix = False if isinstance(other, (int, float, np.ndarray)): # block * constant, create a GAIN block matrix = isinstance(other, np.ndarray) return self._autogain(other, premul=matrix, matrix=matrix, inputs=[self]) else: # value * value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 return self.bd.PROD("**", inputs=[self, other], matrix=matrix, name=name) @oodebug def __rmul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (block) to be multiplied :type self: Block :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X * Y result = X[i] * Y result = C * Y where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__mul__` :meth:`Plug.__rmul__` """ matrix = False if isinstance(other, (int, float, np.ndarray)): # constant * block, create a GAIN block matrix = isinstance(other, np.ndarray) return self._autogain(other, premul=matrix, inputs=[self]) @oodebug def __truediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (block) to be multiplied (dividend) :type self: Block :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the / operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X / Y result = X / Y[j] result = X / C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(1/C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__rtruediv__` :meth:`Plug.__truediv__` """ # value / value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 matrix = False if isinstance(other, (int, float, np.ndarray)): # block / constant, create a CONSTANT block other = self._autoconstant(other) matrix = isinstance(other, np.ndarray) return self.bd.PROD("*/", inputs=(self, other), matrix=matrix, name=name) @oodebug def __rtruediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (block) to be multiplied (dividend) :type self: Block :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD block :rtype: Block subclass This method is implicitly invoked by the / operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X / Y result = X[i] / Y result = C / Y where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("*/")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, a new CONSTANT block named ``_const.N`` is also created. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__truediv__` :meth:`Plug.__rtruediv__` """ # value / value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 matrix = False if isinstance(other, (int, float, np.ndarray)): # constant / block, create a CONSTANT block other = self._autoconstant(other) matrix = isinstance(other, np.ndarray) return self.bd.PROD("*/", inputs=(other, self), matrix=matrix, name=name) # TODO arithmetic with a constant, add a gain block or a constant block def __str__(self): if hasattr(self, "name") and self.name is not None: return self.name else: return self.blockclass + ".??" def __repr__(self): return self.__str__() def _fixname(self, s): return s.translate(self._latex_remove) def inport_names(self, names): """ Set the names of block input ports. :param names: List of port names :type names: list of str Invoked by the ``inames`` argument to the Block constructor. The names can include LaTeX math markup. The LaTeX version is used where appropriate, but the port names are a de-LaTeXd version of the given string with backslash, caret, braces and dollar signs removed. """ self._inport_names = names for port, name in enumerate(names): fn = self._fixname(name) setattr(self, fn, self[port]) self.portnames.append(fn) def outport_names(self, names): """ Set the names of block output ports. :param names: List of port names :type names: list of str Invoked by the ``onames`` argument to the Block constructor. The names can include LaTeX math markup. The LaTeX version is used where appropriate, but the port names are a de-LaTeXd version of the given string with backslash, caret, braces and dollar signs removed. """ self._outport_names = names for port, name in enumerate(names): fn = self._fixname(name) setattr(self, fn, self[port]) self.portnames.append(fn) def state_names(self, names): self._state_names = names def sourcename(self, port): """ Get the name of output port driving this input port. :param port: Input port :type port: int :return: Port name :rtype: str Return the name of the output port that drives the specified input port. The name can be: - a LaTeX string if provided - block name with port number given in square brackets. The block name will the one optionally assigned by the user using the ``name`` keyword, otherwise a systematic default name. :seealso: outport_names """ w = self.input_wires[port] if w.name is not None: return w.name src = w.start.block srcp = w.start.port if src._outport_names is not None: return src._outport_names[srcp] return str(w.start) # @property # def fullname(self): # return self.blockclass + "." + str(self) def reset(self): if self.nin > 0: self.inputs = [None] * self.nin self.updated = False def add_output_wire(self, w): port = w.start.port assert port < len(self.output_wires), "port number too big" self.output_wires[port].append(w) def add_input_wire(self, w): port = w.end.port assert ( self.input_wires[port] is None ), "attempting to connect second wire to an input" self.input_wires[port] = w self.sources[port] = w.start # def setinput(self, port, value): # """ # Receive input from a wire # :param self: Block to be updated # :type wire: Block # :param port: Input port to be updated # :type port: int # :param value: Input value # :type val: any # """ # # stash it away # self.inputs[port] = value # def setinputs(self, *pos): # assert len(pos) == self.nin, 'mismatch in number of inputs' # self.reset() # for i, val in enumerate(pos): # self.inputs[i] = val def start(self, simstate): # begin a simulation pass def check(self): # check validity of block parameters at start assert hasattr(self, "nin"), f"block {self.name} has no nin specified" assert hasattr(self, "nout"), f"block {self.name} has no nout specified" assert ( self.nin > 0 or self.nout > 0 ), f"block {self.name} no inputs or outputs specified" assert ( hasattr(self, "initd") and self.initd ), "Block superclass not initalized. was super().__init__ called?" def done(self, **kwargs): # end of simulation pass def savefig(self, *pos, **kwargs): pass class SinkBlock(Block): """ A SinkBlock is a subclass of Block that represents a block that has inputs but no outputs. Typically used to save data to a variable, file or graphics. """ blockclass = "sink" def __init__(self, **blockargs): """ Create a sink block. :param blockargs: |BlockOptions| :type blockargs: dict :return: sink block base class :rtype: SinkBlock This is the parent class of all sink blocks. """ # print('Sink constructor') super().__init__(**blockargs) self.nout = 0 self.nstates = 0 def step(self, t, inports): # valid pass class SourceBlock(Block): """ A SourceBlock is a subclass of Block that represents a block that has outputs but no inputs. Its output is a function of parameters and time. """ blockclass = "source" def __init__(self, **blockargs): """ Create a source block. :param blockargs: |BlockOptions| :type blockargs: dict :return: source block base class :rtype: SourceBlock This is the parent class of all source blocks. """ # print('Source constructor') super().__init__(**blockargs) self.nin = 0 self.nstates = 0 class TransferBlock(Block): """ A TransferBlock is a subclass of Block that represents a block with inputs outputs and states. Typically used to describe a continuous time dynamic system, either linear or nonlinear. """ blockclass = "transfer" def __init__(self, nstates=1, **blockargs): """ Create a transfer function block. :param blockargs: |BlockOptions| :type blockargs: dict :return: transfer function block base class :rtype: TransferBlock This is the parent class of all transfer function blocks. """ # print('Transfer constructor') self.nstates = nstates super().__init__(**blockargs) def reset(self): super().reset() self._x = self._x0 # return self._x def setstate(self, x): x = np.array(x) self._x = x[: self.nstates] # take as much state vector as we need return x[self.nstates :] # return the rest def getstate0(self): return self._x0 def check(self): assert len(self._x0) == self.nstates, "incorrect length for initial state" assert self.nin > 0 or self.nout > 0, "no inputs or outputs specified" class FunctionBlock(Block): """ A FunctionBlock is a subclass of Block that represents a block that has inputs and outputs but no state variables. Typically used to describe operations such as gain, summation or various mappings. """ blockclass = "function" def __init__(self, **blockargs): """ Create a function block. :param blockargs: |BlockOptions| :type blockargs: dict :return: function block base class :rtype: FunctionBlock This is the parent class of all function blocks. """ # print('Function constructor') super().__init__(**blockargs) self.nstates = 0 class SubsystemBlock(Block): """ A SubSystem s a subclass of Block that represents a block that has inputs and outputs but no state variables. Typically used to describe operations such as gain, summation or various mappings. """ blockclass = "subsystem" def __init__(self, **blockargs): """ Create a subsystem block. :param blockargs: |BlockOptions| :type blockargs: dict :return: subsystem block base class :rtype: SubsystemBlock This is the parent class of all subsystem blocks. """ # print('Subsystem constructor') super().__init__(**blockargs) self.nstates = 0 class ClockedBlock(Block): """ A ClockedBlock is a subclass of Block that represents a block with inputs outputs and discrete states. Typically used to describe a discrete time dynamic system, either linear or nonlinear. """ blockclass = "clocked" def __init__(self, clock=None, **blockargs): """ Create a clocked block. :param blockargs: |BlockOptions| :type blockargs: dict :return: clocked block base class :rtype: ClockedBlock This is the parent class of all clocked blocks. """ # print('Clocked constructor') super().__init__(**blockargs) assert clock is not None, "clocked block must have a clock" self._clocked = True self.clock = clock clock.add_block(self) def reset(self): super().reset() # self._x = self._x0 # return self._x def setstate(self, x): self._x = x[: self.ndstates] # take as much state vector as we need # print('** set block state to ', self._x) return x[self.ndstates :] # return the rest def getstate0(self): return self._x0 def check(self): assert len(self._x0) == self.ndstates, "incorrect length for initial state" assert self.nin > 0 or self.nout > 0, "no inputs or outputs specified" self._x = self._x0 class EventSource: pass # c = Clock(5) # c1 = Clock(5, 2) # print(c, c1) # print(c.next(0), c1.next(0)) if __name__ == "__main__": # opt = OptionsBase(dict(foo=1, bar='hello')) # print(opt.foo) # print(opt.bar) # opt.set(foo=3) # print(opt.foo) # from bdsim.blocks.functions import Sum # print(Sum.parameters()) import bdsim sim = bdsim.BDSim() # create simulator print(sim.moduledicts) ================================================ FILE: bdsim/graphics.py ================================================ import sys import matplotlib import matplotlib.pyplot as plt from matplotlib import animation from bdsim.components import SinkBlock class GraphicsBlock(SinkBlock): """ A GraphicsBlock is a subclass of SinkBlock that represents a block that has inputs but no outputs and creates/updates a graphical display. """ blockclass = "graphics" def __init__(self, movie=None, **blockargs): """ Create a graphical display block. :param movie: Save animation in this file in MP4 format, defaults to None :type movie: str, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: transfer function block base class :rtype: TransferBlock This is the parent class of all graphic display blocks. """ super().__init__(**blockargs) self._graphics = True self.movie = movie def start(self, simstate): # plt.draw() # plt.show(block=False) self._simstate = simstate self._enabled = simstate.options.graphics if self.movie is not None and not simstate.options.animation: print( "enabling global animation option to allow movie option on block", self ) if not simstate.options.animation: print("must enable animation to render a movie") if self.movie is not None: try: self.writer = animation.FFMpegWriter( fps=10, extra_args=["-vcodec", "libx264"] ) self.writer.setup(fig=self.fig, outfile=self.movie) print("movie block", self, " --> ", self.movie) except FileNotFoundError: self.fatal("cannot save movie, please install ffmpeg") def step(self, t, inports): super().step(t, inports) # bring the figure up to date in a backend-specific way if self._simstate.options.animation: if self._simstate.backend == "TkAgg": self.fig.canvas.flush_events() plt.show(block=False) plt.show(block=False) elif self._simstate.backend == "Qt5Agg": self.fig.canvas.flush_events() self.fig.canvas.draw() else: self.fig.canvas.draw() if self.movie is not None: try: self.writer.grab_frame() except AttributeError: self.fatal("cannot save movie, please install ffmpeg") def done(self, block=False): if self.fig is not None: self.fig.canvas.start_event_loop(0.001) if self.movie is not None: self.writer.finish() # self.cleanup() plt.show(block=block) def savefig(self, filename=None, format="pdf", **kwargs): """ Save the figure as an image file :param fname: Name of file to save graphics to :type fname: str :param ``**kwargs``: Options passed to `savefig `_ The file format is taken from the file extension and can be jpeg, png or pdf. """ try: plt.figure(self.fig.number) # make block's figure the current one if filename is None: filename = self.name filename += "." + format print("saved {} -> {}".format(str(self), filename)) plt.savefig(filename, **kwargs) # save the current figure except: pass def create_figure(self, state): def move_figure(f, x, y): """Move figure's upper left corner to pixel (x, y)""" backend = matplotlib.get_backend() x = int(x) + gstate.xoffset y = int(y) if backend == "TkAgg": f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y)) elif backend == "WXAgg": f.canvas.manager.window.SetPosition((x, y)) else: # This works for QT and GTK # You can also use window.setGeometry try: f.canvas.manager.window.move(x, y) except AttributeError: pass # can't do this for MacOSX gstate = state options = state.options self.bd.runtime.DEBUG( "graphics", "{} matplotlib figures exist", len(plt.get_fignums()) ) if gstate.fignum == 0: # no figures yet created, lazy initialization self.bd.runtime.DEBUG("graphics", "lazy initialization") if options.backend is None: if sys.platform == "darwin": # for MacOS, use Qt5Agg if its installed # otherwise use default (MacOSX) if "Qt5Agg" in matplotlib.rcsetup.all_backends: try: import PyQt5 matplotlib.use("Qt5Agg") print( "no graphics backend specified: Qt5Agg found, using" " instead of MacOSX" ) except: pass else: try: matplotlib.use(options.backend) except ImportError: self.fatal(f"can't select backend: {options.backend}") mpl_backend = matplotlib.get_backend() gstate.backend = mpl_backend self.bd.runtime.DEBUG("graphics", " backend={:s}", mpl_backend) # split the string ntiles = [int(x) for x in options.tiles.split("x")] xoffset = 0 if options.shape is None: if mpl_backend == "Qt5Agg": # next line actually creates a figure if none already exist QScreen = plt.get_current_fig_manager().canvas.screen() # this is a QScreenClass object, see https://doc.qt.io/qt-5/qscreen.html#availableGeometry-prop # next line creates a figure sz = QScreen.availableSize() dpiscale = ( QScreen.devicePixelRatio() ) # is 2.0 for Mac laptop screen self.bd.runtime.DEBUG( "graphics", " {} x {} @ {}dpi", sz.width(), sz.height(), dpiscale, ) # check for a second screen if options.altscreen: vsize = QScreen.availableVirtualGeometry().getCoords() if vsize[0] < 0: # extra monitor to the left xoffset = vsize[0] elif vsize[0] >= sz.width(): # extra monitor to the right xoffset = vsize[0] self.bd.runtime.DEBUG( "graphics", " altscreen offset {}", xoffset ) screen_width, screen_height = sz.width(), sz.height() dpi = QScreen.physicalDotsPerInch() f = plt.gcf() elif mpl_backend == "TkAgg": window = plt.get_current_fig_manager().window screen_width, screen_height = ( window.winfo_screenwidth(), window.winfo_screenheight(), ) dpiscale = 1 self.bd.runtime.DEBUG( "graphics", " screensize: {:d} x {:d}", screen_width, screen_height, ) f = plt.gcf() dpi = f.dpi else: # all other backends f = plt.figure() dpi = f.dpi dpiscale = 2 screen_width, screen_height = f.get_size_inches() * f.dpi # compute fig size in inches (width, height) figsize = [ screen_width / ntiles[1] / dpi, screen_height / ntiles[0] / dpi, ] else: # shape is given explictly screen_width, screen_height = [int(x) for x in options.shape.split("x")] f = plt.gcf() f.canvas.manager.set_window_title(f"bdsim: Figure {f.number:d}") # save graphics info away in state gstate.figsize = figsize gstate.dpi = dpi gstate.screensize_pix = (screen_width, screen_height) gstate.ntiles = ntiles gstate.xoffset = xoffset # resize the figure f.set_dpi(gstate.dpi * dpiscale) f.set_size_inches(figsize, forward=True) plt.ion() else: # subsequent figures f = plt.figure(figsize=gstate.figsize, dpi=gstate.dpi) # move the figure to right place on screen row = gstate.fignum // gstate.ntiles[0] col = gstate.fignum % gstate.ntiles[0] scale = 1.02 move_figure( f, col * gstate.figsize[0] * gstate.dpi * scale, row * gstate.figsize[1] * gstate.dpi * scale, ) gstate.fignum += 1 def onkeypress(event): if event.key == "x": print("\nclosing all windows") plt.close("all") elif event.key == "ctrl+c": print("\nterminating bdsim") sys.exit(1) else: print("key pressed", event.key) f.canvas.mpl_connect("key_press_event", onkeypress) self.bd.runtime.DEBUG( "graphics", "create figure {:d} at ({:d}, {:d})", gstate.fignum, row, col ) return f ================================================ FILE: bdsim/run_realtime.py ================================================ import os from pathlib import Path import sys import importlib import inspect from collections import Counter, namedtuple import argparse import types import warnings from bdsim.blockdiagram import BlockDiagram from bdsim.components import OptionsBase, Block, Clock, BDStruct, Plug, clocklist import copy import tempfile import subprocess import webbrowser import numpy as np import scipy.integrate as integrate import matplotlib.pyplot as plt import re from colored import fg, attr import math import threading import time import threading from bdsim.run_sim import BDSim, TimeQ, blockname # class TimeQRT(TimeQ): # """ # Time-ordered queue for events # The list comprises tuples of (time, block) to reflect an event associated # with the specified block at the specified time. # The list is not ordered, and is sorted on a pop event. # """ # def __init__(self): # self.q = [] # self.dirty = False # # super().__init__() # init threading class # self.sem = threading.Semaphore(0) # self.done = False # self.t = None # # def wait(self): # # self.sem.acquire() # # # print(f' wake at {self.t}') # # return self.t, self.clocks # def run(self, callback): # nok = 0 # noverrun = 0 # print('run') # t0 = time.time() # stop = t0 # tmax = 0 # while not self.done: # t, clocks = self.pop() # if t is None: # print('E', end='') # time.sleep(0.02) # continue # # print('dequeue', t) # stop = t0 + t # ts = time.time() # sleep_time = stop - ts # if sleep_time > 0: # # print('sleeping for', sleep_time) # time.sleep(sleep_time) # tmax = max(tmax, time.time()-ts) # # if tmax > 0.2: # # print(tmax, sleep_time) # print('.', end='') # nok += 1 # else: # # print('timer overrun') # print('x', end='') # noverrun += 1 # # self.t = t # # self.clocks = clocks # # self.sem.release() # callback(t, clocks) # sys.stdout.flush() # print(fg('yellow')) # print(f'tmax {tmax}') # print(f'n ok {nok} ({nok/(nok+noverrun)*100:.1f}%)') # print(f'n overrun {noverrun} ({noverrun/(nok+noverrun)*100:.1f}%)') # print(attr(0)) # def stop(self): # self.done = True # self.join() class BDRealTimeState: """ :ivar x: state vector :vartype x: np.ndarray :ivar T: maximum simulation time (seconds) :vartype T: float :ivar t: current simulation time (seconds) :vartype t: float :ivar fignum: number of next matplotlib figure to create :vartype fignum: int :ivar stop: reference to block wanting to stop simulation, else None :vartype stop: Block subclass :ivar checkfinite: halt simulation if any wire has inf or nan :vartype checkfinite: bool :ivar graphics: enable graphics :vartype graphics: bool """ def __init__(self): self.x = None # continuous state vector numpy.ndarray self.T = None # maximum.BlockDiagram time self.t = None # current time self.fignum = 0 self.stop = None self.checkfinite = True self.debugger = True self.t_stop = None # time-based breakpoint self.eventq = TimeQ() def declare_event(self, block, t): self.eventq.push((t, block)) class SimpleStats: def __init__(self): self._n = 0 self._sum = 0 self._sum2 = 0 self._max = 0 def update(self, x): self._n += 1 self._sum += x self._sum2 += x**2 self._max = max(self._max, x) @property def n(self): return self._n @property def mean(self): return self._sum / self._n @property def sdev(self): return math.sqrt((self._sum2 - self._sum**2 / self._n) / (self._n - 1)) @property def max(self): return self._max class BDRealTime(BDSim): def run( self, bd, T=5, dt=None, block=None, checkfinite=True, watch=[], samples=True, ): """ Run the block diagram :param T: maximum integration time, defaults to 10.0 :type T: float, optional :param dt: maximum time step :type dt: float, optional :param solver: integration method, defaults to ``RK45`` :type solver: str, optional :param block: matplotlib block at end of run, default False :type block: bool :param checkfinite: error if inf or nan on any wire, default True :type checkfinite: bool :param minstepsize: minimum step length, default 1e-6 :type minstepsize: float :param watch: list of input ports to log :type watch: list :param solver_args: arguments passed to ``scipy.integrate`` :type solver_args: dict :return: time history of signals and states :rtype: Sim class Assumes that the network has been compiled. The system is simulated from time 0 to ``T``. The integration step time ``dt`` defaults to ``T/100`` but can be specified. Finer control can be achieved using ``max_step`` and ``first_step`` parameters to the underlying integrator using the ``solver_args`` parameter. Results are returned in a class with attributes: - ``t`` the time vector: ndarray, shape=(M,) - ``x`` is the state vector: ndarray, shape=(M,N) - ``xnames`` is a list of the names of the states corresponding to columns of `x`, eg. "plant.x0", defined for the block using the ``snames`` argument - ``yN`` for a watched input where N is the index of the port mentioned in the ``watch`` argument - ``ynames`` is a list of the names of the input ports being watched, same order as in ``watch`` argument If there are no dynamic elements in the diagram, ie. no states, then ``x`` and ``xnames`` are not present. The ``watch`` argument is a list of one or more input ports whose value during simulation will be recorded. The elements of the list can be: - a ``Block`` reference, which is interpretted as input port 0 - a ``Plug`` reference, ie. a block with an index or attribute - a string of the form "block[i]" which is port i of the block named block. The debug string comprises single letter flags: - 'p' debug network value propagation - 's' debug state vector - 'd' debug state derivative .. note:: Simulation stops if the step time falls below ``minsteplength`` which typically indicates that the solver is struggling with a very harsh non-linearity. """ assert bd.compiled, "Network has not been compiled" state = BDRealTimeState() self.state = state self.bd = bd state.T = T state.dt = dt state.options = self.options # process the watchlist # elements can be: # - block or Plug reference # - str in the form BLOCKNAME[PORT] watchlist = [] watchnamelist = [] re_block = re.compile(r"(?P[^[]+)(\[(?P[0-9]+)\])") for w in watch: if isinstance(w, str): # a name was given, with optional port number m = re_block.match(w) if m is None: raise ValueError("watch block[port] not found: " + w) name = m.group("name") port = int(m.group("port")) b = bd.blocknames[name] plug = b[port] elif isinstance(w, Block): # a block was given, defaults to port 0 plug = w[0] elif isinstance(w, Plug): # a plug was given plug = w watchlist.append(plug) watchnamelist.append(str(plug)) state.watchlist = watchlist state.watchnamelist = watchnamelist # for clock in bd.clocklist: # clock.start(state) # tell all blocks we're starting a BlockDiagram bd.start(state) state.tlist = [] state.xlist = [] state.plist = [[] for p in state.watchlist] print("run") nok = 0 decimate = 0 noverrun = 0 self.running = True stats = SimpleStats() t0 = time.time() t = 0 while self.running: # evaluate the block diagram te_0 = time.time() bd.schedule_evaluate([], t) # record the ports on the watchlist for i, p in enumerate(state.watchlist): b = p.block output = b.output(t, b.inputs, b._x)[p.port] state.plist[i].append(output) state.tlist.append(t) # check execution time for this sample step te_1 = time.time() dte = te_1 - te_0 stats.update(dte) # compute stats on time to execute the block diagram if samples: if dte > dt: print("x", end="") # overrun else: print(".", end="") sys.stdout.flush() if dte > dt: noverrun += 1 else: nok += 1 # check whether to continue, and pause till next sample time tnow = time.time() - t0 if tnow > T: break t += dt # time of next sample t_sleep = t - tnow if t_sleep < 0: # be tolerant to a sample overrun t_sleep = 0 time.sleep(t_sleep) # sleep till next tick # save buffered data in a Struct out = BDStruct(name="results") out.t = np.array(state.tlist) # out.x = np.array(state.xlist) # out.xnames = bd.statenames # save the watchlist into variables named y0, y1 etc. for i, p in enumerate(watchlist): out["y" + str(i)] = np.array(state.plist[i]) out.ynames = watchnamelist if noverrun > 0: print(fg("red")) else: print(fg("yellow")) print("run time performance:") print(f" overrun {noverrun} / {nok} ({noverrun/(nok+noverrun)*100:.1f}%)") print(f" t_max {stats.max*1000:.1f} ms") print(f" t_mean {stats.mean*1000:.1f} ms") print(f" t_sdev {stats.sdev*1000:.1f} ms") print(f" t_max / dt {stats.max/dt*100:.1f}%") print(attr(0)) return out # assert bd.compiled, "Network has not been compiled" # state = BDRealTimeState() # self.state = state # # process the watchlist # # elements can be: # # - block or Plug reference # # - str in the form BLOCKNAME[PORT] # watchlist = [] # watchnamelist = [] # re_block = re.compile(r"(?P[^[]+)(\[(?P[0-9]+)\])") # for w in watch: # if isinstance(w, str): # # a name was given, with optional port number # m = re_block.match(w) # if m is None: # raise ValueError("watch block[port] not found: " + w) # name = m.group("name") # port = int(m.group("port")) # b = bd.blocknames[name] # plug = b[port] # elif isinstance(w, Block): # # a block was given, defaults to port 0 # plug = w[0] # elif isinstance(w, Plug): # # a plug was given # plug = w # watchlist.append(plug) # watchnamelist.append(str(plug)) # state.watchlist = watchlist # state.watchnamelist = watchnamelist # for clock in bd.clocklist: # clock.start(state) # state.tlist = [] # state.xlist = [] # state.plist = [[] for p in state.watchlist] # print("run") # t0 = time.time() # stop = t0 # tmax = 0 # nok = 0 # n = 0 # tsum = 0 # tsum2 = 0 # tmax = 0 # noverrun = 0 # self.running = True # while self.running: # tnext, sources = self.state.eventq.pop() # if tnext is None: # print("E", end="") # time.sleep(0.02) # continue # if tnext > T: # break # # print('dequeue', t) # stop = t0 + tnext # ts = time.time() # sleep_time = stop - ts # if sleep_time > 0: # # print('sleeping for', sleep_time) # time.sleep(sleep_time) # tmax = max(tmax, time.time() - ts) # # if tmax > 0.2: # # print(tmax, sleep_time) # print(".", end="") # nok += 1 # else: # # print('timer overrun') # print("x", end="") # noverrun += 1 # # self.t = t # # self.clocks = clocks # # self.sem.release() # # evaluate the block diagram # te_0 = time.time() # bd.evaluate_plan([], tnext) # te_1 = time.time() # dt = te_1 - te_0 # n += 1 # tsum += dt # tsum2 += dt * dt # tmax = max(tmax, dt) # # visit all the blocks and clocks that have an event now # for source in sources: # # if isinstance(source, Clock): # # # clock ticked, save its state # # clock.savestate(tnext) # source.next_event(self.state) # # visit all the blocks and clocks that have an event now # for source in sources: # if isinstance(source, Clock): # # clock ticked, save its state # clock.savestate(tnext) # clock.next_event(self.state) # # get the new state # clock._x = clock.getstate() # # stash the results # state.tlist.append(tnext) # # record the ports on the watchlist # for i, p in enumerate(state.watchlist): # state.plist[i].append(p.block.output(tnext)[p.port]) # sys.stdout.flush() # # save buffered data in a Struct # out = BDStruct(name="results") # # out.t = np.array(state.tlist) # # out.x = np.array(state.xlist) # # out.xnames = bd.statenames # # save clocked states # for c in bd.clocklist: # name = c.name.replace(".", "") # clockdata = BDStruct(name) # clockdata.t = np.array(c.t) # clockdata.x = np.array(c.x) # out.add(name, clockdata) # # save the watchlist into variables named y0, y1 etc. # for i, p in enumerate(watchlist): # out["y" + str(i)] = np.array(state.plist[i]) # out.ynames = watchnamelist # print(fg("yellow")) # print(f"tmax {tmax}") # print(f"n ok {nok} ({nok/(nok+noverrun)*100:.1f}%)") # print(f"n overrun {noverrun} ({noverrun/(nok+noverrun)*100:.1f}%)") # print(f"t mean {tsum/n*1000:.1f} ms") # print(f"t sdev {math.sqrt((tsum2 - tsum**2/n)/(n-1)*1000):.1f} ms") # print(f"t max {tmax*1000:.1f} ms") # print(attr(0)) # return out # self.state.eventq.start() # n = 0 # tsum = 0 # tsum2 = 0 # tmax = 0 # while True: # t, clocks = self.state.eventq.wait() # # print('run wakes up', t, clocks) # state.t = t # if t > T: # break # # evaluate the block diagram # t0 = time.time() # bd.evaluate_plan([], t) # t1 = time.time() # # visit all the blocks and clocks that have an event now # for clock in clocks: # # if isinstance(source, Clock): # # # clock ticked, save its state # # clock.savestate(tnext) # clock.next_event(self.state) # # update some stats about block diagram execution time # dt = t1 - t0 # n += 1 # tsum += dt # tsum2 += dt*dt # tmax = max(tmax, dt) # self.state.eventq.stop() # print(fg('yellow')) # print(f't mean {tsum/n*1000:.1f} ms') # print(f't sdev {math.sqrt((tsum2 - tsum**2/n)/(n-1)*1000):.1f} ms') # print(f't max {tmax*1000:.1f} ms') # print(attr(0)) ================================================ FILE: bdsim/run_sim.py ================================================ import os from pathlib import Path import sys import importlib import traceback as tb import inspect from collections import Counter, namedtuple import argparse import types import warnings import time from bdsim.blockdiagram import BlockDiagram from bdsim.components import OptionsBase, Block, Clock, BDStruct, Plug, clocklist import spatialmath.base as smb import tempfile import subprocess import webbrowser import traceback import numpy as np import scipy.integrate as integrate import matplotlib.pyplot as plt import re from colored import fg, attr try: from progress.bar import FillingCirclesBar _FillingCirclesBar = True except ImportError: _FillingCirclesBar = False class Progress: # print a progress bar # https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console @staticmethod def printProgressBar( fraction, prefix="", suffix="", decimals=1, length=50, fill="█", printEnd="\r" ): percent = ("{0:." + str(decimals) + "f}").format(fraction * 100) filledLength = int(length * fraction) bar = fill * filledLength + "-" * (length - filledLength) print(f"\r{prefix} |{bar}| {percent}% {suffix}", end=printEnd) def __init__(self, enable=True): self.enable = enable self.length = 60 if not enable: return def start(self, T): self.T = T if not self.enable: return if _FillingCirclesBar: self.bar = FillingCirclesBar( "bdsim", max=100, suffix="%(percent).1f%% - %(eta)ds" ) else: self.printProgressBar( 0, prefix="Progress:", suffix="complete", length=self.length ) def end(self): """ Clean up progress bar """ if not self.enable: return if _FillingCirclesBar: self.bar.finish() else: print("\r" + " " * (self.length + 20) + "\r") def update(self, t): """ Update progress bar :param t: current simulation time, defaults to None :type t: float, optional Update progress bar as a percentage of the maximum simulation time, given as an argument to ``run``. :seealso: :meth:`run` :meth:`progress_done` """ if not self.enable: return if _FillingCirclesBar: self.bar.goto(round(t / self.T * 100)) else: self.printProgressBar( t / self.T, prefix="Progress:", suffix="complete", length=self.length ) class TimeQ: """ Time-ordered queue for events The list comprises tuples of (time, block) to reflect an event associated with the specified block at the specified time. The list is not ordered, and is sorted on a pop event. """ def __init__(self): self.q = [] self.dirty = False def __len__(self): """ Length of time-ordered queue :return: number of items in the queue :rtype: int """ return len(self.q) def __str__(self): """ String representation of time-ordered queue :return: show length and first item :rtype: str """ if len(self) == 0: return f"TimeQ: len={len(self)}" else: return f"TimeQ: len={len(self)}, first out {self.q[0]}" def __repr__(self): events = [] for t in self.q: events.append(str(t)) return "\n".join(events) def push(self, value): """ Push value onto time-ordered queue :param value: tuple (time, block) :type value: tuple Push a block and a time onto the queue. """ self.q.append(value) self.dirty = True def pop(self, dt=0): """ Pop nearest items from the time-ordered queue :param dt: time window, defaults to 0 :type dt: float, optional :return: time of first block in queue and a list of blocks within the time window :rtype: float, list The next block is popped from the queue and all blocks in the time window, that occur no more than ``dt`` later, are also popped. """ if len(self) == 0: return None, [] if self.dirty: self.q.sort(key=lambda x: x[0]) self.dirty = False qfirst = self.q.pop(0) t = qfirst[0] blocks = [qfirst[1]] while len(self.q) > 0 and self.q[0][0] < (t + dt): blocks.append(self.q.pop(0)[1]) return t, blocks def pop_until(self, t): """ Pop nearest items from time-ordered queue :param t: time :type t: float :return: list of blocks remaining sorted by receding time :rtype: list Pops all items with time less than or equal to ``t``. """ if len(self) == 0: return [] if self.dirty: self.q.sort(key=lambda x: x[0]) self.dirty = False i = 0 while True: if self.q[i][0] > t: out = self.q[:i] self.q = self.q[i:] return out i += 1 # convert class name to BLOCK name # strip underscores and capitalize def blockname(name): return name.upper() class BDSimState: """ :ivar x: state vector :vartype x: np.ndarray :ivar T: maximum simulation time (seconds) :vartype T: float :ivar t: current simulation time (seconds) :vartype t: float :ivar fignum: number of next matplotlib figure to create :vartype fignum: int :ivar stop: reference to block wanting to stop simulation, else None :vartype stop: Block subclass :ivar checkfinite: halt simulation if any wire has inf or nan :vartype checkfinite: bool :ivar graphics: enable graphics :vartype graphics: bool """ def __init__(self): self.x = None # continuous state vector numpy.ndarray self.T = None # maximum.BlockDiagram time self.t = None # current time self.fignum = 0 self.stop = None self.checkfinite = True self.debugger = True self.t_stop = None # time-based breakpoint self.eventq = TimeQ() def declare_event(self, block, t): self.eventq.push((t, block)) class BDSim: _blocklibrary = None def __init__(self, banner=True, packages=None, load=True, toolboxes=True, **kwargs): """ :param banner: display docstring banner, defaults to True :type banner: bool, optional :param packages: colon-separated list of folders to search for blocks :type packages: str :param load: dynamically load blocks from libraries, defaults to True :type load: bool,optional :param sysargs: process options from sys.argv, defaults to True :type sysargs: bool, optional :param graphics: enable graphics, defaults to True :type graphics: bool, optional :param animation: enable animation, defaults to False :type animation: bool, optional :param progress: enable progress bar, defaults to True :type progress: bool, optional :param debug: debug options, defaults to None :type debug: str, optional :param backend: matplotlib backend, defaults to 'Qt5Agg'' :type backend: str, optional :param tiles: figure tile layout on monitor, defaults to '3x4' :type tiles: str, optional :raises ImportError: syntax error in block :return: parent object for blockdiagram simulation :rtype: BDSim If ``sysargs`` is True, process command line arguments and passed options. Command line arguments have precedence. =================== ========= ======== =========================================== Command line switch Argument Default Behaviour =================== ========= ======== =========================================== --graphics, +g graphics True enable graphical display --animation, +a animation True update graphics at each time step --hold, +h hold True hold graphics in done() --no-graphics, -g graphics True disable graphical display --no-animation, -a animation True don't update graphics at each time step --no-hold, -H hold True do not hold graphics in done() --no-progress, -p progress True do not display simulation progress bar --backend BE backend 'Qt5Agg' matplotlib backend --tiles RxC, -t RxC tiles '3x4' arrangement of figure tiles on the display --shape WxH shape None window size, default matplotlib size --altscreen, +A, altscreen True display plots on second monitor --no-altscreen, -A altscreen True do not display plots on second monitor --debug F, -d F debug '' debug flag string --simtime T[,dt] simtime (10,) simulation time --verbose, -v verbose False be verbose --quiet, -q quiet False suppress reports -o outfile None output pickled simulation results to bd.out --out OUTFILE outfile None file to save pickled simulation results --set P, -s P setparam [] override block parameter using ``P=block:param=value`` --global G setglob [] override global parameter using ``G=var=value`` =================== ========= ======== =========================================== .. note:: ``animation`` and ``graphics`` options are coupled. If ``graphics=False``, all graphics is suppressed. If ``graphics=True`` then graphics are shown and the behaviour depends on ``animation``. ``animation=False`` shows graphs at the end of the simulation, while ``animation=True` will animate the graphs during simulation. :seealso: :meth:`set_globals()` """ self.packages = packages # process command line and overall options self.options = Options(**kwargs) # print docstring as a startup banner if banner and not self.options.quiet: calling_frame = inspect.currentframe().f_back try: doc = calling_frame.f_locals["__doc__"] if doc is not None: for line in doc.strip().split("\n"): print("* " + line) except KeyError: pass # load modules from the blocks folder if BDSim._blocklibrary is None and load: BDSim._blocklibrary = self.load_blocks( self.options.verbose, toolboxes=toolboxes ) if self.options.blocks: self.blocks() def blockinfo(self, block=None): """Return info about all blocks :param block: name of block to return info for, otherwise list of info for all :type block: str, optional :returns: parameters of blocks :rtype: dict or list of dicts Detailed metadata about a block is obtained by introspection and parsing the block's docstring. ========== ===================================================== Key Description ========== ===================================================== path Path to the folder containing block definition classname Name of class url URL of online documentation class Reference to the class module Name of the module package.blocks.module package Name of the package, eg. bdsim, roboticstoolbox params Dict of (type, descrip), indexed by parameter name inputs List of names of block inputs outputs List of names of block outputs nin Number of inputs, -1 if variable nout Number of outputs, -1 if variable blockclass Block class, eg. source, sink etc. ========== ===================================================== """ if block is None: return self._blocklibrary else: return self._blocklibrary[block] def __str__(self): """ String representation of simulation :return: single line summary of simulation environment :rtype: str """ s = f"BDSim: {len(self._blocklibrary)} blocks in library\n" return s def __repr__(self): s = ( f"Block diagram simulation runtime, {len(self._blocklibrary)} blocks" " imported to library.\n" ) s += "simulation options:\n" for k, v in self.state.options.items(): s += " {:s}: {}\n".format(k, v) return s def run( self, bd, T=5, dt=None, solver="RK45", solver_args={}, debug="", block=None, checkfinite=True, minstepsize=1e-12, watch=[], ): """ Run the block diagram :param T: maximum integration time, defaults to 10.0 :type T: float, optional :param dt: maximum time step :type dt: float, optional :param solver: integration method, defaults to ``RK45`` :type solver: str, optional :param block: matplotlib block at end of run, default False :type block: bool :param checkfinite: error if inf or nan on any wire, default True :type checkfinite: bool :param minstepsize: minimum step length, default 1e-6 :type minstepsize: float :param watch: list of input ports to log :type watch: list :param solver_args: arguments passed to ``scipy.integrate`` :type solver_args: dict :return: time history of signals and states :rtype: Sim class Assumes that the network has been compiled. The system is simulated from time 0 to ``T``. The integration step time ``dt`` defaults to ``T/100`` but can be specified. Finer control can be achieved using ``max_step`` and ``first_step`` parameters to the underlying integrator using the ``solver_args`` parameter. Results are returned in a class with attributes: - ``t`` the time vector: ndarray, shape=(M,) - ``x`` is the state vector: ndarray, shape=(M,N) - ``xnames`` is a list of the names of the states corresponding to columns of `x`, eg. "plant.x0", defined for the block using the ``snames`` argument - ``yN`` for a watched input where N is the index of the port mentioned in the ``watch`` argument - ``ynames`` is a list of the names of the input ports being watched, same order as in ``watch`` argument If there are no dynamic elements in the diagram, ie. no states, then ``x`` and ``xnames`` are not present. The ``watch`` argument is a list of one or more input ports whose value during simulation will be recorded. The elements of the list can be: - a ``Block`` reference, which is interpretted as input port 0 - a ``Plug`` reference, ie. a block with an index or attribute - a string of the form "block[i]" which is port i of the block named block. The debug string comprises single letter flags: - 'p' debug network value propagation - 's' debug state vector - 'd' debug state derivative .. note:: Simulation stops if the step time falls below ``minsteplength`` which typically indicates that the solver is struggling with a very harsh non-linearity. """ assert bd.compiled, "Network has not been compiled" # get simulation time # --simtime=T or --simtime=T,dt if self.options.simtime is not None: try: default_times = eval(self.options.simtime) if isinstance(default_times, (int, float)): T = default_times elif isinstance(default_times, tuple): T, dt = default_times else: raise ValueError( "bad simtime option passed " + self.options.simtime ) except: raise ValueError("bad simtime option passed " + self.options.simtime) # final default values # T = T or 5 # dt = dt or 0.01 simstate = BDSimState() self.simstate = simstate simstate.T = T if dt is None and not "max_step" in solver_args: dt = T / 100 simstate.dt = dt simstate.count = 0 simstate.bdtime = 0.0 simstate.gtime = 0.0 # last graphics update simstate.solver = solver simstate.solver_args = solver_args simstate.minstepsize = minstepsize simstate.stop = None # allow any block to stop.BlockDiagram by setting this to the block's name simstate.checkfinite = checkfinite # state.options = copy.copy(self.options) simstate.options = self.options self.bd = bd simstate.t_stop = None if debug: # append debug flags if debug not in simstate.options.debug: simstate.options.debug += debug # turn off progress bar if any debug options are given if len(simstate.options.debug) > 0: self.options.progress = False if block is not None: self.options.hold = block # process the watchlist # elements can be: # - block or Plug reference # - str in the form BLOCKNAME[PORT] watchlist = [] watchnamelist = [] re_block = re.compile(r"(?P[^[]+)(\[(?P[0-9]+)\])?") for w in watch: if isinstance(w, str): # a name was given, with optional port number m = re_block.match(w) if m is None: raise ValueError("watch block[port] not found: " + w) name = m.group("name") # get optional port number port = m.group("port") if port is None: port = 0 else: port = int(port) b = bd.blocknames[name] plug = b[port] elif isinstance(w, Block): # a block was given, defaults to port 0 plug = w[0] elif isinstance(w, Plug): # a plug was given plug = w watchlist.append(plug) watchnamelist.append(str(plug)) simstate.watchlist = watchlist simstate.watchnamelist = watchnamelist x0 = bd.getstate0() if not self.options.quiet: print(fg("yellow")) print(f">>> Start simulation: T = {T}, dt = {dt}") print(f" Continuous state variables: {bd.nstates}") print(" x0 = ", x0) print(f" Discrete state variables: {bd.ndstates}") # get the number of discrete states from all clocks ndstates = 0 for clock in bd.clocklist: nds = 0 for b in clock.blocklist: nds += b.ndstates ndstates += nds if not self.options.quiet: print(f" {clock.name}: x0 = ", clock.getstate0()) if not self.options.quiet: print(attr(0)) # update block parameters given on command line self.update_parameters(bd) # tell all blocks we're starting a BlockDiagram self.bd.start(simstate) # initialize list of time and states simstate.tlist = [] simstate.xlist = [] simstate.plist = [[] for p in simstate.watchlist] self.progress = Progress(enable=self.options.progress) self.progress.start(T) if len(simstate.eventq) == 0: # no simulation events, solve it in one go self.run_interval(bd, 0, T, x0, simstate=simstate) nintervals = 1 else: # we have simulation events, solve it in chunks simstate.declare_event(None, T) # add an event at end of simulation # ignore all the events at zero tprev = 0 simstate.eventq.pop_until(tprev) # get the state vector x = x0 nintervals = 0 while True: # get next event from the queue and the list of blocks or # clocks at that time tnext, sources = simstate.eventq.pop(dt=1e-6) if tnext is None: break # run system until next event time x = self.run_interval(bd, tprev, tnext, x, simstate=simstate) nintervals += 1 # visit all the blocks and clocks that have an event now for source in sources: if isinstance(source, Clock): # clock ticked, save its state source.savestate(tnext) source.next_event(self.simstate) # get the new state source._x = source.getstate(tnext) tprev = tnext # are we done? if simstate.t is not None and simstate.t >= T: break # finished integration self.progress.end() # cleanup the progress bar # print some info about the integration if not self.options.quiet: print(fg("yellow")) print("<<< Simulation complete") print(f" block diagram evaluations: {simstate.count}") print( " block diagram exec time: " f" {simstate.bdtime / simstate.count * 1000.0:.3f} ms" ) print(f" time steps: {len(simstate.tlist)}") print(f" integration intervals: {nintervals}") print(attr(0)) # save buffered data in a Struct out = BDStruct(name="results") out.t = np.array(simstate.tlist) out.x = np.array(simstate.xlist) out.xnames = bd.statenames # save clocked states for c in bd.clocklist: name = c.name.replace(".", "") clockdata = BDStruct(name) clockdata.t = np.array(c.t) clockdata.x = np.array(c.x) out.add(name, clockdata) # save the watchlist into variables named y0, y1 etc. for i, p in enumerate(watchlist): out["y" + str(i)] = np.array(simstate.plist[i]) out.ynames = watchnamelist # the command line options -o or --out saves results as a pickle file # -o defaults to bd.out # --out FILE allows the filename to be specified # # we can visualize the output file by # # % python -mpickle bd.out # t = ndarray:float64 (123,) # x = ndarray:float64 (123, 1) # xnames = ['plantx0'] (list) # ynames = [] (list) if self.options.outfile is not None: out.dump(self.options.outfile) if not self.options.quiet: print("simulation results pickled --> ", self.options.outfile) # pause until all graphics blocks close if self.options.graphics and self.options.hold: self.done(self.bd, block=self.options.hold) return out def update_parameters(self, bd): """ Set value of parameters according to command line arguments Command line arguments of the form: ``-s block:param=value`` ``--set block:param=value`` are stored as list items in ``options.setparam`` ``block`` can be either: - the block's name as a string, either user assigned or bdsim assigned - the block ``id`` as displayed by the ``report`` method ``param`` is the name of the parameter used in the constructor ``value`` is the new value of the variable """ re_set = re.compile(r"(?P[\w\.]+):(?P[\w]+)=(?P.*)") for s in self.options.setparam: m = re_set.match(s) if m is None: raise ValueError("bad set parameter: " + s) # get block reference blockname = m["block"] try: blockname = int(blockname) except ValueError: pass block = bd[blockname] param = m["param"] try: prev_value = getattr(block, param) except ValueError: raise ValueError(f"block {block.name} has no parameter '{param}'") # get the parameter value = m["value"] new_value = None try: if ";" in value: new_value = smb.str2array(value) else: try: new_value = int(value) except ValueError: new_value = float(value) except ValueError: raise ValueError("cannot parse value " + value) # change the value setattr(block, param, new_value) print( f"changed value of {block.name}:{param} from {prev_value} ->" f" {new_value}" ) def run_interval(self, bd, t0, T, x0, simstate=None): """ Integrate system over interval :param bd: the system blockdiagram :type bd: BlockDiagram :param t0: initial time :type t0: float :param tf: final time :type tf: float :param x0: initial state vector :type x0: ndarray(n) :param simstate: simulation state object :type simstate: SimState :return: final state vector xf :rtype: ndarray(n) The system is integrated from from ``x0`` to ``xf`` over the interval ``t0`` to ``tf``. """ try: if bd.nstates > 0: # system has continuous states, solve it using numerical integration # print('initial state x0 = ', x0) # block diagram contains states, solve it using numerical integration scipy_integrator = integrate.__dict__[ simstate.solver ] # get user specified integrator def ydot(t, y): simstate.t = t simstate.count += 1 t0 = time.time() yd = bd.schedule_evaluate(y, t, sinks=False, simstate=simstate) t1 = time.time() simstate.bdtime += t1 - t0 return yd if simstate.dt is not None: simstate.solver_args["max_step"] = simstate.dt # print(f"run interval: from {t0} to {t0+T}, args={state.solver_args}, x0={x0}") integrator = scipy_integrator( ydot, t0=t0, y0=x0, t_bound=T, **simstate.solver_args ) # integrate while integrator.status == "running": # step the integrator, calls _deriv and evaluate block diagram multiple times message = integrator.step() if integrator.status == "failed": print( fg("red") + f"\nintegration completed with failed status: {message}" + attr(0) ) break # stash the results simstate.t = integrator.t simstate.tlist.append(integrator.t) simstate.xlist.append(integrator.y) # record the ports on the watchlist for i, p in enumerate(simstate.watchlist): b = p.block out = b.output(integrator.t, b.inputs, b._x)[p.port] simstate.plist[i].append(out) # update all blocks that need to know if (integrator.t - simstate.gtime) > (simstate.T / 200): bd.step(integrator.t) simstate.gtime = integrator.t # bd.step(integrator.t) self.progress.update(simstate.t) # update the progress bar if integrator.status == "finished": break # has any block called a stop? if simstate.stop is not None: print( fg("red") + f"\n--- stop requested at t={simstate.t:.4f} by" f" {simstate.stop}" + attr(0) ) break if ( simstate.minstepsize is not None and integrator.step_size < simstate.minstepsize ): print( fg("red") + "\n--- stopping on minimum step size at" f" t={simstate.t:.4f} with last stepsize" f" {integrator.step_size:g}" + attr(0) ) break if "i" in simstate.options.debug: bd._debugger(simstate, integrator) return integrator.y # return final state vector elif len(clocklist) == 0: # block diagram has no continuous or discrete states assert simstate.dt is not None, "if no states must specify dt" for t in np.arange(t0, T, simstate.dt): # step through the time range # evaluate the block diagram simstate.t = t simstate.count += 1 t0 = time.time() bd.schedule_evaluate([], t) t1 = time.time() simstate.bdtime += t1 - t0 # stash the results simstate.tlist.append(t) # record the ports on the watchlist for i, p in enumerate(simstate.watchlist): b = p.block out = b.output(integrator.t, b.inputs, b._x)[p.port] simstate.plist[i].append(out) # update all blocks that need to know bd.step(t) self.progress.update(t) # update the progress bar # has any block called a stop? if simstate.stop is not None: print( fg("red") + f"\n--- stop requested at t={simstate.t:.4f} by" f" {simstate.stop}" + attr(0) ) break if "i" in simstate.options.debug: bd._debugger(simstate, integrator) else: # block diagram has no continuous states t = t0 simstate.t = t # evaluate the block diagram simstate.count += 1 t0 = time.time() bd.schedule_evaluate([], t) t1 = time.time() simstate.bdtime += t1 - t0 # stash the results simstate.tlist.append(t) # record the ports on the watchlist for i, p in enumerate(simstate.watchlist): b = p.block out = b.output(integrator.t, b.inputs, b._x)[p.port] simstate.plist[i].append(out) # update all blocks that need to know if (t - simstate.gtime) > (simstate.T / 200): bd.step(t) simstate.gtime = t # bd.step(t) self.progress.update(simstate.t) # update the progress bar # has any block called a stop? if simstate.stop is not None: print( fg("red") + f"\n--- stop requested at t={simstate.t:.4f} by" f" {simstate.stop}" + attr(0) ) if "i" in simstate.options.debug: bd._debugger(simstate) except RuntimeError as err: # bad things happens, print a message and return no result print("unrecoverable error in evaluation: ", err) raise def blockdiagram(self, name="main") -> BlockDiagram: """ Instantiate a new block diagram object. :param name: diagram name, defaults to 'main' :type name: str, optional :return: parent object for blockdiagram :rtype: BlockDiagram This object describes the connectivity of a set of blocks and wires. It is an instantiation of the ``BlockDiagram`` class with a factory method for every dynamically loaded block which returns an instance of the block. These factory methods have names which are all upper case, for example, the method ``.GAIN`` invokes the constructor for the ``Gain`` class. :seealso: :func:`BlockDiagram` """ # instantiate a new blockdiagram bd = BlockDiagram(name=name) def new_method(cls, bd): # return a wrapper for the block constructor that automatically # adds the block to the diagram's blocklist def block_init_wrapper(self, *args, **kwargs): block = cls(*args, bd=bd, **kwargs) # call __init__ on the block return block # return a function that invokes the class constructor f = block_init_wrapper # move the __init__ docstring to the class to allow BLOCK.__doc__ f.__doc__ = cls.__init__.__doc__ return f # bind the block constructors as new methods on this instance self.blockdict = {} for blockname, info in self._blocklibrary.items(): # create a function to invoke the block's constructor f = new_method(info["class"], bd) # set a bound version of this function as an attribute of the instance # method = types.MethodType(new_method, bd) # setattr(bd, block.name, method) setattr(bd, blockname, f.__get__(self)) # add a clone of the options # bd.options = copy.copy(self.options) bd.runtime = self return bd def DEBUG(self, debug, fmt, *args): if debug[0] in self.options.debug: print(f"DEBUG.{debug:s}: " + fmt.format(*args)) def done(self, bd, block=False): if self.options.hold: block = self.options.hold try: plt.show(block=block) except KeyboardInterrupt: print("bdsim: closing all windows") plt.close("all") # sys.exit(1) # not sure why we have this return bd.done() plt.close("all") plt.pause(0.5) # let the event handler do its work def closefigs(self): for i in range(self.simstate.fignum): print("close", i + 1) plt.close(i + 1) plt.pause(0.1) self.simstate.fignum = 0 # reset figure counter def savefig(self, block, filename=None, format="pdf", **kwargs): block.savefig(filename=filename, format=format, **kwargs) def savefigs(self, bd, format="pdf", **kwargs): from bdsim.graphics import GraphicsBlock for b in bd.blocklist: if isinstance(b, GraphicsBlock): b.savefig(filename=b.name, format=format, **kwargs) def showgraph(self, bd, **kwargs): # create the temporary dotfile dotfile = tempfile.TemporaryFile(mode="w") bd.dotfile(dotfile, **kwargs) # rewind the dot file, create PDF file in the filesystem, run dot dotfile.seek(0) pdffile = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) subprocess.run("dot -Tpdf", shell=True, stdin=dotfile, stdout=pdffile) # open the PDF file in browser (hopefully portable), then cleanup webbrowser.open(f"file://{pdffile.name}") os.remove(pdffile.name) def fatal(self, message, retval=1): """ Fatal simulation error :param message: Error message :type message: str :param retval: system return value (*nix only) defaults to 1 :type retval: int, optional Display the error message then terminate the process. For operating systems that support it, return an integer code. """ # TODO print text in some color print(message) sys.exit(retval) def load_blocks(self, verbose=True, toolboxes=True): """ Dynamically load all block definitions. :raises ImportError: module could not be imported :return: dictionary of block metadata :rtype: dict of dict Reads blocks from .py files found in bdsim/bdsim/blocks, folders given by colon separated list in envariable BDSIMPATH, and the command line option ``packages``. The result is a dict indexed by the upper-case block name with elements: - ``path`` to the folder holding the Python file defining the block - ``classname`` - ``blockname``, upper case version of ``classname`` - ``url`` of online documentation for the block - ``package`` containing the block - `doc` is the docstring from the class constructor """ def parse_docstring(ds): # this should have two versions: sphinx, numpy doc styles import re from collections import OrderedDict re_isfield = re.compile(r"\s*:[a-zA-Zα-ωΑ-Ω0-9_ ]+:") re_field = re.compile( r"^\s*:(?P[a-zA-Z]+)(?:" r" +(?P[a-zA-Zα-ωΑ-Ω0-9_]+))?:(?P.+)$" ) # a-zA-Zα-ωΑ-Ω0-9_ def indent(s): return len(s) - len(s.lstrip()) fieldnames = ("param", "type", "input", "output") excludevars = ("kwargs", "inputs") # parse out all lines of the form: # # :field var: body # or # :field var: body with a very long description that # carries over to another line or two fieldlines = [] for para in ds.split("\n\n"): # print(para) # print('--') indent_prev = None infield = False for line in para.split("\n"): if len(line) == 0: continue if indent_prev is None: indent_prev = indent(line) if re_isfield.match(line) is not None: fieldlines.append(line.lstrip()) infield = True if indent(line) > indent_prev and infield: fieldlines[-1] += " " + line.lstrip() if indent(line) == indent_prev: infield = False # fieldlines is a list of lines of the form # # :field var: body # # where extension lines have been concatenated # create a dict of dicts # # dict[field][var] -> body dict = OrderedDict() for line in fieldlines: m = re_field.match(line) if m is not None: field, var, body = m.groups() if var in excludevars or field not in fieldnames: continue if field not in dict: dict[field] = {var: body} else: dict[field][var] = body dict[m.group("field")] # now connect pairs of lines of the form # # :param X: param description # :type X: type description # # params[X] = (type description, param description) params = {} if "param" in dict: for var, descrip in dict["param"].items(): typ = dict["type"].get(var, None) params[var] = (typ, descrip) return params block = namedtuple("block", "name, cls, path") if toolboxes: packages = [ "bdsim", "roboticstoolbox", "machinevisiontoolbox", ] else: packages = ["bdsim"] env = os.getenv("BDSIMPATH") if env is not None: packages += env.split(":") if self.packages is not None: packages += self.packages.split(":") blocks = {} moduledicts = {} for package in packages: try: spec = importlib.util.find_spec(".blocks", package=package) except ModuleNotFoundError as err: print( f"package {package} not loaded: not found, not a proper package, no blocks module" ) continue if spec is None: print(f"package {package} not found or has no blocks module") continue try: pkg = spec.loader.load_module() except Exception as err: print(f"package {package} contains a compile error") exc = sys.exception() print(fg("red")) tb.print_exception(exc, limit=-4) print(attr(0)) continue # except ImportError: # print(f"package {package} load error, continuing") # import textwrap # print(textwrap.indent(traceback.format_exc(), " ")) # continue moduledict = {} for name, value in pkg.__dict__.items(): # check if it's a valid block class if not inspect.isclass(value): continue if Block not in inspect.getmro(value): continue if name.endswith("Block"): continue if value.blockclass in ("source", "transfer", "function"): # must have an output function valid = ( hasattr(value, "output") and callable(value.output) and len(inspect.signature(value.output).parameters) == 4 ) if not valid: print( "block {:s} has missing/improper output method".format( value.__name__ ) ) continue if value.blockclass == "sink": # must have a step function with at least one # parameter: step(self [,state]) valid = ( hasattr(value, "step") and callable(value.step) and len(inspect.signature(value.step).parameters) == 3 ) if not valid: print( "block {:s} has missing/improper step method".format( value.__name__ ) ) continue # add it to the dict of blocks indexed by module if value.__module__ in moduledict: moduledict[value.__module__].append(name) else: moduledict[value.__module__] = [name] # create a dict for the block with metadata block_info = {} block_info["path"] = ( pkg.__path__ ) # path to folder holding block definition block_info["classname"] = name block_info["blockname"] = blockname(name) try: block_info["url"] = ( pkg.__dict__["url"] + "#" + block.__module__ + "." + name ) except KeyError: block_info["url"] = None block_info["class"] = value block_info["module"] = value.__module__ block_info["package"] = package # get the docstring from the class and the constructor ds = "" if value.__doc__ is not None: ds += value.__doc__ if value.__init__.__doc__ is not None: ds += value.__init__.__doc__ if ds is None: raise ValueError("block has no docstring") block_info["doc"] = ds param_dict = parse_docstring(ds) block_info["params"] = param_dict # now add all the other stuff we know about the block block_info["inputs"] = param_dict.get("input") block_info["outputs"] = param_dict.get("output") block_info["nin"] = value.nin block_info["nout"] = value.nout block_info["blockclass"] = value.__base__.__name__.lower().replace( "block", "" ) blocks[blockname(name)] = block_info moduledicts[package] = moduledict self.moduledicts = moduledicts return blocks def blocks(self): """ List all loaded blocks. Example:: 73 blocks loaded bdsim.blocks.functions..................: Sum Prod Gain Clip Function Interpolate bdsim.blocks.sources....................: Constant Time WaveForm Piecewise Step Ramp bdsim.blocks.sinks......................: Print Stop Null Watch bdsim.blocks.transfers..................: Integrator PoseIntegrator LTI_SS LTI_SISO bdsim.blocks.discrete...................: ZOH DIntegrator DPoseIntegrator bdsim.blocks.linalg.....................: Inverse Transpose Norm Flatten Slice2 Slice1 Det Cond bdsim.blocks.displays...................: Scope ScopeXY ScopeXY1 bdsim.blocks.connections................: Item Dict Mux DeMux Index SubSystem InPort OutPort roboticstoolbox.blocks.arm..............: FKine IKine Jacobian Tr2Delta Delta2Tr Point2Tr TR2T FDyn IDyn Gravload ........................................: Inertia Inertia_X FDyn_X ArmPlot Traj JTraj LSPB CTraj CirclePath roboticstoolbox.blocks.mobile...........: Bicycle Unicycle DiffSteer VehiclePlot roboticstoolbox.blocks.uav..............: MultiRotor MultiRotorMixer MultiRotorPlot machinevisiontoolbox.blocks.camera......: Camera Visjac_p EstPose_p ImagePlane """ def dots(s, n=40): return s + "." * (n - len(s)) print(len(self._blocklibrary), " blocks loaded") for pkg, dict in self.moduledicts.items(): for k, v in dict.items(): s = "" once = False while len(v) > 0: n = v.pop(0) + " " if len(s + n) < 80: s += n continue else: # line will be too long if not once: print(f"{dots(k)}: {s}") once = True else: print(f"{dots('')}: {s}") s = "" if len(s) > 0: if once: print(f"{dots('')}: {s}") else: print(f"{dots(k)}: {s}") def set_options(self, **options): self.options.set(**options) warnings.warn("use sim.options.OPT=VALUE instead", DeprecationWarning) def set_globals(self, globs): """ Set globals as specified by command line :param globs: global variables :type globs: dict The command line option ``--global var=value`` can be used to request the change of global variables. However, actually changing them requires explicit code support in the user's program after the ``BDSim`` constructor. Example:: sim.set_globals(globals()) Messages are displayed by defaulting, indicating which variables are changed, and their old and new values. """ # handle the globals for s in self.options.setglob: var, value = s.split("=") new_value = eval(value) print(f"changed value of global {var} from {globs[var]} -> {new_value}") globs[var] = new_value def report(self, bd, type="summary", **kwargs): """Print block diagram report :param bd: the block diagram to be reported :type bd: :class:`BlockDiagram` :param type: report type, one of: "summary" (default), "lists", "schedule" :type type: str, optional :param style: table style, one of: ansi (default), markdown, latex :type style: str Single method wrapper for various block diagram reports. Obeys the ``-q`` option to suppress all reports at runtime. :seealso: :meth:`BlockDiagram.report_summary` :meth:`BlockDiagram.report_lists` :meth:`BlockDiagram.report_schedule` """ if self.options.quiet: return if type == "lists": bd.report_lists(**kwargs) elif type == "summary": bd.report_summary(**kwargs) elif type == "schedule": bd.report_schedule(**kwargs) class Options(OptionsBase): def __init__(self, sysargs=True, **options): default_options = { "backend": None, "tiles": "3x4", "graphics": True, "animation": False, "hold": True, "shape": None, "altscreen": True, "progress": True, "verbose": False, "debug": "", "simtime": None, "blocks": False, "outfile": None, "quiet": False, "setparam": [], "setglob": [], } # modify defaults according to envariable BDSIM which is comma/semicolon # separated list of key=value pairs # eg. setenv BDSIM graphics=True,hold=True env = os.getenv("BDSIM") if env is not None: for key_value in env.split(",;"): # for each key=value pair key, value = [s.strip() for s in key_value.split("=")] # attempt an eval, resolves True, False try: value = eval(value) except SyntaxError: pass try: default_options[key] = value except KeyError: print("envariable BDSIM, unknown option", key) if sysargs: # command line arguments and graphics parser = argparse.ArgumentParser( prefix_chars="-+", formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="Block diagram simulation framework", epilog=( "set defaults using environment variable BDSIM as a single string" " containing command line options" ), ) parser.add_argument( "--backend", "-b", type=str, metavar="BACKEND", help="matplotlib backend to choose", ) parser.add_argument( "--tiles", "-t", type=str, metavar="ROWSxCOLS", help="window tiling as NxM", ) parser.add_argument( "--shape", type=str, metavar="WIDTHxHEIGHT", help="window size as WxH, defaults to matplotlib default", ) parser.add_argument( "--blocks", action="store_const", const=True, default=False, dest="blocks", help="Display blocks at startup", ) parser.add_argument( "-g", "--no-graphics", action="store_const", const=False, dest="graphics", help="disable graphic display, also does --no-animation", ) parser.add_argument( "+g", "--graphics", action="store_const", const=True, dest="graphics", help="enable graphic display", ) parser.add_argument( "-a", "--no-animation", action="store_const", const=False, dest="animation", help="do not animate graphics", ) parser.add_argument( "+a", "--animation", action="store_const", const=True, dest="animation", help="animate graphics, also does ++graphics", ) parser.add_argument( "-H", "--no-hold", action="store_const", const=False, dest="hold", help="do not hold graphics in done()", ) parser.add_argument( "+H", "--hold", action="store_const", const=True, dest="hold", help="hold graphics in done()", ) parser.add_argument( "+A", "--altscreen", action="store_const", const=True, dest="altscreen", help="display plots on second monitor", ) parser.add_argument( "-A", "--no-altscreen", action="store_const", const=False, dest="altscreen", help="do not display plots on second monitor", ) parser.add_argument( "--no-progress", "-p", action="store_const", const=False, dest="progress", help="animate graphics", ) parser.add_argument( "--verbose", "-v", action="store_const", const=True, help="debug flags" ) parser.add_argument( "--debug", "-d", type=str, metavar="[psd]", help="debug flags: p/ropagate, s/tate, d/eriv, i/nteractive", ) parser.add_argument( "--simtime", "-S", type=str, help="simulation time: T or T,dt" ) parser.add_argument( "--quiet", "-q", action="store_const", const=True, help="suppress reports", ) parser.add_argument( "-o", action="store_const", const="bd.out", dest="outfile", help="output pickled simulation results to bd.out", ) parser.add_argument( "--out", type=str, dest="outfile", help="file to save pickled simulation results", ) parser.add_argument( "--set", "-s", dest="setparam", action="append", type=str, help="override block parameter using block:param=value", ) parser.add_argument( "--global", dest="setglob", action="append", type=str, help="override global parameter using var=value", ) args, unknownargs = parser.parse_known_args() cmdline_options = vars(args) # get args as a dictionary # keep only the options that are not None, ie. those that were # explicitly set on the command line cmdline_options = { option: value for option, value in cmdline_options.items() if value is not None } if "graphics" in cmdline_options: # -g or +g present if not cmdline_options["graphics"]: # -g then disable animation cmdline_options["animation"] = False elif "animation" in cmdline_options and cmdline_options["animation"]: # +a present cmdline_options["graphics"] = True else: cmdline_options = dict() # empty dictionary super().__init__(readonly=cmdline_options, args=default_options) # now handle the passed options self.set(**options) if self.verbose: print(self) self._argv = unknownargs # save non-bdsim arguments def sanity(self, options): # ensure graphics is enabled if animation is requested # ensure animation is disabled if graphics is disabled if "graphics" in options and "animation" in options: if options["animation"] and not options["graphics"]: raise ValueError("cannot enable animation but disable graphics") elif "graphics" in options and not options["graphics"]: options["animation"] = False elif "animation" in options and options["animation"]: options["graphics"] = True return options ================================================ FILE: bdsim/tk_editor/bdeditor.py ================================================ #!/usr/bin/env python3 import sys import os import string import time import tkinter as tk from screeninfo import get_monitors from tkinter import ttk from edit import Editor import bdsim # eventually from a config file appearance = { "block:width": 100, "block:height": 60, "block:bg": "white", "block:outline": "black", "block:font": ("Helvetica", 12), } def center_on_monitor(window, monitor_index, width, height): """ Centers a Tkinter window on a specific monitor. Args: window: The Tkinter instance (root or Toplevel). monitor_index: Index of the monitor (0 = Primary). width: Desired width of the window. height: Desired height of the window. """ try: monitors = get_monitors() # Safety check: if index is too high, default to primary (0) if monitor_index >= len(monitors): monitor_index = 0 target_monitor = monitors[monitor_index] # 1. Calculate the center position relative to the monitor # (Monitor Width - Window Width) / 2 x_offset = (target_monitor.width - width) // 2 y_offset = (target_monitor.height - height) // 2 # 2. Add the monitor's absolute global position final_x = target_monitor.x + x_offset final_y = target_monitor.y - y_offset # 3. Apply geometry: "WidthxHeight+X+Y" window.geometry(f"{width}x{height}+{final_x}+{final_y}") except Exception as e: print(f"Error centering window: {e}") # Fallback: just set size and let OS decide position window.geometry(f"{width}x{height}") # def pairwise(iterable): # "s -> (s0,s1), (s1,s2), (s2, s3), ..." # a, b = tee(iterable) # next(b, None) # return zip(a, b) def init_diagram(editor): b1 = editor.add_block(1, 1, (100, 100), image_path="Icons/integrator.png") b2 = editor.add_block(0, 1, (200, 100)) b3 = editor.add_block(3, 1, (300, 100)) b4 = editor.add_block(2, 3, (400, 100)) editor.add_wire("block4.out0", "block3.in0") # print(ed.coords(b4.item)) # print(ed.bbox(b4.item)) # print(canvas.bbox(b4.id)) # canvas.create_rectangle(canvas.bbox(b4.id), outline="black", fill="") def create_ui(root, blockmenu): center_on_monitor( root, 1, 1200, 600 ) # set geometry to 800x600 centered on monitor 1 root.title("bdsim editor") # Mac-specific app name setting try: root.tk.call("tk", "appname", "BDSim Editor") except: try: root.wm_class("BDSim Editor", "BDSim Editor") except: pass # Not supported # --------------------------------------------------------------------- # App task bar menubar = tk.Menu(root) root.config(menu=menubar) # File menu file_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="File", menu=file_menu) file_menu.add_command( label="New", accelerator="Cmd+N", command=lambda: print("New") ) file_menu.add_command( label="Open...", accelerator="Cmd+O", command=lambda: print("Open") ) file_menu.add_separator() file_menu.add_command( label="Save", accelerator="Cmd+S", command=lambda: print("Save") ) file_menu.add_command( label="Save As...", accelerator="Shift+Cmd+S", command=lambda: print("Save As") ) file_menu.add_separator() file_menu.add_command( label="Close", accelerator="Cmd+W", command=lambda: root.quit() ) # Edit menu edit_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Edit", menu=edit_menu) edit_menu.add_command( label="Undo", accelerator="Cmd+Z", command=lambda: print("Undo") ) edit_menu.add_command( label="Redo", accelerator="Shift+Cmd+Z", command=lambda: print("Redo") ) edit_menu.add_separator() edit_menu.add_command( label="Cut", accelerator="Cmd+X", command=lambda: print("Cut") ) edit_menu.add_command( label="Copy", accelerator="Cmd+C", command=lambda: print("Copy") ) edit_menu.add_command( label="Paste", accelerator="Cmd+V", command=lambda: print("Paste") ) edit_menu.add_separator() edit_menu.add_command( label="Select All", accelerator="Cmd+A", command=lambda: print("Select All") ) # Block menu block_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Block", menu=block_menu) block_menu.add_command(label="Add Block", command=lambda: print("Add Block")) block_menu.add_command(label="Delete Block", command=lambda: print("Delete Block")) block_menu.add_separator() block_menu.add_command(label="Connect Blocks", command=lambda: print("Connect")) # View menu view_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="View", menu=view_menu) view_menu.add_command( label="Zoom In", accelerator="Cmd+=", command=lambda: print("Zoom In") ) view_menu.add_command( label="Zoom Out", accelerator="Cmd+-", command=lambda: print("Zoom Out") ) view_menu.add_command( label="Actual Size", accelerator="Cmd+0", command=lambda: print("Actual Size") ) view_menu.add_separator() view_menu.add_command(label="Show Grid", command=lambda: print("Toggle Grid")) # Help menu help_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Help", menu=help_menu) help_menu.add_command(label="About BDSim Editor", command=lambda: print("About")) help_menu.add_command(label="Documentation", command=lambda: print("Docs")) # --------------------------------------------------------------------- # Top toolbar across the window toolbar = ttk.Frame(root, padding=(4, 2)) toolbar.pack(side=tk.TOP, fill=tk.X) btn_new = ttk.Button(toolbar, text="New", command=lambda: print("New")) btn_open = ttk.Button(toolbar, text="Open", command=lambda: print("Open")) btn_save = ttk.Button(toolbar, text="Save", command=lambda: print("Save")) btn_add = ttk.Button(toolbar, text="Add Block", command=lambda: print("Add block")) btn_zoom_in = ttk.Button(toolbar, text="Zoom +", command=lambda: print("Zoom in")) btn_zoom_out = ttk.Button(toolbar, text="Zoom -", command=lambda: print("Zoom out")) btn_new.pack(side=tk.LEFT, padx=2) btn_open.pack(side=tk.LEFT, padx=2) btn_save.pack(side=tk.LEFT, padx=2) ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=6) btn_add.pack(side=tk.LEFT, padx=2) ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=6) btn_zoom_in.pack(side=tk.LEFT, padx=2) btn_zoom_out.pack(side=tk.LEFT, padx=2) # --------------------------------------------------------------------- # Main PanedWindow with left Treeview and right Editor canvas panes = ttk.Panedwindow(root, orient=tk.HORIZONTAL) panes.pack(fill="both", expand=True) left = ttk.Frame(panes, width=100) right = ttk.Frame(panes) panes.add(left, weight=0) panes.add(right, weight=4) # --------------- Treeview in left pane tree_frame = ttk.Frame(left) tree_frame.pack(fill="both", expand=True, padx=5, pady=5) tree_scroll = ttk.Scrollbar(tree_frame, orient="vertical") tree = ttk.Treeview(tree_frame, yscrollcommand=tree_scroll.set, show="tree") tree_scroll.config(command=tree.yview) tree_scroll.pack(side="right", fill="y") tree.pack(side="left", fill="both", expand=False) # populate the tree from blockmenu if blockmenu: for cat, items in blockmenu.items(): parent = tree.insert("", "end", text=cat, open=True) for it in items: tree.insert(parent, "end", text=it) # collapse tree categories at startup for iid in tree.get_children(): tree.item(iid, open=False) # Convenience helpers def get_selected_block(): sel = tree.selection() if not sel: return None iid = sel[0] # return block name only for leaf items if tree.parent(iid): return tree.item(iid, "text") return None # --------------- Canvas in right pane canvas = Editor( right, bg="white", height=500, width=1000, relief=tk.RIDGE, background="white", borderwidth=1, ) canvas.pack(fill="both", expand=True, padx=5, pady=5) # Double-click on tree item to add block to canvas def tree_click(event): print("Tree double-click") block = get_selected_block() if block: # is a leaf item tree._last_activated_block = block canvas.event_generate("<>", when="tail", data=tree) tree.bind("", tree_click) return canvas def get_blocks(): sim = bdsim.BDSim() # create simulator blockmenu = {} for block, info in sim._blocklibrary.items(): cls = info["blockclass"].capitalize() if cls in blockmenu: blockmenu[cls].append(block) else: blockmenu[cls] = [block] return blockmenu if __name__ == "__main__": root = tk.Tk() # block_menu = get_blocks() block_menu = None canvas = create_ui(root, block_menu) init_diagram(canvas) root.mainloop() ================================================ FILE: bdsim/tk_editor/edit.py ================================================ import tkinter as tk import numpy as np import re from itertools import tee, pairwise from PIL import Image, ImageTk # from sklearn import tree verbose_events = True blockdict = {} wirelist = [] class Block: blockWidth = 50 # block width blockHeight = 100 # block height a = 20 # distance from top/bottom b = 5 # half width of output portSize = 10 # size of port square portSep = 15 blocknum = 0 # block index: 1, 2, ... def __init__(self, pos, nin, nout): self.nin = nin # number of input ports self.nout = nout # number of output ports self.pos = pos # position of top-left corner on canvas self.tag = None self.inpos = [] self.outpos = [] self.item_id = None # canvas item id self.flipped = False Block.blocknum += 1 self.name = f"block{Block.blocknum}" def port_y(self, i, n): if n == 0: return None else: return self.pos[1] + self.blockHeight / 2 - ((n - 1) / 2 - i) * self.portSep class Wire: wirenum = 0 def __init__(self, start: tuple[Block, int], end: tuple[Block, int]): self.start = start self.end = end self.item_id = None # canvas item id Wire.wirenum += 1 def start_coord(self): block, port = self.start return block.outpos[port] def end_coord(self): block, port = self.end return block.inpos[port] class Editor(tk.Canvas): """ Represents a graphical block element for a block diagram editor. The GBlock class is responsible for creating and managing the visual representation of a block with configurable input and output ports. The block is drawn as a polygon on a canvas, with triangles on the right side for outputs and squares on the left side for inputs. The positions of input and output ports are calculated and stored for later use. Attributes: W (int): Width of the block. H (int): Height of the block. a (int): Vertical margin from the top and bottom edges. b (int): Half-width of the output triangle and input square. _inpos (list): List of input port positions relative to the block. _outpos (list): List of output port positions relative to the block. outline (list): List of points defining the block's outline polygon. id (int): Canvas object ID for the block polygon. Args: nin (int): Number of input ports. nout (int): Number of output ports. pos (tuple): (x, y) position of the block's top-left corner on the canvas. fill (str, optional): Fill color for the block. Defaults to "red". **kwargs: Additional keyword arguments passed to the canvas.create_polygon method. Methods: flip(): Flips the block horizontally on the canvas. inpos(i): Returns the absolute position of the i-th input port. outpos(i): Returns the absolute position of the i-th output port. """ _inpos = [] _outpos = [] _iports = [] _oports = [] active_output = None def event_print(self, event_name, event): if verbose_events: print(f"!! {event_name}, {event}, ({self.itemcget(tk.CURRENT, 'tags')})") def gridlines(canvas, line_distance): canvas.root.update() print(canvas.winfo_width()) canvas_width = 1000 canvas_height = 500 # vertical lines at an interval of "line_distance" pixel for x in range(line_distance, canvas_width, line_distance): canvas.create_line(x, 0, x, canvas_height, fill="#f0f0f0") # horizontal lines at an interval of "line_distance" pixel for y in range(line_distance, canvas_height, line_distance): canvas.create_line(0, y, canvas_width, y, fill="#f0f0f0") def get_active_block(self): return self.blockdict[self.gettags(tk.CURRENT)[0]] def ortho_line( self, start: tuple[int, int], end: tuple[int, int], tag=[], **kwargs ) -> tuple[tuple[float, float], ...]: if start[0] < end[0]: if start[1] == end[1]: return (start, end) else: xmid = (start[0] + end[0]) // 2 return ( start, (xmid, start[1]), (xmid, end[1]), end, ) else: # line has to go backwards L = 20 H = 100 if end[1] < start[1]: # new block above y = end[1] - H else: y = end[1] + H return ( start, (start[0] + L, start[1]), (start[0] + L, y), (end[0] - L, y), (end[0] - L, end[1]), end, ) def draw_ortho_line( self, start: tuple[int, int], end: tuple[int, int], width: int = 2, tag=[], **kwargs, ) -> None: coords = self.ortho_line(start, end) l = self.create_line( *[coord for point in coords for coord in point], arrow=tk.LAST, tags=tag, width=width, **kwargs, ) print("ortholine", self.gettags(l)) print("line id", l) re_wire = re.compile(r"(block\d+).out(\d+)\>(block\d+).in(\d+)") re_wire = re.compile(r"(block\d+).out(\d+)\>(block\d+).in(\d+)") def parse_io(self, s): m = self.re_wire.match(s) from_block = m.group(1) from_port = int(m.group(2)) to_block = m.group(3) to_port = int(m.group(4)) print(from_block, from_port, to_block, to_port) return from_block, from_port, to_block, to_port def redraw_wires(self, obj_id): # pass print("redraw_wires", obj_id) print("redraw_wires", self.gettags(obj_id)) block = self.gettags(obj_id)[0] ids = self.find_withtag(f"wire&&{block}") print("wires to redraw:", ids) re_wire = re.compile(r"(block\d+).out(\d+)\>(block\d+).in(\d+)") for id in ids: tags = self.gettags(id) print("wire tags:", tags) m = re_wire.match(tags[3]) from_block = m.group(1) from_port = int(m.group(2)) to_block = m.group(3) to_port = int(m.group(4)) print(from_block, from_port, to_block, to_port) self.delete(id) self.draw_ortho_line( self.blockdict[from_block].outpos[from_port], self.blockdict[to_block].inpos[to_port], tag=tags, ) def add_block( self, nin: int, nout: int, pos: tuple[int, int], image_path: str = None, fill: str = "white", outline: str = "black", **kwargs, ) -> None: """ Initialize a block object with specified input/output ports and position. Args: nin (int): Number of input ports. nout (int): Number of output ports. pos (tuple): (x, y) position of the block on the canvas. image_path (str, optional): Path to PNG image to display in block. fill (str, optional): Fill color for the block. Defaults to "red". **kwargs: Additional keyword arguments passed to the canvas.create_polygon method. """ block = Block(pos, nin, nout) blockdict[block.name] = block # draw the block block.item_id = self.create_rectangle( pos[0], pos[1], pos[0] + block.blockWidth, pos[1] + block.blockHeight, fill=fill, outline=outline, tags=(block.name, "block"), **kwargs, ) # Add image if provided if image_path: try: # Load and resize image to fit inside block img = Image.open(image_path) img_width = block.blockWidth - 10 # 5px margin on each side img = img.resize( (img_width, img.height * img_width // img.width), Image.Resampling.BICUBIC, ) photo = ImageTk.PhotoImage(img) # Create image centered in block img_id = self.create_image( pos[0] + block.blockWidth // 2, pos[1] + block.blockHeight // 2, image=photo, anchor=tk.CENTER, tags=(block.name, "block", "image"), ) # Keep reference to prevent garbage collection self._images.append(photo) block.image_id = img_id except Exception as e: print(f"Error loading image {image_path}: {e}") # draw the input ports for i in range(nin): x = block.pos[0] y = block.port_y(i, block.nin) item = self.create_rectangle( x, y - block.portSize / 2, x - block.portSize / 2, y + block.portSize / 2, fill="blue", tags=(block.name, f"{block.name}.in{i}", "inport", "moveable"), ) block.inpos.append((x - block.portSize / 2, y)) # draw the output ports for i in range(nout): x = block.pos[0] + block.blockWidth y = block.port_y(i, block.nout) item = self.create_polygon( x, y - block.portSize / 2, x, y + block.portSize / 2, x + block.portSize, y, fill="black", tags=(block.name, f"{block.name}.out{i}", "outport", "moveable"), ) block.outpos.append((x + block.portSize, y)) # draw the block name self.create_text( pos[0] + block.blockWidth / 2, pos[1] + block.blockHeight * 1.1, text=block.name, tags=(block.name, "label"), ) return block def flip(self): # Flip the block horizontally by reflecting its coordinates for i, port in enumerate(self._iports): coords = canvas.coords(port) x0, y0, x1, y1, x2, y2 = coords x0 = self.blockWidth - x0 x1 = self.blockWidth - x1 x2 = self.blockWidth - x2 canvas.coords(port, x0, y0, x1, y1, x2, y2) self._inpos[i] = (x2, y0) for i, port in enumerate(self._oports): coords = canvas.coords(port) x0, y0, x1, y1, x2, y2 = coords x0 = self.blockWidth - x0 x1 = self.blockWidth - x1 x2 = self.blockWidth - x2 canvas.coords(port, x0, y0, x1, y1, x2, y2) self._outpos[i] = (x2, y0) def add_wire(self, from_name, to_name): print( "Connect", from_name, "to", to_name, ) re_block = re.compile(r"(block\d+)\.(?:in|out)(\d+)") m = re_block.match(from_name) from_block = m.group(1) from_port = int(m.group(2)) m = re_block.match(to_name) to_block = m.group(1) to_port = int(m.group(2)) print(from_block, from_port, to_block, to_port) b1 = blockdict[from_block] b2 = blockdict[to_block] # ortho_line(b1.outpos(0), b2.inpos(0)) print(b1.outpos[from_port]) print(b2.inpos[to_port]) self.draw_ortho_line( b1.outpos[from_port], b2.inpos[to_port], tag=[ "wire", from_block, to_block, f"{from_name}>{to_name}", ], ) def inpos(self, i): # Return the absolute position of the i-th input port coords = canvas.coords(self.tag) return (self._inpos[i][0] + coords[0], self._inpos[i][1] + coords[1]) def outpos(self, i): # Return the absolute position of the i-th output port coords = canvas.coords(self.tag) return (self._outpos[i][0] + coords[0], self._outpos[i][1] + coords[1]) # see http://www.bitflipper.ca/Documentation/dnd_barebones.txt def current_item_id(self) -> int: # return the id of the current item id = self.find_withtag("current")[0] return id def current_item_tag(self) -> str: # return the first tag of the current item id = self.find_withtag("current")[0] tag = self.gettags(id)[0] return tag # ------------ block body events ----------------- # mouse button down, start drag def block_down(self, event): self.event_print("block_down", event) self.loc = 1 self.dragged = 0 widget = event.widget self.x, self.y = widget.canvasx(event.x), widget.canvasy(event.y) if "image" in self.gettags(tk.CURRENT): self.color = None else: self.color = widget.itemcget(tk.CURRENT, "fill") event.widget.bind("", self.block_motion) self.dx = 0 self.dy = 0 # mouse is dragging def block_motion(self, event): self.root.config(cursor="fleur") widget = event.widget widget.itemconfigure(tk.CURRENT, fill="white", outline="grey", dash=(5, 5)) x, y = widget.canvasx(event.x), widget.canvasy(event.y) dx = x - self.x dy = y - self.y self.x = x self.y = y self.dx += dx self.dy += dy if verbose_events: print(self.active_block) # move all elements tagged with this block's tag event.widget.move(self.active_block, dx, dy) # mouse button released def block_up(self, event): self.event_print("block_up", event) self.root.config(cursor="") widget = event.widget widget.unbind("") if self.color: self.itemconfigure(tk.CURRENT, fill=self.color, dash=()) # if self.loc: # is button released in same widget as pressed? # self.block_up(event) # else: # self.dragged = event.time block = blockdict[self.gettags(tk.CURRENT)[0]] for i in range(block.nin): block.inpos[i] = ( block.inpos[i][0] + self.dx, block.inpos[i][1] + self.dy, ) for i in range(block.nout): block.outpos[i] = ( block.outpos[i][0] + self.dx, block.outpos[i][1] + self.dy, ) self.redraw_wires(self.current_item_id()) def block_enter(self, event): self.event_print("block_enter", event) block_name = self.current_item_tag() # need the id of the block, not the image id = blockdict[block_name].item_id # print(" block enter", block_name) self.itemconfigure(id, outline="blue", width=2) self.loc = 1 self.active_id = id # item id of the block rectangle self.active_block_name = block_name if self.dragged == event.time: self.up(event) self.focus_set() def block_leave(self, event): self.event_print("block_leave", event) self.itemconfigure(self.active_id, outline="black", width=1) self.loc = 0 # ------------ output port events ----------------- def outport_down(self, event): selected = self.current_item_id() if self.active_output == selected: # cancelation of selection self.itemconfig(selected, fill="black", outline="") self.active_output = None elif self.active_output is not None: # change selection self.itemconfig(self.active_output, fill="black", outline="") self.itemconfig(selected, fill="yellow", outline="black", width=1) self.active_output = selected else: # new selection self.active_output = selected self.itemconfig(selected, fill="yellow", outline="black", width=1) if verbose_events: print("outport down", selected, self.active_output) def outport_up(self, event): pass # self.itemconfig(tk.CURRENT, fill="black", outline="") def outport_enter(self, event): selected = self.current_item_id() if verbose_events: print("outport enter", selected, self.active_output) self.itemconfig(tk.CURRENT, outline="red", width=2) def outport_leave(self, event): if verbose_events: print("outport leave", self.current_item_id(), self.active_output) if self.current_item_id() != self.active_output: self.itemconfig(tk.CURRENT, outline="") # ------------ input port events ----------------- def inport_down(self, event): # create a wire if an output port is active if self.active_output: self.itemconfig(self.active_output, fill="black", outline="") from_block = self.gettags(self.active_output) to_block = self.gettags(self.current_item_id()) if verbose_events: print("inport down", from_block, to_block) self.active_output = None self.add_wire(from_block[1], to_block[1]) def inport_up(self, event): pass # self.itemconfig(tk.CURRENT, fill="black", outline="") def inport_enter(self, event): self.itemconfig(tk.CURRENT, outline="red", width=2) def inport_leave(self, event): self.itemconfig(tk.CURRENT, outline="") # ------------ wire events ----------------- def wire_motion(self, event): self.event_print("wire_motion", event) self.root.config(cursor="fleur") widget = event.widget # widget.itemconfigure(tk.CURRENT, fill="white", outline="grey", dash=(5, 5)) x, y = widget.canvasx(event.x), widget.canvasy(event.y) id = self.current_item_id() print("wire id:", id) # idx=0, x1, y1 # x0, y0, x1 y1, x2 y2 coords = self.coords(id) print("old coords:", coords) print(self.idx, self.isvertical) if self.isvertical: # change x coordinates coords[2 * self.idx] = x coords[2 * self.idx + 2] = x else: # change y coordinates coords[2 * self.idx + 1] = y coords[2 * self.idx + 3] = y print("new coords:", coords) self.coords(id, *coords) # self.x = x # self.y = y # self.dx += dx # self.dy += dy # if verbose_events: # print(self.active) # event.widget.move(self.active, dx, dy) def wire_down(self, event): self.event_print("wire_down", event) for tag in self.gettags(tk.CURRENT): if ">" in tag: wiretag = tag blocks = tag.split(">") print("wire tag:", blocks[0], "-->", blocks[1]) from_block, from_port, to_block, to_port = self.parse_io(wiretag) coords = self.ortho_line( blockdict[from_block].outpos[from_port], blockdict[to_block].inpos[to_port], ) print(event.x, event.y) print(coords) if len(coords) == 2: # straight line pass elif len(coords) == 3: # dogleg forward line pass else: # backward line for i, (start, end) in enumerate(pairwise(coords[1:-1])): if is_close_numpy((event.x, event.y), start, end, tolerance=5): print("close to segment", start, end) self.dragged = 0 widget = event.widget self.x, self.y = widget.canvasx(event.x), widget.canvasy(event.y) # self.color = widget.itemcget(tk.CURRENT, "fill") event.widget.bind("", self.wire_motion) self.dx = 0 self.dy = 0 self.idx = i + 1 self.isvertical = (i & 1) == 0 # is vertical segment pass def wire_up(self, event): self.event_print("wire_up", event) self.root.config(cursor="") widget = event.widget widget.unbind("") def wire_enter(self, event): self.event_print("wire_enter", event) self.itemconfig(tk.CURRENT, width=3) def wire_leave(self, event): self.event_print("wire_leave", event) self.itemconfig(tk.CURRENT, width=1) def wire_delete(self, event): self.event_print("wire delete", event) self.delete(self.current_item_id()) # ------------ UI events ----------------- def new_block(event): self.event_print("new block", event) tree = getattr(event, "data", None) block = getattr(tree, "_last_activated_block", None) if block: print("Activated block:", block) # ------------ keyboard events ----------------- # "x" to list all items def list_items(self, event): print("list items") for item in self.find_withtag("block"): print( "item", item, self.gettags(item), self.coords(item), ) for item in self.find_withtag("wire"): print( "item", item, self.gettags(item), self.coords(item), ) # "q" to quit def quit(self, event): self.list_items(event) self.root.quit() def flip(self, event): print("flip") b4.flip() def __init__(self, parent, **kwargs): # Initialize the Canvas super().__init__( parent, **kwargs, ) self._images = [] # keep references to icon images self.root = self.winfo_toplevel() self.gridlines(20) self.pack() self.loc = self.dragged = 0 self.tag_bind("block", "", self.block_down) self.tag_bind("block", "", self.block_up) self.tag_bind("block", "", self.block_enter) self.tag_bind("block", "", self.block_leave) self.bind("", self.flip) self.tag_bind("image", "", self.block_down) self.tag_bind("image", "", self.block_up) self.tag_bind("image", "", self.block_enter) self.tag_bind("image", "", self.block_leave) self.bind("", self.flip) self.tag_bind("outport", "", self.outport_down) self.tag_bind("outport", "", self.outport_up) self.tag_bind("outport", "", self.outport_enter) self.tag_bind("outport", "", self.outport_leave) self.tag_bind("inport", "", self.inport_down) self.tag_bind("inport", "", self.inport_up) self.tag_bind("inport", "", self.inport_enter) self.tag_bind("inport", "", self.inport_leave) self.tag_bind("wire", "", self.wire_down) self.tag_bind("wire", "", self.wire_up) self.tag_bind("wire", "", self.wire_delete) self.tag_bind("wire", "", self.wire_enter) self.tag_bind("wire", "", self.wire_leave) self.bind("<>", self.new_block) self.bind("x", self.list_items) self.bind("q", self.quit) def is_close_numpy(point, start, end, tolerance): p = np.array(point) a = np.array(start) b = np.array(end) # Vector AB ab = b - a # Squared length of AB len_sq = np.sum(ab**2) if len_sq == 0: return np.linalg.norm(p - a) <= tolerance # Project point onto line, clamped between 0 and 1 t = np.dot(p - a, ab) / len_sq t = np.clip(t, 0, 1) # Find closest point on segment closest = a + t * ab # Check distance return np.linalg.norm(p - closest) <= tolerance ================================================ FILE: bdsim/tk_editor/editor.py ================================================ import tkinter as tk # init tk root = tk.Tk() root.title("bdsim") # create widget canvas = tk.Canvas(root, bg="white", height=500, width=1000) canvas.pack() # draw arcs coord = 10, 10, 300, 300 # arc = canvas.create_arc(coord, start=0, extent=150, fill="red") # arv2 = canvas.create_arc(coord, start=150, extent=215, fill="green") def grid(canvas, line_distance): root.update() print(canvas.winfo_width()) canvas_width = 1000 canvas_height = 500 # vertical lines at an interval of "line_distance" pixel for x in range(line_distance, canvas_width, line_distance): canvas.create_line(x, 0, x, canvas_height, fill="#f0f0f0") # horizontal lines at an interval of "line_distance" pixel for y in range(line_distance, canvas_height, line_distance): canvas.create_line(0, y, canvas_width, y, fill="#f0f0f0") grid(canvas, 20) def draw_block(nin, nout, pos): W = 50 H = 100 a = 20 # distance from top/bottm b = 5 # half width of output D = H - 2 * a # draw top edge p = [(0, 0), (W, 0)] # draw right side with triangles if nout == 1: y = H / 2 p.extend([(W, y - b), (W + b, y), (W, y + b)]) else: d = D / (nout - 1) for k in range(0, nout): y = a + k * d p.extend([(W, y - b), (W + b, y), (W, y + b)]) # draw bottom edge p.extend([(W, H), (0, H)]) # draw left side with squares if nout == 1: y = H / 2 p.extend([(0, y + b), (-b, y + b), (-b, y - b), (0, y - b)]) else: d = D / (nout - 1) for k in range(0, nout): y = a + k * d p.extend([(0, y + b), (-b, y + b), (-b, y - b), (0, y - b)]) print(p) x0 = 100 y0 = 200 pp = [(x[0] + pos[0], x[1] + pos[1]) for x in p] b1 = canvas.create_polygon(pp, fill="red", tags="box") draw_block(1, 1, (100, 100)) draw_block(0, 1, (200, 100)) draw_block(3, 1, (300, 100)) draw_block(2, 3, (400, 100)) # add to window and show root.mainloop() ================================================ FILE: bdsim/tk_editor/editor2.py ================================================ import sys import os import string import time import tkinter as tk from itertools import tee def pairwise(iterable): "s -> (s0,s1), (s1,s2), (s2, s3), ..." a, b = tee(iterable) next(b, None) return zip(a, b) # A Python example of drag and drop functionality within a single Tk widget. # The trick is in the bindings and event handler functions. # Tom Vrankar twv at ici.net # empirical events between dropee and target, as determined from Tk 8.0 # down. # leave. # up, leave, enter. def ortho_line(start, end, width=2, **kwargs): if start[0] < end[0]: if start[1] == end[1]: canvas.create_line(start, end, tag="wire", **kwargs) else: xmid = (start[0] + end[0]) / 2.0 canvas.create_line( start, (xmid, start[1]), (xmid, end[1]), end, tag="wire", width=width, **kwargs, ) else: # line has to go backwards L = 20 H = 100 if end[1] < start[1]: # new block above y = end[1] - H else: y = end[1] + H canvas.create_line( start, (start[0] + L, start[1]), (start[0] + L, y), (end[0] - L, y), (end[0] - L, end[1]), end, tag="wire", **kwargs, ) def redraw_wires(): canvas.delete("wire") ortho_line(b3.outpos(0), b4.inpos(0)) class CanvasDnD(tk.Frame): def __init__(self, master, canvas): self.master = master self.loc = self.dragged = 0 tk.Frame.__init__(self, master) canvas.tag_bind("DnD", "", self.down) canvas.tag_bind("DnD", "", self.chkup) canvas.tag_bind("DnD", "", self.enter) canvas.tag_bind("DnD", "", self.leave) canvas.bind("", self.flip) # mouse button down, start drag def down(self, event): self.loc = 1 self.dragged = 0 cnv = event.widget self.x, self.y = cnv.canvasx(event.x), cnv.canvasy(event.y) self.color = cnv.itemcget(tk.CURRENT, "fill") event.widget.bind("", self.motion) # mouse is dragging def motion(self, event): root.config(cursor="exchange") cnv = event.widget cnv.itemconfigure(tk.CURRENT, fill="white", outline="grey", dash=(5, 5)) x, y = cnv.canvasx(event.x), cnv.canvasy(event.y) dx = x - self.x dy = y - self.y self.x = x self.y = y # got = event.widget.coords(tk.CURRENT, x, y) event.widget.move(tk.CURRENT, dx, dy) def leave(self, event): print("leave") canvas.itemconfigure(tk.CURRENT, outline="") self.loc = 0 def enter(self, event): print("enter", event) canvas.itemconfigure(tk.CURRENT, outline="darkred", width=2) self.loc = 1 if self.dragged == event.time: self.up(event) canvas.focus_set() # mouse button released def chkup(self, event): event.widget.unbind("") root.config(cursor="") self.target = event.widget.find_withtag(tk.CURRENT) event.widget.itemconfigure(tk.CURRENT, fill=self.color, dash=()) if self.loc: # is button released in same widget as pressed? self.up(event) else: self.dragged = event.time redraw_wires() def up(self, event): event.widget.unbind("") if self.target == event.widget.find_withtag(tk.CURRENT): pass # print("Select %s" % event.widget.itemcget(tk.CURRENT, "text")) else: event.widget.itemconfigure(tk.CURRENT, fill="blue") self.master.update() time.sleep(0.1) # print("%s Drag-N-Dropped onto %s" # % (event.widget.itemcget(self.target, "text"), # event.widget.itemcget(tk.CURRENT, "text"))) event.widget.itemconfigure(tk.CURRENT, fill=self.defaultcolor) def flip(self, event): print("flip") b4.flip() root = tk.Tk() root.title("bdsim editor") # create widget canvas = tk.Canvas( root, bg="white", height=500, width=1000, relief=tk.RIDGE, background="white", borderwidth=1, ) canvas.pack() def grid(canvas, line_distance): root.update() print(canvas.winfo_width()) canvas_width = 1000 canvas_height = 500 # vertical lines at an interval of "line_distance" pixel for x in range(line_distance, canvas_width, line_distance): canvas.create_line(x, 0, x, canvas_height, fill="#f0f0f0") # horizontal lines at an interval of "line_distance" pixel for y in range(line_distance, canvas_height, line_distance): canvas.create_line(0, y, canvas_width, y, fill="#f0f0f0") grid(canvas, 20) class GBlock: W = 50 H = 100 a = 20 # distance from top/bottm b = 5 # half width of output def __init__(self, nin, nout, pos, fill="red", **kwargs): D = self.H - 2 * self.a inpos = [] outpos = [] # draw top edge p = [(0, 0), (self.W, 0)] # draw right side with triangles if nout == 1: y = self.H / 2 p.extend([(self.W, y - self.b), (self.W + self.b, y), (self.W, y + self.b)]) outpos.append((self.W + self.b, y)) else: d = D / (nout - 1) for k in range(0, nout): y = self.a + k * d p.extend( [(self.W, y - self.b), (self.W + self.b, y), (self.W, y + self.b)] ) outpos.append((self.W + self.b, y)) # draw bottom edge p.extend([(self.W, self.H), (0, self.H)]) # draw left side with squares if nout == 1: y = self.H / 2 p.extend( [ (0, y + self.b), (-self.b, y + self.b), (-self.b, y - self.b), (0, y - self.b), ] ) inpos.append((-self.b, y)) else: d = D / (nout - 1) for k in range(0, nout): y = self.H - self.a - k * d p.extend( [ (0, y + self.b), (-self.b, y + self.b), (-self.b, y - self.b), (0, y - self.b), ] ) inpos.append((-self.b, y)) self._inpos = inpos self._outpos = outpos self.outline = p pp = [(x[0] + pos[0], x[1] + pos[1]) for x in p] self.id = canvas.create_polygon(pp, fill=fill, tags="DnD", **kwargs) def flip(self): outline = canvas.coords(self.id) print(outline) A = 2 * (outline[0] + 25) flipped = [] for x, y in pairwise(outline): flipped.extend([A - x, y]) print(flipped) canvas.coords(self.id, flippedgit) def inpos(self, i): pos = canvas.coords(self.id) return (pos[0] + self._inpos[i][0], pos[1] + self._inpos[i][1]) def outpos(self, i): pos = canvas.coords(self.id) return (pos[0] + self._outpos[i][0], pos[1] + self._outpos[i][1]) GBlock(1, 1, (100, 100)) GBlock(0, 1, (200, 100)) b3 = GBlock(3, 1, (300, 100)) b4 = GBlock(2, 3, (400, 100)) # print(canvas.bbox(b4.id)) # canvas.create_rectangle(canvas.bbox(b4.id), outline="black", fill="") print(canvas.coords(b4.id)) print(canvas.bbox(b4.id)) CanvasDnD(root, canvas) root.mainloop() ================================================ FILE: docs/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) open: open build/html/index.html .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ================================================ FILE: docs/make.bat ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd ================================================ FILE: docs/source/bdsim.blocks.rst ================================================ .. _Block library: ************* Block library ************* .. include:: The block diagrams comprise blocks which belong to one of a number of different categories. These come from the package ``bdsim.blocks``, ``roboticstoolbox.blocks``, ``machinevisiontoolbox.blocks``. Icons, if shown to the left of the black header bar, are as used with `bdedit `_. .. inheritance-diagram:: bdsim.components.SourceBlock bdsim.components.SinkBlock bdsim.graphics.GraphicsBlock bdsim.components.FunctionBlock bdsim.components.TransferBlock bdsim.components.SubsystemBlock Source blocks ============= .. automodule:: bdsim.blocks.sources :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Sink blocks =========== .. automodule:: bdsim.blocks.sinks :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Display blocks ============== .. automodule:: bdsim.blocks.displays :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Function blocks =============== Functions --------- .. automodule:: bdsim.blocks.functions :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Linear algebra -------------- .. automodule:: bdsim.blocks.linalg :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Spatial math ------------ .. automodule:: bdsim.blocks.spatial :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Connection blocks ================= .. automodule:: bdsim.blocks.connections :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Dynamics ======== Continuous-time blocks ---------------------- .. automodule:: bdsim.blocks.transfers :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Discrete-time blocks -------------------- .. automodule:: bdsim.blocks.discrete :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels External Toolbox blocksets ========================== These blocks are defined within external Toolboxes or packages. Robot blocks ------------ These blocks are defined within the Robotics Toolbox for Python. Arm robots ^^^^^^^^^^ .. automodule:: roboticstoolbox.blocks.arm :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Mobile robots ^^^^^^^^^^^^^ .. automodule:: roboticstoolbox.blocks.mobile :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Multi rotor flying robots ^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: roboticstoolbox.blocks.uav :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Vision blocks ------------- These blocks are defined within the Machine Vision Toolbox for Python. .. automodule:: machinevisiontoolbox.blocks :members: :undoc-members: :show-inheritance: :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels ================================================ FILE: docs/source/bdsim.rst ================================================ Overview ======== Getting started --------------- We first sketch the dynamic system we want to simulate as a block diagram, for example this simple first-order system .. image:: ../../figs/bd1-sketch.png :width: 800 which we can express concisely with `bdsim` as (see `bdsim/examples/eg1.py `_) .. code-block:: python :linenos: import bdsim sim = bdsim.BDSim() # create simulator bd = sim.blockdiagram() # create an empty block diagram # define the blocks demand = bd.STEP(T=1, name='demand') sum = bd.SUM('+-') gain = bd.GAIN(10) plant = bd.LTI_SISO(0.5, [2, 1], name='plant') scope = bd.SCOPE(styles=['k', 'r--'], movie='eg1.mp4') # connect the blocks bd.connect(demand, sum[0], scope[1]) bd.connect(sum, gain) bd.connect(gain, plant) bd.connect(plant, sum[1], scope[0]) bd.compile() # check the diagram bd.report() # list all blocks and wires out = sim.run(bd, 5) # simulate for 5s print(out) # sim.savefig(scope, 'scope0') # save scope figure as scope0.pdf sim.done(bd, block=True) # keep figures open on screen which is just 16 lines of executable code. The red block annotations on the hand-drawn diagram are used as the names of the variables holding references to the block instance. The blocks can also have user-assigned names, see lines 8 and 11, which are used in diagnostics and as labels in plots. After the blocks are created their input and output ports need to be connected. In `bdsim` all wires are point to point, a *one-to-many* connection is implemented by *many* wires, for example:: bd.connect(source, dest1, dest2, ...) creates individual wires from `source` -> `dest1`, `source` -> `dest2` and so on. Ports are designated using Python indexing notation, for example `block[2]` is port 2 (the third port) of `block`. Whether it is an input or output port depends on context. In the example above an index on the first argument refers to an output port, while on the second (or subsequent) arguments it refers to an input port. If a block has only a single input or output port then no index is required, 0 is assumed. A group of ports can be denoted using slice notation, for example:: bd.connect(source[2:5], dest[3:6) will connect ``source[2]`` -> ``dest[3]``, ``source[3]`` -> ``dest[4]``, ``source[4]`` -> ``dest[5]``. The number of wires in each slice must be consistent. You could even do a cross over by connecting ``source[2:5]`` to ``dest[6:3:-1]``. Line 20 assembles all the blocks and wires, instantiates subsystems, checks connectivity to create a flat wire list, and then builds the dataflow execution plan. Line 21 generates a report, in tabular form, showing a summary of the block diagram: .. code-block:: Blocks:: ┌───┬─────────┬─────┬──────┬────────┬─────────┬───────┐ │id │ name │ nin │ nout │ nstate │ ndstate │ type │ ├───┼─────────┼─────┼──────┼────────┼─────────┼───────┤ │ 0 │ demand │ 0 │ 1 │ 0 │ 0 │ step │ │ 1 │ sum.0 │ 2 │ 1 │ 0 │ 0 │ sum │ │ 2 │ gain.0 │ 1 │ 1 │ 0 │ 0 │ gain │ │ 3 │ plant │ 1 │ 1 │ 1 │ 0 │ LTI │ │ 4 │ scope.0 │ 2 │ 0 │ 0 │ 0 │ scope │ └───┴─────────┴─────┴──────┴────────┴─────────┴───────┘ Wires:: ┌───┬──────┬──────┬──────────────────────────┬─────────┐ │id │ from │ to │ description │ type │ ├───┼──────┼──────┼──────────────────────────┼─────────┤ │ 0 │ 0[0] │ 1[0] │ demand[0] --> sum.0[0] │ int │ │ 1 │ 0[0] │ 4[1] │ demand[0] --> scope.0[1] │ int │ │ 2 │ 3[0] │ 1[1] │ plant[0] --> sum.0[1] │ float64 │ │ 3 │ 1[0] │ 2[0] │ sum.0[0] --> gain.0[0] │ float64 │ │ 4 │ 2[0] │ 3[0] │ gain.0[0] --> plant[0] │ float64 │ │ 5 │ 3[0] │ 4[0] │ plant[0] --> scope.0[0] │ float64 │ └───┴──────┴──────┴──────────────────────────┴─────────┘ .. image:: ../../figs/Figure_1.png :width: 600 The simulation results are returned in a simple container object:: >>> out results: t | ndarray (67,) x | ndarray (67, 1) xnames | list where - `t` the time vector: ndarray, shape=(M,) - `x` is the state vector: ndarray, shape=(M,N), one row per timestep - `xnames` is a list of the names of the states corresponding to columns of `x`, eg. "plant.x0" To record additional simulation variables we "watch" them. This can be specified by wiring the signal to a WATCH block, or more conveniently by an additional option to ``run``:: out = sim.run(bd, 5, watch=[plant,demand]) # simulate for 5s and now the result ``out`` has additional elements:: >>> out results: t | ndarray (67,) x | ndarray (67, 1) xnames | list y0 | ndarray (67,) y1 | ndarray (67,) ynames | list where - `y0` is the time history of the first watched signal - `y1` is the time history of the second watched signal - `ynames` is a list of the names of the states corresponding to columns of `x`, eg. "plant[0]" Line 27 saves the content of the scope to be saved in the file called `scope0.pdf`. Line 28 blocks the script until all figure windows are closed, or the script is killed with SIGINT. Line 29 saves the scope graphics as a PDF file. Line 30 blocks until the last figure is dismissed. A list of available blocks can be obtained by:: >>> sim.blocks() 73 blocks loaded bdsim.blocks.functions..................: Sum Prod Gain Clip Function Interpolate bdsim.blocks.sources....................: Constant Time WaveForm Piecewise Step Ramp bdsim.blocks.sinks......................: Print Stop Null Watch bdsim.blocks.transfers..................: Integrator PoseIntegrator LTI_SS LTI_SISO bdsim.blocks.discrete...................: ZOH DIntegrator DPoseIntegrator bdsim.blocks.linalg.....................: Inverse Transpose Norm Flatten Slice2 Slice1 Det Cond bdsim.blocks.displays...................: Scope ScopeXY ScopeXY1 bdsim.blocks.connections................: Item Dict Mux DeMux Index SubSystem InPort OutPort roboticstoolbox.blocks.arm..............: FKine IKine Jacobian Tr2Delta Delta2Tr Point2Tr TR2T FDyn IDyn Gravload ........................................: Inertia Inertia_X FDyn_X ArmPlot Traj JTraj LSPB CTraj CirclePath roboticstoolbox.blocks.mobile...........: Bicycle Unicycle DiffSteer VehiclePlot roboticstoolbox.blocks.uav..............: MultiRotor MultiRotorMixer MultiRotorPlot machinevisiontoolbox.blocks.camera......: Camera Visjac_p EstPose_p ImagePlane More details can be found at: - `Wiki page `_ - `Adding blocks `_ - `Connecting blocks `_ - `Running the simulation `_ - :ref:`Block library` Using operator overloading -------------------------- Wiring, and some simple arithmetic blocks like GAIN, SUM and PROD can be implicitly generated by overloaded Python operators. This strikes a nice balance between block diagram coding and Pythonic programming. .. code-block:: python :linenos: import bdsim sim = bdsim.BDSim() # create simulator bd = sim.blockdiagram() # create an empty block diagram # define the blocks demand = bd.STEP(T=1, name='demand') plant = bd.LTI_SISO(0.5, [2, 1], name='plant') scope = bd.SCOPE(styles=['k', 'r--'], movie='eg1.mp4') # connect the blocks scope[0] = plant scope[1] = demand plant[0] = 10 * (demand - plant) bd.compile() # check the diagram bd.report() # list all blocks and wires out = sim.run(bd, 5) # simulate for 5s # out = sim.run(bd, 5 watch=[plant,demand]) # simulate for 5s print(out) # sim.savefig(scope, 'scope0') # save scope figure as scope0.pdf sim.done(bd, block=True) # keep figures open on screen This requires fewer lines of code and the code is more readable. Importantly, it results in in *exactly the same* block diagram in terms of blocks and wires:: ┌───┬──────┬──────┬──────────────────────────────┬─────────┐ │id │ from │ to │ description │ type │ ├───┼──────┼──────┼──────────────────────────────┼─────────┤ │ 0 │ 1[0] │ 2[0] │ plant[0] --> scope.0[0] │ float64 │ │ 1 │ 0[0] │ 2[1] │ demand[0] --> scope.0[1] │ int │ │ 2 │ 0[0] │ 3[0] │ demand[0] --> _sum.0[0] │ int │ │ 3 │ 1[0] │ 3[1] │ plant[0] --> _sum.0[1] │ float64 │ │ 4 │ 3[0] │ 4[0] │ _sum.0[0] --> _gain.0(10)[0] │ float64 │ │ 5 │ 4[0] │ 1[0] │ _gain.0(10)[0] --> plant[0] │ float64 │ └───┴──────┴──────┴──────────────────────────────┴─────────┘ The implicitly created blocks have names prefixed with an underscore. ================================================ FILE: docs/source/conf.py ================================================ # bdsim # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys # sys.path.insert(0, os.path.abspath('.')) # defined relative to configuration directory which is where this file conf.py lives sys.path.append(os.path.abspath("exts")) # -- Project information ----------------------------------------------------- project = "Block diagram simulation" copyright = "2020-, Peter Corke." author = "Peter Corke" try: import bdsim version = bdsim.__version__ except AttributeError: import re with open("../../pyproject.toml", "r") as f: m = re.compile(r'version\s*=\s*"([0-9\.]+)"').search(f.read()) version = m[1] # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx.ext.mathjax", "matplotlib.sphinxext.plot_directive", "sphinx.ext.coverage", "sphinx.ext.intersphinx", "sphinx.ext.inheritance_diagram", "sphinx_autodoc_typehints", "sphinx_favicon", "blockname", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["test_*"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" html_show_sourcelink = True html_theme = "sphinx_rtd_theme" # html_theme = 'alabaster' # html_theme = 'pyramid' # html_theme = 'sphinxdoc' github_url = "https://github.com/petercorke/bdsim" html_theme_options = { "github_host": "gitlab.com", "github_user": "petercorke", "github_repo": "bdsim", "display_github": True, "github_version": "HEAD", "logo_only": False, "display_version": True, "prev_next_buttons_location": "both", "analytics_id": "G-11Q6WJM565", } html_logo = "../../figs/BDSimLogo_NoBackgnd@2x.png" html_last_updated_fmt = "%d-%b-%Y" autoclass_content = "class" html_show_sourcelink = True # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] rst_epilog = """ .. role:: raw-html(raw) :format: html .. |BlockOptions| replace:: :raw-html:`common Block options` """ # -------- RVC maths notation -------------------------------------------------------# # see https://stackoverflow.com/questions/9728292/creating-latex-math-macros-within-sphinx mathjax3_config = { "tex": { "macros": { # RVC Math notation # - not possible to do the if/then/else approach # - subset only "presup": [r"\,{}^{\scriptscriptstyle #1}\!", 1], # groups "SE": [r"\mathbf{SE}(#1)", 1], "SO": [r"\mathbf{SO}(#1)", 1], "se": [r"\mathbf{se}(#1)", 1], "so": [r"\mathbf{so}(#1)", 1], # vectors "vec": [r"\boldsymbol{#1}", 1], "dvec": [r"\dot{\boldsymbol{#1}}", 1], "ddvec": [r"\ddot{\boldsymbol{#1}}", 1], "fvec": [r"\presup{#1}\boldsymbol{#2}", 2], "fdvec": [r"\presup{#1}\dot{\boldsymbol{#2}}", 2], "fddvec": [r"\presup{#1}\ddot{\boldsymbol{#2}}", 2], "norm": [r"\Vert #1 \Vert", 1], # matrices "mat": [r"\mathbf{#1}", 1], "dmat": [r"\dot{\mathbf{#1}}", 1], "fmat": [r"\presup{#1}\mathbf{#2}", 2], # skew matrices "sk": [r"\left[#1\right]", 1], "skx": [r"\left[#1\right]_{\times}", 1], "vex": [r"\vee\left( #1\right)", 1], "vexx": [r"\vee_{\times}\left( #1\right)", 1], # quaternions "q": r"\mathring{q}", "fq": [r"\presup{#1}\mathring{q}", 1], } } } # -------- Options favicon -------------------------------------------------------# html_static_path = ["_static"] # create favicons online using https://favicon.io/favicon-converter/ favicons = [ { "rel": "icon", "sizes": "16x16", "static-file": "favicon-16x16.png", "type": "image/png", }, { "rel": "icon", "sizes": "32x32", "static-file": "favicon-32x32.png", "type": "image/png", }, { "rel": "apple-touch-icon", "sizes": "180x180", "static-file": "apple-touch-icon.png", "type": "image/png", }, { "rel": "android-chrome", "sizes": "192x192", "static-file": "android-chrome-192x192.png ", "type": "image/png", }, { "rel": "android-chrome", "sizes": "512x512", "static-file": "android-chrome-512x512.png ", "type": "image/png", }, ] # -------- Options InterSphinx -------------------------------------------------------# intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "numpy": ("http://docs.scipy.org/doc/numpy/", None), "scipy": ("http://docs.scipy.org/doc/scipy/reference/", None), "matplotlib": ("http://matplotlib.sourceforge.net/", None), "spatialmath": ("https://petercorke.github.io/spatialmath-python/", None), } ================================================ FILE: docs/source/exts/blockname.py ================================================ from docutils import nodes # from docutils.parsers.rst import Directive def block_name(name, rawtext, text, lineno, inliner, options={}, content=[]): # name: The local name of the interpreted role, the role name actually used in the document. # rawtext: A string containing the enitre interpreted text input, including the role and markup. Return it as a problematic node linked to a system message if a problem is encountered. # text: The interpreted text content. # lineno: The line number where the interpreted text begins. # inliner: The docutils.parsers.rst.states.Inliner object that called role_fn. It contains the several attributes useful for error reporting and document tree access. # options: A dictionary of directive options for customization (from the "role" directive), to be interpreted by the role function. Used for additional attributes for the generated elements and other functionality. # content: A list of strings, the directive content for customization (from the "role" directive). To be interpreted by the role function. html = """

{0}

""" # this is the path to the icons within the github repo path = ( "https://github.com/petercorke/bdsim/raw/master/bdsim/blocks/Icons/" + text.lower() + ".png" ) html_node = nodes.raw(text=html.format(text, path), format="html") return [html_node], [] def setup(app): app.add_role("blockname", block_name) return { "version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True, } ================================================ FILE: docs/source/index.rst ================================================ .. Spatial Maths package documentation master file, created by sphinx-quickstart on Sun Apr 12 15:50:23 2020. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Block diagrams for Python ========================= .. raw:: html
   # define the blocks
   demand = bd.STEP(T=1, name='demand')
   sum = bd.SUM('+-')
   gain = bd.GAIN(10)
   plant = bd.LTI_SISO(0.5, [2, 1])
   scope = bd.SCOPE(styles=['k', 'r--'])

   # connect the blocks
   bd.connect(demand, sum[0], scope[1])
   bd.connect(plant, sum[1])
   bd.connect(sum, gain)
   bd.connect(gain, plant)
   bd.connect(plant, scope[0])
   
This Python package enables modelling and simulation of dynamic systems conceptualized in block diagram form, but represented in terms of Python class and method calls. Unlike Simulink® or LabView®, we write Python code rather than drawing boxes and wires. Wires can communicate any Python type such as scalars, strings, lists, dictionaries, numpy arrays, other objects, and even functions. .. toctree:: :maxdepth: 2 :caption: Code documentation: bdsim bdsim.blocks internals ================================================ FILE: docs/source/internals.rst ================================================ Supporting classes ****************** .. inheritance-diagram:: bdsim.components BDSim class =========== This class describes the run-time environment for executing a block diagram. .. autoclass:: bdsim.BDSim :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.Struct :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.BDSimState :members: :undoc-members: :show-inheritance: :special-members: __init__ BlockDiagram class ================== This class describes a block diagram, a collection of blocks and wires that can be "executed". .. autoclass:: bdsim.BlockDiagram :members: :undoc-members: :show-inheritance: Components ========== Wire ---- .. autoclass:: bdsim.Wire :members: :undoc-members: :show-inheritance: :exclude-members: __dict__, __weakref__, __array_ufunc__, __module__ Plug ---- .. autoclass:: bdsim.Plug :members: :undoc-members: :show-inheritance: :special-members: :exclude-members: __dict__, __weakref__, __array_ufunc__, __module__ Blocks ------ .. autoclass:: bdsim.Block :members: :undoc-members: :show-inheritance: :special-members: :exclude-members: __dict__, __weakref__, __array_ufunc__, __module__ Source block ^^^^^^^^^^^^ .. autoclass:: bdsim.SourceBlock :members: :undoc-members: :show-inheritance: :special-members: Sink block ^^^^^^^^^^ .. autoclass:: bdsim.SinkBlock :members: :undoc-members: :show-inheritance: :special-members: Function block ^^^^^^^^^^^^^^ .. autoclass:: bdsim.FunctionBlock :members: :undoc-members: :show-inheritance: :special-members: Transfer function block ^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: bdsim.TransferBlock :members: :undoc-members: :show-inheritance: :special-members: Subsystem block ^^^^^^^^^^^^^^^ .. autoclass:: bdsim.SubsystemBlock :members: :undoc-members: :show-inheritance: :special-members: Graphics block ^^^^^^^^^^^^^^ .. autoclass:: bdsim.GraphicsBlock :members: :undoc-members: :show-inheritance: :special-members: Discrete-time systems --------------------- .. autoclass:: bdsim.ClockedBlock :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.Clock :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.PriorityQ :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs/source/modules.rst ================================================ bdsim ===== .. toctree:: :maxdepth: 4 bdsim ================================================ FILE: docs-aside/.buildinfo ================================================ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. config: d73274dfd5f8b4473dacef6a99ded73b tags: 645f666f9bcd5a90fca523b33c5a78b7 ================================================ FILE: docs-aside/.nojekyll ================================================ ================================================ FILE: docs-aside/_modules/bdsim/bdsim.html ================================================ bdsim.bdsim — Block diagram simulation 0.7 documentation

Source code for bdsim.bdsim

import os
from pathlib import Path
import sys
import importlib
import inspect
from collections import Counter, namedtuple
from typing import NamedTuple
import argparse
import types
from bdsim.components import *
from bdsim.blockdiagram import BlockDiagram
import copy
import tempfile
import subprocess
import webbrowser

import numpy as np
import scipy.integrate as integrate
import re
from colored import fg, attr


block = namedtuple('block', 'name, cls, path')

# convert class name to BLOCK name
# strip underscores and capitalize
def blockname(name):
    return name.upper()


# print a progress bar
# https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
def printProgressBar (fraction, prefix='', suffix='', decimals=1, length=50, fill = '█', printEnd = "\r"):

    percent = ("{0:." + str(decimals) + "f}").format(fraction * 100)
    filledLength = int(length * fraction)
    bar = fill * filledLength + '-' * (length - filledLength)
    print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd)


[docs]class BDSimState: """ :ivar x: state vector :vartype x: np.ndarray :ivar T: maximum simulation time (seconds) :vartype T: float :ivar t: current simulation time (seconds) :vartype t: float :ivar fignum: number of next matplotlib figure to create :vartype fignum: int :ivar stop: reference to block wanting to stop simulation, else None :vartype stop: Block subclass :ivar checkfinite: halt simulation if any wire has inf or nan :vartype checkfinite: bool :ivar graphics: enable graphics :vartype graphics: bool """
[docs] def __init__(self): self.x = None # continuous state vector numpy.ndarray self.T = None # maximum.BlockDiagram time self.t = None # current time self.fignum = 0 self.stop = None self.checkfinite = True self.debugger = True self.t_stop = None # time-based breakpoint self.eventq = PriorityQ()
[docs] def declare_event(self, block, t): self.eventq.push((t, block))
[docs]class BDSim: options = None _blocklibrary = None
[docs] def __init__(self, packages=None, **kwargs): """ :param sysargs: process options from sys.argv, defaults to True :type sysargs: bool, optional :param graphics: enable graphics, defaults to True :type graphics: bool, optional :param animation: enable animation, defaults to False :type animation: bool, optional :param progress: enable progress bar, defaults to True :type progress: bool, optional :param debug: debug options, defaults to None :type debug: str, optional :param backend: matplotlib backend, defaults to 'Qt5Agg'' :type backend: str, optional :param tiles: figure tile layout on monitor, defaults to '3x4' :type tiles: str, optional :raises ImportError: syntax error in block :return: parent object for blockdiagram simulation :rtype: BDSim If ``sysargs`` is True, process command line arguments and passed options. Command line arguments have precedence. =================== ========= ======== =========================================== Command line switch Argument Default Behaviour =================== ========= ======== =========================================== ++nographics, +g graphics True enable graphical display ++animation, +a animation True update graphics at each time step --nographics, -g graphics True disable graphical display --animation, -a animation True don't update graphics at each time step --noprogress, -p progress True do not display simulation progress bar --backend BE backend 'Qt5Agg' matplotlib backend --tiles RxC, -t RxC tiles '3x4' arrangement of figure tiles on the display --shape WxH shape None window size, default matplotlib size --altscreen altscreen True use secondary monitor if it exists --verbose, -v verbose False be verbose --debug F, -d F debug '' debug flag string =================== ========= ======== =========================================== .. note:: ``animation`` and ``graphics`` options are coupled. If ``graphics=False``, all graphics is suppressed. If ``graphics=True`` then graphics are shown and the behaviour depends on ``animation``. ``animation=False`` shows graphs at the end of the simulation, while ``animation=True` will animate the graphs during simulation. :seealso: :meth:`set_options` """ self.packages = packages # process command line and overall options if BDSim.options is None: BDSim.options = self.get_options(**kwargs) # load modules from the blocks folder if BDSim._blocklibrary is None: BDSim._blocklibrary = self.load_blocks(self.options.verbose)
def __str__(self): """ String representation of simulation :return: single line summary of simulation environment :rtype: str """ s = f"BDSim: {len(self._blocklibrary)} blocks in library\n" return s def __repr__(self): s = str(self) for k, v in self.options.items(): s += ' {:s}: {}\n'.format(k, v) return s
[docs] def progress(self, t=None): """ Update progress bar :param t: current simulation time, defaults to None :type t: float, optional Update progress bar as a percentage of the maximum simulation time, given as an argument to ``run``. :seealso: :meth:`run` :meth:`progress_done` """ if self.options.progress: if t is None: t = self.state.t printProgressBar(t / self.state.T, prefix='Progress:', suffix='complete', length=60)
[docs] def progress_done(self): """ Clean up progress bar """ if self.options.progress: print('\r' + ' '* 90 + '\r')
[docs] def run(self, bd, T=10.0, dt=0.1, solver='RK45', solver_args={}, debug='', block=False, checkfinite=True, minstepsize=1e-12, watch=[], ): """ Run the block diagram :param T: maximum integration time, defaults to 10.0 :type T: float, optional :param dt: maximum time step, defaults to 0.1 :type dt: float, optional :param solver: integration method, defaults to ``RK45`` :type solver: str, optional :param block: matplotlib block at end of run, default False :type block: bool :param checkfinite: error if inf or nan on any wire, default True :type checkfinite: bool :param minstepsize: minimum step length, default 1e-6 :type minstepsize: float :param watch: list of input ports to log :type watch: list :param solver_args: arguments passed to ``scipy.integrate`` :type solver_args: dict :return: time history of signals and states :rtype: Sim class Assumes that the network has been compiled. Results are returned in a class with attributes: - ``t`` the time vector: ndarray, shape=(M,) - ``x`` is the state vector: ndarray, shape=(M,N) - ``xnames`` is a list of the names of the states corresponding to columns of `x`, eg. "plant.x0", defined for the block using the ``snames`` argument - ``yN`` for a watched input where N is the index of the port mentioned in the ``watch`` argument - ``ynames`` is a list of the names of the input ports being watched, same order as in ``watch`` argument If there are no dynamic elements in the diagram, ie. no states, then ``x`` and ``xnames`` are not present. The ``watch`` argument is a list of one or more input ports whose value during simulation will be recorded. The elements of the list can be: - a ``Block`` reference, which is interpretted as input port 0 - a ``Plug`` reference, ie. a block with an index or attribute - a string of the form "block[i]" which is port i of the block named block. The debug string comprises single letter flags: - 'p' debug network value propagation - 's' debug state vector - 'd' debug state derivative .. note:: Simulation stops if the step time falls below ``minsteplength`` which typically indicates that the solver is struggling with a very harsh non-linearity. """ assert bd.compiled, 'Network has not been compiled' state = BDSimState() self.state = state state.T = T state.dt = dt state.count = 0 state.solver = solver state.solver_args = solver_args state.minstepsize = minstepsize state.stop = None # allow any block to stop.BlockDiagram by setting this to the block's name state.checkfinite = checkfinite state.options = copy.copy(self.options) self.bd = bd state.t_stop = None if debug: # append debug flags if debug not in state.options.debug: state.options.debug += debug if len(state.options.debug) > 0: state.options.progress = False # process the watchlist # elements can be: # - block or Plug reference # - str in the form BLOCKNAME[PORT] watchlist = [] watchnamelist = [] re_block = re.compile(r'(?P<name>[^[]+)(\[(?P<port>[0-9]+)\])') for w in watch: if isinstance(w, str): # a name was given, with optional port number m = re_block.match(w) if m is None: raise ValueError('watch block[port] not found: ' + w) name = m.group('name') port = int(m.group('port')) b = bd.blocknames[name] plug = b[port] elif isinstance(w, Block): # a block was given, defaults to port 0 plug = w[0] elif isinstance(w, Plug): # a plug was given plug = w watchlist.append(plug) watchnamelist.append(str(plug)) state.watchlist = watchlist state.watchnamelist = watchnamelist x0 = bd.getstate0() print('initial state x0 = ', x0) # get the number of discrete states from all clocks ndstates = 0 for clock in bd.clocklist: nds = 0 for b in clock.blocklist: nds += b.ndstates ndstates += nds print(clock.name, 'initial dstate x0 = ', clock.getstate()) # tell all blocks we're starting a BlockDiagram self.bd.start(state=state, graphics=self.state.options.graphics) # initialize list of time and states state.tlist = [] state.xlist = [] state.plist = [[] for p in state.watchlist] self.progress(0) if len(self.state.eventq) == 0: # no simulation events, solve it in one go self.run_interval(bd, 0, T, x0, state=state) nintervals = 1 else: # we have simulation events, solve it in chunks self.state.declare_event(None, T) # add an event at end of simulation # ignore all the events at zero tprev = 0 self.state.eventq.pop_until(tprev) # get the state vector x = x0 nintervals = 0 while True: # get next event from the queue and the list of blocks or # clocks at that time tnext, sources = self.state.eventq.pop(dt=1e-6) # run system until next event time x = self.run_interval(bd, tprev, tnext, x, state=state) nintervals += 1 # visit all the blocks and clocks that have an event now for source in sources: if isinstance(source, Clock): # clock ticked, save its state clock.savestate(tnext) clock.next_event(self.state) # get the new state clock._x = clock.getstate() tprev = tnext # are we done? if state.t is not None and state.t >= T: break # finished integration self.progress_done() # cleanup the progress bar # print some info about the integration print(fg('yellow')) print(f"integrator steps: {state.count}") print(f"time steps: {len(state.tlist)}") print(f"integration intervals: {nintervals}") print(attr(0)) # save buffered data in a Struct out = Struct('results') out.t = np.array(state.tlist) out.x = np.array(state.xlist) out.xnames = bd.statenames # save clocked states for c in bd.clocklist: name = c.name.replace('.', '') clockdata = Struct(name) clockdata.t = np.array(c.t) clockdata.x = np.array(c.x) out.add(name, clockdata) # save the watchlist into variables named y0, y1 etc. for i, p in enumerate(watchlist): out['y'+str(i)] = np.array(state.plist[i]) out.ynames = watchnamelist # pause until all graphics blocks close bd.done(block=block) return out
[docs] def done(self, bd, **kwargs): bd.done(graphics=self.options.graphics, **kwargs)
[docs] def run_interval(self, bd, t0, T, x0, state): """ Integrate system over interval :param bd: the system blockdiagram :type bd: BlockDiagram :param t0: initial time :type t0: float :param tf: final time :type tf: float :param x0: initial state vector :type x0: ndarray(n) :param simstate: simulation state object :type simstate: SimState :return: final state vector xf :rtype: ndarray(n) The system is integrated from from ``x0`` to ``xf`` over the interval ``t0`` to ``tf``. """ try: if bd.nstates > 0: # system has continuous states, solve it using numerical integration # print('initial state x0 = ', x0) # block diagram contains states, solve it using numerical integration scipy_integrator = integrate.__dict__[state.solver] # get user specified integrator def ydot(t, y): state.t = t return bd.evaluate_plan(y, t) if state.dt is not None: state.solver_args['max_step'] = state.dt integrator = scipy_integrator(ydot, t0=t0, y0=x0, t_bound=T, **state.solver_args) # integrate while integrator.status == 'running': # step the integrator, calls _deriv and evaluate block diagram multiple times message = integrator.step() if integrator.status == 'failed': print(fg('red') + f"\nintegration completed with failed status: {message}" + attr(0)) break # stash the results state.tlist.append(integrator.t) state.xlist.append(integrator.y) # record the ports on the watchlist for i, p in enumerate(state.watchlist): state.plist[i].append(p.block.output(integrator.t)[p.port]) # # update all blocks that need to know bd.step(state=self.state) self.progress() # update the progress bar if integrator.status == 'finished': break # has any block called a stop? if state.stop is not None: print(fg('red') + f"\n--- stop requested at t={state.t:.4f} by {state.stop}" + attr(0)) break if state.minstepsize is not None and integrator.step_size < state.minstepsize: print(fg('red') + f"\n--- stopping on minimum step size at t={state.t:.4f} with last stepsize {integrator.step_size:g}" + attr(0)) break if 'i' in state.options.debug: bd._debugger(integrator) return integrator.y # return final state vector elif len(clocklist) == 0: # block diagram has no continuous or discrete states for t in np.arange(t0, T, state.dt): # step through the time range # evaluate the block diagram state.t = t bd.evaluate_plan([], t) # stash the results state.tlist.append(t) # record the ports on the watchlist for i, p in enumerate(state.watchlist): state.plist[i].append(p.block.output(t)[p.port]) # update all blocks that need to know bd.step(state=state) self.progress() # update the progress bar # has any block called a stop? if state.stop is not None: print(fg('red') + f"\n--- stop requested at t={state.t:.4f} by {state.stop}" + attr(0)) break if 'i' in state.options.debug: bd._debugger(integrator) else: # block diagram has no continuous states t = t0 state.t = t # evaluate the block diagram bd.evaluate_plan([], t) # stash the results state.tlist.append(t) # record the ports on the watchlist for i, p in enumerate(state.watchlist): state.plist[i].append(p.block.output(t)[p.port]) # update all blocks that need to know bd.step(state=state) self.progress() # update the progress bar # has any block called a stop? if state.stop is not None: print(fg('red') + f"\n--- stop requested at t={bd.simstate.t:.4f} by {bd.simstate.stop}" + attr(0)) if 'i' in state.options.debug: bd._debugger(state=state) except RuntimeError as err: # bad things happens, print a message and return no result print('unrecoverable error in evaluation: ', err) raise
[docs] def blockdiagram(self, name='main'): """ Instantiate a new block diagram object. :param name: diagram name, defaults to 'main' :type name: str, optional :return: parent object for blockdiagram :rtype: BlockDiagram This object describes the connectivity of a set of blocks and wires. It is an instantiation of the ``BlockDiagram`` class with a factory method for every dynamically loaded block which returns an instance of the block. These factory methods have names which are all upper case, for example, the method ``.GAIN`` invokes the constructor for the ``Gain`` class. :seealso: :func:`BlockDiagram` """ # instantiate a new blockdiagram bd = BlockDiagram(name=name) def new_method(cls, bd): # return a wrapper for the block constructor that automatically # adds the block to the diagram's blocklist def block_init_wrapper(self, *args, **kwargs): block = cls(*args, bd=bd, **kwargs) # call __init__ on the block return block # return a function that invokes the class constructor f = block_init_wrapper # move the __init__ docstring to the class to allow BLOCK.__doc__ f.__doc__ = cls.__init__.__doc__ return f # bind the block constructors as new methods on this instance self.blockdict = {} for blockname, info in self._blocklibrary.items(): # create a function to invoke the block's constructor f = new_method(info['class'], bd) # set a bound version of this function as an attribute of the instance # method = types.MethodType(new_method, bd) # setattr(bd, block.name, method) setattr(bd, blockname, f.__get__(self)) # add a clone of the options bd.options = copy.copy(self.options) return bd
[docs] def closefigs(self): for i in range(self.simstate.fignum): print('close', i+1) plt.close(i+1) plt.pause(0.1) self.simstate.fignum = 0 # reset figure counter
[docs] def savefig(self, block, filename=None, format='pdf', **kwargs): block.savefig(filename=filename, format=format, **kwargs)
[docs] def savefigs(self, bd, format='pdf', **kwargs): from bdsim.graphics import GraphicsBlock for b in bd.blocklist: if isinstance(b, GraphicsBlock): b.savefig(filename=b.name, format=format, **kwargs)
[docs] def showgraph(self, bd, **kwargs): # create the temporary dotfile dotfile = tempfile.TemporaryFile(mode="w") bd.dotfile(dotfile, **kwargs) # rewind the dot file, create PDF file in the filesystem, run dot dotfile.seek(0) pdffile = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) subprocess.run("dot -Tpdf", shell=True, stdin=dotfile, stdout=pdffile) # open the PDF file in browser (hopefully portable), then cleanup webbrowser.open(f"file://{pdffile.name}") os.remove(pdffile.name)
[docs] def load_blocks(self, verbose=True): """ Dynamically load all block definitions. :raises ImportError: module could not be imported :return: dictionary of block metadata :rtype: dict of dict Reads blocks from .py files found in bdsim/bdsim/blocks, folders given by colon separated list in envariable BDSIMPATH, and the command line option ``packages``. The result is a dict indexed by the upper-case block name with elements: - ``path`` to the folder holding the Python file defining the block - ``classname`` - ``blockname``, upper case version of ``classname`` - ``url`` of online documentation for the block - ``package`` containing the block - `doc` is the docstring from the class constructor """ packages = ['bdsim', 'roboticstoolbox', 'machinevisiontoolbox'] env = os.getenv('BDSIMPATH') if env is not None: packages.append(env.split) if self.packages is not None: packages.append(self.packages.split(':')) blocks = {} moduledicts = {} for package in packages: try: spec = importlib.util.find_spec('.blocks', package=package) if spec is None: print(f"package {package} has no blocks module") continue pkg = spec.loader.load_module() except ModuleNotFoundError: print(f"package {package} not found") continue moduledict = {} for name, value in pkg.__dict__.items(): # check if it's a valid block class if not inspect.isclass(value): continue if inspect.getmro(value)[-2].__name__ != 'Block': continue if name.endswith('Block'): continue if value.blockclass in ('source', 'transfer', 'function'): # must have an output function valid = hasattr(value, 'output') and \ callable(value.output) and \ len(inspect.signature(value.output).parameters) == 2 if not valid: raise ImportError('class {:s} has missing/improper output method'.format(str(value))) if value.blockclass == 'sink': # must have a step function with at least one # parameter: step(self [,state]) valid = hasattr(value, 'step') and \ callable(value.step) and \ len(inspect.signature(value.step).parameters) >= 1 if not valid: raise ImportError('class {:s} has missing/improper step method'.format(str(value))) # add it to the dict of blocks indexed by module if value.__module__ in moduledict: moduledict[value.__module__].append(name) else: moduledict[value.__module__] = [name] # create a dict for the block with metadata block_info = {} block_info['path'] = pkg.__path__ # path to folder holding block definition block_info['classname'] = name block_info['blockname'] = blockname(name) try: block_info['url'] = pkg.__dict__['url'] + "#" \ + block.__module__ + "." + name except KeyError: block_info['url'] = None block_info['class'] = value block_info['module'] = value.__module__ block_info['package'] = package block_info['doc'] = block.__init__.__doc__ #inspect.getdoc(block) blocks[blockname(name)] = block_info moduledicts[package] = moduledict self.moduledicts = moduledicts return blocks
[docs] def blocks(self): """ List all loaded blocks. Example:: 73 blocks loaded bdsim.blocks.functions..................: Sum Prod Gain Clip Function Interpolate bdsim.blocks.sources....................: Constant Time WaveForm Piecewise Step Ramp bdsim.blocks.sinks......................: Print Stop Null Watch bdsim.blocks.transfers..................: Integrator PoseIntegrator LTI_SS LTI_SISO bdsim.blocks.discrete...................: ZOH DIntegrator DPoseIntegrator bdsim.blocks.linalg.....................: Inverse Transpose Norm Flatten Slice2 Slice1 Det Cond bdsim.blocks.displays...................: Scope ScopeXY ScopeXY1 bdsim.blocks.connections................: Item Dict Mux DeMux Index SubSystem InPort OutPort roboticstoolbox.blocks.arm..............: FKine IKine Jacobian Tr2Delta Delta2Tr Point2Tr TR2T FDyn IDyn Gravload ........................................: Inertia Inertia_X FDyn_X ArmPlot Traj JTraj LSPB CTraj CirclePath roboticstoolbox.blocks.mobile...........: Bicycle Unicycle DiffSteer VehiclePlot roboticstoolbox.blocks.uav..............: MultiRotor MultiRotorMixer MultiRotorPlot machinevisiontoolbox.blocks.camera......: Camera Visjac_p EstPose_p ImagePlane """ def dots(s, n=40): return s + '.' * (n - len(s)) print(len(self._blocklibrary), ' blocks loaded') for pkg, dict in self.moduledicts.items(): for k, v in dict.items(): s = '' once = False while len(v) > 0: n = v.pop(0) + ' ' if len(s + n) < 80: s += n continue else: # line will be too long if not once: print(f"{dots(k)}: {s}") once = True else: print(f"{dots('')}: {s}") s = '' if len(s) > 0: if once: print(f"{dots('')}: {s}") else: print(f"{dots(k)}: {s}")
[docs] def set_options(self, **options): """ Set simulation options at run time The options are the same as those for the constructor. Example:: sim = bdsim.BDsim() sim.set_options(graphics=False) :seealso: :meth:`__init__` """ # TODO: # --no-animation -a # --animation +a # --no-altscreen -A # --altscreen +A for key, value in options.items(): self.options[key] = value # animation and graphics options are coupled # # graphics False, no graphics at all # graphics True, animation False, show graphs at end of run # graphics True, animation True, animate graphs during the run if 'animation' in options and options['animation']: self.options.graphics = True if 'graphics' in options and not options['graphics']: self.options.animation = False
[docs] def get_options(self, sysargs=True, **kwargs): # option priority (high to low): # - command line # - argument to BDSim() # - defaults # all switches and their default values defaults = { 'backend': 'Qt5Agg', # 'TkAgg', 'tiles': '3x4', 'graphics': True, 'animation': False, 'shape': None, 'altscreen': True, 'progress': True, 'verbose': False, 'debug': '' } # any passed kwargs can override the defaults options = {**defaults, **kwargs} # second argument has precedence if sysargs: # command line arguments and graphics parser = argparse.ArgumentParser( prefix_chars='-+', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument('--backend', '-b', type=str, metavar='BACKEND', default=options['backend'], help='matplotlib backend to choose') parser.add_argument('--tiles', '-t', type=str, metavar='ROWSxCOLS', default=options['tiles'], help='window tiling as NxM') parser.add_argument('--shape', type=str, metavar='WIDTHxHEIGHT', default=options['shape'], help='window size as WxH, defaults to matplotlib default') parser.add_argument('-g', '--no-graphics', default=options['graphics'], action='store_const', const=False, dest='graphics', help='disable graphic display, also does --no-animation') parser.add_argument('+g', '--graphics', default=options['graphics'], action='store_const', const=True, dest='graphics', help='enable graphic display') parser.add_argument('-a', '--no-animation', default=options['animation'], action='store_const', const=False, dest='animation', help='do not animate graphics') parser.add_argument('+a', '--animation', default=options['animation'], action='store_const', const=True, dest='animation', help='animate graphics, also does ++graphics') parser.add_argument('+A', '--altscreen', default=options['altscreen'], action='store_const', const=True, dest='altscreen', help='display plots on second monitor') parser.add_argument('-A', '--no-altscreen', default=options['altscreen'], action='store_const', const=False, dest='altscreen', help='do not display plots on second monitor') parser.add_argument('--noprogress', '-p', default=options['progress'], action='store_const', const=False, dest='progress', help='animate graphics') parser.add_argument('--verbose', '-v', default=options['verbose'], action='store_const', const=True, help='debug flags') parser.add_argument('--debug', '-d', type=str, metavar='[psd]', default=options['debug'], help='debug flags: p/ropagate, s/tate, d/eriv, i/nteractive') args, unknown = parser.parse_known_args() options = vars(args) # get args as a dictionary # print(options) # ensure graphics is enabled if animation is requested if options['animation']: options['graphics'] = True if options['verbose']: for k, v in options.items(): print('{:10s}: {:}'.format(k, v)) # stash these away options = Struct(**options, name='Options') return options

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blockdiagram.html ================================================ bdsim.blockdiagram — Block diagram simulation 0.7 documentation

Source code for bdsim.blockdiagram

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon May 18 21:43:18 2020

@author: corkep
"""
import os
from pathlib import Path
import sys
import importlib
import inspect
import traceback
from collections import Counter, namedtuple
import numpy as np
from colored import fg, attr


from ansitable import ANSITable, Column

from bdsim.components import *



# ------------------------------------------------------------------------- #    

[docs]class BlockDiagram: """ Block diagram class. This object is the parent of all blocks and wires in the system. :ivar wirelist: all wires in the diagram :vartype wirelist: list of Wire instances :ivar blocklist: all blocks in the diagram :vartype blocklist: list of Block subclass instances :ivar x: state vector :vartype x: np.ndarray :ivar compiled: diagram has successfully compiled :vartype compiled: bool :ivar blockcounter: unique counter for each block type :vartype blockcounter: collections.Counter :ivar blockdict: index of all blocks by category :vartype blockdict: dict of lists :ivar name: name of this diagram :vartype name: str """ def __init__(self, name='main', **kwargs): self.wirelist = [] # list of all wires self.blocklist = [] # list of all blocks self.clocklist = [] # list of all clock sources self.compiled = False # network has been compiled self.blockcounter = Counter() self.name = name self.nstates = 0 self.ndstates = 0 self._issubsystem = False self.blocknames = {} self.options = None self.n_auto_sum = 0 self.n_auto_prod = 0 self.n_auto_const = 0 self.n_auto_gain = 0 def __getitem__(self, b): return self.blocknames[b] def __len__(self): return len(self.blocklist) @property def issubsystem(self): return self._issubsystem
[docs] def clock(self, *args, **kwargs): clock = Clock(*args, **kwargs) clock.bd = self self.clocklist.append(clock) return clock
[docs] def add_block(self, block): if block.name in self.blocknames: raise ValueError('block {} already added'.format(block.name)) block.id = len(self.blocklist) if block.name is None: i = self.blockcounter[block.type] self.blockcounter[block.type] += 1 block.name = "{:s}.{:d}".format(block.type, i) block.bd = self self.blocklist.append(block) # add to the list of available blocks if block in self.blocknames: raise Warning(f"block name {block} is not unique") self.blocknames[block.name] = block
[docs] def add_wire(self, wire, name=None): wire.id = len(self.wirelist) wire.name = name return self.wirelist.append(wire)
def __str__(self): return 'BlockDiagram: {:s}'.format(self.name) def __repr__(self): return str(self) + " with {:d} blocks and {:d} wires".format(len(self.blocklist), len(self.wirelist)) # for block in self.blocklist: # s += str(block) + "\n" # s += "\n" # for wire in self.wirelist: # s += str(wire) + "\n" # return s.lstrip("\n")
[docs] def ls(self): for k,v in self.blockdict.items(): print('{:12s}: '.format(k), ', '.join(v))
[docs] def connect(self, start, *ends, name=None): """ TODO: s.connect(out[3], in1[2], in2[3]) # one to many block[1] = SigGen() # use setitem block[1] = SumJunction(block2[3], block3[4]) * Gain(value=2) """ # convert to default plug on port 0 if need be if isinstance(start, Block): start = Plug(start, 0) start.type = 'start' for end in ends: if isinstance(end, Block): end = Plug(end, 0) end.type = 'end' if start.isslice and end.isslice: # we have a bundle of signals assert start.width == end.width, 'slice wires must have same width' for (s,e) in zip(start.portlist, end.portlist): wire = Wire( Plug(start.block, s, 'start'), Plug(end.block, e, 'end'), name) self.add_wire(wire) elif start.isslice and not end.isslice: # bundle going to a block assert start.width == start.block.nin, "bundle width doesn't match number of input ports" for inport,outport in enumerate(start.portlist): wire = Wire( Plug(start.block, outport, 'start'), Plug(end.block, inport, 'end'), name) self.add_wire(wire) else: wire = Wire(start, end, name) self.add_wire(wire)
# ---------------------------------------------------------------------- #
[docs] def compile(self, subsystem=False, doimport=True, evaluate=True, report=False, verbose=True): """ Compile the block diagram :param subsystem: importing a subsystems, defaults to False :type subsystem: bool, optional :param doimport: import subsystems, defaults to True :type doimport: bool, optional :raises RuntimeError: various block diagram errors :return: Compile status :rtype: bool Performs a number of operations: - Check sanity of block parameters - Recursively clone and import subsystems - Check for loops without dynamics - Check for inputs driven by more than one wire - Check for unconnected inputs and outputs - Link all output ports to outgoing wires - Link all input ports to incoming wires - Evaluate all blocks in the network """ # name the elements self.nblocks = len(self.blocklist) self.nwires = len(self.wirelist) error = False self.nstates = 0 self.ndstates = 0 self.statenames = [] self.dstatenames = [] self.blocknames = {} if not subsystem and verbose: print('\nCompiling:') # process all subsystem imports # ssblocks = [b for b in self.blocklist if b.type == 'subsystem'] # for b in ssblocks: # print(' importing subsystem', b.name) # if b.ssvar is not None: # print('-- Wiring in subsystem', b, 'from module local variable ', b.ssvar) self.blocklist, self.wirelist = self._subsystem_import(self, None) # check that wires all point to valid blocks for w in self.wirelist: if w.start.block not in self.blocklist: raise RuntimeError(f"wire {w} starts at unreferenced block {w.start.block}") if w.end.block not in self.blocklist: raise RuntimeError(f"wire {w} ends at unreferenced block {w.end.block}") # run block specific checks for b in self.blocklist: try: b.check() except: raise RuntimeError('block failed check ' + str(b)) # build a dictionary of all block names for b in self.blocklist: self.blocknames[b.name] = b # visit all stateful blocks for b in self.blocklist: if b.blockclass == 'transfer': self.nstates += b.nstates if b._state_names is not None: assert len(b._state_names) == b.nstates, 'number of state names not consistent with number of states' self.statenames.extend(b._state_names) else: # create default state names self.statenames.extend([b.name + 'x' + str(i) for i in range(0, b.nstates)]) if b.blockclass == 'clocked': self.ndstates += b.ndstates if b._state_names is not None: assert len(b._state_names) == b.nstates, 'number of state names not consistent with number of states' self.dstatenames.extend(b._state_names) else: # create default state names self.statenames.extend([b.name + 'X' + str(i) for i in range(0, b.nstates)]) # initialize lists of input and output ports for b in self.blocklist: b.outports = [[] for i in range(0, b.nout)] b.inports = [None for i in range(0, b.nin)] b._parents = [None for i in range(0, b.nin)] # connect the source and destination blocks to each wire for w in self.wirelist: try: w.start.block.add_outport(w) w.end.block.add_inport(w) w.end.block._parents[w.end.port] = w.start.block except: print('error connecting wire ', w.fullname + ': ', sys.exc_info()[1]) error = True # check connections every block for b in self.blocklist: # check all inputs are connected for port, connection in enumerate(b.inports): if connection is None: print(' ERROR: [{:s}] input {:d} is not connected'.format(str(b), port)) error = True # check all outputs are connected for port,connections in enumerate(b.outports): if len(connections) == 0: print(' INFORMATION: [{:s}] output {:d} is not connected'.format(str(b), port)) if b._inport_names is not None: assert len(b._inport_names) == b.nin, 'incorrect number of input names given: ' + str(b) if b._outport_names is not None: assert len(b._outport_names) == b.nout, 'incorrect number of output names given: ' + str(b) if b._state_names is not None: assert len(b._state_names) == b.nstates, 'incorrect number of state names given: ' + str(b) # check for cycles of function blocks def _DFS(path): start = path[0] tail = path[-1] for outgoing in tail.outports: # for every port on this block for w in outgoing: dest = w.end.block if dest == start: print(' ERROR: cycle found: ', ' - '.join([str(x) for x in path + [dest]])) return True if dest.blockclass == 'function': return _DFS(path + [dest]) # recurse return False for b in self.blocklist: if b.blockclass == 'function': # do depth first search looking for a cycle if _DFS([b]): error = True if error: if not subsystem: raise RuntimeError('could not compile system') # create the execution plan/schedule self.execution_plan() ## evaluate the network once to check out wire types x = self.getstate0() for clock in self.clocklist: clock._x = clock.getstate0() if report: self.report() self.plan_print() if not subsystem and evaluate: # run all the blocks for one step try: self.evaluate_plan(x, 0.0, sinks=False) except RuntimeError as err: print('\nFrom compile: unrecoverable error in value propagation:', err) traceback.print_exc(file=sys.stderr) error = True if error: # show report if there was an error if not report: self.report() if not subsystem: raise RuntimeError('could not compile system') else: self.compiled = True return self.compiled
def _subsystem_import(self, bd, sspath): blocks = [] wires = bd.wirelist for b in bd.blocklist: # rename the block to include subsystem path if sspath is not None: b.name = sspath + '/' + b.name if b.type == 'subsystem': # deal with a subsystem # - recurse to import it # - add its blocks and wires to the set ssb, ssw = self._subsystem_import(b.subsystem, b.name) blocks.extend(ssb) wires.extend(ssw) # INPORT/OUTPORT blocks now become simple pass throughs # same number of inputs and outputs b.inport.nin = b.inport.nout b.outport.nout = b.outport.nin # modify the wiring, keep the INPORT/OUTPORT blocks but lose # the SUBSYSTEM blocks for w in bd.wirelist: # for all wires at this level, find those that connect # to the subsystem and tweak them if w.start.block == b: # SS output w.start.block = b.outport if w.end.block == b: # SS input w.end.block = b.inport else: # not a subsystem, just add the block to the list blocks.append(b) # systematically renumber all blocks and wires for i, b in enumerate(blocks): b.id = i for i, w in enumerate(wires): w.id = i return blocks, wires # ---------------------------------------------------------------------- #
[docs] def evaluate_plan(self, x, t, checkfinite=True, debuglist=[], sinks=True): """ Evaluate all blocks in the network :param x: state :type x: numpy.ndarray :param t: current time :type t: float :param checkfinite: check for Inf or Nan values in block outputs :type checkfinite: bool :return: state derivative :rtype: numpy.ndarray Performs the following steps: 1. Partition the state vector ``x`` to all stateful blocks 2. Execute the blocks in the order given by the ``plan``. The block outputs are "sent" to their connected inputs. Sink blocks are not executed here, but after completion their inputs will all be valid. """ # TODO: don't copy outputs to inputs of next block, have inputs # pull the value from connected inputs try: self.state.t = t except: pass # TODO: this is super expensive because the string formatting # happens regardless of whether debugging is on self.DEBUG('state', '>>>>>>>>> t={}, x={} >>>>>>>>>>>>>>>>', t, x) # reset all the blocks ready for the evalation self.reset() # split the state vector to stateful blocks for b in self.blocklist: if b.blockclass == 'transfer': x = b.setstate(x) # split the discrete state vector to clocked blocks for clock in self.clocklist: clock.setstate() self.DEBUG('propagate', 't={:.3f}', t) for sequence, group in enumerate(self.plan): # self.DEBUG('propagate', '---- sequence = ', sequence) for b in group: # ask the block for output, check for errors try: out = b.output(t) except Exception as err: # output method failed, report it print('--Error at t={:f} when computing output of block {:s}'.format(t, str(b))) print(' {}'.format(err)) print(' inputs were: ', b.inputs) if b.nstates > 0: print(' state was: ', b._x) traceback.print_exc(file=sys.stderr) raise RuntimeError from None self.DEBUG('propagate', 'block {:s}: output = {}', b, out) # check that output is a list of correct length if not isinstance(out, (tuple, list)): raise AssertionError(f"block {b} output {b} must be a list: {type(out)}") if len(out) != b.nout: raise AssertionError(f"block {b} output {b} has incorrect length: {len(out)} instead of {b.nout}") # TODO check output validity once at the start # check it has no nan or inf values if checkfinite and isinstance(out, (int, float, np.ndarray)) and not np.isfinite(out).any(): raise RuntimeError(f"block {b} output contains NaN") # send block outputs to all downstream connected blocks for (port, outwires) in enumerate(b.outports): # every port value = out[port] for w in outwires: # every wire self.DEBUG('propagate', ' [{}] = {} --> {}[{}]', port, value, w.end.block.name, w.end.port) # send value to wire w.send(value) # TODO send return status no longer needed # TODO use common error handler in all cases above # gather the derivative YD = self.deriv() self.DEBUG('deriv', YD) return YD
[docs] def execution_plan(self): """ Create execution plan The plan is saved in the attribute ``plan`` and is a list ``[L0, L1, ... LN]`` where each ``Li`` is a list of blocks. The blocks in the lists are executed sequentially, ie. all the blocks in ``L0`` then all the blocks in ``L1`` etc. The plan ensures that the inputs of all blocks in ``Li`` have been previously computed. .. note:: - The plan is essentially a dataflow graph. - The blocks in list ``Li`` could potentially be executed in parallel. - Constant blocks and stateful blocks are all executed in ``L0`` - The block attribute ``_sequence`` is ``i`` and indicates its execution order :seealso: :func:`plan_print`, :func:`plan_dotfile` """ plan = [] group = [] for b in self.blocklist: b._sequence = None if b.blockclass in ('source', 'transfer', 'clocked'): b._sequence = 0 group.append(b) plan.append(group) sequence = len(plan) while True: group = [] for b in self.blocklist: if b._sequence is not None: continue # already has a sequence assigned if all([p._sequence < sequence if p._sequence is not None else False for p in b._parents]): group.append(b) for b in group.copy(): b._sequence = sequence if b.blockclass in ('sink', 'graphics'): group.remove(b) if len(group) == 0: break plan.append(group) sequence += 1 self.plan = plan
[docs] def plan_print(self): """ Display execution plan in tabular form :seealso: :func:`execution_plan`, :func:`plan_dotfile` """ table = ANSITable( Column("Sequence"), Column("Blocks", colalign='<', headalign='^'), border='thin' ) for sequence, group in enumerate(self.plan): table.row(sequence, ', '.join([str(b) for b in group])) table.print()
[docs] def plan_dotfile(self, filename): """ Write a GraphViz dot file representing the execution schedule :param file: Name of file to write to :type file: str The file can be processed using neato or dot:: % dot -Tpng -o out.png dotfile.dot Display execution plan as a dataflow graph. :seealso: :func:`execution_plan`, :func:`plan_print` """ if isinstance(filename, str): file = open(filename, 'w') else: file = filename header = r"""digraph G { graph [splines=ortho, rankdir=LR, splines=spline] node [shape=box] """ file.write(header) for sequence, group in enumerate(self.plan): # for each execution group, place the blocks in a subgraph file.write('\tsubgraph step{:d} {{\n'.format(sequence)) file.write('\t\trank=same;\n') for b in group: file.write('\t\t"{:s}"\n'.format(b.name)) file.write('\t}\n\n') # connect them to their parents, except if a transfer block for b in self.blocklist: if not b.blockclass == 'transfer': for p in b._parents: file.write('\t"{:s}" -> "{:s}"\n'.format(p.name, b.name)) file.write('}\n')
# ---------------------------------------------------------------------- # def _debugger(self, state=None, integrator=None): if state.t_stop is not None and state.t < state.t_stop: return state.t_stop = None print('\n') while True: cmd = input(f"(bdsim, t={state.t:.4f}) ") if len(cmd) == 0: continue if cmd[0] == 'p': # print variables if len(cmd) > 1: id = int(cmd[1:]) b = self.blocklist[id] print(b.name, b.output(t=state.t)) else: for b in self.blocklist: if b.nout > 0: print(b.name, b.output(t=state.t)) elif cmd[0] == 'i': print(integrator.status, integrator.step_size, integrator.nfev) elif cmd[0] == 's': # step break elif cmd[0] == 'c': # continue self.debug_stop = False self.t_stop = None break elif cmd[0] == 't': self.t_stop = float(cmd[1:]) break elif cmd[0] == 'q': sys.exit(1) elif cmd[0] in 'h?': print("p print all outputs") print("pI print block id I output") print("i print integrator status") print("s single step") print("c continue") print("tT stop at or after time T") print("q quit") # ---------------------------------------------------------------------- #
[docs] def report(self): """ Print a tabular report about the block diagram """ # print all the blocks print('\nBlocks::\n') table = ANSITable( Column("id"), Column("name"), Column("nin"), Column("nout"), Column("nstate"), Column("ndstate"), Column("type", headalign="^", colalign="<"), border="thin" ) for b in self.blocklist: table.row( b.id, str(b), b.nin, b.nout, b.nstates, b.ndstates, b.type) table.print() # print all the wires print('\nWires::\n') table = ANSITable( Column("id"), Column("from", headalign="^"), Column("to", headalign="^"), Column("description", headalign="^", colalign="<"), Column("type", headalign="^", colalign="<"), border="thin" ) for w in self.wirelist: start = "{:d}[{:d}]".format(w.start.block.id, w.start.port) end = "{:d}[{:d}]".format(w.end.block.id, w.end.port) try: value = w.end.block.inputs[w.end.port] typ = type(value).__name__ if isinstance(value, np.ndarray): typ += ' {:s}'.format(str(value.shape)) except: typ = '??' table.row( w.id, start, end, w.fullname, typ) table.print() if len(self.clocklist) > 0: # print all the clocked blocks print('\nClocked blocks::\n') table = ANSITable( Column("id"), Column("block"), Column("clock"), Column("period"), Column("offset"), border="thin" ) for b in self.blocklist: if b.blockclass == 'clocked': c = b.clock table.row( b.id, str(b), c.name, c.T, c.offset) table.print() print('\nContinuous state variables: {:d}'.format(self.nstates)) print( 'Discrete state variables: {:d}'.format(self.ndstates)) if not self.compiled: print('** System has not been compiled, or had a compile time error')
# ---------------------------------------------------------------------- # def _error_handler(self, where, block): # called from except clause import traceback import types t, v, tb = sys.exc_info() # get the exception print(fg('red')) # red text # print the traceback print(f"[{where}]: exception {t.__name__} occurred in {block.type} block {block.name} ") print(f" {v}\n") traceback.print_tb(tb) # print all block inputs print() for i in range(block.nin): input = block.inputs[i] print(f"input {i} from {block.inports[i].start.block.name} [{input.__class__.__name__}]") print(' ', input) print(attr(0)) # default text # traceback = err[2] # back_frame = traceback.tb_frame.f_back # back_tb = types.TracebackType(tb_next=None, # tb_frame=back_frame, # tb_lasti=back_frame.f_lasti, # tb_lineno=back_frame.f_lineno) # raise RuntimeError('Fatal failure').with_traceback(back_tb) raise RuntimeError('Fatal failure') from None
[docs] def getstate0(self): # get the state from each stateful block x0 = np.array([]) for b in self.blocklist: try: if b.blockclass == 'transfer': x0 = np.r_[x0, b.getstate0()] #print('x0', x0) except: self._error_handler('getstate0', b) return x0
[docs] def reset(self): """ Reset conditions within every active block. Most importantly, all inputs are marked as unknown. Invokes the `reset` method on all blocks. """ for b in self.blocklist: try: b.reset() except: self._error_handler('reset', b)
[docs] def step(self, state=None): """ Step all blocks :param state: simulation state, defaults to None :type state: SimState, optional :param graphics: graphics enabled, defaults to False :type graphics: bool, optional Tell all blocks to take action on new inputs by invoking their ``step`` method and passing the ``state`` object. Used to save results to a figure or file .. note:: - if ``graphics`` is False, Graphics blocks are not called """ # TODO could be done by output method, even if no outputs for b in self.blocklist: if state.options.graphics and b.isgraphics: try: b.step(state=state) state.count += 1 except: self._error_handler('step', b)
[docs] def deriv(self): """ Harvest derivatives from all blocks . """ YD = np.array([]) for b in self.blocklist: if b.blockclass == 'transfer': try: yd = b.deriv().flatten() if not isinstance(yd, np.ndarray): raise AssertionError(f"deriv: block {b} did not return ndarray") if yd.ndim != 1 or yd.shape[0] != b.nstates: raise AssertionError(f"deriv: block {b} returns wrong shape {yd.shape}, should be ({b.nstates},)") YD = np.r_[YD, yd] except: self._error_handler('deriv', b) return YD
[docs] def start(self, graphics=False, state=None, **kwargs): """ Start all blocks :param state: simulation state, defaults to None :type state: SimState, optional :param graphics: graphics enabled, defaults to False :type graphics: bool, optional Inform all blocks that BlockDiagram execution is about to start by invoking their ``start`` method and passing the ``state`` object. Used to open files, create figures etc. .. note:: if ``graphics`` is False, Graphics blocks are not called """ for c in self.clocklist: try: c.start(state=state, **kwargs) except: self._error_handler('start clock', c) # safe wrapper for block starting, does error handling for b in self.blocklist: if b.isgraphics and not graphics: continue # print('starting block', b) try: b.start(state=state, **kwargs) except: self._error_handler('block.start', b)
[docs] def initialstate(self): for b in self.blocklist: if b.blockclass in ('transfer', 'clocked'): b._x = b._x0
[docs] def done(self, graphics=False, **kwargs): """ Finishup all blocks :param state: simulation state, defaults to None :type state: SimState, optional :param graphics: graphics enabled, defaults to False :type graphics: bool, optional Inform all blocks that BlockDiagram execution is complete by invoking their ``done`` method and passing options. Used to close files, display figures etc. .. note:: if ``graphics`` is False, Graphics blocks are not called """ for b in self.blocklist: if b.isgraphics and not graphics: continue try: b.done(**kwargs) except: self._error_handler('block.done', b)
[docs] def dotfile(self, filename): """ Write a GraphViz dot file representing the network. :param file: Name of file to write to :type file: str The file can be processed using neato or dot:: % dot -Tpng -o out.png dotfile.dot """ if isinstance(filename, str): file = open(filename, 'w') else: file = filename header = r"""digraph G { graph [splines=ortho, rankdir=LR] node [shape=box] """ file.write(header) # add the blocks for b in self.blocklist: options = [] if b.blockclass == "source": options.append("shape=box3d") elif b.blockclass == "sink": options.append("shape=folder") elif b.blockclass == "function": if b.type == 'gain': options.append("shape=triangle") options.append("orientation=-90") options.append('label="{:g}"'.format(b.gain)) elif b.type == 'sum': options.append("shape=point") elif b.blockclass == 'transfer': options.append("shape=component") if b.pos is not None: options.append('pos="{:g},{:g}!"'.format(b.pos[0], b.pos[1])) options.append('xlabel=<<BR/><FONT POINT-SIZE="8" COLOR="blue">{:s}</FONT>>'.format(b.type)) file.write('\t"{:s}" [{:s}]\n'.format(b.name, ', '.join(options))) # add the wires for w in self.wirelist: options = [] #options.append('xlabel="{:s}"'.format(w.name)) if w.end.block.type == 'sum': options.append('headlabel="{:s} "'.format(w.end.block.signs[w.end.port])) file.write('\t"{:s}" -> "{:s}" [{:s}]\n'.format(w.start.block.name, w.end.block.name, ', '.join(options))) file.write('}\n')
[docs] def blockvalues(self): for b in self.blocklist: print('Block {:s}:'.format(b.name)) print(' inputs: ', b.inputs) print(' outputs: ', b.output(t=0))
[docs] def DEBUG(self, debug, fmt, *args): if debug[0] in self.options.debug: print('DEBUG.{:s}: ' + fmt.format(*args))
if __name__ == "__main__": import bdsim bd = bdsim.BlockDiagram() # define the blocks demand = bd.STEP(T=1, pos=(0,0), name='demand') sum = bd.SUM('+-', pos=(1,0)) gain = bd.GAIN(10, pos=(1.5,0)) plant = bd.LTI_SISO(0.5, [2, 1], name='plant', pos=(3,0)) #scope = bd.SCOPE(pos=(4,0), styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}) scope = bd.SCOPE(nin=2, styles=['k', 'r--'], pos=(4,0)) # connect the blocks bd.connect(demand, sum[0], scope[1]) bd.connect(plant, sum[1]) bd.connect(sum, gain) bd.connect(gain, plant) bd.connect(plant, scope[0]) bd.compile() # check the diagram bd.report() # list all blocks and wires bd.run(5, debug=True) # from pathlib import Path # exec(open(Path(__file__).parent.absolute() / "test_blockdiagram.py").read())

© Copyright 2020, Peter Corke Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blocks/connections.html ================================================ bdsim.blocks.connections — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.connections


"""
Connection blocks are in two categories:

1. Signal manipulation:
    - have inputs and outputs
    - have no state variables
    - are a subclass of ``FunctionBlock`` |rarr| ``Block``
2. Subsystem support
    - have inputs or outputs
    - have no state variables
    - are a subclass of ``SubsysytemBlock`` |rarr| ``Block``

"""

# The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance.

import importlib.util
import numpy as np
import copy

import bdsim
from bdsim.components import SubsystemBlock, SourceBlock, SinkBlock, FunctionBlock

# ------------------------------------------------------------------------ #
[docs]class Item(FunctionBlock): """ :blockname:`ITEM` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | dict | any | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, item, **kwargs): """ Selector item from a dictionary signal. :param item: name of dictionary item :type item: str :param kwargs: |BlockOptions| :type kwargs: dict :return: An ITEM block :rtype: Item instance For a dictionary type input signal, select one item as the output signal. For example:: ITEM('xd') selects the ``xd`` item from the dictionary signal input to the block. A dictionary signal can serve a similar purpose to a "bus" in Simulink(R). This is somewhat like a demultiplexer :class:`DeMux` but allows for named heterogeneous data. :seealso: :class:`Dict` """ super().__init__(**kwargs) self.item = item
def output(self, t=None): # TODO, handle inputs that are vectors themselves assert isinstance(self.inputs[0], dict), 'Input signal must be a dict' assert self.item in self.inputs[0], 'Item is not in input dict' return [self.inputs[0][self.item]]
[docs]class Dict(FunctionBlock): """ :blockname:`DICT` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | N | 1 | 0 | +------------+---------+---------+ | any | dict | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, item, **kwargs): """ Create a dictionary signal. :param keys: list of dictionary keys :type keys: list :param kwargs: |BlockOptions| :type kwargs: dict :return: A DICT block :rtype: Dict instance Inputs are assigned to a dictionary signal, using the corresponding names from ``keys``. For example:: DICT(['x', 'xd', 'xdd']) expects three inputs and assigns them to dictionary items ``x``, ``xd``, ``xdd`` of the output dictionary respectively. A dictionary signal can serve a similar purpose to a "bus" in Simulink(R). This is somewhat like a multiplexer :class:`Mux` but allows for named heterogeneous data. :seealso: :class:`Item` :class:`Mux` """ super().__init__(**kwargs) self.item = item
def output(self, t=None): # TODO, handle inputs that are vectors themselves assert isinstance(self.inputs[0], dict), 'Input signal must be a dict' assert self.item in self.inputs[0], 'Item is not in signal dict' return [self.inputs[0][self.item]]
# ------------------------------------------------------------------------ #
[docs]class Mux(FunctionBlock): """ :blockname:`MUX` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | nin | 1 | 0 | +------------+---------+---------+ | float, | A(M,) | | | A(N,) | A(M,) | | +------------+---------+---------+ """ nin = -1 nout = 1
[docs] def __init__(self, nin=1, **kwargs): """ Multiplex signals. :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param kwargs: |BlockOptions| :type kwargs: dict :return: A MUX block :rtype: Mux instance This block takes a number of scalar or 1D-array signals and concatenates them into a single 1-D array signal. For example:: MUX(2, inputs=(func1[2], sum3)) multiplexes the outputs of blocks ``func1`` (port 2) and ``sum3`` into a single output vector as a 1D-array. :seealso: :class:`Dict` """ super().__init__(nin=nin, **kwargs)
def output(self, t=None): # TODO, handle inputs that are vectors themselves out = [] for input in self.inputs: if isinstance(input, (int, float, bool)): out.append(input) elif isinstance(input, np.ndarray): out.extend(input.flatten().tolist()) return [ np.array(out) ]
# ------------------------------------------------------------------------ #
[docs]class DeMux(FunctionBlock): """ :blockname:`DEMUX` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | nout | 0 | +------------+---------+---------+ | float, | float | | | A(nout,) | | | +------------+---------+---------+ """ nin = 1 nout = -1
[docs] def __init__(self, nout=1, **kwargs): """ Demultiplex signals. :param nout: number of outputs, defaults to 1 :type nout: int, optional :param kwargs: |BlockOptions| :type kwargs: dict :return: A DEMUX block :rtype: DeMux instance This block has a single input port and ``nout`` output ports. A 1D-array input signal (with ``nout`` elements) is routed element-wise to individual scalar output ports. """ super().__init__(nout=nout, **kwargs)
def output(self, t=None): # TODO, handle inputs that are vectors themselves assert len(self.inputs[0]) == self.nout, 'Input width not equal to number of output ports' return list(self.inputs[0])
# ------------------------------------------------------------------------ #
[docs]class Index(FunctionBlock): """ :blockname:`INDEX` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | ndarray | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, index=[], **kwargs): """ Index an iterable signal. :param index: elements of input array, defaults to [] :type index: list, slice or str, optional :param kwargs: |BlockOptions| :type kwargs: dict :return: An INDEX block :rtype: Index instance The specified element(s) of the input iterable (list, string, etc.) are output. The index can be an integer, sequence of integers, a Python slice object, or a string with Python slice notation, eg. ``"::-1"``. :seealso: :class:`Slice1` :class:`Slice2` """ super().__init__(**kwargs) if isinstance(index, str): args = [None if a == '' else int(a) for a in index.split(':')] self.index = slice(*args) self.index = index
def output(self, t=None): if len(self.index) == 1: return [self.inputs[0][self.index[0]]] else: return [np.r_[[self.inputs[0][i] for i in self.index]]]
# ------------------------------------------------------------------------ #
[docs]class SubSystem(SubsystemBlock): """ :blockname:`SUBSYSTEM` .. table:: :align: left +------------+------------+---------+ | inputs | outputs | states | +------------+------------+---------+ | ss.in.nout | ss.out.nin | 0 | +------------+------------+---------+ | any | any | | +------------+------------+---------+ """ nin = -1 nout = -1
[docs] def __init__(self, subsys, nin=1, nout=1, **kwargs): """ Instantiate a subsystem. :param subsys: Subsystem as either a filename or a ``BlockDiagram`` instance :type subsys: str or BlockDiagram :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param nout: Number of output ports, defaults to 1 :type nout: int, optional :param kwargs: |BlockOptions| :type kwargs: dict :raises ImportError: DESCRIPTION :raises ValueError: DESCRIPTION :return: A SUBSYSTEM block :rtype: SubSystem instance This block represents a subsystem in a block diagram. The definition of the subsystem can be: - the name of a module which is imported and must contain only only ``BlockDiagram`` instance, or - a ``BlockDiagram`` instance The referenced block diagram must contain one or both of: - one ``InPort`` block, which has outputs but no inputs. These outputs are connected to the inputs to the enclosing ``SubSystem`` block. - one ``OutPort`` block, which has inputs but no outputs. These inputs are connected to the outputs to the enclosing ``SubSystem`` block. .. note:: - The referenced block diagram is treated like a macro and copied into the parent block diagram at compile time. The ``SubSystem``, ``InPort`` and ``OutPort`` blocks are eliminated, that is, all hierarchical structure is lost. - The same subsystem can be used multiple times, its blocks and wires will be cloned. Subsystems can also include subsystems. - The number of input and output ports is not specified, they are computed from the number of ports on the ``InPort`` and ``OutPort`` blocks within the subsystem. """ super().__init__(**kwargs) if isinstance(subsys, str): # attempt to import the file try: module = importlib.import_module(subsys, package='.') except SyntaxError: print('-- syntax error in block definiton: ' + subsys) except ModuleNotFoundError: print('-- module not found ', subsys) # get all the bdsim.BlockDiagram instances simvars = [name for name, ref in module.__dict__.items() if isinstance(ref, bdsim.BlockDiagram)] if len(simvars) == 0: raise ImportError('no bdsim.Simulation instances in imported module') elif len(simvars) > 1: raise ImportError('multiple bdsim.Simulation instances in imported module' + str(simvars)) subsys = module.__dict__[simvars[0]] self.ssvar = simvars[0] elif isinstance(subsys, bdsim.BlockDiagram): # use an in-memory digram self.ssvar = None else: raise ValueError('argument must be filename or BlockDiagram instance') # check if valid input and output ports ninp = 0 noutp = 0 for b in subsys.blocklist: if b.type == 'inport': ninp += 1 elif b.type == 'outport': noutp += 1 if ninp > 1: raise ValueError('subsystem cannot have more than one INPORT block') if noutp > 1: raise ValueError('subsystem cannot have more than one OUTPORT block') if ninp + noutp == 0: raise ValueError('subsystem cannot have zero INPORT or OUTPORT blocks') # it's valid, make a deep copy self.subsystem = copy.deepcopy(subsys) # get references to the input and output port blocks self.inport = None self.outport = None for b in self.subsystem.blocklist: if b.type == 'inport': self.inport = b elif b.type == 'outport': self.outport = b self.ssname = subsys.name
# ------------------------------------------------------------------------ #
[docs]class InPort(SubsystemBlock): """ :blockname:`INPORT` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 0 | nout | 0 | +------------+---------+---------+ | | any | | +------------+---------+---------+ """ nin = 0 nout = -1
[docs] def __init__(self, nout=1, **kwargs): """ Input ports for a subsystem. :param nout: Number of output ports, defaults to 1 :type nout: int, optional :param kwargs: |BlockOptions| :type kwargs: dict :return: An INPORT block :rtype: InPort instance This block connects a subsystem to a parent block diagram. Inputs to the parent-level ``SubSystem`` block appear as the outputs of this block. .. note:: Only one ``INPORT`` block can appear in a block diagram but it can have multiple ports. This is different to Simulink(R) which would require multiple single-port input blocks. """ super().__init__(nout=nout, **kwargs)
def output(self, t=None): # signal feed through return self.inputs
# ------------------------------------------------------------------------ #
[docs]class OutPort(SubsystemBlock): """ :blockname:`OUTPORT` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | nin | 0 | 0 | +------------+---------+---------+ | any | | | +------------+---------+---------+ """ nin = -1 nout = 0
[docs] def __init__(self, nin=1, **kwargs): """ Output ports for a subsystem. :param nin: Number of input ports, defaults to 1 :type nin: int, optional :param kwargs: |BlockOptions| :type kwargs: dict :return: A OUTPORT block :rtype: OutPort instance This block connects a subsystem to a parent block diagram. The the inputs of this block become the outputs of the parent-level ``SubSystem`` block. .. note:: Only one ``OUTPORT`` block can appear in a block diagram but it can have multiple ports. This is different to Simulink(R) which would require multiple single-port output blocks. """ super().__init__(nin=nin, **kwargs)
def output(self, t=None): # signal feed through return self.inputs
if __name__ == "__main__": import pathlib import os.path exec(open(os.path.join(pathlib.Path(__file__).parent.absolute(), "test_connections.py")).read())

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blocks/discrete.html ================================================ bdsim.blocks.discrete — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.discrete

"""
Transfer blocks:

- have inputs and outputs
- have state variables
- are a subclass of ``TransferBlock`` |rarr| ``Block``

"""

import numpy as np
import math
from math import sin, cos, atan2, sqrt, pi

import matplotlib.pyplot as plt
import inspect
from spatialmath import base, Twist3, SE3

from bdsim.components import ClockedBlock

# ------------------------------------------------------------------------ 


[docs]class ZOH(ClockedBlock): """ :blockname:`ZOH` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | N | +------------+---------+---------+ | float, | float, | | | A(N,) | A(N,) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, clock, x0=0, **blockargs): """ Zero-order hold. :param clock: clock source :type clock: Clock :param x0: Initial value of the hold, defaults to 0 :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a ZOH block :rtype: Integrator instance Output is the input at the previous clock time. The state can be a scalar or a vector, this is given by the type of ``x0``. .. note:: If input is not a scalar, ``x0`` must have the shape of the input signal. """ self.type = 'sampler' super().__init__(nin=1, nout=1, clock=clock, **blockargs) x0 = base.getvector(x0) self._x0 = x0 self.ndstates = len(x0)
# print('nstates', self.nstates) def output(self, t=None): # print('* output, x is ', self._x) return [self._x]
[docs] def next(self): xnext = np.array(self.inputs) return xnext
# ------------------------------------------------------------------------
[docs]class DIntegrator(ClockedBlock): """ :blockname:`DINTEGRATOR` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | N | +------------+---------+---------+ | float, | float, | | | A(N,) | A(N,) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, clock, x0=0, gain=1.0, min=None, max=None, **blockargs): """ Discrete-time integrator. :param clock: clock source :type clock: Clock :param x0: Initial state, defaults to 0 :type x0: array_like, optional :param min: Minimum value of state, defaults to None :type min: float or array_like, optional :param max: Maximum value of state, defaults to None :type max: float or array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: an INTEGRATOR block :rtype: Integrator instance Create a discrete-time integrator block. Output is the time integral of the input. The state can be a scalar or a vector, this is given by the type of ``x0``. The minimum and maximum values can be: - a scalar, in which case the same value applies to every element of the state vector, or - a vector, of the same shape as ``x0`` that applies elementwise to the state. """ super().__init__(clock=clock, **blockargs) if isinstance(x0, (int, float)): self.ndstates = 1 if min is None: min = -math.inf if max is None: max = math.inf else: if isinstance(x0, np.ndarray): if x0.ndim > 1: raise ValueError('state must be a 1D vector') else: x0 = base.getvector(x0) self.ndstates = x0.shape[0] if min is None: min = [-math.inf] * self.nstates elif len(min) != self.nstates: raise ValueError('minimum bound length must match x0') if max is None: max = [math.inf] * self.nstates elif len(max) != self.nstates: raise ValueError('maximum bound length must match x0') self._x0 = np.r_[x0] self.min = np.r_[min] self.max = np.r_[max] self.gain = gain self.ndstates = len(x0)
def output(self, t=None): return [self._x]
[docs] def next(self): xnext = self._x + self.gain * self.clock.T * np.array(self.inputs[0]) return xnext
[docs]class DPoseIntegrator(ClockedBlock): """ :blockname:`DPOSEINTEGRATOR` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | N | +------------+---------+---------+ | A(6,) | SE3 | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('ν',) outlabels = ('ξ',)
[docs] def __init__(self, clock, x0=None, **blockargs): r""" Discrete-time spatial velocity integrator. :param clock: clock source :type clock: Clock :param x0: Initial pose, defaults to null :type x0: SE3, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: an DPOSEINTEGRATOR block :rtype: Integrator instance This block integrates spatial velocity over time. The block input is a spatial velocity as a 6-vector :math:`(v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)` and the output is pose as an ``SE3`` instance. .. note:: State is a velocity twist. """ super().__init__(clock=clock, **blockargs) if x0 is None: x0 = Twist3() elif isinstance(x0, SE3): x0 = Twist3(x0).A elif isinstance(x0, Twist3): x0 = x0.A elif isvector(x0, 6): x0 = getvector(x0, 6) self.ndstates = 6 self._x0 = x0 print('nstates', self.nstates, x0)
def output(self, t=None): return [Twist3(self._x).SE3()]
[docs] def next(self): T_delta = SE3.Delta(self.inputs[0] * self.clock.T) pose = Twist3(self._x).SE3() * T_delta return Twist3(pose).A
# ------------------------------------------------------------------------ # # @block # class LTI_SS(TransferBlock): # """ # :blockname:`LTI_SS` # .. table:: # :align: left # +------------+---------+---------+ # | inputs | outputs | states | # +------------+---------+---------+ # | 1 | 01 | nc | # +------------+---------+---------+ # | float, | float, | | # | A(nb,) | A(nc,) | | # +------------+---------+---------+ # """ # def __init__(self, *inputs, A=None, B=None, C=None, x0=None, verbose=False, **blockargs): # r""" # :param ``*inputs``: Optional incoming connections # :type ``*inputs``: Block or Plug # :param N: numerator coefficients, defaults to 1 # :type N: array_like, optional # :param D: denominator coefficients, defaults to [1, 1] # :type D: array_like, optional # :param x0: initial states, defaults to zero # :type x0: array_like, optional # :param ``**blockargs``: |BlockOptions| # :return: A SCOPE block # :rtype: LTI_SISO instance # Create a state-space LTI block. # Describes the dynamics of a single-input single-output (SISO) linear # time invariant (LTI) system described by numerator and denominator # polynomial coefficients. # Coefficients are given in the order from highest order to zeroth # order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``. # Only proper transfer functions, where order of numerator is less # than denominator are allowed. # The order of the states in ``x0`` is consistent with controller canonical # form. # Examples:: # LTI_SISO(N=[1,2], D=[2, 3, -4]) # is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`. # """ # #print('in SS constructor') # self.type = 'LTI SS' # assert A.shape[0] == A.shape[1], 'A must be square' # n = A.shape[0] # if len(B.shape) == 1: # nin = 1 # B = B.reshape((n, 1)) # else: # nin = B.shape[1] # assert B.shape[0] == n, 'B must have same number of rows as A' # if len(C.shape) == 1: # nout = 1 # assert C.shape[0] == n, 'C must have same number of columns as A' # C = C.reshape((1, n)) # else: # nout = C.shape[0] # assert C.shape[1] == n, 'C must have same number of columns as A' # super().__init__(nin=nin, nout=nout, inputs=inputs, **blockargs) # self.A = A # self.B = B # self.C = C # self.nstates = A.shape[0] # if x0 is None: # self._x0 = np.zeros((self.nstates,)) # else: # self._x0 = x0 # def output(self, t=None): # return list(self.C @ self._x) # def deriv(self): # return self.A @ self._x + self.B @ np.array(self.inputs) # # ------------------------------------------------------------------------ # # @block # class LTI_SISO(LTI_SS): # """ # :blockname:`LTI_SISO` # .. table:: # :align: left # +------------+---------+---------+ # | inputs | outputs | states | # +------------+---------+---------+ # | 1 | 1 | n | # +------------+---------+---------+ # | float | float | | # +------------+---------+---------+ # """ # def __init__(self, N=1, D=[1, 1], *inputs, x0=None, verbose=False, **blockargs): # r""" # :param N: numerator coefficients, defaults to 1 # :type N: array_like, optional # :param D: denominator coefficients, defaults to [1, 1] # :type D: array_like, optional # :param ``*inputs``: Optional incoming connections # :type ``*inputs``: Block or Plug # :param x0: initial states, defaults to zero # :type x0: array_like, optional # :param ``**blockargs``: |BlockOptions| # :return: A SCOPE block # :rtype: LTI_SISO instance # Create a SISO LTI block. # Describes the dynamics of a single-input single-output (SISO) linear # time invariant (LTI) system described by numerator and denominator # polynomial coefficients. # Coefficients are given in the order from highest order to zeroth # order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``. # Only proper transfer functions, where order of numerator is less # than denominator are allowed. # The order of the states in ``x0`` is consistent with controller canonical # form. # Examples:: # LTI_SISO(N=[1, 2], D=[2, 3, -4]) # is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`. # """ # #print('in SISO constscutor') # if not isinstance(N, list): # N = [N] # if not isinstance(D, list): # D = [D] # self.N = N # self.D = N # n = len(D) - 1 # nn = len(N) # if x0 is None: # x0 = np.zeros((n,)) # assert nn <= n, 'direct pass through is not supported' # # convert to numpy arrays # N = np.r_[np.zeros((len(D) - len(N),)), np.array(N)] # D = np.array(D) # # normalize the coefficients to obtain # # # # b_0 s^n + b_1 s^(n-1) + ... + b_n # # --------------------------------- # # a_0 s^n + a_1 s^(n-1) + ....+ a_n # # normalize so leading coefficient of denominator is one # D0 = D[0] # D = D / D0 # N = N / D0 # A = np.eye(len(D) - 1, k=1) # control canonic (companion matrix) form # A[-1, :] = -D[1:] # B = np.zeros((n, 1)) # B[-1] = 1 # C = (N[1:] - N[0] * D[1:]).reshape((1, n)) # if verbose: # print('A=', A) # print('B=', B) # print('C=', C) # super().__init__(A=A, B=B, C=C, x0=x0, **blockargs) # self.type = 'LTI' # if __name__ == "__main__": # import pathlib # import os.path # exec(open(os.path.join(pathlib.Path( # __file__).parent.absolute(), "test_transfers.py")).read())

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blocks/functions.html ================================================ bdsim.blocks.functions — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.functions

"""
Function blocks:

- have inputs and outputs
- have no state variables
- are a subclass of ``FunctionBlock`` |rarr| ``Block``

"""

# The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance.

import numpy as np
import scipy.interpolate
import math
import inspect

from bdsim.components import FunctionBlock


# PID
# product
# saturation
# transform 3D points

        
[docs]class Sum(FunctionBlock): """ :blockname:`SUM` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | len(signs) | 1 | 0 | +------------+---------+---------+ | float, | float, | | | A(N,), | A(N,), | | | A(N,M) | A(N,M) | | +------------+---------+---------+ """ nin = -1 nout = 1
[docs] def __init__(self, signs='++', angles=False, **blockargs): """ Summing junction. :param signs: signs associated with input ports, accepted characters: + or -, defaults to '++' :type signs: str, optional :param angles: the signals are angles, wraps to [-pi,pi], defaults to False :type angles: bool, optional :param blockargs: common `Block options <https://petercorke.github.io/bdsim/bdsim.html?highlight=block.__init__#bdsim.components.Block.__init__`_ :type blockargs: dict :return: A SUM block :rtype: Sum instance Add or subtract input signals according to the `signs` string. The number of input ports is the length of this string. For example:: sum = bd.SUM('+-+') is a 3-input summing junction which computes port0 - port1 + port2. Implicit SUM blocks are created by:: sum = block1 + block2 which will create a summation block named "_sum.N". .. note:: The signals must be compatible, all scalars, or all arrays of the same shape. """ super().__init__(nin=len(signs), **blockargs) assert isinstance(signs, str), 'first argument must be signs string' assert all([x in '+-' for x in signs]), 'invalid sign' self.signs = signs self.angles = angles
def output(self, t=None): for i, input in enumerate(self.inputs): # code makes no assumption about types of inputs # NOTE: use sum = sum =/- input rather than sum +/-= input since # these are references if self.signs[i] == '-': if i == 0: sum = -input else: sum = sum - input else: if i == 0: sum = input else: sum = sum + input if self.angles: sum = np.mod(sum + math.pi, 2 * math.pi) - math.pi return [sum]
# ------------------------------------------------------------------------ #
[docs]class Prod(FunctionBlock): """ :blockname:`PROD` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | len(ops) | 1 | 0 | +------------+---------+---------+ | float, | float, | | | A(N,), | A(N,), | | | A(N,M) | A(N,M) | | +------------+---------+---------+ """ nin = -1 nout = 1
[docs] def __init__(self, ops='**', matrix=False, **blockargs): """ Product junction. :param ops: operations associated with input ports, accepted characters: * or /, defaults to '**' :type ops: str, optional :param inputs: Optional incoming connections :type inputs: Block or Plug :param matrix: Arguments are matrices, defaults to False :type matrix: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A PROD block :rtype: Prod instance Multiply or divide input signals according to the `ops` string. The number of input ports is the length of this string. For example:: prod = PROD('*/*') is a 3-input product junction which computes port0 / port 1 * port2. Implicit PROD blocks are created by:: sum = block1 block2 which will create a summation block named "_prod.N". .. note:: - The inputs can be scalars or NumPy arrays. - By default the ``*`` and ``/`` operators are used. - The option ``matrix`` will instead use ``@`` and ``@ np.linalg.inv()``. - the shapes of matrices must conform. - only square matrices are supported. """ super().__init__(nin=len(ops), **blockargs) assert isinstance(ops, str), 'first argument must be signs string' assert all([x in '*/' for x in ops]), 'invalid op' self.ops = ops self.matrix = matrix
def output(self, t=None): for i, input in enumerate(self.inputs): if i == 0: if self.ops[i] == '*': prod = input else: if self.matrix: prod = numpy.linalg.inv(input) prod = 1.0 / input else: if self.ops[i] == '*': if self.matrix: prod = prod @ input else: prod = prod * input else: if self.matrix: prod = prod @ numpy.linalg.inv(input) else: prod = prod / input return [prod]
# ------------------------------------------------------------------------ #
[docs]class Gain(FunctionBlock): """ :blockname:`GAIN` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | float, | float, | | | A(N,), | A(N,), | | | A(N,M) | A(N,M) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, K=1, premul=False, **blockargs): """ Gain block. :param K: The gain value, defaults to 1 :type K: array_like :param premul: premultiply by constant, default is postmultiply, defaults to False :type premul: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A GAIN block :rtype: Gain instance Scale the input signal. If the input is :math:`u` the output is :math:`u K`. Either or both the input and gain can be Numpy arrays and Numpy will compute the appropriate product :math:`u K`. If :math:`u` and ``K`` are both NumPy arrays the ``@`` operator is used and :math:`u` is postmultiplied by the gain. To premultiply by the gain, to compute :math:`K u` use the ``premul`` option. For example:: gain = bd.GAIN(constant) """ super().__init__(**blockargs) self.K = K self.premul = premul self.add_param('K')
def output(self, t=None): input = self.inputs[0] if isinstance(input, np.ndarray) and isinstance(self.K, np.ndarray): # array x array case if self.premul: # premultiply by gain return [self.K @ input] else: # postmultiply by gain return [input @ self.K] else: return [self.inputs[0] * self.K]
# ------------------------------------------------------------------------ #
[docs]class Clip(FunctionBlock): """ :blockname:`CLIP` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | float, | float, | | | A(N,) | A(N,) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, min=-math.inf, max=math.inf, **blockargs): """ Signal clipping. :param min: Minimum value, defaults to -math.inf :type min: float or array_like, optional :param max: Maximum value, defaults to math.inf :type max: float or array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A CLIP block :rtype: Clip instance The input signal is clipped to the range from ``minimum`` to ``maximum`` inclusive. The signal can be a 1D-array in which case each element is clipped. The minimum and maximum values can be: - a scalar, in which case the same value applies to every element of the input vector , or - a 1D-array, of the same shape as the input vector that applies elementwise to the input vector. For example:: clip = bd.CLIP() """ super().__init__(**blockargs) self.min = min self.max = max
def output(self, t=None): input = self.inputs[0] if isinstance(input, np.ndarray): out = np.clip(input, self.min, self.max) else: out = min(self.max, max(input, self.min)) return [ out ]
# ------------------------------------------------------------------------ # # TODO can have multiple outputs: pass in a tuple of functions, return a tuple
[docs]class Function(FunctionBlock): """ :blockname:`FUNCTION` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | nin | nout | 0 | +------------+---------+---------+ | any | any | | +------------+---------+---------+ """ nin = -1 nout = -1
[docs] def __init__(self, func=None, nin=1, nout=1, persistent=False, args=None, kwargs=None, **blockargs): """ Python function. :param func: A function or lambda, or list thereof, defaults to None :type func: callable or sequence of callables, optional :param nin: number of inputs, defaults to 1 :type nin: int, optional :param nout: number of outputs, defaults to 1 :type nout: int, optional :param persistent: pass in a reference to a dictionary instance to hold persistent state, defaults to False :type persistent: bool, optional :param args: extra positional arguments passed to the function, defaults to [] :type args: list, optional :param kwargs: extra keyword arguments passed to the function, defaults to {} :type kwargs: dict, optional :param blockargs: |BlockOptions| :type blockargs: dict, optional :return: A FUNCTION block :rtype: A Function instance Inputs to the block are passed as separate arguments to the function. Programmatic ositional or keyword arguments can also be passed to the function. A block with one output port that sums its two input ports is:: FUNCTION(lambda u1, u2: u1+u2, nin=2) A block with a function that takes two inputs and has two additional arguments:: def myfun(u1, u2, param1, param2): pass FUNCTION(myfun, nin=2, args=(p1,p2)) If we need access to persistent (static) data, to keep some state:: def myfun(u1, u2, param1, param2, state): pass FUNCTION(myfun, nin=2, args=(p1,p2), persistent=True) where a dictionary is passed in as the last argument and which is kept from call to call. A block with a function that takes two inputs and additional keyword arguments:: def myfun(u1, u2, param1=1, param2=2, param3=3, param4=4): pass FUNCTION(myfun, nin=2, kwargs=dict(param2=7, param3=8)) A block with two inputs and two outputs, the outputs are defined by two lambda functions with the same inputs:: FUNCTION( [ lambda x, y: x_t, lambda x, y: x* y]) A block with two inputs and two outputs, the outputs are defined by a single function which returns a list:: def myfun(u1, u2): return [ u1+u2, u1*u2 ] FUNCTION( myfun, nin=2, nout=2) For example:: func = bd.FUNCTION(myfun, args) If inputs are specified then connections are automatically made and are assigned to sequential input ports:: func = bd.FUNCTION(myfun, block1, block2, args) is equivalent to:: func = bd.FUNCTION(myfun, args) bd.connect(block1, func[0]) bd.connect(block2, func[1]) """ super().__init__(nin=nin, nout=nout, **blockargs) if args is None: args = list() if kwargs is None: kwargs = dict() # TODO, don't know why this happens if len(args) > 0 and args[0] == {}: args = [] if isinstance(func, (list, tuple)): for f in func: assert callable(f), 'Function must be a callable' if kwargs is None: # we can check the number of arguments n = len(inspect.signature(func).parameters) if persistent: n -= 1 # discount dict if used if nin + len(args) != n: raise ValueError( f"argument count mismatch: function has {n} args, dict={dict}, nin={nin}" ) elif callable(func): if len(kwargs) == 0: # we can check the number of arguments n = len(inspect.signature(func).parameters) if persistent: n -= 1 # discount dict if used if nin + len(args) != n: raise ValueError( f"argument count mismatch: function has {n} args, dict={dict}, nin={nin}" ) # self.nout = nout self.func = func if persistent: self.userdata = dict() args += (self.userdata,) else: self.userdata = None self.args = args self.kwargs = kwargs
def start(self, state=None): super().start() if self.userdata is not None: self.userdata.clear() print('clearing user data') def output(self, t=None): if callable(self.func): # single function try: val = self.func(*self.inputs, *self.args, **self.kwargs) except TypeError: raise RuntimeError('Function invocation failed, check number of arguments') from None if isinstance(val, (list, tuple)): if len(val) != self.nout: raise RuntimeError('Function returns wrong number of arguments: ' + str(self)) return val else: if self.nout != 1: raise RuntimeError('Function returns wrong number of arguments: ' + str(self)) return [val] else: # list of functions out = [] for f in self.func: try: val = f(*self.inputs, *self.args, **self.kwargs) except TypeError: raise RuntimeError('Function invocation failed, check number of arguments') from None out.append(val) return out
# ------------------------------------------------------------------------ #
[docs]class Interpolate(FunctionBlock): """ :blockname:`INTERPOLATE` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 0 or 1 | 1 | 0 | +------------+---------+---------+ | float | any | | +------------+---------+---------+ """ nin = -1 nout = 1
[docs] def __init__(self, x=None, y=None, xy=None, time=False, kind='linear', **blockargs): """ Interpolate signal. :param x: x-values of function, defaults to None :type x: array_like, shape (N,) optional :param y: y-values of function, defaults to None :type y: array_like, optional :param xy: combined x- and y-values of function, defaults to None :type xy: array_like, optional :param time: x new is simulation time, defaults to False :type time: bool, optional :param kind: interpolation method, defaults to 'linear' :type kind: str, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: An INTERPOLATE block :rtype: An Interpolate instance Interpolate the input signal using to a piecewise function. A simple triangle function with domain [0,10] and range [0,1] can be defined by:: INTERPOLATE(x=(0,5,10), y=(0,1,0)) We might also express this as a list of 2D-coordinats:: INTERPOLATE(xy=[(0,0), (5,1), (10,0)]) The data can also be expressed as Numpy arrays. If that is the case, the interpolation function can be vector valued. ``x`` has a shape of (N,1) and ``y`` has a shape of (N,M). Alternatively ``xy`` has a shape of (N,M+1) and the first column is the x-data. The input to the interpolator comes from: - Input port 0 - Simulation time, if ``time=True``. In this case the block has no input ports and is a ``Source`` not a ``Function``. """ self.time = time if time: nin = 0 self.blockclass = 'source' else: nin = 1 super().__init__(nin=nin, **blockargs) if xy is None: # process separate x and y vectors x = np.array(x) y = np.array(y) assert x.shape[0] == y.shape[0], 'x and y data must be same length' else: # process mixed xy data if isinstance(xy, (list, tuple)): x = [_[0] for _ in xy] y = [_[1] for _ in xy] # x = np.array(x).T # y = np.array(y).T print(x, y) elif isinstance(xy, np.ndarray): x = xy[:,0] y = xy[:,1:] self.f = scipy.interpolate.interp1d(x=x, y=y, kind=kind, axis=0) self.x = x
def start(self, state, **blockargs): if self.time: assert self.x[0] >= 0, 'interpolation not defined for t<0' assert self.x[-1] <= state.T, 'interpolation not defined for t>T' def output(self, t=None): if self.time: xnew = t else: xnew = self.inputs[0] return [self.f(xnew)]
if __name__ == "__main__": import pathlib import os.path exec(open(os.path.join(pathlib.Path(__file__).parent.absolute(), "test_functions.py")).read())

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blocks/linalg.html ================================================ bdsim.blocks.linalg — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.linalg

"""
Linear algebra blocks:

- have inputs and outputs
- have no state variables
- are a subclass of ``FunctionBlock`` |rarr| ``Block``

"""

# The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance.

import numpy as np
import math

from bdsim.components import FunctionBlock

[docs]class Inverse(FunctionBlock): """ :blockname:`INVERSE` .. table:: :align: left +----------+---------+---------+ | inputs | outputs | states | +----------+---------+---------+ | 1 | 2 | 0 | +----------+---------+---------+ | A(M,N) | A(N,M) | | | | float | | +----------+---------+---------+ """ nin = 1 nout = 2 onames = ('inv', 'cond')
[docs] def __init__(self, pinv=False, **blockargs): """ Matrix inverse. :param pinv: force pseudo inverse, defaults to False :type pinv: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: An INVERSE block :rtype: Inverse instance Compute inverse of the 2D-array input signal. If the matrix is square the inverse is computed unless the ``pinv`` flag is True. For a non-square matrix the pseudo-inverse is used. The condition number is output on the second port. :seealso: :func:`numpy.linalg.inv` :func:`numpy.linalg.pinv` :func:`numpy.linalg.cond` """ super().__init__(**blockargs) self.type = 'inverse' self.pinv = pinv
def output(self, t=None): mat = self.inputs[0] if isinstance(mat, np.ndarray): if mat.shape[0] != mat.shape[1]: pinv = True else: pinv = self.pinv if pinv: out = np.linalg.pinv(mat) else: try: out = np.linalg.inv(mat) except LinAlgError: raise RuntimeError('matrix is singular') return [out, np.linalg.cond(mat)] elif hasattr(mat, 'inv'): # ask the object to invert itself return [mat.inv(), None] else: raise RuntimeError('object cannot be inverted')
# ------------------------------------------------------------------------ #
[docs]class Transpose(FunctionBlock): """ :blockname:`TRANSPOSE` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | A(M,N) | A(N,M) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, **blockargs): """ Matrix transpose. :param blockargs: |BlockOptions| :type blockargs: dict :return: A TRANSPOSE block :rtype: Transpose instance Compute transpose of the 2D-array input signal. .. note:: - An input 1D-array of shape (N,) is turned into a 2D-array column vector with shape (N,1). - An input 2D-array column vector of shape (N,1) becomes a 2D-array row vector with shape (1,N). :seealso: :func:`numpy.transpose` """ super().__init__(**blockargs) self.type = 'transpose'
def output(self, t=None): mat = self.inputs[0] if ndim == 1: out = mat.reshape((mat.shape[0], 1)) else: out = mat.T return [out]
# ------------------------------------------------------------------------ #
[docs]class Norm(FunctionBlock): """ :blockname:`NORM` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | A(N,) | float | | | A(N,M) | | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, ord=None, axis=None, **blockargs): """ Array norm. :param axis: specifies the axis along which to compute the vector norms, defaults to None. :type axis: int, optional :param ord: Order of the norm, default to None. :type ord: int or str :param blockargs: |BlockOptions| :type blockargs: dict :return: A NORM block :rtype: Norm instance Computes the specified norm for a 1D- or 2D-array. :seealso: :func:`numpy.linalg.norm` """ super().__init__(**blockargs) self.type = 'norm' self.args = dict(ord=ord, axis=axis)
def output(self, t=None): vec = self.inputs[0] out = np.linalg.norm(vec, **self.args) return [out]
# ------------------------------------------------------------------------ #
[docs]class Flatten(FunctionBlock): """ :blockname:`FLATTEN` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | A(N,M ) | A(NM,) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, order='C', **blockargs): """ Flatten a multi-dimensional array. :param order: flattening order, either "C" or "F", defaults to "C" :type order: str :param blockargs: |BlockOptions| :type blockargs: dict :return: A FLATTEN block :rtype: Flatten instance Flattens the incoming array in either row major ('C') or column major ('F') order. :seealso: :func:`numpy.flatten` """ super().__init__(**blockargs) self.type = 'flatten' self.order = order
def output(self, t=None): vec = self.inputs[0] out = vec.flatten(self.order) return [out]
# ------------------------------------------------------------------------ #
[docs]class Slice2(FunctionBlock): """ :blockname:`SLICE2` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | A(N,M) | A(K,L) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, rows=None, cols=None, **blockargs): """ Slice out subarray of 2D-array. :param rows: row selection, defaults to None :type rows: tuple(3) or list :param cols: column selection, defaults to None :type cols: tuple(3) or list :param blockargs: |BlockOptions| :type blockargs: dict :return: A SLICE2 block :rtype: Slice2 instance Compute a 2D slice of input 2D array. If ``rows`` or ``cols`` is ``None`` it means all rows or columns respectively. If ``rows`` or ``cols`` is a list, perform NumPy fancy indexing, returning the specified rows or columns Example:: SLICE2(rows=[2,3]) # return rows 2 and 3, all columns SLICE2(cols=[4,1]) # return columns 4 and 1, all rows SLICE2(rows=[2,3], cols=[4,1]) # return elements [2,4] and [3,1] as a 1D array If a single row or column is selected, the result will be a 1D array If ``rows`` or ``cols`` is a tuple, it must have three elements. It describes a Python slice ``(start, stop, step)`` where any element can be ``None`` * ``start=None`` means start at first element * ``stop=None`` means finish at last element * ``step=None`` means step by one ``rows=None`` is equivalent to ``rows=(None, None, None)``. Example:: SLICE2(rows=(None,None,2)) # return every second row SLICE2(cols=(None,None,-1)) # reverse the columns The list and tuple notation can be mixed, for example, one for rows and one for columns. :seealso: :class:`Slice1` :class:`Index` """ super().__init__(**blockargs) self.type = 'slice2' if rows is None: self.rows = slice() elif isinstance(rows, list): self.rows = rows elif isinstance(rows, tuple) and len(rows) == 3: self.rows = slice(*rows) else: raise ValueError('bad rows specifier') if cols is None: self.cols = slice() elif isinstance(cols, list): self.rows = cols elif isinstance(cols, tuple) and len(cols) == 3: self.cols = slice(*cols) else: raise ValueError('bad rows specifier')
def output(self, t=None): array = self.inputs[0] if array.ndim != 2: raise RuntimeError('flatten2 block expecting 2d array') return [out[rows, cols]]
# ------------------------------------------------------------------------ #
[docs]class Slice1(FunctionBlock): """ :blockname:`SLICE1` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | A(N) | A(M) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, index, **blockargs): """ Slice out subarray of 1D-array. :param index: slice, defaults to None :type index: tuple(3) :param blockargs: |BlockOptions| :type blockargs: dict :return: A SLICE1 block :rtype: Slice1 instance Compute a 1D slice of input 1D array. If ``index`` is ``None`` it means all elements. If ``index`` is a list, perform NumPy fancy indexing, returning the specified elements Example:: SLICE1(index=[2,3]) # return elements 2 and 3 as a 1D array SLICE1(index=[2]) # return element 2 as a 1D array SLICE1(index=2) # return element 2 as a NumPy scalar If ``index`` is a tuple, it must have three elements. It describes a Python slice ``(start, stop, step)`` where any element can be ``None`` * ``start=None`` means start at first element * ``stop=None`` means finish at last element * ``step=None`` means step by one ``rows=None`` is equivalent to ``rows=(None, None, None)``. Example:: SLICE1(index=(None,None,2)) # return every second element SLICE1(index=(None,None,-1)) # reverse the elements :seealso: :class:`Slice1` """ super().__init__(**blockargs) self.type = 'slice1' if index is None: self.index = slice() elif isinstance(index, list): self.index = index elif isinstance(index, tuple) and len(index) == 3: self.index = slice(*index) else: raise ValueError('bad index specifier')
def output(self, t=None): array = self.inputs[0] if array.ndim != 1: raise RuntimeError('flatten1 block expecting 1d array') return [array[self.index]]
# ------------------------------------------------------------------------ #
[docs]class Det(FunctionBlock): """ :blockname:`DET` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | A(N,N) | float | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, **blockargs): """ Matrix determinant :param blockargs: |BlockOptions| :type blockargs: dict :return: A DET block :rtype: Det instance Compute the matrix determinant. :seealso: :func:`numpy.linalg.inv` """ super().__init__(**blockargs) self.type = 'det'
def output(self, t=None): mat = self.inputs[0] out = np.linalg.det(mat) return [mat]
# ------------------------------------------------------------------------ #
[docs]class Cond(FunctionBlock): """ :blockname:`COND` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | A(N,M) | float | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, **blockargs): """ Compute the matrix condition number. :param blockargs: |BlockOptions| :type blockargs: dict :return: A COND block :rtype: Cond instance :seealso: :func:`numpy.linalg.cond` """ super().__init__(**blockargs) self.type = 'cond'
def output(self, t=None): mat = self.inputs[0] out = np.linalg.cond(mat) return [mat]
if __name__ == "__main__": import pathlib import os.path exec(open(os.path.join(pathlib.Path(__file__).parent.absolute(), "test_functions.py")).read())

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blocks/robots.html ================================================ bdsim.blocks.robots — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.robots

"""
Robot blocks:
- have inputs and outputs
- are a subclass of ``FunctionBlock`` |rarr| ``Block`` for kinematics and have no states
- are a subclass of ``TransferBlock`` |rarr| ``Block`` for dynamics and have states

"""
# The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance.


# TODO: quadrotor dyanmics and display

import numpy as np
from math import sin, cos, atan2, tan, sqrt, pi

import matplotlib.pyplot as plt
import time

from bdsim.components import TransferBlock, block

# ------------------------------------------------------------------------ #
[docs]@block class Bicycle(TransferBlock): """ :blockname:`BICYCLE` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 2 | 3 | 3 | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """
[docs] def __init__(self, *inputs, x0=None, L=1, vlim=1, slim=1, **kwargs): """ :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param x0: Inital state, defaults to 0 :type x0: array_like, optional :param L: Wheelbase, defaults to 1 :type L: float, optional :param vlim: Velocity limit, defaults to 1 :type vlim: float, optional :param slim: Steering limit, defaults to 1 :type slim: float, optional :param ``**kwargs``: common Block options :return: a BICYCLE block :rtype: Bicycle instance Create a vehicle model with Bicycle kinematics. Bicycle kinematic model with state :math:`[x, y, \theta]`. The block has two input ports: 1. Vehicle speed (metres/sec). The velocity limit ``vlim`` is applied to the magnitude of this input. 2. Steering wheel angle (radians). The steering limit ``slim`` is applied to the magnitude of this input. and three output ports: 1. x position in the world frame (metres) 2. y positon in the world frame (metres) 3. heading angle with respect to the world frame (radians) """ super().__init__(nin=2, nout=3, inputs=inputs, **kwargs) self.nstates = 3 self.vlim = vlim self.slim = slim self.type = 'bicycle' self.L = L if x0 is None: self._x0 = np.zeros((self.nstates,)) else: assert len(x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(len(x0), self.nstates) self._x0 = x0 self.inport_names(('v', '$\gamma$')) self.outport_names(('x', 'y', r'$\theta$')) self.state_names(('x', 'y', r'$\theta$'))
def output(self, t): return list(self._x) def deriv(self): theta = self._x[2] # get inputs and clip them v = self.inputs[0] v = min(self.vlim, max(v, -self.vlim)) gamma = self.inputs[1] gamma = min(self.slim, max(gamma, -self.slim)) xd = np.r_[v * cos(theta), v * sin(theta), v * tan(gamma)/self.L ] return xd
# ------------------------------------------------------------------------ #
[docs]@block class Unicycle(TransferBlock): """ :blockname:`UNICYCLE` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 2 | 3 | 3 | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """
[docs] def __init__(self, *inputs, x0=None, **kwargs): r""" :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param x0: Inital state, defaults to 0 :type x0: array_like, optional :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param ``**kwargs``: common Block options :return: a UNICYCLE block :rtype: Unicycle instance Create a vehicle model with Unicycle kinematics. Unicycle kinematic model with state :math:`[x, y, \theta]`. The block has two input ports: 1. Vehicle speed (metres/sec). 2. Angular velocity (radians/sec). and three output ports: 1. x position in the world frame (metres) 2. y positon in the world frame (metres) 3. heading angle with respect to the world frame (radians) """ super().__init__(nin=2, nout=3, inputs=inputs, **kwargs) self.nstates = 3 self.type = 'unicycle' if x0 is None: self._x0 = np.zeros((slef.nstates,)) else: assert len(x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(len(x0), self.nstates) self._x0 = x0
def output(self, t): return list(self._x) def deriv(self): theta = self._x[2] v = self.inputs[0] omega = self.inputs[1] xd = np.r_[v * cos(theta), v * sin(theta), omega] return xd
# ------------------------------------------------------------------------ #
[docs]@block class DiffSteer(TransferBlock): """ :blockname:`DIFFSTEER` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 2 | 3 | 3 | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """
[docs] def __init__(self, *inputs, R=1, W=1, x0=None, **kwargs): r""" :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param x0: Inital state, defaults to 0 :type x0: array_like, optional :param R: Wheel radius, defaults to 1 :type R: float, optional :param W: Wheel separation in lateral direction, defaults to 1 :type R: float, optional :param ``**kwargs``: common Block options :return: a DIFFSTEER block :rtype: DifSteer instance Create a differential steer vehicle model with Unicycle kinematics, with inputs given as wheel angular velocity. Unicycle kinematic model with state :math:`[x, y, \theta]`. The block has two input ports: 1. Left-wheel angular velocity (radians/sec). 2. Right-wheel angular velocity (radians/sec). and three output ports: 1. x position in the world frame (metres) 2. y positon in the world frame (metres) 3. heading angle with respect to the world frame (radians) Note: - wheel velocity is defined such that if both are positive the vehicle moves forward. """ super().__init__(nin=2, nout=3, inputs=inputs, **kwargs) self.nstates = 3 self.type = 'diffsteer' self.R = R self.W = W if x0 is None: self._x0 = np.zeros((slef.nstates,)) else: assert len(x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(len(x0), self.nstates) self._x0 = x0
def output(self, t): return list(self._x) def deriv(self): theta = self._x[2] v = self.R * (self.inputs[0] + self.inputs[1]) / 2 omega = (self.inputs[1] + self.inputs[0]) / self.W xd = np.r_[v * cos(theta), v * sin(theta), omega] return xd
# seriallink # RNE # fkine # robot plot # ------------------------------------------------------------------------ #
[docs]@block class MultiRotor(TransferBlock): """ :blockname:`MULTIROTOR` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 16 | +------------+---------+---------+ | A(4,) | dict | | +------------+---------+---------+ """ # Flyer2dynamics lovingly coded by Paul Pounds, first coded 12/4/04 # A simulation of idealised X-4 Flyer II flight dynamics. # version 2.0 2005 modified to be compatible with latest version of Matlab # version 3.0 2006 fixed rotation matrix problem # version 4.0 4/2/10, fixed rotor flapping rotation matrix bug, mirroring # version 5.0 8/8/11, simplified and restructured # version 6.0 25/10/13, fixed rotation matrix/inverse wronskian definitions, flapping cross-product bug # # New in version 2: # - Generalised rotor thrust model # - Rotor flapping model # - Frame aerodynamic drag model # - Frame aerodynamic surfaces model # - Internal motor model # - Much coolage # # Version 1.3 # - Rigid body dynamic model # - Rotor gyroscopic model # - External motor model # # ARGUMENTS # u Reference inputs 1x4 # tele Enable telemetry (1 or 0) 1x1 # crash Enable crash detection (1 or 0) 1x1 # init Initial conditions 1x12 # # INPUTS # u = [N S E W] # NSEW motor commands 1x4 # # CONTINUOUS STATES # z Position 3x1 (x,y,z) # v Velocity 3x1 (xd,yd,zd) # n Attitude 3x1 (Y,P,R) # o Angular velocity 3x1 (wx,wy,wz) # w Rotor angular velocity 4x1 # # Notes: z-axis downward so altitude is -z(3) # # CONTINUOUS STATE MATRIX MAPPING # x = [z1 z2 z3 n1 n2 n3 z1 z2 z3 o1 o2 o3 w1 w2 w3 w4] # # # CONTINUOUS STATE EQUATIONS # z` = v # v` = g*e3 - (1/m)*T*R*e3 # I*o` = -o X I*o + G + torq # R = f(n) # n` = inv(W)*o #
[docs] def __init__(self, model, *inputs, groundcheck=True, speedcheck=True, x0=None, **kwargs): r""" :param model: Vehicle geometric and inertial parameters :type model: dict :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param groundcheck: Prevent vehicle moving below ground , defaults to True :type groundcheck: bool :param speedcheck: Check for zero rotor speed, defaults to True :type speedcheck: bool :param x0: Initial state, defaults to all zeros :type x0: TYPE, optional :param ``**kwargs``: common Block options :return: a MULTIROTOR block :rtype: MultiRotor instance Create a a multi-rotor dynamic model block. The block has one input port which is a vector of input rotor speeds in (radians/sec). These are, looking down, clockwise from the front rotor which lies on the x-axis. The block has one output port which is a dictionary signal with the following items: - ``x`` pose in the world frame as :math:`[x, y, z, \theta_Y, \theta_P, \theta_R]` - ``vb`` translational velocity in the world frame (metres/sec) - ``w`` angular rates in the world frame as yaw-pitch-roll rates (radians/second) - ``a1s`` longitudinal flapping angles (radians) - ``b1s`` lateral flapping angles (radians) Based on MATLAB code developed by Pauline Pounds 2004. """ super().__init__(nin=1, nout=1, inputs=inputs, **kwargs) self.type = 'quadrotor' try: nrotors = model['nrotors'] except KeyError: raise RuntimeError('vehicle model does not contain nrotors') assert nrotors % 2 == 0, 'Must have an even number of rotors' self.nstates = 12 if x0 is not None: assert len(x0) == self.nstates, "x0 is the wrong length" else: x0 = np.zeros((self.nstates,)) self._x0 = x0 self.nrotors = nrotors self.model = model self.groundcheck = groundcheck self.speedcheck = speedcheck self.D = np.zeros((3,self.nrotors)) for i in range(0, self.nrotors): theta = i / self.nrotors * 2 * pi # Di Rotor hub displacements (1x3) # first rotor is on the x-axis, clockwise order looking down from above self.D[:,i] = np.r_[ model['d'] * cos(theta), model['d'] * sin(theta), model['h']] self.a1s = np.zeros((self.nrotors,)) self.b1s = np.zeros((self.nrotors,))
def output(self, t=None): model = self.model # compute output vector as a function of state vector # z Position 3x1 (x,y,z) # v Velocity 3x1 (xd,yd,zd) # n Attitude 3x1 (Y,P,R) # o Angular velocity 3x1 (Yd,Pd,Rd) n = self._x[3:6] # RPY angles phi = n[0] # yaw the = n[1] # pitch psi = n[2] # roll # rotz(phi)*roty(the)*rotx(psi) # BBF > Inertial rotation matrix R = np.array([ [cos(the) * cos(phi), sin(psi) * sin(the) * cos(phi) - cos(psi) * sin(phi), cos(psi) * sin(the) * cos(phi) + sin(psi) * sin(phi)], [cos(the) * sin(phi), sin(psi) * sin(the) * sin(phi) + cos(psi) * cos(phi), cos(psi) * sin(the) * sin(phi) - sin(psi) * cos(phi)], [-sin(the), sin(psi) * cos(the), cos(psi) * cos(the)] ]) #inverted Wronskian iW = np.array([ [0, sin(psi), cos(psi)], [0, cos(psi) * cos(the), -sin(psi) * cos(the)], [cos(the), sin(psi) * sin(the), cos(psi) * sin(the)] ]) / cos(the) # return velocity in the body frame out = {} out['x'] = self._x[0:6] out['vb'] = np.linalg.inv(R) @ self._x[6:9] # translational velocity mapped to body frame out['w'] = iW @ self._x[9:12] # RPY rates mapped to body frame out['a1s'] = self.a1s out['b1s'] = self.b1s return [out] def deriv(self): model = self.model # Body-fixed frame references # ei Body fixed frame references 3x1 e3 = np.r_[0, 0, 1] # process inputs w = self.inputs[0] if len(w) != self.nrotors: raise RuntimeError('input vector wrong size') if self.speedcheck and np.any(w == 0): # might need to fix this, preculudes aerobatics :( # mu becomes NaN due to 0/0 raise RuntimeError('quadrotor_dynamics: not defined for zero rotor speed'); # EXTRACT STATES FROM X z = self._x[0:3] # position in {W} n = self._x[3:6] # RPY angles {W} v = self._x[6:9] # velocity in {W} o = self._x[9:12] # angular velocity in {W} # PREPROCESS ROTATION AND WRONSKIAN MATRICIES phi = n[0] # yaw the = n[1] # pitch psi = n[2] # roll # rotz(phi)*roty(the)*rotx(psi) # BBF > Inertial rotation matrix R = np.array([ [cos(the)*cos(phi), sin(psi)*sin(the)*cos(phi)-cos(psi)*sin(phi), cos(psi)*sin(the)*cos(phi)+sin(psi)*sin(phi)], [cos(the)*sin(phi), sin(psi)*sin(the)*sin(phi)+cos(psi)*cos(phi), cos(psi)*sin(the)*sin(phi)-sin(psi)*cos(phi)], [-sin(the), sin(psi)*cos(the), cos(psi)*cos(the)] ]) # Manual Construction # Q3 = [cos(phi) -sin(phi) 0;sin(phi) cos(phi) 0;0 0 1]; % RZ %Rotation mappings # Q2 = [cos(the) 0 sin(the);0 1 0;-sin(the) 0 cos(the)]; % RY # Q1 = [1 0 0;0 cos(psi) -sin(psi);0 sin(psi) cos(psi)]; % RX # R = Q3*Q2*Q1 %Rotation matrix # # RZ * RY * RX # inverted Wronskian iW = np.array([ [0, sin(psi), cos(psi)], [0, cos(psi)*cos(the), -sin(psi)*cos(the)], [cos(the), sin(psi)*sin(the), cos(psi)*sin(the)] ]) / cos(the) # ROTOR MODEL T = np.zeros((3,4)) Q = np.zeros((3,4)) tau = np.zeros((3,4)) a1s = self.a1s b1s = self.b1s for i in range(0, self.nrotors): # for each rotor # Relative motion Vr = np.cross(o, self.D[:,i]) + v mu = sqrt(np.sum(Vr[0:2]**2)) / (abs(w[i]) * model['r']) # Magnitude of mu, planar components lc = Vr[2] / (abs(w[i]) * model['r']) # Non-dimensionalised normal inflow li = mu # Non-dimensionalised induced velocity approximation alphas = atan2(lc, mu) j = atan2(Vr[1], Vr[0]) # Sideslip azimuth relative to e1 (zero over nose) J = np.array([ [cos(j), -sin(j)], [sin(j), cos(j)] ]) # BBF > mu sideslip rotation matrix # Flapping beta = np.array([ [((8/3*model['theta0'] + 2 * model['theta1']) * mu - 2 * lc * mu) / (1 - mu**2 / 2)], # Longitudinal flapping [0] # Lattitudinal flapping (note sign) ]) # sign(w) * (4/3)*((Ct/sigma)*(2*mu*gamma/3/a)/(1+3*e/2/r) + li)/(1+mu^2/2)]; beta = J.T @ beta; # Rotate the beta flapping angles to longitudinal and lateral coordinates. a1s[i] = beta[0] - 16 / model['gamma'] / abs(w[i]) * o[1] b1s[i] = beta[1] - 16 / model['gamma'] / abs(w[i]) * o[0] # Forces and torques # Rotor thrust, linearised angle approximations T[:,i] = model['Ct'] * model['rho'] * model['A'] * model['r']**2 * w[i]**2 * \ np.r_[-cos(b1s[i]) * sin(a1s[i]), sin(b1s[i]), -cos(a1s[i])*cos(b1s[i])] # Rotor drag torque - note that this preserves w[i] direction sign Q[:,i] = -model['Cq'] * model['rho'] * model['A'] * model['r']**3 * w[i] * abs(w[i])* e3 tau[:,i] = np.cross(T[:,i], self.D[:,i]) # Torque due to rotor thrust # RIGID BODY DYNAMIC MODEL dz = v dn = iW @ o dv = model['g'] * e3 + R @ np.sum(T, axis=1) / model['M'] # vehicle can't fall below ground, remember z is down if self.groundcheck and z[2] > 0: z[0] = 0 dz[0] = 0 do = np.linalg.inv(model['J']) @ (np.cross(-o, model['J'] @ o) + np.sum(tau, axis=1) + np.sum(Q, axis=1)) # row sum of torques # stash the flapping information for plotting self.a1s = a1s self.b1s = b1s return np.r_[dz, dn, dv, do] # This is the state derivative vector
if __name__ == "__main__": import pathlib import os.path exec(open(os.path.join(pathlib.Path(__file__).parent.absolute(), "test_robots.py")).read())
================================================ FILE: docs-aside/_modules/bdsim/blocks/sinks.html ================================================ bdsim.blocks.sinks — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.sinks

"""
Sink blocks:

- have inputs but no outputs
- have no state variables
- are a subclass of ``SinkBlock`` |rarr| ``Block``
- that perform graphics are a subclass of  ``GraphicsBlock`` |rarr| ``SinkBlock`` |rarr| ``Block``

"""


import numpy as np
from math import pi, sqrt, sin, cos, atan2

import matplotlib.pyplot as plt
from matplotlib.pyplot import Polygon


import spatialmath.base as sm
from bdsim.components import SinkBlock

# ------------------------------------------------------------------------ #

[docs]class Print(SinkBlock): """ :blockname:`PRINT` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 1 | 0 | 0 | +--------+---------+---------+ | any | | | +--------+---------+---------+ """ nin = 1 nout = 0
[docs] def __init__(self, fmt=None, file=None, **blockargs): """ Print signal. :param fmt: Format string, defaults to None :type fmt: str, optional :param file: file to write data to, defaults to None :type file: file object, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A PRINT block :rtype: Print instance Creates a console print block which displays the value of a signal at each simulation time step. The display format is like:: PRINT(print.0 @ t=0.100) [-1.0 0.2] and includes the block name, time, and the formatted value. The numerical formatting of the signal is controlled by ``fmt``: - if not provided, ``str()`` is used to format the signal - if provided: - a scalar is formatted by the ``fmt.format()`` - a NumPy array is formatted by ``fmt.format()`` applied to every element Examples:: pr = bd.PRINT() # create PRINT block bd.connect(x, inputs=pr) # its input comes from x bd.PRINT(x) # create PRINT block with input from x bd.PRINT(x, name='X') # block name appears in the printed text bd.PRINT(x, fmt="{:.1f}") # print with explicit format .. note:: - By default writes to stdout - The output is cleaner if progress bar printing is disabled. """ super().__init__(**blockargs) self.format = fmt
# TODO format can be a string or function def step(self, state=None): prefix = '{:12s}'.format( 'PRINT({:s} (t={:.3f})'.format(self.name, state.t) ) value = self.inputs[0] if self.format is None: # no format string print(prefix, str(value)) else: # format string provided if isinstance(value, (int, float)): print(prefix, self.format.format(value)) elif isinstance(value, np.ndarray): with np.printoptions(formatter={'all':lambda x: self.format.format(x)}): print(prefix, value) else: print(prefix, str(value))
# ------------------------------------------------------------------------ #
[docs]class Stop(SinkBlock): """ :blockname:`STOP` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 1 | 0 | 0 | +--------+---------+---------+ | any | | | +--------+---------+---------+ """ nin = 1 nout = 0
[docs] def __init__(self, func=None, **blockargs): """ Conditional simulation stop. :param func: evaluate stop condition, defaults to None :type func: callable, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A STOP block :rtype: Stop instance Conditionally stop the simulation if the input is: - bool type and True - numeric type and > 0 If ``func`` is provided, then it is applied to the block input and if it returns True the simulation is stopped. """ super().__init__(**blockargs) if not callable(func): raise TypeError('argument must be a callable') self.stopfunc = func
def step(self, state=None): value = self.inputs[0] if self.stopfunc is not None: value = self.stopfunc(value) stop = False if isinstance(value, bool): stop = value else: try: stop = value > 0 except: raise RuntimeError('bad input type to stop block') # we signal stop condition by setting state.stop to the block calling # the stop if stop: state.stop = self
# ------------------------------------------------------------------------ #
[docs]class Null(SinkBlock): """ :blockname:`NULL` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | N | 0 | 0 | +--------+---------+---------+ | any | | | +--------+---------+---------+ """ nin = -1 nout = 0
[docs] def __init__(self, nin=1, **blockargs): """ Discard signal. :param nin: number of input ports, defaults to 1 :type nin: int, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A NULL block :rtype: Null instance Create a sink block with arbitrary number of input ports that discards all data. Useful for testing. """ super().__init__(nin=nin, **blockargs)
# ------------------------------------------------------------------------ #
[docs]class Watch(SinkBlock): """ :blockname:`WATCH` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | N | 0 | 0 | +--------+---------+---------+ | 1 | | | +--------+---------+---------+ """ nin = 1 nout = 0
[docs] def __init__(self, **blockargs): """ Watch a signal. :param blockargs: |BlockOptions| :type blockargs: dict :return: A NULL block :rtype: Null instance Causes the input signal to be logged during the simulation run. Equivalent to adding it as the ``watch=`` argument to ``bdsim.run``. :seealso: :method:`BDSim.run` """ super().__init__(**blockargs)
def start(self, state=None): # called at start of simulation, add this block to the watchlist plug = self.inports[0].start # start plug for input wire # append to the watchlist, bdsim.run() will do the rest state.watchlist.append(plug) state.watchnamelist.append(str(plug))
if __name__ == "__main__": from pathlib import Path import os.path exec(open(Path(__file__).parent.parent.parent / "tests" / "test_sinks.py").read())

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blocks/sources.html ================================================ bdsim.blocks.sources — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.sources

"""
Source blocks:

- have outputs but no inputs
- have no state variables
- are a subclass of ``SourceBlock`` |rarr| ``Block``

"""

import numpy as np
import math
from bdsim.components import SourceBlock

# ------------------------------------------------------------------------ #
[docs]class Constant(SourceBlock): """ :blockname:`CONSTANT` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 0 | 1 | 0 | +--------+---------+---------+ | | float, | | | | A(N,) | | +--------+---------+---------+ """ nin = 0 nout = 1
[docs] def __init__(self, value=None, **blockargs): """ Constant value. :param value: the constant, defaults to None :type value: any, optional :param blockargs: |BlockOptions| :return: a CONSTANT block :rtype: Constant instance This block has only one output port, but the value can be any Python type, for example float, list or Numpy ndarray. """ super().__init__(**blockargs) if isinstance(value, (tuple, list)): value = np.array(value) self.value = value self.add_param('value')
def output(self, t=None): return [self.value]
# ------------------------------------------------------------------------ #
[docs]class Time(SourceBlock): """ :blockname:`TIME` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 0 | 1 | 0 | +--------+---------+---------+ | | float | | +--------+---------+---------+ """ nin = 0 nout = 1
[docs] def __init__(self, value=None, **blockargs): """ Simulation time. :param blockargs: |BlockOptions| :type blockargs: dict :return: a TIME block :rtype: Time instance The block has only one output port which is the current simulation time. """ super().__init__(**blockargs)
def output(self, t=None): return [t]
# ------------------------------------------------------------------------ #
[docs]class WaveForm(SourceBlock): """ :blockname:`WAVEFORM` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 0 | 1 | 0 | +--------+---------+---------+ | | float | | +--------+---------+---------+ """ nin = 0 nout = 1
[docs] def __init__(self, wave='square', freq=1, unit='Hz', phase=0, amplitude=1, offset=0, min=None, max=None, duty=0.5, **blockargs): """ Waveform as function of time. :param wave: type of waveform to generate, one of: 'sine', 'square', 'triangle', defaults to 'square' :type wave: str, optional :param freq: frequency, defaults to 1 :type freq: float, optional :param unit: frequency unit, one of: 'rad/s', 'Hz', defaults to 'Hz' :type unit: str, optional :param amplitude: amplitude, defaults to 1 :type amplitude: float, optional :param offset: signal offset, defaults to 0 :type offset: float, optional :param phase: Initial phase of signal in the range [0,1], defaults to 0 :type phase: float, optional :param min: minimum value, defaults to 0 :type min: float, optional :param max: maximum value, defaults to 1 :type max: float, optional :param duty: duty cycle for square wave in range [0,1], defaults to 0.5 :type duty: float, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a WAVEFORM block :rtype: WaveForm instance Create a waveform generator block. Examples:: WAVEFORM(wave='sine', freq=2) # 2Hz sine wave varying from -1 to 1 WAVEFORM(wave='square', freq=2, unit='rad/s') # 2rad/s square wave varying from -1 to 1 The minimum and maximum values of the waveform are given by default in terms of amplitude and offset. The signals are symmetric about the offset value. For example:: WAVEFORM(wave='sine') varies between -1 and +1 WAVEFORM(wave='sine', amplitude=2) varies between -2 and +2 WAVEFORM(wave='sine', offset=1) varies between 0 and +2 WAVEFORM(wave='sine', amplitude=2, offset=1) varies between -1 and +3 Alternatively we can specify the minimum and maximum values which override amplitude and offset:: WAVEFORM(wave='triangle', min=0, max=5) varies between 0 and +5 At time 0 the sine and triangle wave are zero and increasing, and the square wave has its first rise. We can specify a phase shift with a number in the range [0,1] where 1 corresponds to one cycle. .. note:: For discontinuous signals (square, triangle) the block declares events for every discontinuity. :seealso :meth:`declare_events` """ super().__init__(**blockargs) assert 0<duty<1, 'duty must be in range [0,1]' if wave in ('square', 'triangle', 'sine'): self.wave = wave else: raise ValueError('bad waveform') if unit == 'Hz': self.freq = freq elif unit == 'rad/s': self.freq = freq / (2 * math.pi) else: raise ValueError('bad unit') if 0 <= phase <= 1: self.phase = phase else: raise ValueError('phase out of range') if max is not None and min is not None: amplitude = (max - min) / 2 offset = (max + min) / 2 self.min = min self.mablock = max if 0 <= duty <= 1: self.duty = duty else: raise ValueError('duty out of range') self.amplitude = amplitude self.offset = offset
def start(self, state=None): if self.wave == 'square': t1 = self.phase / self.freq t2 = (self.duty + self.phase) / self.freq elif self.wave == 'triangle': t1 = (0.25 + self.phase) / self.freq t2 = (0.75 + self.phase) / self.freq else: return # t1 < t2 T = 1.0 / self.freq while t1 < self.bd.simstate.T: self.bd.simstate.declare_event(self, t1) self.bd.simstate.declare_event(self, t2) t1 += T t2 += T def output(self, t=None): T = 1.0 / self.freq phase = (t * self.freq - self.phase ) % 1.0 # define all signals in the range -1 to 1 if self.wave == 'square': if phase < self.duty: out = 1 else: out = -1 elif self.wave == 'triangle': if phase < 0.25: out = phase * 4 elif phase < 0.75: out = 1 - 4 * (phase - 0.25) else: out = -1 + 4 * (phase - 0.75) elif self.wave == 'sine': out = math.sin(phase*2*math.pi) else: raise ValueError('bad option for signal') out = out * self.amplitude + self.offset #print('waveform = ', out) return [out]
# ------------------------------------------------------------------------ #
[docs]class Piecewise(SourceBlock): """ :blockname:`PIECEWISE` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 0 | 1 | 0 | +--------+---------+---------+ | | float | | +--------+---------+---------+ """ nin = 0 nout = 1
[docs] def __init__(self, *seq, **blockargs): """ Piecewise constant signal. :param seq: sequence of time, value pairs :type seq: list of 2-tuples :param blockargs: |BlockOptions| :type blockargs: dict :return: a PIECEWISE block :rtype: Piecewise instance Outputs a piecewise constant function of time. This is described as a series of 2-tuples (time, value). The output value is taken from the active tuple, that is, the latest one in the list whose time is no greater than simulation time. .. note:: - The tuples must be order by monotonically increasing time. - There is no default initial value, the list should contain a tuple with time zero otherwise the output will be undefined. .. note:: The block declares an event for the start of each segment. :seealso: :meth:`declare_events` """ super().__init__(**blockargs) self.t = [ x[0] for x in seq] self.y = [ x[1] for x in seq]
def start(self, state=None): for t in self.t: state.declare_event(self, t) def output(self, t): i = sum([ 1 if t >= _t else 0 for _t in self.t]) - 1 out = self.y[i] #print(out) return [out]
# ------------------------------------------------------------------------ #
[docs]class Step(SourceBlock): """ :blockname:`STEP` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 0 | 1 | 0 | +--------+---------+---------+ | | float | | +--------+---------+---------+ """ nin = 0 nout = 1
[docs] def __init__(self, T=1, off=0, on=1, **blockargs): """ Step signal. :param T: time of step, defaults to 1 :type T: float, optional :param off: initial value, defaults to 0 :type off: float, optional :param on: final value, defaults to 1 :type on: float, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a STEP block :rtype: Step Output a step signal that transitions from the value ``off`` to ``on`` when time equals ``T``. .. note:: The block declares an event for the step time. :seealso: :meth:`declare_events` """ super().__init__(**blockargs) self.T = T self.off = off self.on = on
def start(self, state=None): state.declare_event(self, self.T) def output(self, t=None): if t >= self.T: out = self.on else: out = self.off #print(out) return [out]
# ------------------------------------------------------------------------ #
[docs]class Ramp(SourceBlock): """ :blockname:`RAMP` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 0 | 1 | 0 | +--------+---------+---------+ | | float | | +--------+---------+---------+ """ nin = 0 nout = 1
[docs] def __init__(self, T=1, off=0, slope=1, **blockargs): """ Ramp signal. :param T: time of ramp start, defaults to 1 :type T: float, optional :param off: initial value, defaults to 0 :type off: float, optional :param slope: gradient of slope, defaults to 1 :type slope: float, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a RAMP block :rtype: Ramp Output a ramp signal that starts increasing from the value ``off`` when time equals ``T`` linearly with time, with a gradient of ``slope``. .. note:: The block declares an event for the ramp start time. :seealso: :method:`declare_event` """ super().__init__(**blockargs) self.T = T self.off = off self.slope = slope
def start(self, state=None): state.declare_event(self, self.T) def output(self, t=None): if t >= self.T: out = self.off + self.slope * (t - self.T) else: out = self.off #print(out) return [out]
if __name__ == "__main__": import pathlib import os.path exec(open(os.path.join(pathlib.Path(__file__).parent.absolute(), "test_sources.py")).read())

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/blocks/transfers.html ================================================ bdsim.blocks.transfers — Block diagram simulation 0.7 documentation

Source code for bdsim.blocks.transfers

"""
Transfer blocks:

- have inputs and outputs
- have state variables
- are a subclass of ``TransferBlock`` |rarr| ``Block``

"""

import numpy as np
import scipy.signal
import math
from math import sin, cos, atan2, sqrt, pi
import matplotlib.pyplot as plt
from spatialmath import base

from bdsim.components import TransferBlock


[docs]class Integrator(TransferBlock): """ :blockname:`INTEGRATOR` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | N | +------------+---------+---------+ | float, | float, | | | A(N,) | A(N,) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, x0=0, min=None, max=None, **blockargs): """ Integrator. :param x0: Initial state, defaults to 0 :type x0: array_like, optional :param min: Minimum value of state, defaults to None :type min: float or array_like, optional :param max: Maximum value of state, defaults to None :type max: float or array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: an INTEGRATOR block :rtype: Integrator instance Output is the time integral of the input. The state can be a scalar or a vector. The initial state, and type, is given by ``x0``. The shape of the input signal must match ``x0``. The minimum and maximum values can be: - a scalar, in which case the same value applies to every element of the state vector, or - a vector, of the same shape as ``x0`` that applies elementwise to the state. """ super().__init__(**blockargs) if isinstance(x0, (int, float)): self.nstates = 1 if min is None: min = -math.inf if max is None: max = math.inf else: if isinstance(x0, np.ndarray): if x0.ndim > 1: raise ValueError('state must be a 1D vector') else: x0 = base.getvector(x0) self.nstates = x0.shape[0] if min is None: min = [-math.inf] * self.nstates elif len(min) != self.nstates: raise ValueError('minimum bound length must match x0') if max is None: max = [math.inf] * self.nstates elif len(max) != self.nstates: raise ValueError('maximum bound length must match x0') self._x0 = np.r_[x0] self.min = np.r_[min] self.max = np.r_[max] print('nstates', self.nstates)
def output(self, t=None): return [self._x] def deriv(self): xd = np.array(self.inputs) for i in range(0, self.nstates): if self._x[i] < self.min[i] or self._x[i] > self.max[i]: xd[i] = 0 return xd
[docs]class PoseIntegrator(TransferBlock): """ :blockname:`POSEINTEGRATOR` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | N | +------------+---------+---------+ | A(N,) | A(N,) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, x0=None, **blockargs): r""" Pose integrator :param x0: Initial pose, defaults to null :type x0: SE3, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: an INTEGRATOR block :rtype: Integrator instance This block integrates spatial velocity over time. The block input is a spatial velocity as a 6-vector :math:`(v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)` and the output is pose as an ``SE3`` instance. .. note:: State is a velocity twist. .. warning:: NOT WORKING YET """ super().__init__(**blockargs) if x0 is None: x0 = SE3() self.nstates = 6 self._x0 = np.r_[x0] print('nstates', self.nstates)
def output(self, t=None): return [Twist3(self._x).SE3(1)] def deriv(self): xd = np.array(self.inputs) for i in range(0, self.nstates): if self._x[i] < self.min[i] or self._x[i] > self.max[i]: xd[i] = 0 return xd
# ------------------------------------------------------------------------ #
[docs]class LTI_SS(TransferBlock): """ :blockname:`LTI_SS` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | nc | +------------+---------+---------+ | float, | float, | | | A(nb,) | A(nc,) | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, A=None, B=None, C=None, x0=None, **blockargs): r""" State-space LTI dynamics. :param N: numerator coefficients, defaults to 1 :type N: array_like, optional :param D: denominator coefficients, defaults to [1,1] :type D: array_like, optional :param x0: initial states, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A SCOPE block :rtype: LTI_SISO instance Implements the dynamics of a single-input single-output (SISO) linear time invariant (LTI) system described by numerator and denominator polynomial coefficients. Coefficients are given in the order from highest order to zeroth order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``. Only proper transfer functions, where order of numerator is less than denominator are allowed. The order of the states in ``x0`` is consistent with controller canonical form. Examples:: LTI_SISO(N=[1,2], D=[2, 3, -4]) is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`. """ #print('in SS constructor') assert A.shape[0] == A.shape[1], 'A must be square' n = A.shape[0] if len(B.shape) == 1: nin = 1 B = B.reshape((n, 1)) else: nin = B.shape[1] assert B.shape[0] == n, 'B must have same number of rows as A' if len(C.shape) == 1: nout = 1 assert C.shape[0] == n, 'C must have same number of columns as A' C = C.reshape((1, n)) else: nout = C.shape[0] assert C.shape[1] == n, 'C must have same number of columns as A' super().__init__(**blockargs) self.A = A self.B = B self.C = C self.nstates = A.shape[0] if x0 is None: self._x0 = np.zeros((self.nstates,)) else: self._x0 = x0
def output(self, t=None): return list(self.C @ self._x) def deriv(self): return self.A @ self._x + self.B @ np.array(self.inputs)
# ------------------------------------------------------------------------ #
[docs]class LTI_SISO(LTI_SS): """ :blockname:`LTI_SISO` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | n | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, N=1, D=[1, 1], x0=None, **blockargs): r""" SISO LTI dynamics. :param N: numerator coefficients, defaults to 1 :type N: array_like, optional :param D: denominator coefficients, defaults to [1,1] :type D: array_like, optional :param x0: initial states, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A SCOPE block :rtype: LTI_SISO instance Implements the dynamics of a single-input single-output (SISO) linear time invariant (LTI) system described by numerator and denominator polynomial coefficients. Coefficients are given in the order from highest order to zeroth order, ie. :math:`2s^2 - 4s +3` is ``[2, -4, 3]``. Only proper transfer functions, where order of numerator is less than denominator are allowed. The order of the states in ``x0`` is consistent with controller canonical form. Examples:: LTI_SISO(N=[1, 2], D=[2, 3, -4]) is the transfer function :math:`\frac{s+2}{2s^2+3s-4}`. """ #print('in SISO constscutor') if not isinstance(N, list): N = [N] if not isinstance(D, list): D = [D] self.N = N self.D = N n = len(D) - 1 nn = len(N) if x0 is None: x0 = np.zeros((n,)) assert nn <= n, 'direct pass through is not supported' # convert to numpy arrays N = np.r_[np.zeros((len(D) - len(N),)), np.array(N)] D = np.array(D) # normalize the coefficients to obtain # # b_0 s^n + b_1 s^(n-1) + ... + b_n # --------------------------------- # a_0 s^n + a_1 s^(n-1) + ....+ a_n # normalize so leading coefficient of denominator is one # D0 = D[0] # D = D / D0 # N = N / D0 # A = np.eye(len(D) - 1, k=1) # control canonic (companion matrix) form # A[-1, :] = -D[1:] # B = np.zeros((n, 1)) # B[-1] = 1 # C = (N[1:] - N[0] * D[1:]).reshape((1, n)) A, B, C, D = scipy.signal.tf2ss(N, D) self.num = N self.den = D if len(np.flatnonzero(D)) > 0: raise ValueError('D matrix is not zero') super().__init__(A=A, B=B, C=C, x0=x0, **blockargs) if self.verbose: print('A=', A) print('B=', B) print('C=', C) def change_param(self, param, newvalue): if param == 'num': self.num = newvalue elif param == 'den': self.den = newvalue self.A, self.B, self.C, self.D = scipy.signal.tf2ss(self.num, self.den) self.add_param('num', change_param) self.add_param('den', change_param)
if __name__ == "__main__": import pathlib import os.path exec(open(os.path.join(pathlib.Path( __file__).parent.absolute(), "test_transfers.py")).read())

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/components.html ================================================ bdsim.components — Block diagram simulation 0.7 documentation

Source code for bdsim.components

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Components of the simulation system, namely blocks, wires and plugs.
"""

import math
from re import S
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from collections import UserDict
[docs]class Struct(UserDict): """ A dict like object that allows items to be added by attribute or by key. For example:: >>> d = Struct('thing') >>> d.a = 1 >>> d['b'] = 2 >>> d.a 1 >>> d['a'] 1 >>> d.b 2 >>> str(d) "thing {'a': 1, 'b': 2}" """
[docs] def __init__(self, name='Struct', **kwargs): super().__init__() self.name = name for key, value in kwargs.items(): self[key] = value
def __setattr__(self, name, value): # invoked by struct[name] = value if name in ['data', 'name']: super().__setattr__(name, value) else: self.data[name] = value
[docs] def add(self, name, value): self.data[name] = value
def __getattr__(self, name): # return self.data[name] # some tricks to make this deepcopy safe # https://stackoverflow.com/questions/40583131/python-deepcopy-with-custom-getattr-and-setattr # https://stackoverflow.com/questions/25977996/supporting-the-deep-copy-operation-on-a-custom-class try: return super().__getattribute__('data')[name] except KeyError: raise AttributeError('unknown attribute ' + name) def __repr__(self): return str(self) def __str__(self): def fmt(k, v, indent=0): if isinstance(v, Struct): s = '{:12s}: {:12s}\n'.format(k, type(v).__name__) for k, v in v.items(): s += fmt(k, v, indent + 1) return s elif isinstance(v, np.ndarray): s = ' > ' * indent + '{:12s}| {:s}\n'.format(k, type(v).__name__ + ' ' + str(v.shape)) else: s = ' > ' * indent + '{:12s}| {:s} = {}\n'.format(k, type(v).__name__, v) return s s = '' for k, v in self.data.items(): if k.startswith('_'): continue s += fmt(k, v) return self.name + ':\n' + s
[docs]class PriorityQ:
[docs] def __init__(self): self.q = []
def __len__(self): return len(self.q) def __str__(self): if len(self) == 0: return f"PriorityQ: len={len(self)}" else: return f"PriorityQ: len={len(self)}, first out {self.q[0]}" def __repr__(self): return str(self)
[docs] def push(self, value): self.q.append(value)
[docs] def pop(self, dt=0): if len(self) == 0: return None, [] self.q.sort(key=lambda x: x[0]) qfirst = self.q.pop(0) t = qfirst[0] blocks = [qfirst[1]] while len(self.q) > 0 and self.q[0][0] < (t + dt): blocks.append(self.q.pop(0)[1]) return t, blocks
[docs] def pop_until(self, t): if len(self) == 0: return [] self.q.sort(key=lambda x: x[0]) i = 0 while True: if self.q[i][0] > t: out = self.q[:i] self.q = self.q[i:] return out i += 1
[docs]class Wire: """ Create a wire. :param start: Plug at the start of a wire, defaults to None :type start: Plug, optional :param end: Plug at the end of a wire, defaults to None :type end: Plug, optional :param name: Name of wire, defaults to None :type name: str, optional :return: A wire object :rtype: Wire A Wire object connects two block ports. A Wire has a reference to the start and end ports. A wire records all the connections defined by the user. At compile time wires are used to build inter-block references. Between two blocks, a wire can connect one or more ports, ie. it can connect a set of output ports on one block to a same sized set of input ports on another block. """ def __init__(self, start=None, end=None, name=None): self.name = name self.id = None self.start = start self.end = end self.value = None self.type = None self.name = None @property def info(self): """ Interactive display of wire properties. Displays all attributes of the wire for debugging purposes. """ print("wire:") for k,v in self.__dict__.items(): print(" {:8s}{:s}".format(k+":", str(v)))
[docs] def send(self, value, sinks=True): """ Send a value to the port at end of this wire. :param value: A port value :type value: float, numpy.ndarray, etc. The value is sent to the input port connected to the end of this wire. """ # dest is a Wire return self.end.block.setinput(self.end.port, value)
def __repr__(self): """ Display wire with name and connection details. :return: Long-form wire description :rtype: str String format:: wire.5: d2goal[0] --> Kv[0] """ return str(self) + ": " + self.fullname @property def fullname(self): """ Display wire connection details. :return: Wire name :rtype: str String format:: d2goal[0] --> Kv[0] """ return "{:s}[{:d}] --> {:s}[{:d}]".format(str(self.start.block), self.start.port, str(self.end.block), self.end.port) def __str__(self): """ Display wire name. :return: Wire name :rtype: str String format:: wire.5 """ s = "wire." if self.name is not None: s += self.name elif self.id is not None: s += str(self.id) else: s += '??' return s
# ------------------------------------------------------------------------- #
[docs]class Plug: """ Create a plug. :param block: The block being plugged into :type block: Block :param port: The port on the block, defaults to 0 :type port: int, optional :param type: 'start' or 'end', defaults to None :type type: str, optional :return: Plug object :rtype: Plug Plugs are the interface between a wire and block and have information about port number and wire end. Plugs are on the end of each wire, and connect a Wire to a specific port on a Block. The ``type`` argument indicates if the ``Plug`` is at: - the start of a wire, ie. the port is an output port - the end of a wire, ie. the port is an input port A plug can specify a set of ports on a block. """ __array_ufunc__ = None # allow block operators with NumPy values
[docs] def __init__(self, block, port=0, type=None): self.block = block self.port = port self.type = type # start
@property def isslice(self): """ Test if port number is a slice. :return: Whether the port is a slice :rtype: bool Returns ``True`` if the port is a slice, eg. ``[0:3]``, and ``False`` for a simple index, eg. ``[2]``. """ return isinstance(self.port, slice) @property def portlist(self): """ Return port numbers. :return: Port numbers :rtype: list of int If the port is a simple index, eg. ``[2]`` returns [2]. If the port is a slice, eg. ``[0:3]``, returns [0, 1, 2]. For the case ``[2:]`` the upper bound is the maximum number of input or output ports of the block. """ if isinstance(self.port, int): # easy case, this plug is a single wire return [self.port] elif isinstance(self.port, slice): # this plug is a bunch of wires start = self.port.start or 0 step = self.port.step or 1 if self.port.stop is None: if self.type == 'start': stop = self.block.nout else: stop = self.block.nin else: stop = self.port.stop return range(start, stop, step) else: return ValueError('bad plug index') @property def width(self): """ Return number of ports connected. :return: Number of ports :rtype: int If the port is a simple index, eg. ``[2]`` returns 1. If the port is a slice, eg. ``[0:3]``, returns 3. """ return len(self.portlist)
[docs] def __rshift__(left, right): """ Overloaded >> operator for implicit wiring. :param left: A plug to be wired from :type left: Plug :param right: A block or plug to be wired to :type right: Block or Plug :return: ``right`` :rtype: Block or Plug Implements implicit wiring, where the left-hand operator is a Plug, for example:: a = bike[2] >> bd.GAIN(3) will connect port 2 of ``bike`` to the input of the GAIN block. Note that:: a = bike[2] >> func[1] will connect port 2 of ``bike`` to port 1 of ``func``, and port 1 of ``func`` will be assigned to ``a``. To specify a different outport port on ``func`` we need to use parentheses:: a = (bike[2] >> func[1])[0] which will connect port 2 of ``bike`` to port 1 of ``func``, and port 0 of ``func`` will be assigned to ``a``. :seealso: Block.__mul__ """ # called for the cases: # block * block # block * plug s = left.block.bd #assert isinstance(right, Block), 'arguments to * must be blocks not ports (for now)' w = s.connect(left, right) # add a wire #print('plug * ' + str(w)) return right
[docs] def __add__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (plug) to be added :type self: Plug :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] + Y result = X[i] + Y[j] result = X[i] + C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("++")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. :seealso: :meth:`Plug.__radd__` :meth:`Block.__add__` """ if isinstance(other, (int, float, np.ndarray)): # plug + constant, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM('++', inputs=(self, other))
[docs] def __radd__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (plug) to be added :type self: Plug :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the right operand is a ``Plug`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X + Y[j] result = X[i] + Y[j] result = C + Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("++") block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: The inputs to the summing junction are reversed: right then left operand. :seealso: :meth:`Plug.__add__` :meth:`Block.__radd__` """ if isinstance(other, (int, float, np.ndarray)): # constant + plug, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM('++', inputs=(other, self))
[docs] def __sub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (plug) to be added (minuend) :type self: Plug :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] - Y result = X[i] - Y[j] result = X[i] - C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. :seealso: :meth:`Plug.__rsub__` :meth:`Block.__sub__` """ if isinstance(other, (int, float, np.ndarray)): # plug - constant, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM('+-', inputs=(self, other))
[docs] def __rsub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (plug) to be added (minuend) :type self: Plug :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X - Y[j] result = X[i] - Y[j] result = C - Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: The inputs to the summing junction are reversed: right then left operand. :seealso: :meth:`Plug.__sub__` :meth:`Block.__rsub__` """ # TODO deal with other cases as per above if isinstance(other, (int, float, np.ndarray)): # constant - plug, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.SUM('+-', inputs=(other, self))
[docs] def __neg__(self): """ Overloaded unary minus operator for implicit block creation. :param self: A signal (plug) to be negated :type self: Plug :return: GAIN block :rtype: Block subclass This method is implicitly invoked by the - operator for unary minus when the operand is a ``Plug``:: result = -X[i] where ``X`` is a block. Create a ``GAIN(-1)`` block named ``_gain.N`` whose input is the operand. :seealso: :meth:`Block.__neg__` """ return self.block.bd.GAIN(-1, inputs=[self])
[docs] def __mul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (plug) to be multiplied :type self: Plug :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] * Y result = X[i] * Y[j] result = X[i] * C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__rmul__` :meth:`Block.__mul__` """ if isinstance(other, (int, float, np.ndarray)): # plug * constant, create a GAIN block return self.block._autogain(other, premul=matrix, inputs=[self]) else: # value * value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 return self.block.bd.PROD('**', matrix=True, name=name, inputs=[self, other])
[docs] def __rmul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (plug) to be multiplied :type self: Plug :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the right operand is a ``Plug`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X * Y[j] result = X[i] * Y[j] result = C * Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__mul__` :meth:`Block.__rmul__` """ if isinstance(other, (int, float, np.ndarray)): # constant * plug, create a CONSTANT block matrix = isinstance(other, np.ndarray) return self.block._autogain(other, premul=matrix, inputs=[self])
[docs] def __truediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (plug) to be multiplied (dividend) :type self: Plug :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the / operator when the left operand is a ``Plug`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X[i] / Y result = X[i] / Y[j] result = X[i] / C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(1/C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__rtruediv__` :meth:`Block.__truediv__` """ if isinstance(other, (int, float, np.ndarray)): # plug / constant , create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.PROD('*/', inputs=(self, other))
[docs] def __rtruediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (plug) to be multiplied (dividend) :type self: Plug :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD block :rtype: Block subclass This method is implicitly invoked by the / operator when the right operand is a ``Plug`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X / Y[j] result = X[i] / Y[j] result = C / Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("*/")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, a new CONSTANT block named ``_const.N`` is also created. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Plug.__truediv__` :meth:`Block.__rtruediv__` """ if isinstance(other, (int, float, np.ndarray)): # constant / plug, create a CONSTANT block other = self.block.bd.CONSTANT(other) return self.block.bd.PROD('*/', inputs=(other, self))
[docs] def __repr__(self): """ Display plug details. :return: Plug description :rtype: str String format:: bicycle.0[1] """ return str(self.block) + "[" + str(self.port) + "]"
# ------------------------------------------------------------------------- # clocklist = []
[docs]class Clock:
[docs] def __init__(self, arg, unit='s', offset=0, name=None): global clocklist if unit == 's': self.T = arg elif unit == 'ms': self.T = arg / 1000 elif unit == 'Hz': self.T = 1 / arg else: raise ValueError('unknown clock unit', unit) self.offset = offset self.blocklist = [] self.x = [] # discrete state vector numpy.ndarray self.t = [] self.tick = 0 self.name = "clock." + str(len(clocklist)) clocklist.append(self)
# events happen at time t = kT + offset
[docs] def add_block(self, block): self.blocklist.append(block)
def __repr__(self): return str(self) def __str__(self): s = f"{self.name}: T={self.T} sec" if self.offset != 0: s += f", offset={self.offset}" s += f", clocking {len(self.blocklist)} blocks" return s
[docs] def getstate0(self): # get the state from each stateful block on this clock x0 = np.array([]) for b in self.blocklist: x0 = np.r_[x0, b.getstate0()] #print('x0', x0) return x0
[docs] def getstate(self): x = np.array([]) for b in self.blocklist: # update dstate x = np.r_[x, b.next().flatten()] return x
[docs] def setstate(self): x = self._x for b in self.blocklist: x = b.setstate(x) # send it to blocks
[docs] def start(self, state=None): self.i = 1 state.declare_event(self, self.time(self.i)) self.i += 1
[docs] def next_event(self, state=None): state.declare_event(self, self.time(self.i)) self.i += 1
[docs] def time(self, i): # return (math.floor((t - self.offset) / self.T) + 1) * self.T + self.offset # k = int((t - self.offset) / self.T + 0.5) return i * self.T + self.offset
[docs] def savestate(self, t): # save clock state at time t self.t.append(t) self.x.append(self.getstate())
# ------------------------------------------------------------------------- #
[docs]class Block: varinputs = False varoutputs = False __array_ufunc__ = None # allow block operators with NumPy values
[docs] def __new__(cls, *args, bd=None, **kwargs): """ Construct a new Block object. :param cls: The class to construct :type cls: class type :param *args: positional args passed to constructor :type *args: list :param **kwargs: keyword args passed to constructor :type **kwargs: dict :return: new Block instance :rtype: Block instance """ # print('Block __new__', args,bd, kwargs) block = super(Block, cls).__new__(cls) # create a new instance # we overload setattr, so need to know whether it is being passed a port # name. Add this attribute now to allow proper operation. block.__dict__['portnames'] = [] # must be first, see __setattr__ block.bd = bd block.nstates = 0 block.ndstates = 0 block._sequence = None return block
_latex_remove = str.maketrans({'$':'', '\\':'', '{':'', '}':'', '^':''})
[docs] def __init__(self, name=None, nin=None, nout=None, inputs=None, type=None, inames=None, onames=None, snames=None, pos=None, bd=None, blockclass=None, verbose=False, **kwargs): """ Construct a new block object. :param name: Name of the block, defaults to None :type name: str, optional :param nin: Number of inputs, defaults to None :type nin: int, optional :param nout: Number of outputs, defaults to None :type nout: int, optional :param inputs: Optional incoming connections :type inputs: Block, Plug or list of Block or Plug :param inames: Names of input ports, defaults to None :type inames: list of str, optional :param onames: Names of output ports, defaults to None :type onames: list of str, optional :param snames: Names of states, defaults to None :type snames: list of str, optional :param pos: Position of block on the canvas, defaults to None :type pos: 2-element tuple or list, optional :param bd: Parent block diagram, defaults to None :type bd: BlockDiagram, optional :param verbose: enable diagnostic prints, defaults to False :type verbose: bool, optional :param kwargs: Unused arguments :type kwargs: dict :return: A Block superclass :rtype: Block A block object is the superclass of all blocks in the simulation environment. This is the top-level initializer, and handles most options passed to the superclass initializer for each block in the library. """ # print('Block constructor, bd = ', bd) if name is not None: self.name_tex = name self.name = self._fixname(name) else: self.name_tex = None self.name = None self.bd = bd self.pos = pos self.id = None self.out = [] self.inputs = None self.updated = False self.shape = 'block' # for box self._inport_names = None self._outport_names = None self._state_names = None self.initd = True self._clocked = False self._graphics = False self._parameters = {} self.verbose = verbose if nin is not None: self.nin = nin if nout is not None: self.nout = nout if blockclass is not None: self.blockclass = blockclass if type is None: self.type = self.__class__.__name__.lower() if bd is not None: bd.add_block(self) if inames is not None: self.inport_names(inames) if onames is not None: self.outport_names(onames) if snames is not None: self.state_names(snames) if isinstance(inputs, Block): inputs = (inputs,) if inputs is not None and len(inputs) > 0: #assert len(inputs) == self.nin, 'Number of input connections must match number of inputs' for i, input in enumerate(inputs): self.bd.connect(input, Plug(self, port=i)) if len(kwargs) > 0: print('WARNING: unused arguments', kwargs.keys())
[docs] def add_param(self, param, handler=None): if handler == None: def handler(self, name, newvalue): setattr(self, name, newvalue) self.__dict__['_parameters'][param] = handler
[docs] def set_param(self, name, newvalue): print(f"setting parameter {name} of block {self.name} to {newvalue}") self._parameters[name](self, name, newvalue)
@property def info(self): """ Interactive display of block properties. Displays all attributes of the block for debugging purposes. """ print("block: " + type(self).__name__) for k,v in self.__dict__.items(): if k != 'sim': print(" {:11s}{:s}".format(k+":", str(v))) @property def isclocked(self): return self._clocked @property def isgraphics(self): return self._graphics # for use in unit testing def _eval(self, *inputs, t=None): """ Evaluate a block for unit testing. :param *inputs: List of input port values :type *inputs: list :param t: Simulation time, defaults to None :type t: float, optional :return: Block output port values :rtype: list The output ports of the block are evaluated for a given set of input port values and simulation time. Input and output port values are treated as lists. Mostly used for making concise unit tests. """ assert len(inputs) == self.nin, 'wrong number of inputs provided' self.inputs = inputs out = self.output(t=t) assert isinstance(out, list), 'result must be a list' assert len(out) == self.nout, 'result list is wrong length' return out
[docs] def __getitem__(self, port): """ Convert a block slice reference to a plug. :param port: Port number :type port: int :return: A port plug :rtype: Plug Invoked whenever a block is referenced as a slice, for example:: c = bd.CONSTANT(1) bd.connect(x, c[0]) bd.connect(c[0], x) In both cases ``c[0]`` is converted to a ``Plug`` by this method. """ # block[i] is a plug object #print('getitem called', self, port) return Plug(self, port)
[docs] def __setitem__(self, port, src): """ Convert a LHS block slice reference to a wire. :param port: Port number :type port: int :param src: the RHS :type src: Block or Plug Used to create a wired connection by assignment, for example:: X[0] = Y where ``X`` and ``Y`` are blocks. This method is implicitly invoked and creates a wire from ``Y`` to input port 0 of ``X``. .. note:: The square brackets on the left-hand-side is critical, and ``X = Y`` will simply overwrite the reference to ``X``. """ # b[port] = src # src --> b[port] #print('connecting', src, self, port) self.bd.connect(src, self[port])
[docs] def __setattr__(self, name, value): """ Convert a LHS block name reference to a wire. :param name: Port name :type port: str :param value: the RHS :type value: Block or Plug Used to create a wired connection by assignment, for example:: c = bd.CONSTANT(1, inames=['u']) c.u = x Ths method is invoked to create a wire from ``x`` to port 'u' of the constant block ``c``. Notes: - this overloaded method handles all instances of ``setattr`` and implements normal functionality as well, only creating a wire if ``name`` is a known port name. """ # b[port] = src # src --> b[port] # gets called for regular attribute settings, as well as for wiring if name in self.portnames: # we're doing wiring #print('in __setattr___', self, name, value) self.bd.connect(value, getattr(self, name)) else: # regular case, add attribute to the instance's dictionary self.__dict__[name] = value
[docs] def __rshift__(left, right): """ Operator for implicit wiring. :param left: A block to be wired from :type left: Block :param right: A block or plugto be wired to :type right: Block or Plug :return: ``right`` :rtype: Block or Plug Implements implicit wiring, for example:: a = bd.CONSTANT(1) >> bd.GAIN(2) will connect the output of the CONSTANT block to the input of the GAIN block. The result will be GAIN block, whose output in this case will be assigned to ``a``. Note that:: a = bd.CONSTANT(1) >> func[1] will connect port 0 of CONSTANT to port 1 of ``func``, and port 1 of ``func`` will be assigned to ``a``. To specify a different outport port on ``func`` we need to use parentheses:: a = (bd.CONSTANT(1) >> func[1])[0] which will connect port 0 of CONSTANT ` to port 1 of ``func``, and port 0 of ``func`` will be assigned to ``a``. :seealso: Plug.__rshift__ """ # called for the cases: # block * block # block * plug s = left.bd #assert isinstance(right, Block), 'arguments to * must be blocks not ports (for now)' w = s.connect(left, right) # add a wire #print('block * ' + str(w)) return right
# make connection, return a plug def _autoconstant(self, value): if isinstance(value, (int, float, str)): name = "_const.{:d}({})".format(self.bd.n_auto_const, value) else: name = "_const.{:d}<{}>".format(self.bd.n_auto_const, type(value).__name__) self.bd.n_auto_const += 1 return self.bd.CONSTANT(value, name=name) def _autogain(self, value, **kwargs): if isinstance(value, (int, float, str)): name = "_gain.{:d}({})".format(self.bd.n_auto_gain, value) else: name = "_gain.{:d}<{}>".format(self.bd.n_auto_gain, type(value).__name__) self.bd.n_auto_gain += 1 return self.bd.GAIN(value, name=name, **kwargs)
[docs] def __add__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (block) to be added :type self: Block :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X + Y result = X + Y[j] result = X + C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("++") block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: The inputs to the summing junction are reversed: right then left operand. :seealso: :meth:`Block.__radd__` :meth:`Plug.__add__` """ # value + value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # block + constant, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM('++', inputs=(self, other), name=name)
[docs] def __radd__(self, other): """ Overloaded + operator for implicit block creation. :param self: A signal (block) to be added :type self: Block :param other: A signal (block or plug) to be added :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the + operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X + Y[j] result = X[i] + Y[j] result = C + Y[j] where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("++") block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: The inputs to the summing junction are reversed: right then left operand. :seealso: :meth:`Block.__add__` :meth:`Plug._r_add__` """ # value + value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # constant + block, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM('++', inputs=(other, self), name=name)
[docs] def __sub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (block) to be added (minuend) :type self: Block :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X - Y result = X - Y[j] result = X - C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. :seealso: :meth:`Block.__rsub__` :meth:`Plug.__sub__` """ # value - value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # block - constant, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM('+-', inputs=(self, other), name=name)
[docs] def __rsub__(self, other): """ Overloaded - operator for implicit block creation. :param self: A signal (block) to be added (minuend) :type self: Block :param other: A signal (block or plug) to be subtracted (subtrahend) :type other: Block or Plug :return: SUM block :rtype: Block subclass This method is implicitly invoked by the - operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X - Y result = X[i] - Y result = C - Y where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Creates a ``SUM("+-")`` block named ``_sum.N`` whose inputs are the left and right operands. For the third case, a new ``CONSTANT(C)`` block named ``_const.N`` is also created. .. note:: The inputs to the summing junction are reversed: right then left operand. :seealso: :meth:`Block.__sub__` :meth:`Plug.__rsub__` """ # value - value, create a SUM block name = "_sum.{:d}".format(self.bd.n_auto_sum) self.bd.n_auto_sum += 1 if isinstance(other, (int, float, np.ndarray)): # constant - block, create a CONSTANT block other = self._autoconstant(other) return self.bd.SUM('+-', inputs=(other, self), name=name)
[docs] def __neg__(self): """ Overloaded unary minus operator for implicit block creation. :param self: A signal (block) to be negated :type self: Block :return: GAIN block :rtype: Block subclass This method is implicitly invoked by the - operator for unary minus when the operand is a ``Block``:: result = -X where ``X`` is a block. Creates a ``GAIN(-1)`` block named ``_gain.N`` whose input is the operand. :seealso: :meth:`Plug.__neg__` """ return self._autogain(-1.0, inputs=[self])
[docs] def __mul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (block) to be multiplied :type self: Block :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X * Y result = X * Y[j] result = X * C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__rmul__` :meth:`Plug.__mul__` """ matrix = False if isinstance(other, (int, float, np.ndarray)): # block * constant, create a GAIN block matrix = isinstance(other, np.ndarray) return self._autogain(other, premul=matrix, matrix=matrix, inputs=[self]) else: # value * value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 return self.bd.PROD('**', inputs=[self, other], matrix=matrix, name=name)
[docs] def __rmul__(self, other): """ Overloaded * operator for implicit block creation. :param self: A signal (block) to be multiplied :type self: Block :param other: A signal (block or plug) to be multiplied :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the * operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X * Y result = X[i] * Y result = C * Y where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__mul__` :meth:`Plug.__rmul__` """ matrix = False if isinstance(other, (int, float, np.ndarray)): # constant * block, create a GAIN block matrix = isinstance(other, np.ndarray) return self._autogain(other, premul=matrix, inputs=[self])
[docs] def __truediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (block) to be multiplied (dividend) :type self: Block :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD or GAIN block :rtype: Block subclass This method is implicitly invoked by the / operator when the left operand is a ``Block`` and the right operand is a ``Plug``, ``Block`` or constant:: result = X / Y result = X / Y[j] result = X / C where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. Create a ``PROD("**")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, create a ``GAIN(1/C)`` block named ``_gain.N``. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__rtruediv__` :meth:`Plug.__truediv__` """ # value / value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 matrix = False if isinstance(other, (int, float, np.ndarray)): # block / constant, create a CONSTANT block other = self._autoconstant(other) matrix = isinstance(other, np.ndarray) return self.bd.PROD('*/', inputs=(self, other), matrix=matrix, name=name)
[docs] def __rtruediv__(self, other): """ Overloaded / operator for implicit block creation. :param self: A signal (block) to be multiplied (dividend) :type self: Block :param other: A signal (block or plug) to be divided (divisor) :type other: Block or Plug :return: PROD block :rtype: Block subclass This method is implicitly invoked by the / operator when the right operand is a ``Block`` and the left operand is a ``Plug``, ``Block`` or constant:: result = X / Y result = X[i] / Y result = C / Y where ``X`` and ``Y`` are blocks and ``C`` is a Python or NumPy constant. For the first two cases, a ``PROD("*/")`` block named ``_prod.N`` whose inputs are the left and right operands. For the third case, a new CONSTANT block named ``_const.N`` is also created. .. note:: Signals are assumed to be scalars, but if ``C`` is a NumPy array then the option ``matrix`` is set to True. :seealso: :meth:`Block.__truediv__` :meth:`Plug.__rtruediv__` """ # value / value, create a PROD block name = "_prod.{:d}".format(self.bd.n_auto_prod) self.bd.n_auto_prod += 1 matrix = False if isinstance(other, (int, float, np.ndarray)): # constant / block, create a CONSTANT block other = self._autoconstant(other) matrix = isinstance(other, np.ndarray) return self.bd.PROD('*/', inputs=(other, self), matrix=matrix, name=name)
# TODO arithmetic with a constant, add a gain block or a constant block
[docs] def __str__(self): if hasattr(self, 'name') and self.name is not None: return self.name else: return self.blockclass + '.??'
[docs] def __repr__(self): return self.__str__()
def _fixname(self, s): return s.translate(self._latex_remove)
[docs] def inport_names(self, names): """ Set the names of block input ports. :param names: List of port names :type names: list of str Invoked by the ``inames`` argument to the Block constructor. The names can include LaTeX math markup. The LaTeX version is used where appropriate, but the port names are a de-LaTeXd version of the given string with backslash, caret, braces and dollar signs removed. """ self._inport_names = names for port, name in enumerate(names): fn = self._fixname(name) setattr(self, fn, self[port]) self.portnames.append(fn)
[docs] def outport_names(self, names): """ Set the names of block output ports. :param names: List of port names :type names: list of str Invoked by the ``onames`` argument to the Block constructor. The names can include LaTeX math markup. The LaTeX version is used where appropriate, but the port names are a de-LaTeXd version of the given string with backslash, caret, braces and dollar signs removed. """ self._outport_names = names for port, name in enumerate(names): fn = self._fixname(name) setattr(self, fn, self[port]) self.portnames.append(fn)
[docs] def state_names(self, names): self._state_names = names
[docs] def sourcename(self, port): """ Get the name of output port driving this input port. :param port: Input port :type port: int :return: Port name :rtype: str Return the name of the output port that drives the specified input port. The name can be: - a LaTeX string if provided - block name with port number given in square brackets. The block name will the one optionally assigned by the user using the ``name`` keyword, otherwise a systematic default name. :seealso: outport_names """ w = self.inports[port] if w.name is not None: return w.name src = w.start.block srcp = w.start.port if src._outport_names is not None: return src._outport_names[srcp] return str(w.start)
# @property # def fullname(self): # return self.blockclass + "." + str(self)
[docs] def reset(self): if self.nin > 0: self.inputs = [None] * self.nin self.updated = False
[docs] def add_outport(self, w): port = w.start.port assert port < len(self.outports), 'port number too big' self.outports[port].append(w)
[docs] def add_inport(self, w): port = w.end.port assert self.inports[port] is None, 'attempting to connect second wire to an input' self.inports[port] = w
[docs] def setinput(self, port, value): """ Receive input from a wire :param self: Block to be updated :type wire: Block :param port: Input port to be updated :type port: int :param value: Input value :type val: any """ # stash it away self.inputs[port] = value
[docs] def setinputs(self, *pos): assert len(pos) == self.nin, 'mismatch in number of inputs' self.reset() for i, val in enumerate(pos): self.inputs[i] = val
[docs] def start(self, **kwargs): # begin of a simulation pass
[docs] def check(self): # check validity of block parameters at start assert hasattr(self, 'nin'), f"block {self.name} has no nin specified" assert hasattr(self, 'nout'), f"block {self.name} has no nout specified" assert self.nin > 0 or self.nout > 0, f"block {self.name} no inputs or outputs specified" assert hasattr(self, 'initd') and self.initd, 'Block superclass not initalized. was super().__init__ called?'
[docs] def done(self, **kwargs): # end of simulation pass
[docs] def step(self, **kwargs): # valid pass
[docs] def savefig(self, *pos, **kwargs): pass
[docs]class SinkBlock(Block): """ A SinkBlock is a subclass of Block that represents a block that has inputs but no outputs. Typically used to save data to a variable, file or graphics. """ blockclass='sink'
[docs] def __init__(self, **blockargs): """ Create a sink block. :param blockargs: |BlockOptions| :type blockargs: dict :return: sink block base class :rtype: SinkBlock This is the parent class of all sink blocks. """ # print('Sink constructor') super().__init__(**blockargs) self.nout = 0 self.nstates = 0
[docs]class SourceBlock(Block): """ A SourceBlock is a subclass of Block that represents a block that has outputs but no inputs. Its output is a function of parameters and time. """ blockclass = 'source'
[docs] def __init__(self, **blockargs): """ Create a source block. :param blockargs: |BlockOptions| :type blockargs: dict :return: source block base class :rtype: SourceBlock This is the parent class of all source blocks. """ # print('Source constructor') super().__init__(**blockargs) self.nin = 0 self.nstates = 0
[docs]class TransferBlock(Block): """ A TransferBlock is a subclass of Block that represents a block with inputs outputs and states. Typically used to describe a continuous time dynamic system, either linear or nonlinear. """ blockclass = 'transfer'
[docs] def __init__(self, nstates=1, **blockargs): """ Create a transfer function block. :param blockargs: |BlockOptions| :type blockargs: dict :return: transfer function block base class :rtype: TransferBlock This is the parent class of all transfer function blocks. """ # print('Transfer constructor') self.nstates = nstates super().__init__(**blockargs)
[docs] def reset(self): super().reset() self._x = self._x0
# return self._x
[docs] def setstate(self, x): x = np.array(x) self._x = x[:self.nstates] # take as much state vector as we need return x[self.nstates:] # return the rest
[docs] def getstate0(self): return self._x0
[docs] def check(self): assert len(self._x0) == self.nstates, 'incorrect length for initial state' assert self.nin > 0 or self.nout > 0, 'no inputs or outputs specified'
[docs]class FunctionBlock(Block): """ A FunctionBlock is a subclass of Block that represents a block that has inputs and outputs but no state variables. Typically used to describe operations such as gain, summation or various mappings. """ blockclass = 'function'
[docs] def __init__(self, **blockargs): """ Create a function block. :param blockargs: |BlockOptions| :type blockargs: dict :return: function block base class :rtype: FunctionBlock This is the parent class of all function blocks. """ # print('Function constructor') super().__init__(**blockargs) self.nstates = 0
[docs]class SubsystemBlock(Block): """ A SubSystem s a subclass of Block that represents a block that has inputs and outputs but no state variables. Typically used to describe operations such as gain, summation or various mappings. """ blockclass = 'subsystem'
[docs] def __init__(self, **blockargs): """ Create a subsystem block. :param blockargs: |BlockOptions| :type blockargs: dict :return: subsystem block base class :rtype: SubsystemBlock This is the parent class of all subsystem blocks. """ # print('Subsystem constructor') super().__init__(**blockargs) self.nstates = 0
[docs]class ClockedBlock(Block): """ A ClockedBlock is a subclass of Block that represents a block with inputs outputs and discrete states. Typically used to describe a discrete time dynamic system, either linear or nonlinear. """ blockclass = 'clocked'
[docs] def __init__(self, clock=None, **blockargs): """ Create a clocked block. :param blockargs: |BlockOptions| :type blockargs: dict :return: clocked block base class :rtype: ClockedBlock This is the parent class of all clocked blocks. """ # print('Clocked constructor') super().__init__(**blockargs) assert clock is not None, 'clocked block must have a clock' self._clocked = True self.clock = clock clock.add_block(self)
[docs] def reset(self): super().reset()
# self._x = self._x0 # return self._x
[docs] def setstate(self, x): self._x = x[:self.ndstates] # take as much state vector as we need # print('** set block state to ', self._x) return x[self.ndstates:] # return the rest
[docs] def getstate0(self): return self._x0
[docs] def check(self): assert len(self._x0) == self.ndstates, 'incorrect length for initial state' assert self.nin > 0 or self.nout > 0, 'no inputs or outputs specified' self._x = self._x0
# c = Clock(5) # c1 = Clock(5, 2) # print(c, c1) # print(c.next(0), c1.next(0))

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/bdsim/graphics.html ================================================ bdsim.graphics — Block diagram simulation 0.7 documentation

Source code for bdsim.graphics

import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
from bdsim.components import SinkBlock

[docs]class GraphicsBlock(SinkBlock): """ A GraphicsBlock is a subclass of SinkBlock that represents a block that has inputs but no outputs and creates/updates a graphical display. """ blockclass='graphics'
[docs] def __init__(self, movie=None, **blockargs): """ Create a graphical display block. :param movie: Save animation in this file in MP4 format, defaults to None :type movie: str, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: transfer function block base class :rtype: TransferBlock This is the parent class of all graphic display blocks. """ super().__init__(**blockargs) self._graphics = True self.movie = movie
[docs] def start(self): plt.draw() plt.show(block=False) if self.movie is not None and not self.bd.options.animation: print('enabling global animation option to allow movie option on block', self) self.bd.options.animation = True if self.movie is not None: self.writer = animation.FFMpegWriter(fps=10, extra_args=['-vcodec', 'libx264']) self.writer.setup(fig=self.fig, outfile=self.movie) print('movie block', self, ' --> ', self.movie)
[docs] def step(self, state=None): super().step() if state.options.animation: self.fig.canvas.flush_events() if self.movie is not None: self.writer.grab_frame()
[docs] def done(self, state=None, block=False, **kwargs): if self.fig is not None: self.fig.canvas.start_event_loop(0.001) if self.movie is not None: self.writer.finish() # self.cleanup() plt.show(block=block)
[docs] def savefig(self, filename=None, format='pdf', **kwargs): """ Save the figure as an image file :param fname: Name of file to save graphics to :type fname: str :param ``**kwargs``: Options passed to `savefig <https://matplotlib.org/3.2.2/api/_as_gen/matplotlib.pyplot.savefig.html>`_ The file format is taken from the file extension and can be jpeg, png or pdf. """ try: plt.figure(self.fig.number) # make block's figure the current one if filename is None: filename = self.name filename += "." + format print('saved {} -> {}'.format(str(self), filename)) plt.savefig(filename, **kwargs) # save the current figure except: pass
[docs] def create_figure(self, state): def move_figure(f, x, y): """Move figure's upper left corner to pixel (x, y)""" backend = matplotlib.get_backend() x = int(x) + gstate.xoffset y = int(y) if backend == 'TkAgg': f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y)) elif backend == 'WXAgg': f.canvas.manager.window.SetPosition((x, y)) else: # This works for QT and GTK # You can also use window.setGeometry f.canvas.manager.window.move(x, y) gstate = state options = state.options print('#figs', plt.get_fignums()) if gstate.fignum == 0: # no figures yet created, lazy initialization matplotlib.use(options.backend) mpl_backend = matplotlib.get_backend() # split the string ntiles = [int(x) for x in options.tiles.split('x')] print("Graphics:") print(' backend:', mpl_backend) xoffset = 0 if options.shape is None: if mpl_backend == 'Qt5Agg': # next line actually creates a figure if none already exist screen = plt.get_current_fig_manager().canvas.screen() # this is a QScreenClass object, see https://doc.qt.io/qt-5/qscreen.html#availableGeometry-prop # next line creates a figure sz = screen.availableSize() dpiscale = screen.devicePixelRatio() # is 2.0 for Mac laptop screen print(sz.width(), sz.height(), dpiscale) # check for a second screen if options.altscreen: vsize = screen.availableVirtualGeometry().getCoords() if vsize[0] < 0: # extra monitor to the left xoffset = vsize[0] elif vsize[0] >= sz[0]: # extra monitor to the right xoffset = vsize[0] screen_width, screen_height = sz.width(), sz.height() dpi = screen.physicalDotsPerInch() f = plt.gcf() elif mpl_backend == 'TkAgg': print(' #figs', plt.get_fignums()) window = plt.get_current_fig_manager().window screen_width, screen_height = window.winfo_screenwidth(), window.winfo_screenheight() dpiscale = 1 print(' Size: %d x %d' % (screen_width, screen_height)) f = plt.gcf() dpi = f.dpi else: # all other backends f = plt.figure() dpi = f.dpi screen_width, screen_height = f.get_size_inches() * f.dpi # compute fig size in inches (width, height) figsize = [ screen_width / ntiles[1] / dpi, screen_height / ntiles[0] / dpi ] else: # shape is given explictly screen_width, screen_height = [int(x) for x in options.shape.split(':')] f = plt.gcf() f.canvas.manager.set_window_title(f"bdsim: Figure {f.number:d}") # save graphics info away in state gstate.figsize = figsize gstate.dpi = dpi gstate.screensize_pix = (screen_width, screen_height) gstate.ntiles = ntiles gstate.xoffset = xoffset # resize the figure f.set_size_inches(figsize, forward=True) plt.ion() else: # subsequent figures f = plt.figure(figsize=gstate.figsize) print(' #figs', plt.get_fignums()) # move the figure to right place on screen row = gstate.fignum // gstate.ntiles[0] col = gstate.fignum % gstate.ntiles[0] move_figure(f, col * gstate.figsize[0] * gstate.dpi, row * gstate.figsize[1] * gstate.dpi) gstate.fignum += 1 #print('create figure', self.fignum, row, col) print(f) return f

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/index.html ================================================ Overview: module code — Block diagram simulation 0.7 documentation ================================================ FILE: docs-aside/_modules/roboticstoolbox/blocks/arm.html ================================================ roboticstoolbox.blocks.arm — Block diagram simulation 0.7 documentation

Source code for roboticstoolbox.blocks.arm

import numpy as np
from math import sin, cos, atan2, tan, sqrt, pi

import matplotlib.pyplot as plt
import time
from spatialmath import base, SE3

from bdsim.components import TransferBlock, FunctionBlock, SourceBlock
from bdsim.graphics import GraphicsBlock

from roboticstoolbox import tpoly_func, lspb_func

"""
Robot blocks:
- have inputs and outputs
- are a subclass of ``FunctionBlock`` |rarr| ``Block`` for kinematics and have no states
- are a subclass of ``TransferBlock`` |rarr| ``Block`` for dynamics and have states

"""
# The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance.

# ------------------------------------------------------------------------ #
[docs]class FKine(FunctionBlock): """ :blockname:`FKINE` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | ndarray | SE3 | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('q',) outlabels = ('T',)
[docs] def __init__(self, robot=None, args={}, **blockargs): """ :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param robot: Robot model, defaults to None :type robot: Robot subclass, optional :param args: Options for fkine, defaults to {} :type args: dict, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a FORWARD_KINEMATICS block :rtype: Foward_Kinematics instance Robot arm forward kinematic model. **Block ports** :input q: Joint configuration vector as an ndarray. :output T: End-effector pose as an SE(3) object """ super().__init__(inputs=inputs, **blockargs) self.type = "forward-kinematics" self.robot = robot self.args = args self.inport_names(("q",)) self.outport_names(("T",))
def output(self, t=None): return [self.robot.fkine(self.inputs[0], **self.args)]
[docs]class IKine(FunctionBlock): """ :blockname:`IKINE` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | SE3 | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('T',) outlabels = ('q',)
[docs] def __init__( self, robot=None, q0=None, useprevious=True, ik=None, **blockargs ): """ :param robot: Robot model, defaults to None :type robot: Robot subclass, optional :param q0: Initial joint angles, defaults to None :type q0: array_like(n), optional :param useprevious: Use previous IK solution as q0, defaults to True :type useprevious: bool, optional :param ik: Specify an IK function, defaults to 'ikine_LM' :type ik: callable f(T) :param blockargs: |BlockOptions| :type blockargs: dict :return: an INVERSE_KINEMATICS block :rtype: Inverse_Kinematics instance Robot arm inverse kinematic model. The block has one input port: 1. End-effector pose as an SE(3) object and one output port: 1. Joint configuration vector as an ndarray. """ super().__init__(inputs=inputs, **blockargs) self.type = "inverse-kinematics" self.robot = robot self.q0 = q0 self.qprev = q0 self.useprevious = useprevious self.ik = None self.inport_names(("T",)) self.outport_names(("q",))
def start(self): super().start() if self.useprevious: self.qprev = self.q0 def output(self, t=None): if self.useprevious: q0 = self.qprev else: q0 = self.q0 if self.ik is None: sol = self.robot.ikine_LM(self.inputs[0], q0=q0) else: sol = self.ik(self.inputs[0]) if not sol.success: raise RuntimeError("inverse kinematic failure for pose", self.inputs[0]) if self.useprevious: self.qprev = sol.q return [sol.q]
# ------------------------------------------------------------------------ #
[docs]class Jacobian(FunctionBlock): """ :blockname:`JACOBIAN` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | ndarray | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('q',) outlabels = ('J',)
[docs] def __init__( self, robot, frame="0", inverse=False, pinv=False, transpose=False, **blockargs ): """ :param robot: Robot model :type robot: Robot subclass :param frame: Frame to compute Jacobian for, one of: '0' [default], 'e' :type frame: str, optional :param inverse: output inverse of Jacobian, defaults to False :type inverse: bool, optional :param pinv: output pseudo-inverse of Jacobian, defaults to False :type pinv: bool, optional :param transpose: output transpose of Jacobian, defaults to False :type transpose: bool, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a JACOBIAN block :rtype: Jacobian instance Robot arm Jacobian. The block has one input port: 1. Joint configuration vector as an ndarray. and one output port: 1. Jacobian matrix as an ndarray(6,n) .. notes:: - Only one of ``inverse`` or ``pinv`` can be True - ``inverse`` or ``pinv`` can be used in conjunction with ``transpose`` - ``inverse`` requires that the Jacobian is square - If ``inverse`` is True and the Jacobian is singular a runtime error will occur. """ super().__init__(inputs=inputs, **blockargs) self.robot = robot if frame == "0": self.jfunc = robot.jacob0 elif frame == "e": self.jfunc = robot.jacobe else: raise ValueError("unknown frame") if inverse and robot.n != 6: raise ValueError("cannot invert a non square Jacobian") if inverse and pinv: raise ValueError("can only set one of inverse and pinv") self.inverse = inverse self.pinv = pinv self.transpose = transpose self.inport_names(("q",)) self.outport_names(("J",))
def output(self, t=None): J = self.jfunc(self.inputs[0]) if self.inverse: J = np.linalg.inv(J) if self.pinv: J = np.linalg.pinv(J) if self.transpose: J = J.T return [J]
[docs]class Tr2Delta(FunctionBlock): """ :blockname:`TR2DELTA` .. table:: :align: left +------------+------------+---------+ | inputs | outputs | states | +------------+------------+---------+ | 2 | 1 | 0 | +------------+------------+---------+ | SE3, SE3 | ndarray(6) | | +------------+------------+---------+ """ nin = 2 nout = 1 inlabels = ('T1', 'T2') outlabels = ('Δ',)
[docs] def __init__(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict :return: a TR2DELTA block :rtype: Tr2Delta instance Difference between T1 and T2 as a 6-vector The block has two input port: 1. T1 as an SE3. 2. T2 as an SE3. and one output port: 1. delta as an ndarray(6,n) :seealso: :func:`spatialmath.base.tr2delta` """ super().__init__(inputs=inputs, **blockargs) self.inport_names(("T1", "T2")) self.outport_names(("$\delta$",))
def output(self, t=None): return [base.tr2delta(self.inputs[0].A, self.inputs[1].A)]
# ------------------------------------------------------------------------ #
[docs]class Delta2Tr(FunctionBlock): """ :blockname:`DELTA2TR` .. table:: :align: left +------------+----------+---------+ | inputs | outputs | states | +------------+----------+---------+ | 1 | 1 | 0 | +------------+----------+---------+ | ndarray(6) | SE3 | | +------------+----------+---------+ """ nin = 1 nout = 1 outlabels = ('T',) inlabels = ('Δ',)
[docs] def __init__(self, **blockargs): """ :param blockargs: |BlockOptions| :type blockargs: dict :return: a DELTA2TR block :rtype: Delta2Tr instance Delta to SE(3) The block has one input port: 1. delta as an ndarray(6,n) and one output port: 1. T as an SE3 :seealso: :func:`spatialmath.base.delta2tr` """ super().__init__(inputs=inputs, **blockargs) self.inport_names(("$\delta$",)) self.outport_names(("T",))
def output(self, t=None): return [SE3.Delta(self.inputs[0])]
# ------------------------------------------------------------------------ #
[docs]class Point2Tr(FunctionBlock): """ :blockname:`POINT2TR` .. table:: :align: left +------------+----------+---------+ | inputs | outputs | states | +------------+----------+---------+ | 1 | 1 | 0 | +------------+----------+---------+ | ndarray(3) | SE3 | | +------------+----------+---------+ """ nin = 1 nout = 1
[docs] def __init__(self, T, **blockargs): """ :param T: the transform :type T: SE3 :param blockargs: |BlockOptions| :type blockargs: dict :return: a POINT2TR block :rtype: Point2Tr instance The block has one input port: 1. a 3D point as an ndarray(3) and one output port: 1. T as an SE3 with its position part replaced by the input :seealso: :func:`spatialmath.base.delta2tr` """ super().__init__(inputs=inputs, **blockargs) self.inport_names(("t",)) self.outport_names(("T",)) self.pose = T
def output(self, t=None): T = SE3.SO3(self.pose.R, t=self.inputs[0]) return [T]
# ------------------------------------------------------------------------ #
[docs]class TR2T(FunctionBlock): """ :blockname:`TR2T` .. table:: :align: left +------------+----------+---------+ | inputs | outputs | states | +------------+----------+---------+ | 1 | 3 | 0 | +------------+----------+---------+ | SE3 | float | | +------------+----------+---------+ """ nin = 1 nout = 3 inlabels = ('T',) outlabels = ('x', 'y', 'z')
[docs] def __init__(self, **blockargs): """ :param T: the transform :type T: SE3 :param blockargs: |BlockOptions| :type blockargs: dict :return: a POINT2TR block :rtype: Point2Tr instance The block has one input port: 1. a 3D point as an ndarray(3) and one output port: 1. T as an SE3 with its position part replaced by the input :seealso: :func:`spatialmath.base.delta2tr` """ super().__init__(inputs=inputs, **blockargs) self.inport_names(("T",)) self.outport_names(("x", "y", "z"))
def output(self, t=None): t = self.inputs[0].t return list(t)
# ------------------------------------------------------------------------ #
[docs]class FDyn(TransferBlock): """ :blockname:`FDYN` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 3 | 0 | +------------+---------+---------+ | ndarray | ndarray,| | | | ndarray,| | | | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 3 outlabels = ('q', 'qd', 'qdd') inlabels = ('τ')
[docs] def __init__(self, robot, q0=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param q0: Initial joint configuration :type q0: array_like(n) :param blockargs: |BlockOptions| :type blockargs: dict :return: a FORWARD_DYNAMICS block :rtype: Foward_Dynamics instance Robot arm forward dynamics model. The block has one input port: 1. Joint force/torque as an ndarray. and three output ports: 1. joint configuration 2. joint velocity 3. joint acceleration """ super().__init__(**blockargs) self.type = "forward-dynamics" self.robot = robot self.nstates = robot.n * 2 # state vector is [q qd] self.inport_names(("$\tau$",)) self.outport_names(("q", "qd", "qdd")) if q0 is None: q0 = np.zeros((robot.n,)) else: q0 = base.getvector(q0, robot.n) self._x0 = np.r_[q0, np.zeros((robot.n,))] self._qdd = None
def output(self, t=None): n = self.robot.n q = self._x[:n] qd = self._x[n:] qdd = self._qdd # from last deriv return [q, qd, qdd] def deriv(self): # return [qd qdd] Q = self.inputs[0] n = self.robot.n assert len(Q) == n, "torque vector wrong size" q = self._x[:n] qd = self._x[n:] qdd = self.robot.accel(q, qd, Q) self._qdd = qdd return np.r_[qd, qdd]
[docs]class IDyn(FunctionBlock): """ :blockname:`IDYN` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 3 | 1 | 0 | +------------+---------+---------+ | ndarray, | ndarray | | | ndarray, | | | | ndarray | | | +------------+---------+---------+ """ nin = 3 nout = 1 inlabels = ('q', 'qd', 'qdd') outlabels = ('τ')
[docs] def __init__(self, robot, gravity=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param gravity: gravitational acceleration :type gravity: float :param blockargs: |BlockOptions| :type blockargs: dict :return: an INVERSE_DYNAMICS block :rtype: Inverse_Dynamics instance Robot arm forward dynamics model. The block has three input port: 1. Joint configuration vector as an ndarray. 2. Joint velocity vector as an ndarray. 3. Joint acceleration vector as an ndarray. and one output port: 1. joint torque/force .. TODO:: end-effector wrench input, base wrench output, payload input """ super().__init__(inputs=inputs, **blockargs) self.type = "inverse-dynamics" self.robot = robot self.gravity = gravity # state vector is [q qd] self.inport_names(("q", "qd", "qdd")) self.outport_names(("$\tau$",))
def output(self, t=None): tau = self.robot.rne(self.inputs[0], self.inputs[1], self.inputs[2], gravity=gravity) return [tau]
[docs]class Gravload(FunctionBlock): """ :blockname:`GRAVLOAD` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | ndarray | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('q',) outlabels = ('τ')
[docs] def __init__(self, robot, gravity=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param gravity: gravitational acceleration :type gravity: float :param blockargs: |BlockOptions| :type blockargs: dict :return: a GRAVLOAD block :rtype: Gravload instance Robot arm gravity torque. The block has one input port: 1. Joint configuration vector as an ndarray. and one output port: 1. joint torque/force due to gravity """ super().__init__(**blockargs) self.type = "gravload" self.robot = robots self.gravity = gravity self.inport_names(("q",)) self.outport_names(("$\tau$",))
def output(self, t=None): tau = self.robot.gravload(self.inputs[0], gravity=self.gravity) return [tau]
[docs]class Gravload_X(FunctionBlock): """ :blockname:`GRAVLOAD_X` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | ndarray | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('q',) outlabels = ('w')
[docs] def __init__(self, robot, gravity=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param gravity: gravitational acceleration :type gravity: float :param blockargs: |BlockOptions| :type blockargs: dict :return: a GRAVLOAD block :rtype: Gravload instance Robot arm gravity torque. The block has one input port: 1. Joint configuration vector as an ndarray. and one output port: 1. joint torque/force due to gravity """ super().__init__(**blockargs) self.type = "gravload-x" self.robot = robots self.gravity = gravity self.inport_names(("q",)) self.outport_names(("$\tau$",))
def output(self, t=None): q = self.inputs[0] tau = self.robot.gravload(q, gravity=self.gravity) J = self.robot.jacob0(q) if J.shape[0] == J.shape[1]: w = np.linalg.inv(J).T * tau else: w = np.linalg.pinv(J).T * tau return [w]
[docs]class Inertia(FunctionBlock): """ :blockname:`INERTIA` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | ndarray | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('q',) outlabels = ('M')
[docs] def __init__(self, robot, gravity=None, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param blockargs: |BlockOptions| :type blockargs: dict :return: an INERTIA block :rtype: Inertia instance Robot arm inertia matrix. The block has one input port: 1. Joint configuration vector as an ndarray. and one output port: 1. Joint-space inertia matrix :math:`\mat{M}(q)` """ super().__init__(**blockargs) self.type = "inertia" self.robot = robots self.inport_names(("q",)) self.outport_names(("M",))
def output(self, t=None): M = self.robot.inertia(self.inputs[0]) return [M]
[docs]class Inertia_X(FunctionBlock): """ :blockname:`INERTIA_X` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 0 | +------------+---------+---------+ | ndarray | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 1 inlabels = ('q',) outlabels = ('M')
[docs] def __init__(self, robot, representation=None, pinv=False, **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param blockargs: |BlockOptions| :type blockargs: dict :return: an INERTIA_X block :rtype: Inertia_X instance Robot arm task-space inertia matrix. The block has one input port: 1. Joint configuration vector as an ndarray. and one output port: 1. Task-space inertia matrix :math:`\mat{M}_x(q)` """ super().__init__(**blockargs) self.type = "inertia-x" self.robot = robot self.representation = representation self.pinv = pinv self.inport_names(("q",)) self.outport_names(("M",))
def output(self, t=None): q = self.inputs[0] Mx = self.robot.inertia_x(q, pinv=self.pinv, representation=self.representation) return [Mx]
# ------------------------------------------------------------------------ #
[docs]class FDyn_X(TransferBlock): """ :blockname:`FDYN_X` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 3 | 0 | +------------+---------+---------+ | ndarray | ndarray,| | | | ndarray,| | | | ndarray | | +------------+---------+---------+ """ nin = 1 nout = 5 outlabels = ('q', 'qd', 'x', 'xd', 'xdd') inlabels = ('w')
[docs] def __init__(self, robot, q0=None, gravcomp=False, velcomp=False, representation='rpy/xyz', **blockargs): """ :param robot: Robot model :type robot: Robot subclass :param end: Link to compute pose of, defaults to end-effector :type end: Link or str :param blockargs: |BlockOptions| :type blockargs: dict :return: a FDYN_X block :rtype: FDyn_X instance Robot arm forward dynamics model. The block has one input port: 1. Applied end-effector wrench as an ndarray. and three output ports: 1. task space pose 2. task space velocity 3. task space acceleration """ super().__init__(**blockargs) self.type = "forward-dynamics-x" self.robot = robot self.nstates = robot.n * 2 self.gravcomp = gravcomp self.velcomp = velcomp self.representation = representation # state vector is [q qd] self.inport_names(("w",)) self.outport_names(("q", "qd", "x", "xd", "xdd")) if q0 is None: q0 = np.zeros((robot.n,)) else: q0 = base.getvector(q0, robot.n) # append qd0, assumed to be zero self._x0 = np.r_[q0, np.zeros((robot.n,))] self._qdd = None
def output(self, t=None): n = self.robot.n q = self._x[:n] qd = self._x[n:] qdd = self._qdd # from last deriv T = self.robot.fkine(q) x = base.tr2x(T.A) Ja = self.robot.jacob0(q, analytical=self.representation) xd = Ja @ qd # print(q) # print(qd) # print(xd) # print(Ja) # print() if qdd is None: xdd = None else: Ja_dot = self.robot.jacob_dot(q, qd, J0=Ja) xdd = Ja @ qdd + Ja_dot @ qd return [q, qd, x, xd, xdd] def deriv(self): # return [qd qdd] # get current joint space state n = self.robot.n q = self._x[:n] qd = self._x[n:] # compute joint forces w = self.inputs[0] assert len(w) == 6, "wrench vector wrong size" Q = self.robot.jacob0(q, analytical=self.representation).T @ w if self.gravcomp or self.velcomp: if self.velcomp: qd_rne = qd else: qd_rne = np.zeros((n,)) Q_rne = self.robot.rne(q, qd_rne, np.zeros((n,))) qdd = self.robot.accel(q, qd, Q + Q_rne) self._qdd = qdd return np.r_[qd, qdd]
# ------------------------------------------------------------------------ #
[docs]class ArmPlot(GraphicsBlock): """ :blockname:`ARMPLOT` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 1 | 0 | 0 | +--------+---------+---------+ | ndarray| | | +--------+---------+---------+ """ nin = 1 nout = 0 inlabels = ('q',)
[docs] def __init__(self, robot=None, *inputs, q0=None, backend=None, **blockargs): """ :param ``*inputs``: Optional incoming connections :type ``*inputs``: Block or Plug :param robot: Robot model :type robot: Robot subclass :param backend: RTB backend name, defaults to 'pyplot' :type backend: str, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: An ARMPLOT block :rtype: ArmPlot instance Create a robot animation. Notes: - Uses RTB ``plot`` method Example of vehicle display (animated). The label at the top is the block name. """ super().__init__(inputs=inputs, **blockargs) self.inport_names(("q",)) if q0 is None: q0 = np.zeros((robot.n,)) self.robot = robot self.backend = backend self.q0 = q0 self.env = None
def start(self, state, **blockargs): # create the plot super().reset() if state.options.graphics: self.fig = self.create_figure(state) self.env = self.robot.plot( self.q0, backend=self.backend, fig=self.fig, block=False ) super().start() def step(self, state): # inputs are set self.robot.q = self.inputs[0] if state.options.animation: self.env.step() super().step(state) def done(self, block=False, **blockargs): if self.bd.options.graphics: plt.show(block=block) super().done()
# ------------------------------------------------------------------------ #
[docs]class Traj(FunctionBlock): """ :blockname:`TRAJ` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 0 or 1 | 1 | 0 | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """ nin = -1 nout = 3 outlabels = ('q',)
[docs] def __init__(self, y0=0, yf=1, T=None, time=False, traj="lspb", **blockargs): """ :param y0: initial value, defaults to 0 :type y0: array_like(m), optional :param yf: final value, defaults to 1 :type yf: array_like(m), optional :param T: time vector or number of steps, defaults to None :type T: array_like or int, optional :param time: x is simulation time, defaults to False :type time: bool, optional :param traj: trajectory type, one of: 'lspb' [default], 'tpoly' :type traj: str, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: TRAJ block :rtype: Traj instance Create a trajectory block. A block that generates a trajectory using a trapezoidal or quintic polynomial profile. """ self.time = time if time: nin = 1 blockclass = "function" else: nin = 0 blockclass = "source" super().__init__(nin=nin, blockclass=blockclass, inputs=inputs, **blockargs) y0 = base.getvector(y0) yf = base.getvector(yf) assert len(y0) == len(yf), "y0 and yf must have same length" self.y0 = y0 self.yf = yf self.time = time self.T = T self.traj = traj self.outport_names(("y", "yd", "ydd"))
def start(self, **blockargs): if self.time: assert self.x[0] <= 0, "interpolation not defined for t=0" assert self.x[-1] >= self.bd.T, "interpolation not defined for t=T" if self.traj == "lspb": trajfunc = lspb_func elif self.traj == "tpoly": trajfunc = tpoly_func self.trajfuncs = [] for i in range(len(self.y0)): self.trajfuncs.append(trajfunc(self.y0[i], self.yf[i], self.T)) def output(self, t=None): if self.time: t = self.inputs[0] out = [] for i in range(len(self.y0)): out.append(self.trajfuncs[i](t)) # we have a list of tuples out[i][j] # i is the timestep, j is y/yd/ydd y = [o[0] for o in out] yd = [o[1] for o in out] ydd = [o[2] for o in out] return [np.hstack(y), np.hstack(yd), np.hstack(ydd)]
# ------------------------------------------------------------------------ #
[docs]class JTraj(SourceBlock): """ :blockname:`JTRAJ` .. table:: :align: left +------------+------------+---------+ | inputs | outputs | states | +------------+------------+---------+ | 0 | 3 | 0 | +------------+------------+---------+ | | ndarray(n) | | +------------+------------+---------+ """ nin = 0 nout = 3 outlabels = ('q', 'qd', 'qdd')
[docs] def __init__(self, q0, qf, qd0=None, qdf=None, T=None, **blockargs): """ Compute a joint-space trajectory :param q0: initial joint coordinate :type q0: array_like(n) :param qf: final joint coordinate :type qf: array_like(n) :param T: time vector or number of steps, defaults to None :type T: array_like or int, optional :param qd0: initial velocity, defaults to None :type qd0: array_like(n), optional :param qdf: final velocity, defaults to None :type qdf: array_like(n), optional :param blockargs: |BlockOptions| :type blockargs: dict :return: TRAJ block :rtype: Traj instance - ``tg = jtraj(q0, qf, N)`` is a joint space trajectory where the joint coordinates vary from ``q0`` (M) to ``qf`` (M). A quintic (5th order) polynomial is used with default zero boundary conditions for velocity and acceleration. Time is assumed to vary from 0 to 1 in ``N`` steps. - ``tg = jtraj(q0, qf, t)`` as above but ``t`` is a uniformly-spaced time vector The return value is an object that contains position, velocity and acceleration data. Notes: - The time vector, if given, scales the velocity and acceleration outputs assuming that the time vector starts at zero and increases linearly. :seealso: :func:`ctraj`, :func:`qplot`, :func:`~SerialLink.jtraj` """ super().__init__(**blockargs) self.type = "source" self.outport_names( ( "q", "qd", "qdd", ) ) q0 = base.getvector(q0) qf = base.getvector(qf) if not len(q0) == len(qf): raise ValueError("q0 and q1 must be same size") if qd0 is None: qd0 = np.zeros(q0.shape) else: qd0 = getvector(qd0) if not len(qd0) == len(q0): raise ValueError("qd0 has wrong size") if qdf is None: qdf = np.zeros(q0.shape) else: qd1 = getvector(qdf) if not len(qd1) == len(q0): raise ValueError("qd1 has wrong size") self.q0 = q0 self.qf = qf self.qd0 = qd0 self.qdf = qf # call start now, so that output works when called by compile # set T to 1 just for now if T is None: self.T = 1 self.start() self.T = T
def start(self, state=None): if self.T is None: # use simulation tmax self.T = state.T tscal = self.T self.tscal = tscal q0 = self.q0 qf = self.qf qd0 = self.qd0 qdf = self.qdf # compute the polynomial coefficients A = 6 * (qf - q0) - 3 * (qdf + qd0) * tscal B = -15 * (qf - q0) + (8 * qd0 + 7 * qdf) * tscal C = 10 * (qf - q0) - (6 * qd0 + 4 * qdf) * tscal E = qd0 * tscal F = q0 self.coeffs = np.array([A, B, C, np.zeros(A.shape), E, F]) self.dcoeffs = np.array( [np.zeros(A.shape), 5 * A, 4 * B, 3 * C, np.zeros(A.shape), E] ) self.ddcoeffs = np.array( [ np.zeros(A.shape), np.zeros(A.shape), 20 * A, 12 * B, 6 * C, np.zeros(A.shape), ] ) def output(self, t=None): tscal = self.tscal ts = t / tscal tt = np.array([ts ** 5, ts ** 4, ts ** 3, ts ** 2, ts, 1]).T qt = tt @ self.coeffs # compute velocity qdt = tt @ self.dcoeffs / tscal # compute acceleration qddt = tt @ self.ddcoeffs / tscal ** 2 return [qt, qdt, qddt]
# ------------------------------------------------------------------------ #
[docs]class LSPB(SourceBlock): """ :blockname:`LSPB` .. table:: :align: left +------------+------------+---------+ | inputs | outputs | states | +------------+------------+---------+ | 0 | 3 | 0 | +------------+------------+---------+ | | float | | +------------+------------+---------+ """ nin = 0 nout = 3 outlabels = ('q', 'qd', 'qdd')
[docs] def __init__(self, q0, qf, V=None, T=None, **blockargs): """ Compute a joint-space trajectory :param q0: initial joint coordinate :type q0: array_like(n) :param qf: final joint coordinate :type qf: array_like(n) :param T: time vector or number of steps, defaults to None :type T: array_like or int, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: LSPB block :rtype: LSPB instance - ``tg = jtraj(q0, qf, N)`` is a joint space trajectory where the joint coordinates vary from ``q0`` (M) to ``qf`` (M). A quintic (5th order) polynomial is used with default zero boundary conditions for velocity and acceleration. Time is assumed to vary from 0 to 1 in ``N`` steps. - ``tg = jtraj(q0, qf, t)`` as above but ``t`` is a uniformly-spaced time vector The return value is an object that contains position, velocity and acceleration data. Notes: - The time vector, if given, scales the velocity and acceleration outputs assuming that the time vector starts at zero and increases linearly. :seealso: :func:`ctraj`, :func:`qplot`, :func:`~SerialLink.jtraj` """ super().__init__(nout=3, **blockargs) self.type = "source" self.T = T self.q0 = q0 self.qf = qf
def start(self): if self.T is None: self.T = self.bd.state.T self.lspbfunc = lspb_func(self.q0, self.qf, self.T) def output(self, t=None): return self.lspbfunc(t)
# ------------------------------------------------------------------------ #
[docs]class CTraj(SourceBlock): """ :blockname:`CTRAJ` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 0 | 1 | 0 | +------------+---------+---------+ | | float | | +------------+---------+---------+ """ nin = 0 nout = 1 outlabels = ('T',)
[docs] def __init__( self, T1, T2, T, lspb=True, **blockargs ): """ [summary] :param T1: initial pose :type T1: SE3 :param T2: final pose :type T2: SE3 :param T: motion time :type T: float :param lspb: Use LSPB motion profile along the path :type lspb: bool :param blockargs: |BlockOptions| :type blockargs: dict :return: CTRAJ block :rtype: CTraj instance Create a Cartesian motion block. The block outputs a pose that varies smoothly from ``T1`` to ``T2`` over the course of ``T`` seconds. If ``T`` is not given it defaults to the simulation time. If ``lspb`` is True then an LSPB motion profile is used along the path to provide initial acceleration and final deceleration. Otherwise, motion is at constant velocity. :seealso: :method:`SE3.interp` """ # TODO # flag to rotate the frame rather than just translate it super().__init__(**blockargs) self.T1 = T1 self.T2 = T2 self.T = T
def start(self, state): if self.T is None: self.T = self.bd.state.T if self.lspb: self.lspbfunc = lspb_func(self.q0, self.qf, self.T) def output(self, t=None): if lspb: s = self.lspbfunc(t) else: s = np.min(t / self.T, 1.0) return self.T1.interp(self.T2, s)
# ------------------------------------------------------------------------ #
[docs]class CirclePath(SourceBlock): """ :blockname:`CIRCLEPATH` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 0 or 1 | 1 | 0 | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """ nin = 0 nout = 1
[docs] def __init__( self, radius=1, centre=(0, 0, 0), pose=None, frequency=1, unit="rps", phase=0, **blockargs ): """ :param radius: radius of circle, defaults to 1 :type radius: float :param centre: center of circle, defaults to [0,0,0] :type centre: array_like(3) :param pose: SE3 pose of output, defaults to None :type pose: SE3 :param frequency: rotational frequency, defaults to 1 :type frequency: float :param unit: unit for frequency, one of: 'rps' [default], 'rad' :type unit: str :param phase: phase :type phase: float :param blockargs: |BlockOptions| :type blockargs: dict :return: TRAJ block :rtype: Traj instance Create a circular motion block. The block outputs the coordinates of a point moving in a circle of radius ``r`` centred at ``centre`` and parallel to the xy-plane. By default the output is a 3-vector :math:`(x, y, z)` but if ``pose`` is an ``SE3`` instance the output is a copy of that pose with its translation set to the coordinate of the moving point. This is the motion of a frame with fixed orientation following a circular path. """ # TODO # flag to rotate the frame rather than just translate it super().__init__(**blockargs) if unit == "rps": omega = frequency * 2 * pi phase = frequency * 2 * pi elif unit == "rad": omega = frequency # Redundant assignment, commented for LGTM # phase = phase else: raise ValueError("bad units: rps or rad") self.radius = radius assert len(centre) == 3, "centre must be a 3 vector" self.centre = centre self.pose = pose self.omega = omega self.phase = phase self.outport_names(("y",))
def output(self, t=None): theta = t * self.omega + self.phase x = self.radius * cos(theta) + self.centre[0] y = self.radius * sin(theta) + self.centre[1] p = (x, y, self.centre[2]) if self.pose is not None: pp = SE3.Rt(self.pose.R, p) p = pp return [p]
if __name__ == "__main__": import pathlib import os.path exec( open( os.path.join(pathlib.Path(__file__).parent.absolute(), "test_robots.py") ).read() )

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/roboticstoolbox/blocks/mobile.html ================================================ roboticstoolbox.blocks.mobile — Block diagram simulation 0.7 documentation

Source code for roboticstoolbox.blocks.mobile

from typing import Type
import numpy as np
from math import sin, cos, atan2, tan, sqrt, pi

import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import time

from bdsim.components import TransferBlock
from bdsim.graphics import GraphicsBlock

from spatialmath import base
from roboticstoolbox import mobile

# ------------------------------------------------------------------------ #
[docs]class Bicycle(TransferBlock): """ :blockname:`BICYCLE` .. table:: :align: left +------------+------------+---------+ | inputs | outputs | states | +------------+------------+---------+ | 2 | 1 | 3 | +------------+------------+---------+ | float | ndarray(3) | | +------------+------------+---------+ """ nin = 2 nout = 1 inlabels = ('v', 'γ') outlabels = ('q',)
[docs] def __init__(self, L=1, speed_max=np.inf, accel_max=np.inf, steer_max=0.45 * pi, x0=None, **blockargs): r""" Create a vehicle model with Bicycle kinematics. :param L: Wheelbase, defaults to 1 :type L: float, optional :param speed_max: Velocity limit, defaults to 1 :type speed_max: float, optional :param accel_max: maximum acceleration, defaults to math.inf :type accel_max: float, optional :param steer_max: maximum steering angle, defaults to math.pi*0.45 :type steer_max: float, optional :param x0: Inital state, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a BICYCLE block :rtype: Bicycle instance Bicycle kinematic model with state :math:`[x, y, \theta]`. **Block ports** :input v: Vehicle speed (metres/sec). The velocity limit ``vlim`` is applied to the magnitude of this input. :input γ: Steering wheel angle (radians). The steering limit ``slim`` is applied to the magnitude of this input. :output q: configuration (x, y, θ) :seealso: :class:`mobile.Bicycle` :class:`DiffSteer` """ # TODO: add option to model the effect of steering arms, responds to # gamma dot super().__init__(nstates=3, **blockargs) self.vehicle = mobile.Bicycle(L=L, steer_max=steer_max, speed_max=speed_max, accel_max=accel_max) if x0 is None: self._x0 = np.zeros((self.nstates,)) else: assert len(x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(len(x0), self.nstates) self._x0 = x0 self.inport_names(('v', '$\gamma$')) self.outport_names(('q',)) self.state_names(('x', 'y', r'$\theta$'))
def output(self, t): return [self._x] # one output which is ndarray(3) def deriv(self): return self.vehicle.deriv(self._x, self.inputs)
# ------------------------------------------------------------------------ #
[docs]class Unicycle(TransferBlock): """ :blockname:`UNICYCLE` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 2 | 1 | 3 | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """ nin = 2 nout = 1 inlabels = ('v', 'ω') outlabels = ('q',)
[docs] def __init__(self, w=1, speed_max=np.inf, accel_max=np.inf, steer_max=None, a=0, x0=None, **blockargs): r""" Create a vehicle model with Unicycle kinematics. :param w: vehicle width, defaults to 1 :type w: float, optional :param speed_max: Velocity limit, defaults to 1 :type speed_max: float, optional :param accel_max: maximum acceleration, defaults to math.inf :type accel_max: float, optional :param steer_max: maximum steering rate, defaults to 1 :type steer_max: float, optional :param x0: Inital state, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a UNICYCLE block :rtype: Unicycle instance Unicycle kinematic model with state :math:`[x, y, \theta]`. **Block ports** :input v: Vehicle speed (metres/sec). The velocity limit ``vlim`` is applied to the magnitude of this input. :input ω: Angular velocity (radians/sec). The steering limit ``slim`` is applied to the magnitude of this input. :output q: configuration (x, y, θ) :seealso: :class:`Bicycle` :class:`DiffSteer` """ super().__init__(nstates=3, **blockargs) if x0 is None: self._x0 = np.zeros((self.nstates,)) else: assert len(x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(len(x0), self.nstates) self._x0 = x0 self.vehicle = mobile.Unicycle(w=w, steer_max=steer_max, speed_max=speed_max, accel_max=accel_max)
#TODO, add support for origin shift # If ``a`` is non-zero then the planar velocity of that point $x=a$ # can be controlled by # .. math:: # \begin{pmatrix} v \\ \omega \end{pmatrix} = # \begin{pmatrix} # \cos \theta & \sin \theta \\ # -\frac{1}{a}\sin \theta & \frac{1}{a}\cos \theta # \end{pmatrix}\begin{pmatrix} # \dot{x} \\ \dot{y} # \end{pmatrix} def output(self, t): return self._x def deriv(self): return self.vehicle.deriv(self._x, self.inputs)
# ------------------------------------------------------------------------ #
[docs]class DiffSteer(TransferBlock): """ :blockname:`DIFFSTEER` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 2 | 3 | 3 | +------------+---------+---------+ | float | float | | +------------+---------+---------+ """ nin = 2 nout = 1 inlabels = ('ωL', 'ωR') outlabels = ('q',)
[docs] def __init__(self, w=1, R=1, speed_max=np.inf, accel_max=np.inf, steer_max=None, a=0, x0=None, **blockargs): """ Create a differential steer vehicle model :param w: vehicle width, defaults to 1 :type w: float, optional :param R: Wheel radius, defaults to 1 :type R: float, optional :param speed_max: Velocity limit, defaults to 1 :type speed_max: float, optional :param accel_max: maximum acceleration, defaults to math.inf :type accel_max: float, optional :param steer_max: maximum steering rate, defaults to 1 :type steer_max: float, optional :param x0: Inital state, defaults to None :type x0: array_like, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a DIFFSTEER block :rtype: DifSteer instance Unicycle kinematic model with state :math:`[x, y, \theta]`, with with inputs given as wheel angular velocity. **Block ports** :input ωL: Left-wheel angular velocity (radians/sec). :input ωR: Right-wheel angular velocity (radians/sec). :output q: configuration (x, y, θ) .. note:: Wheel velocity is defined such that if both are positive the vehicle moves forward. :seealso: :class:`Bicycle` :class:`Unicycle` """ super().__init__(nstates=3, **blockargs) self.type = 'diffsteer' self.R = R if x0 is None: self._x0 = np.zeros((slef.nstates,)) else: assert len(x0) == self.nstates, "x0 is {:d} long, should be {:d}".format(len(x0), self.nstates) self._x0 = x0 self.vehicle = mobile.Unicycle(w=w, steer_max=steer_max, speed_max=speed_max, accel_max=accel_max)
def output(self, t): return self._x def deriv(self): # compute (v, omega) from left/right wheel speeds v = self.R * (self.inputs[0] + self.inputs[1]) / 2 omega = (self.inputs[1] + self.inputs[0]) / self.W return self.vehicle.deriv(self._x, (v, omega))
# ------------------------------------------------------------------------ #
[docs]class VehiclePlot(GraphicsBlock): """ :blockname:`VEHICLEPLOT` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 1 | 0 | 0 | +--------+---------+---------+ | ndarray| | | +--------+---------+---------+ """ nin = 1 nout = 0 inlabels = ('q',) # TODO add ability to render an image instead of an outline
[docs] def __init__(self, animation=None, path=None, labels=['X', 'Y'], square=True, init=None, scale=True, **blockargs): """ Create a vehicle animation :param animation: Graphical animation of vehicle, defaults to None :type animation: VehicleAnimation subclass, optional :param path: linestyle to plot path taken by vehicle, defaults to None :type path: str or dict, optional :param labels: axis labels (xlabel, ylabel), defaults to ["X","Y"] :type labels: array_like(2) or list :param square: Set aspect ratio to 1, defaults to True :type square: bool, optional :param init: initialize graphics, defaults to None :type init: callable, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: A VEHICLEPLOT block :rtype: VehiclePlot instance Create a vehicle animation similar to the figure below. **Block ports** :input q: configuration (x, y, θ) Notes: - The ``init`` function is called after the axes are initialized and can be used to draw application specific detail on the plot. In the example below, this is the dot and star. - A dynamic trail, showing path to date can be animated if the option ``path`` is set to a linestyle. .. figure:: ../../figs/rvc4_4.gif :width: 500px :alt: example of generated graphic Example of vehicle display (animated). The label at the top is the block name. """ super().__init__(**blockargs) self.xdata = [] self.ydata = [] self.type = 'vehicleplot' if init is not None: assert callable(init), 'graphics init function must be callable' self.init = init self.square = square self.pathstyle = path if scale != 'auto': if len(scale) == 2: scale = scale * 2 self.scale = scale self.labels = labels if animation is None: animation = mobile.VehiclePolygon() elif not isinstance(animation, mobile.VehicleAnimationBase): raise TypeError('animation object must be VehicleAnimationBase subclass') self.animation = animation
def start(self, state=None): # create the plot super().reset() try: print('graphics start') self.fig = self.create_figure(state) print('fig created') self.ax = self.fig.gca() print('axes') except: print('aaargh') if self.square: self.ax.set_aspect('equal') print('done') self.ax.grid(True) self.ax.set_xlabel(self.labels[0]) self.ax.set_ylabel(self.labels[1]) self.ax.set_title(self.name) if self.scale != 'auto': self.ax.set_xlim(*self.scale[0:2]) self.ax.set_ylim(*self.scale[2:4]) if self.init is not None: self.init(self.ax) if isinstance(self.pathstyle, str): self.line, = plt.plot(0, 0, self.pathstyle) elif isinstance(self.pathstyle, dict): self.line, = plt.plot(0, 0, **self.pathstyle) self.animation.add() plt.draw() plt.show(block=False) super().start() def step(self, state=None, **kwargs): # inputs are set xyt = self.inputs[0] # update the path line self.xdata.append(xyt[0]) self.ydata.append(xyt[1]) #plt.figure(self.fig.number) if self.pathstyle is not None: self.line.set_data(self.xdata, self.ydata) # update the vehicle pose self.animation.update(xyt) if self.scale == 'auto': self.ax.relim() self.ax.autoscale_view() super().step(state=state) def done(self, block=False, **kwargs): if self.bd.options.graphics: plt.show(block=block) super().done()

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_modules/roboticstoolbox/blocks/uav.html ================================================ roboticstoolbox.blocks.uav — Block diagram simulation 0.7 documentation

Source code for roboticstoolbox.blocks.uav

import numpy as np
from math import sin, cos, atan2, tan, sqrt, pi

import matplotlib.pyplot as plt
import time

from bdsim.components import TransferBlock, FunctionBlock
from bdsim.graphics import GraphicsBlock
# ------------------------------------------------------------------------ #

[docs]class MultiRotor(TransferBlock): """ :blockname:`MULTIROTOR` .. table:: :align: left +------------+---------+---------+ | inputs | outputs | states | +------------+---------+---------+ | 1 | 1 | 16 | +------------+---------+---------+ | A(4,) | dict | | +------------+---------+---------+ """ nin = 1 nout = 1 # Flyer2dynamics lovingly coded by Paul Pounds, first coded 12/4/04 # A simulation of idealised X-4 Flyer II flight dynamics. # version 2.0 2005 modified to be compatible with latest version of Matlab # version 3.0 2006 fixed rotation matrix problem # version 4.0 4/2/10, fixed rotor flapping rotation matrix bug, mirroring # version 5.0 8/8/11, simplified and restructured # version 6.0 25/10/13, fixed rotation matrix/inverse wronskian definitions, flapping cross-product bug # # New in version 2: # - Generalised rotor thrust model # - Rotor flapping model # - Frame aerodynamic drag model # - Frame aerodynamic surfaces model # - Internal motor model # - Much coolage # # Version 1.3 # - Rigid body dynamic model # - Rotor gyroscopic model # - External motor model # # ARGUMENTS # u Reference inputs 1x4 # tele Enable telemetry (1 or 0) 1x1 # crash Enable crash detection (1 or 0) 1x1 # init Initial conditions 1x12 # # INPUTS # u = [N S E W] # NSEW motor commands 1x4 # # CONTINUOUS STATES # z Position 3x1 (x,y,z) # v Velocity 3x1 (xd,yd,zd) # n Attitude 3x1 (Y,P,R) # o Angular velocity 3x1 (wx,wy,wz) # w Rotor angular velocity 4x1 # # Notes: z-axis downward so altitude is -z(3) # # CONTINUOUS STATE MATRIX MAPPING # x = [z1 z2 z3 n1 n2 n3 z1 z2 z3 o1 o2 o3 w1 w2 w3 w4] # # # CONTINUOUS STATE EQUATIONS # z` = v # v` = g*e3 - (1/m)*T*R*e3 # I*o` = -o X I*o + G + torq # R = f(n) # n` = inv(W)*o #
[docs] def __init__(self, model, groundcheck=True, speedcheck=True, x0=None, **blockargs): r""" Create a a multi-rotor dynamic model block. :param model: Vehicle geometric and inertial parameters :type model: dict :param groundcheck: Prevent vehicle moving below ground, defaults to True :type groundcheck: bool :param speedcheck: Check for zero rotor speed, defaults to True :type speedcheck: bool :param x0: Initial state, defaults to None :type x0: float, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: a MULTIROTOR block :rtype: MultiRotor instance **Block ports** :input ω: a vector of input rotor speeds in (radians/sec). These are, looking down, clockwise from the front rotor which lies on the x-axis. :output x: a dictionary signal with the following items: - ``x`` pose in the world frame as :math:`[x, y, z, \theta_Y, \theta_P, \theta_R]` - ``vb`` translational velocity in the world frame (metres/sec) - ``w`` angular rates in the world frame as yaw-pitch-roll rates (radians/second) - ``a1s`` longitudinal flapping angles (radians) - ``b1s`` lateral flapping angles (radians) Based on MATLAB code developed by Pauline Pounds 2004. """ super().__init__(nin=1, nout=1, **blockargs) self.type = 'quadrotor' try: nrotors = model['nrotors'] except KeyError: raise RuntimeError('vehicle model does not contain nrotors') assert nrotors % 2 == 0, 'Must have an even number of rotors' self.nstates = 12 if x0 is not None: assert len(x0) == self.nstates, "x0 is the wrong length" else: x0 = np.zeros((self.nstates,)) self._x0 = x0 self.nrotors = nrotors self.model = model self.groundcheck = groundcheck self.speedcheck = speedcheck self.D = np.zeros((3,self.nrotors)) self.theta = np.zeros((self.nrotors,)) for i in range(0, self.nrotors): theta = i / self.nrotors * 2 * pi # Di Rotor hub displacements (1x3) # first rotor is on the x-axis, clockwise order looking down from above self.D[:,i] = np.r_[ model['d'] * cos(theta), model['d'] * sin(theta), model['h']] self.theta[i] = theta self.a1s = np.zeros((self.nrotors,)) self.b1s = np.zeros((self.nrotors,))
def output(self, t=None): model = self.model # compute output vector as a function of state vector # z Position 3x1 (x,y,z) # v Velocity 3x1 (xd,yd,zd) # n Attitude 3x1 (Y,P,R) # o Angular velocity 3x1 (Yd,Pd,Rd) n = self._x[3:6] # RPY angles phi = n[0] # yaw the = n[1] # pitch psi = n[2] # roll # rotz(phi)*roty(the)*rotx(psi) # BBF > Inertial rotation matrix R = np.array([ [cos(the) * cos(phi), sin(psi) * sin(the) * cos(phi) - cos(psi) * sin(phi), cos(psi) * sin(the) * cos(phi) + sin(psi) * sin(phi)], [cos(the) * sin(phi), sin(psi) * sin(the) * sin(phi) + cos(psi) * cos(phi), cos(psi) * sin(the) * sin(phi) - sin(psi) * cos(phi)], [-sin(the), sin(psi) * cos(the), cos(psi) * cos(the)] ]) #inverted Wronskian iW = np.array([ [0, sin(psi), cos(psi)], [0, cos(psi) * cos(the), -sin(psi) * cos(the)], [cos(the), sin(psi) * sin(the), cos(psi) * sin(the)] ]) / cos(the) # return velocity in the body frame out = {} out['x'] = self._x[0:6] out['vb'] = np.linalg.inv(R) @ self._x[6:9] # translational velocity mapped to body frame out['w'] = iW @ self._x[9:12] # RPY rates mapped to body frame out['a1s'] = self.a1s out['b1s'] = self.b1s out['X'] = self._x return [out] def deriv(self): model = self.model # Body-fixed frame references # ei Body fixed frame references 3x1 e3 = np.r_[0, 0, 1] # process inputs w = self.inputs[0] if len(w) != self.nrotors: raise RuntimeError('input vector wrong size') if self.speedcheck and np.any(w == 0): # might need to fix this, preculudes aerobatics :( # mu becomes NaN due to 0/0 raise RuntimeError('quadrotor_dynamics: not defined for zero rotor speed'); # EXTRACT STATES FROM X z = self._x[0:3] # position in {W} n = self._x[3:6] # RPY angles {W} v = self._x[6:9] # velocity in {W} o = self._x[9:12] # angular velocity in {W} # PREPROCESS ROTATION AND WRONSKIAN MATRICIES phi = n[0] # yaw the = n[1] # pitch psi = n[2] # roll # rotz(phi)*roty(the)*rotx(psi) # BBF > Inertial rotation matrix R = np.array([ [cos(the)*cos(phi), sin(psi)*sin(the)*cos(phi)-cos(psi)*sin(phi), cos(psi)*sin(the)*cos(phi)+sin(psi)*sin(phi)], [cos(the)*sin(phi), sin(psi)*sin(the)*sin(phi)+cos(psi)*cos(phi), cos(psi)*sin(the)*sin(phi)-sin(psi)*cos(phi)], [-sin(the), sin(psi)*cos(the), cos(psi)*cos(the)] ]) # Manual Construction # Q3 = [cos(phi) -sin(phi) 0;sin(phi) cos(phi) 0;0 0 1]; % RZ %Rotation mappings # Q2 = [cos(the) 0 sin(the);0 1 0;-sin(the) 0 cos(the)]; % RY # Q1 = [1 0 0;0 cos(psi) -sin(psi);0 sin(psi) cos(psi)]; % RX # R = Q3*Q2*Q1 %Rotation matrix # # RZ * RY * RX # inverted Wronskian iW = np.array([ [0, sin(psi), cos(psi)], [0, cos(psi)*cos(the), -sin(psi)*cos(the)], [cos(the), sin(psi)*sin(the), cos(psi)*sin(the)] ]) / cos(the) # ROTOR MODEL T = np.zeros((3,4)) Q = np.zeros((3,4)) tau = np.zeros((3,4)) a1s = self.a1s b1s = self.b1s for i in range(0, self.nrotors): # for each rotor # Relative motion Vr = np.cross(o, self.D[:,i]) + v mu = sqrt(np.sum(Vr[0:2]**2)) / (abs(w[i]) * model['r']) # Magnitude of mu, planar components lc = Vr[2] / (abs(w[i]) * model['r']) # Non-dimensionalised normal inflow li = mu # Non-dimensionalised induced velocity approximation alphas = atan2(lc, mu) j = atan2(Vr[1], Vr[0]) # Sideslip azimuth relative to e1 (zero over nose) J = np.array([ [cos(j), -sin(j)], [sin(j), cos(j)] ]) # BBF > mu sideslip rotation matrix # Flapping beta = np.array([ [((8/3*model['theta0'] + 2 * model['theta1']) * mu - 2 * lc * mu) / (1 - mu**2 / 2)], # Longitudinal flapping [0] # Lattitudinal flapping (note sign) ]) # sign(w) * (4/3)*((Ct/sigma)*(2*mu*gamma/3/a)/(1+3*e/2/r) + li)/(1+mu^2/2)]; beta = J.T @ beta; # Rotate the beta flapping angles to longitudinal and lateral coordinates. a1s[i] = beta[0] - 16 / model['gamma'] / abs(w[i]) * o[1] b1s[i] = beta[1] - 16 / model['gamma'] / abs(w[i]) * o[0] # Forces and torques # Rotor thrust, linearised angle approximations T[:,i] = model['Ct'] * model['rho'] * model['A'] * model['r']**2 * w[i]**2 * \ np.r_[-cos(b1s[i]) * sin(a1s[i]), sin(b1s[i]), -cos(a1s[i])*cos(b1s[i])] # Rotor drag torque - note that this preserves w[i] direction sign Q[:,i] = -model['Cq'] * model['rho'] * model['A'] * model['r']**3 * w[i] * abs(w[i])* e3 tau[:,i] = np.cross(T[:,i], self.D[:,i]) # Torque due to rotor thrust # RIGID BODY DYNAMIC MODEL dz = v dn = iW @ o dv = model['g'] * e3 + R @ np.sum(T, axis=1) / model['M'] # vehicle can't fall below ground, remember z is down if self.groundcheck and z[2] > 0: z[0] = 0 dz[0] = 0 do = np.linalg.inv(model['J']) @ (np.cross(-o, model['J'] @ o) + np.sum(tau, axis=1) + np.sum(Q, axis=1)) # row sum of torques # # stash the flapping information for plotting # self.a1s = a1s # self.b1s = b1s return np.r_[dz, dn, dv, do] # This is the state derivative vector
# ------------------------------------------------------------------------ #
[docs]class MultiRotorMixer(FunctionBlock): """ :blockname:`MULTIROTORMIXER` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 4 | 1 | 0 | +--------+---------+---------+ | float | | | +--------+---------+---------+ """ nin = 4 nout = 1 inlabels = ('𝛕r', '𝛕p', '𝛕y', 'T') outlabels = ('ω',)
[docs] def __init__(self, maxw=1000, minw=5, **blockargs): """ Create a block that displays/animates a multi-rotor flying vehicle. :param maxw: maximum rotor speed in rad/s, defaults to 1000 :type maxw: float :param minw: minimum rotor speed in rad/s, defaults to 5 :type minw: float :param blockargs: |BlockOptions| :type blockargs: dict :return: a MULTIROTORMIXER block :rtype: MultiRotorMixer instance **Block ports** :input 𝛕r: roll torque :input 𝛕p: pitch torque :input 𝛕y: yaw torque :input T: total thrust :output ω: 1D array of rotor speeds Derived from Simulink model by Pauline Pounds 2004 """ super().__init__(inputs=inputs, **blockargs) self.type = 'multirotormixer' self.minw = minw self.maxw = maxw
def output(self, t): w = np.zeros((self.nrotors,)) tau = self.inputs for i in self.nrotors: # roll and pitch coupling w[i] += -tau[0] * sin(self.theta[i]) + tau[1] * cos(self.theta[i]) # yaw coupling sign = 1 if i % 1 == 0 else -1 w[i] += sign * tau[2] # overall thrust w[i] += tau[3] / self.nrotors # clip the rotor speeds to the range [minw, maxw] w = np.clip(w, self.minw, self.maxw) # convert to thrust w = np.sqrt(w) / self.model['b'] # negate alterate rotors to indicate counter-rotation for i in self.nrotors: if i % 1 == 0: w[i] = -w[i] return [w]
# ------------------------------------------------------------------------ #
[docs]class MultiRotorPlot(GraphicsBlock): """ :blockname:`MULTIROTORPLOT` .. table:: :align: left +--------+---------+---------+ | inputs | outputs | states | +--------+---------+---------+ | 1 | 0 | 0 | +--------+---------+---------+ | dict | | | +--------+---------+---------+ """ nin = 1 nout = 0 inlabels = ('x',) # Based on code lovingly coded by Paul Pounds, first coded 17/4/02 # version 2 2004 added scaling and ground display # version 3 2010 improved rotor rendering and fixed mirroring bug # Displays X-4 flyer position and attitude in a 3D plot. # GREEN ROTOR POINTS NORTH # BLUE ROTOR POINTS EAST # PARAMETERS # s defines the plot size in meters # swi controls flyer attitude plot; 1 = on, otherwise off. # INPUTS # 1 Center X position # 2 Center Y position # 3 Center Z position # 4 Yaw angle in rad # 5 Pitch angle in rad # 6 Roll angle in rad
[docs] def __init__(self, model, scale=[-2, 2, -2, 2, 10], flapscale=1, projection='ortho', **blockargs): """ Create a block that displays/animates a multi-rotor flying vehicle. :param model: A dictionary of vehicle geometric and inertial properties :type model: dict :param scale: dimensions of workspace: xmin, xmax, ymin, ymax, zmin, zmax, defaults to [-2,2,-2,2,10] :type scale: array_like, optional :param flapscale: exagerate flapping angle by this factor, defaults to 1 :type flapscale: float :param projection: 3D projection, one of: 'ortho' [default], 'perspective' :type projection: str :param blockargs: |BlockOptions| :type blockargs: dict :return: a MULTIROTORPLOT block :rtype: MultiRotorPlot instance **Block ports** :input y: a dictionary signal that includes the item: - ``x`` pose in the world frame as :math:`[x, y, z, \theta_Y, \theta_P, \theta_R]` - ``X`` pose in the world frame as :math:`[x, y, z, \theta_Y, \theta_P, \theta_R]` - ``a1s`` - ``b1s`` .. figure:: ../../figs/multirotorplot.png :width: 500px :alt: example of generated graphic Example of quad-rotor display. Written by Pauline Pounds 2004 """ super().__init__(nin=1, **blockargs) self.type = 'quadrotorplot' self.model = model self.scale = scale self.nrotors = model['nrotors'] self.projection = projection self.flapscale = flapscale
def start(self, state): quad = self.model # vehicle dimensons d = quad['d']; # Hub displacement from COG r = quad['r']; # Rotor radius #C = np.zeros((3, self.nrotors)) ## WHERE USED? self.D = np.zeros((3,self.nrotors)) for i in range(0, self.nrotors): theta = i / self.nrotors * 2 * pi # Di Rotor hub displacements (1x3) # first rotor is on the x-axis, clockwise order looking down from above self.D[:,i] = np.r_[ quad['d'] * cos(theta), quad['d'] * sin(theta), quad['h']] #draw ground self.fig = plt.figure() # no axes in the figure, create a 3D axes self.ax = self.fig.add_subplot(111, projection='3d', proj_type=self.projection) # ax.set_aspect('equal') self.ax.set_xlabel('X') self.ax.set_ylabel('Y') self.ax.set_zlabel('-Z (height above ground)') self.panel = self.ax.text2D(0.05, 0.95, '', transform=self.ax.transAxes, fontsize=10, family='monospace', verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', edgecolor='black')) # TODO allow user to set maximum height of plot volume self.ax.set_xlim(self.scale[0], self.scale[1]) self.ax.set_ylim(self.scale[2], self.scale[3]) self.ax.set_zlim(0, self.scale[4]) # plot the ground boundaries and the big cross self.ax.plot([self.scale[0], self.scale[1]], [self.scale[2], self.scale[3]], [0, 0], 'b-') self.ax.plot([self.scale[0], self.scale[1]], [self.scale[3], self.scale[2]], [0, 0], 'b-') self.ax.grid(True) self.shadow, = self.ax.plot([0, 0], [0, 0], 'k--') self.groundmark, = self.ax.plot([0], [0], [0], 'kx') self.arm = [] self.disk = [] for i in range(0, self.nrotors): h, = self.ax.plot([0], [0], [0]) self.arm.append(h) if i == 0: color = 'b-' else: color = 'g-' h, = self.ax.plot([0], [0], [0], color) self.disk.append(h) self.a1s = np.zeros((self.nrotors,)) self.b1s = np.zeros((self.nrotors,)) def step(self, state): def plot3(h, x, y, z): h.set_data_3d(x, y, z) # h.set_data(x, y) # h.set_3d_properties(np.r_[z]) # READ STATE z = self.inputs[0]['x'][0:3] n = self.inputs[0]['x'][3:6] # TODO, check input dimensions, 12 or 12+2N, deal with flapping a1s = self.inputs[0]['a1s'] b1s = self.inputs[0]['b1s'] quad = self.model # vehicle dimensons d = quad['d'] # Hub displacement from COG r = quad['r'] # Rotor radius # PREPROCESS ROTATION MATRIX phi, the, psi = n # Euler angles # BBF > Inertial rotation matrix R = np.array([ [cos(the) * cos(phi), sin(psi) * sin(the) * cos(phi) - cos(psi) * sin(phi), cos(psi) * sin(the) * cos(phi) + sin(psi) * sin(phi)], [cos(the) * sin(phi), sin(psi) * sin(the) * sin(phi) + cos(psi) * cos(phi), cos(psi) * sin(the) * sin(phi) - sin(psi)* cos(phi)], [-sin(the), sin(psi)*cos(the), cos(psi) * cos(the)] ]) # Manual Construction #Q3 = [cos(psi) -sin(psi) 0;sin(psi) cos(psi) 0;0 0 1]; %Rotation mappings #Q2 = [cos(the) 0 sin(the);0 1 0;-sin(the) 0 cos(the)]; #Q1 = [1 0 0;0 cos(phi) -sin(phi);0 sin(phi) cos(phi)]; #R = Q3*Q2*Q1; %Rotation matrix # CALCULATE FLYER TIP POSITONS USING COORDINATE FRAME ROTATION F = np.array([ [1, 0, 0], [0, -1, 0], [0, 0, -1] ]) # Draw flyer rotors theta = np.linspace(0, 2 * pi, 20) circle = np.zeros((3, 20)) for j, t in enumerate(theta): circle[:,j] = np.r_[r * sin(t), r * cos(t), 0] hub = np.zeros((3, self.nrotors)) tippath = np.zeros((3, 20, self.nrotors)) for i in range(0, self.nrotors): hub[:,i] = F @ (z + R @ self.D[:,i]) # points in the inertial frame q = self.flapscale # Flapping angle scaling for output display - makes it easier to see what flapping is occurring # Rotor -> Plot frame Rr = np.array([ [cos(q * a1s[i]), sin(q * b1s[i]) * sin(q * a1s[i]), cos(q * b1s[i]) * sin(q * a1s[i])], [0, cos(q * b1s[i]), -sin(q*b1s[i])], [-sin(q * a1s[i]), sin(q * b1s[i]) * cos(q * a1s[i]), cos(q * b1s[i]) * cos(q * a1s[i])] ]) tippath[:,:,i] = F @ R @ Rr @ circle plot3(self.disk[i], hub[0,i] + tippath[0,:,i], hub[1,i] + tippath[1,:,i], hub[2,i] + tippath[2,:,i]) # Draw flyer hub0 = F @ z # centre of vehicle for i in range(0, self.nrotors): # line from hub to centre plot3([hub(1,N) hub(1,S)],[hub(2,N) hub(2,S)],[hub(3,N) hub(3,S)],'-b') plot3(self.arm[i], [hub[0,i], hub0[0]], [hub[1,i], hub0[1]], [hub[2,i], hub0[2]]) # plot a circle at the hub itself #plot3([hub(1,i)],[hub(2,i)],[hub(3,i)],'o') # plot the vehicle's centroid on the ground plane plot3(self.shadow, [z[0], 0], [-z[1], 0], [0, 0]) plot3(self.groundmark, z[0], -z[1], 0) textstr = f"t={state.t: .2f}\nh={z[2]: .2f}\nγ={n[0]: .2f}" self.panel.set_text(textstr) super().step(state=state)

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/_sources/bdsim.blocks.rst.txt ================================================ .. _Block library: ************* Block library ************* .. include:: The block diagrams comprise blocks which belong to one of a number of different categories. These come from the package ``bdsim.blocks``, ``roboticstoolbox.blocks``, ``machinevisiontoolbox.blocks``. Icons, if shown to the left of the black header bar, are as used with `bdedit `_. .. inheritance-diagram:: bdsim.components.SourceBlock bdsim.components.SinkBlock bdsim.graphics.GraphicsBlock bdsim.components.FunctionBlock bdsim.components.TransferBlock bdsim.components.SubsystemBlock Source blocks ============= .. automodule:: bdsim.blocks.sources :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Sink blocks =========== .. automodule:: bdsim.blocks.sinks :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Function blocks =============== .. automodule:: bdsim.blocks.functions :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Transfer blocks =============== .. automodule:: bdsim.blocks.transfers :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Discrete-time blocks ==================== .. automodule:: bdsim.blocks.discrete :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Linear algebra blocks ===================== .. automodule:: bdsim.blocks.linalg :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels Connection blocks ================= .. automodule:: bdsim.blocks.connections :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels External Toolbox blocksets ========================== These blocks are defined within external Toolboxes or packages. Robot blocks ------------ These blocks are defined within the Robotics Toolbox for Python. Arm robots ^^^^^^^^^^ .. automodule:: roboticstoolbox.blocks.arm :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Mobile robots ^^^^^^^^^^^^^ .. automodule:: roboticstoolbox.blocks.mobile :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Multi rotor flying robots ^^^^^^^^^^^^^^^^^^^^^^^^^ .. automodule:: roboticstoolbox.blocks.uav :members: :undoc-members: :show-inheritance: :special-members: __init__ :exclude-members: output, reset, step, start, done, deriv, nin, nout, inlabels, outlabels Vision blocks ------------- These blocks are defined within the Machine Vision Toolbox for Python. .. automodule:: machinevisiontoolbox.blocks :members: :undoc-members: :show-inheritance: :exclude-members: output, reset, step, start, done, nin, nout, inlabels, outlabels ================================================ FILE: docs-aside/_sources/bdsim.rst.txt ================================================ Overview ======== Getting started --------------- We first sketch the dynamic system we want to simulate as a block diagram, for example this simple first-order system .. image:: ../../figs/bd1-sketch.png :width: 800 which we can express concisely with `bdsim` as (see `bdsim/examples/eg1.py `_) .. code-block:: python :linenos: import bdsim sim = bdsim.BDSim() # create simulator bd = sim.blockdiagram() # create an empty block diagram # define the blocks demand = bd.STEP(T=1, name='demand') sum = bd.SUM('+-') gain = bd.GAIN(10) plant = bd.LTI_SISO(0.5, [2, 1], name='plant') scope = bd.SCOPE(styles=['k', 'r--'], movie='eg1.mp4') # connect the blocks bd.connect(demand, sum[0], scope[1]) bd.connect(plant, sum[1]) bd.connect(sum, gain) bd.connect(gain, plant) bd.connect(plant, scope[0]) bd.compile() # check the diagram bd.report() # list all blocks and wires out = sim.run(bd, 5) # simulate for 5s print(out) # sim.savefig(scope, 'scope0') # save scope figure as scope0.pdf sim.done(bd, block=True) # keep figures open on screen which is just 16 lines of executable code. The red block annotations on the hand-drawn diagram are used as the names of the variables holding references to the block instance. The blocks can also have user-assigned names, see lines 8 and 11, which are used in diagnostics and as labels in plots. After the blocks are created their input and output ports need to be connected. In `bdsim` all wires are point to point, a *one-to-many* connection is implemented by *many* wires, for example:: bd.connect(source, dest1, dest2, ...) creates individual wires from `source` -> `dest1`, `source` -> `dest2` and so on. Ports are designated using Python indexing notation, for example `block[2]` is port 2 (the third port) of `block`. Whether it is an input or output port depends on context. In the example above an index on the first argument refers to an output port, while on the second (or subsequent) arguments it refers to an input port. If a block has only a single input or output port then no index is required, 0 is assumed. A group of ports can be denoted using slice notation, for example:: bd.connect(source[2:5], dest[3:6) will connect ``source[2]`` -> ``dest[3]``, ``source[3]`` -> ``dest[4]``, ``source[4]`` -> ``dest[5]``. The number of wires in each slice must be consistent. You could even do a cross over by connecting ``source[2:5]`` to ``dest[6:3:-1]``. Line 20 assembles all the blocks and wires, instantiates subsystems, checks connectivity to create a flat wire list, and then builds the dataflow execution plan. Line 21 generates a report, in tabular form, showing a summary of the block diagram: .. code-block:: Blocks:: ┌───┬─────────┬─────┬──────┬────────┬─────────┬───────┐ │id │ name │ nin │ nout │ nstate │ ndstate │ type │ ├───┼─────────┼─────┼──────┼────────┼─────────┼───────┤ │ 0 │ demand │ 0 │ 1 │ 0 │ 0 │ step │ │ 1 │ sum.0 │ 2 │ 1 │ 0 │ 0 │ sum │ │ 2 │ gain.0 │ 1 │ 1 │ 0 │ 0 │ gain │ │ 3 │ plant │ 1 │ 1 │ 1 │ 0 │ LTI │ │ 4 │ scope.0 │ 2 │ 0 │ 0 │ 0 │ scope │ └───┴─────────┴─────┴──────┴────────┴─────────┴───────┘ Wires:: ┌───┬──────┬──────┬──────────────────────────┬─────────┐ │id │ from │ to │ description │ type │ ├───┼──────┼──────┼──────────────────────────┼─────────┤ │ 0 │ 0[0] │ 1[0] │ demand[0] --> sum.0[0] │ int │ │ 1 │ 0[0] │ 4[1] │ demand[0] --> scope.0[1] │ int │ │ 2 │ 3[0] │ 1[1] │ plant[0] --> sum.0[1] │ float64 │ │ 3 │ 1[0] │ 2[0] │ sum.0[0] --> gain.0[0] │ float64 │ │ 4 │ 2[0] │ 3[0] │ gain.0[0] --> plant[0] │ float64 │ │ 5 │ 3[0] │ 4[0] │ plant[0] --> scope.0[0] │ float64 │ └───┴──────┴──────┴──────────────────────────┴─────────┘ .. image:: ../../figs/Figure_1.png :width: 600 The simulation results are returned in a simple container object:: >>> out results: t | ndarray (67,) x | ndarray (67, 1) xnames | list where - `t` the time vector: ndarray, shape=(M,) - `x` is the state vector: ndarray, shape=(M,N), one row per timestep - `xnames` is a list of the names of the states corresponding to columns of `x`, eg. "plant.x0" To record additional simulation variables we "watch" them. This can be specified by wiring the signal to a WATCH block, or more conveniently by an additional option to ``run``:: out = sim.run(bd, 5, watch=[plant,demand]) # simulate for 5s and now the result ``out`` has additional elements:: >>> out results: t | ndarray (67,) x | ndarray (67, 1) xnames | list y0 | ndarray (67,) y1 | ndarray (67,) ynames | list where - `y0` is the time history of the first watched signal - `y1` is the time history of the second watched signal - `ynames` is a list of the names of the states corresponding to columns of `x`, eg. "plant[0]" Line 27 saves the content of the scope to be saved in the file called `scope0.pdf`. Line 28 blocks the script until all figure windows are closed, or the script is killed with SIGINT. Line 29 saves the scope graphics as a PDF file. Line 30 blocks until the last figure is dismissed. A list of available blocks can be obtained by:: >>> sim.blocks() 73 blocks loaded bdsim.blocks.functions..................: Sum Prod Gain Clip Function Interpolate bdsim.blocks.sources....................: Constant Time WaveForm Piecewise Step Ramp bdsim.blocks.sinks......................: Print Stop Null Watch bdsim.blocks.transfers..................: Integrator PoseIntegrator LTI_SS LTI_SISO bdsim.blocks.discrete...................: ZOH DIntegrator DPoseIntegrator bdsim.blocks.linalg.....................: Inverse Transpose Norm Flatten Slice2 Slice1 Det Cond bdsim.blocks.displays...................: Scope ScopeXY ScopeXY1 bdsim.blocks.connections................: Item Dict Mux DeMux Index SubSystem InPort OutPort roboticstoolbox.blocks.arm..............: FKine IKine Jacobian Tr2Delta Delta2Tr Point2Tr TR2T FDyn IDyn Gravload ........................................: Inertia Inertia_X FDyn_X ArmPlot Traj JTraj LSPB CTraj CirclePath roboticstoolbox.blocks.mobile...........: Bicycle Unicycle DiffSteer VehiclePlot roboticstoolbox.blocks.uav..............: MultiRotor MultiRotorMixer MultiRotorPlot machinevisiontoolbox.blocks.camera......: Camera Visjac_p EstPose_p ImagePlane More details can be found at: - `Wiki page `_ - `Adding blocks `_ - `Connecting blocks `_ - `Running the simulation `_ - :ref:`Block library` Using operator overloading -------------------------- Wiring, and some simple arithmetic blocks like GAIN, SUM and PROD can be implicitly generated by overloaded Python operators. This strikes a nice balance between block diagram coding and Pythonic programming. .. code-block:: python :linenos: import bdsim sim = bdsim.BDSim() # create simulator bd = sim.blockdiagram() # create an empty block diagram # define the blocks demand = bd.STEP(T=1, name='demand') plant = bd.LTI_SISO(0.5, [2, 1], name='plant') scope = bd.SCOPE(styles=['k', 'r--'], movie='eg1.mp4') # connect the blocks scope[0] = plant scope[1] = demand plant[0] = 10 * (demand - plant) bd.compile() # check the diagram bd.report() # list all blocks and wires out = sim.run(bd, 5) # simulate for 5s # out = sim.run(bd, 5 watch=[plant,demand]) # simulate for 5s print(out) # sim.savefig(scope, 'scope0') # save scope figure as scope0.pdf sim.done(bd, block=True) # keep figures open on screen This requires fewer lines of code and the code is more readable. Importantly, it results in in *exactly the same* block diagram in terms of blocks and wires:: ┌───┬──────┬──────┬──────────────────────────────┬─────────┐ │id │ from │ to │ description │ type │ ├───┼──────┼──────┼──────────────────────────────┼─────────┤ │ 0 │ 1[0] │ 2[0] │ plant[0] --> scope.0[0] │ float64 │ │ 1 │ 0[0] │ 2[1] │ demand[0] --> scope.0[1] │ int │ │ 2 │ 0[0] │ 3[0] │ demand[0] --> _sum.0[0] │ int │ │ 3 │ 1[0] │ 3[1] │ plant[0] --> _sum.0[1] │ float64 │ │ 4 │ 3[0] │ 4[0] │ _sum.0[0] --> _gain.0(10)[0] │ float64 │ │ 5 │ 4[0] │ 1[0] │ _gain.0(10)[0] --> plant[0] │ float64 │ └───┴──────┴──────┴──────────────────────────────┴─────────┘ The implicitly created blocks have names prefixed with an underscore. ================================================ FILE: docs-aside/_sources/index.rst.txt ================================================ .. Spatial Maths package documentation master file, created by sphinx-quickstart on Sun Apr 12 15:50:23 2020. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Block diagrams for Python ========================= .. raw:: html
   # define the blocks
   demand = bd.STEP(T=1, name='demand')
   sum = bd.SUM('+-')
   gain = bd.GAIN(10)
   plant = bd.LTI_SISO(0.5, [2, 1])
   scope = bd.SCOPE(styles=['k', 'r--'])

   # connect the blocks
   bd.connect(demand, sum[0], scope[1])
   bd.connect(plant, sum[1])
   bd.connect(sum, gain)
   bd.connect(gain, plant)
   bd.connect(plant, scope[0])
   
This Python package enables modelling and simulation of dynamic systems conceptualized in block diagram form, but represented in terms of Python class and method calls. Unlike Simulink® or LabView®, we write Python code rather than drawing boxes and wires. Wires can communicate any Python type such as scalars, strings, lists, dictionaries, numpy arrays, other objects, and even functions. .. toctree:: :maxdepth: 2 :caption: Code documentation: bdsim bdsim.blocks internals ================================================ FILE: docs-aside/_sources/internals.rst.txt ================================================ Supporting classes ****************** .. inheritance-diagram:: bdsim.components BDSim class =========== This class describes the run-time environment for executing a block diagram. .. autoclass:: bdsim.BDSim :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.Struct :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.BDSimState :members: :undoc-members: :show-inheritance: :special-members: __init__ BlockDiagram class ================== This class describes a block diagram, a collection of blocks and wires that can be "executed". .. autoclass:: bdsim.blockdiagram :members: :undoc-members: :show-inheritance: Components ========== Wire ---- .. autoclass:: bdsim.Wire :members: :undoc-members: :show-inheritance: :exclude-members: __dict__, __weakref__, __array_ufunc__, __module__ Plug ---- .. autoclass:: bdsim.Plug :members: :undoc-members: :show-inheritance: :special-members: :exclude-members: __dict__, __weakref__, __array_ufunc__, __module__ Blocks ------ .. autoclass:: bdsim.Block :members: :undoc-members: :show-inheritance: :special-members: :exclude-members: __dict__, __weakref__, __array_ufunc__, __module__ Source block ^^^^^^^^^^^^ .. autoclass:: bdsim.SourceBlock :members: :undoc-members: :show-inheritance: :special-members: Sink block ^^^^^^^^^^ .. autoclass:: bdsim.SinkBlock :members: :undoc-members: :show-inheritance: :special-members: Function block ^^^^^^^^^^^^^^ .. autoclass:: bdsim.FunctionBlock :members: :undoc-members: :show-inheritance: :special-members: Transfer function block ^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: bdsim.TransferBlock :members: :undoc-members: :show-inheritance: :special-members: Subsystem block ^^^^^^^^^^^^^^^ .. autoclass:: bdsim.SubsystemBlock :members: :undoc-members: :show-inheritance: :special-members: Graphics block ^^^^^^^^^^^^^^ .. autoclass:: bdsim.GraphicsBlock :members: :undoc-members: :show-inheritance: :special-members: Discrete-time systems --------------------- .. autoclass:: bdsim.ClockedBlock :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.Clock :members: :undoc-members: :show-inheritance: :special-members: __init__ .. autoclass:: bdsim.PriorityQ :members: :undoc-members: :show-inheritance: :special-members: __init__ ================================================ FILE: docs-aside/_sources/modules.rst.txt ================================================ bdsim ===== .. toctree:: :maxdepth: 4 bdsim ================================================ FILE: docs-aside/_static/alabaster.css ================================================ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: Georgia, serif; font-size: 17px; background-color: #fff; color: #000; margin: 0; padding: 0; } div.document { width: 940px; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 220px; } div.sphinxsidebar { width: 220px; font-size: 14px; line-height: 1.5; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #fff; color: #3E4349; padding: 0 30px 0 30px; } div.body > .section { text-align: left; } div.footer { width: 940px; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } p.caption { font-family: inherit; font-size: inherit; } div.relations { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0; margin: -10px 0 0 0px; text-align: center; } div.sphinxsidebarwrapper h1.logo { margin-top: -10px; text-align: center; margin-bottom: 5px; text-align: left; } div.sphinxsidebarwrapper h1.logo-name { margin-top: 0px; } div.sphinxsidebarwrapper p.blurb { margin-top: 0; font-style: normal; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: Georgia, serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar ul li.toctree-l1 > a { font-size: 120%; } div.sphinxsidebar ul li.toctree-l2 > a { font-size: 110%; } div.sphinxsidebar input { border: 1px solid #CCC; font-family: Georgia, serif; font-size: 1em; } div.sphinxsidebar hr { border: none; height: 1px; color: #AAA; background: #AAA; text-align: left; margin-left: 0; width: 50%; } div.sphinxsidebar .badge { border-bottom: none; } div.sphinxsidebar .badge:hover { border-bottom: none; } /* To address an issue with donation coming after search */ div.sphinxsidebar h3.donation { margin-top: 10px; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: Georgia, serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #DDD; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #EAEAEA; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { margin: 20px 0px; padding: 10px 30px; background-color: #EEE; border: 1px solid #CCC; } div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { background-color: #FBFBFB; border-bottom: 1px solid #fafafa; } div.admonition p.admonition-title { font-family: Georgia, serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: #fff; } dt:target, .highlight { background: #FAF3E8; } div.warning { background-color: #FCC; border: 1px solid #FAA; } div.danger { background-color: #FCC; border: 1px solid #FAA; -moz-box-shadow: 2px 2px 4px #D52C2C; -webkit-box-shadow: 2px 2px 4px #D52C2C; box-shadow: 2px 2px 4px #D52C2C; } div.error { background-color: #FCC; border: 1px solid #FAA; -moz-box-shadow: 2px 2px 4px #D52C2C; -webkit-box-shadow: 2px 2px 4px #D52C2C; box-shadow: 2px 2px 4px #D52C2C; } div.caution { background-color: #FCC; border: 1px solid #FAA; } div.attention { background-color: #FCC; border: 1px solid #FAA; } div.important { background-color: #EEE; border: 1px solid #CCC; } div.note { background-color: #EEE; border: 1px solid #CCC; } div.tip { background-color: #EEE; border: 1px solid #CCC; } div.hint { background-color: #EEE; border: 1px solid #CCC; } div.seealso { background-color: #EEE; border: 1px solid #CCC; } div.topic { background-color: #EEE; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt, code { font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } .hll { background-color: #FFC; margin: 0 -12px; padding: 0 12px; display: block; } img.screenshot { } tt.descname, tt.descclassname, code.descname, code.descclassname { font-size: 0.95em; } tt.descname, code.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #EEE; -webkit-box-shadow: 2px 2px 4px #EEE; box-shadow: 2px 2px 4px #EEE; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #EEE; -webkit-box-shadow: 2px 2px 4px #EEE; box-shadow: 2px 2px 4px #EEE; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #EEE; background: #FDFDFD; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.field-list p { margin-bottom: 0.8em; } /* Cloned from * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 */ .field-name { -moz-hyphens: manual; -ms-hyphens: manual; -webkit-hyphens: manual; hyphens: manual; } table.footnote td.label { width: .1px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { /* Matches the 30px from the narrow-screen "li > ul" selector below */ margin: 10px 0 10px 30px; padding: 0; } pre { background: #EEE; padding: 7px 30px; margin: 15px 0px; line-height: 1.3em; } div.viewcode-block:target { background: #ffd; } dl pre, blockquote pre, li pre { margin-left: 0; padding-left: 30px; } tt, code { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, code.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid #fff; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } /* Don't put an underline on images */ a.image-reference, a.image-reference:hover { border-bottom: none; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt, a:hover code { background: #EEE; } @media screen and (max-width: 870px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } li > ul { /* Matches the 30px from the "ul, ol" selector above */ margin-left: 30px; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } @media screen and (max-width: 875px) { body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: #fff; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: -20px -30px 20px -30px; padding: 10px 20px; background: #333; color: #FFF; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: #fff; } div.sphinxsidebar a { color: #AAA; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; } } @media screen and (min-width: 876px) { div.sphinxsidebar { position: fixed; margin-left: 0; } } /* misc. */ .revsys-inline { display: none!important; } /* Make nested-list/multi-paragraph items look better in Releases changelog * pages. Without this, docutils' magical list fuckery causes inconsistent * formatting between different release sub-lists. */ div#changelog > div.section > ul > li > p:only-child { margin-bottom: 0; } /* Hide fugly table cell borders in ..bibliography:: directive output */ table.docutils.citation, table.docutils.citation td, table.docutils.citation th { border: none; /* Below needed in some edge cases; if not applied, bottom shadows appear */ -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } /* relbar */ .related { line-height: 30px; width: 100%; font-size: 0.9rem; } .related.top { border-bottom: 1px solid #EEE; margin-bottom: 20px; } .related.bottom { border-top: 1px solid #EEE; } .related ul { padding: 0; margin: 0; list-style: none; } .related li { display: inline; } nav#rellinks { float: right; } nav#rellinks li+li:before { content: "|"; } nav#breadcrumbs li+li:before { content: "\00BB"; } /* Hide certain items when printing */ @media print { div.related { display: none; } } ================================================ FILE: docs-aside/_static/basic.css ================================================ /* * basic.css * ~~~~~~~~~ * * Sphinx stylesheet -- basic theme. * * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /* -- main layout ----------------------------------------------------------- */ div.clearer { clear: both; } div.section::after { display: block; content: ''; clear: left; } /* -- relbar ---------------------------------------------------------------- */ div.related { width: 100%; font-size: 90%; } div.related h3 { display: none; } div.related ul { margin: 0; padding: 0 0 0 10px; list-style: none; } div.related li { display: inline; } div.related li.right { float: right; margin-right: 5px; } /* -- sidebar --------------------------------------------------------------- */ div.sphinxsidebarwrapper { padding: 10px 5px 0 10px; } div.sphinxsidebar { float: left; width: 230px; margin-left: -100%; font-size: 90%; word-wrap: break-word; overflow-wrap : break-word; } div.sphinxsidebar ul { list-style: none; } div.sphinxsidebar ul ul, div.sphinxsidebar ul.want-points { margin-left: 20px; list-style: square; } div.sphinxsidebar ul ul { margin-top: 0; margin-bottom: 0; } div.sphinxsidebar form { margin-top: 10px; } div.sphinxsidebar input { border: 1px solid #98dbcc; font-family: sans-serif; font-size: 1em; } div.sphinxsidebar #searchbox form.search { overflow: hidden; } div.sphinxsidebar #searchbox input[type="text"] { float: left; width: 80%; padding: 0.25em; box-sizing: border-box; } div.sphinxsidebar #searchbox input[type="submit"] { float: left; width: 20%; border-left: none; padding: 0.25em; box-sizing: border-box; } img { border: 0; max-width: 100%; } /* -- search page ----------------------------------------------------------- */ ul.search { margin: 10px 0 0 20px; padding: 0; } ul.search li { padding: 5px 0 5px 20px; background-image: url(file.png); background-repeat: no-repeat; background-position: 0 7px; } ul.search li a { font-weight: bold; } ul.search li div.context { color: #888; margin: 2px 0 0 30px; text-align: left; } ul.keywordmatches li.goodmatch a { font-weight: bold; } /* -- index page ------------------------------------------------------------ */ table.contentstable { width: 90%; margin-left: auto; margin-right: auto; } table.contentstable p.biglink { line-height: 150%; } a.biglink { font-size: 1.3em; } span.linkdescr { font-style: italic; padding-top: 5px; font-size: 90%; } /* -- general index --------------------------------------------------------- */ table.indextable { width: 100%; } table.indextable td { text-align: left; vertical-align: top; } table.indextable ul { margin-top: 0; margin-bottom: 0; list-style-type: none; } table.indextable > tbody > tr > td > ul { padding-left: 0em; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2; } img.toggler { margin-right: 3px; margin-top: 3px; cursor: pointer; } div.modindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } div.genindex-jumpbox { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; margin: 1em 0 1em 0; padding: 0.4em; } /* -- domain module index --------------------------------------------------- */ table.modindextable td { padding: 2px; border-collapse: collapse; } /* -- general body styles --------------------------------------------------- */ div.body { min-width: 450px; max-width: 800px; } div.body p, div.body dd, div.body li, div.body blockquote { -moz-hyphens: auto; -ms-hyphens: auto; -webkit-hyphens: auto; hyphens: auto; } a.headerlink { visibility: hidden; } a.brackets:before, span.brackets > a:before{ content: "["; } a.brackets:after, span.brackets > a:after { content: "]"; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink, caption:hover > a.headerlink, p.caption:hover > a.headerlink, div.code-block-caption:hover > a.headerlink { visibility: visible; } div.body p.caption { text-align: inherit; } div.body td { text-align: left; } .first { margin-top: 0 !important; } p.rubric { margin-top: 30px; font-weight: bold; } img.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } img.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } img.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } img.align-default, .figure.align-default { display: block; margin-left: auto; margin-right: auto; } .align-left { text-align: left; } .align-center { text-align: center; } .align-default { text-align: center; } .align-right { text-align: right; } /* -- sidebars -------------------------------------------------------------- */ div.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; padding: 7px; background-color: #ffe; width: 40%; float: right; clear: right; overflow-x: auto; } p.sidebar-title { font-weight: bold; } div.admonition, div.topic, blockquote { clear: left; } /* -- topics ---------------------------------------------------------------- */ div.topic { border: 1px solid #ccc; padding: 7px; margin: 10px 0 10px 0; } p.topic-title { font-size: 1.1em; font-weight: bold; margin-top: 10px; } /* -- admonitions ----------------------------------------------------------- */ div.admonition { margin-top: 10px; margin-bottom: 10px; padding: 7px; } div.admonition dt { font-weight: bold; } p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; } div.body p.centered { text-align: center; margin-top: 25px; } /* -- content of sidebars/topics/admonitions -------------------------------- */ div.sidebar > :last-child, div.topic > :last-child, div.admonition > :last-child { margin-bottom: 0; } div.sidebar::after, div.topic::after, div.admonition::after, blockquote::after { display: block; content: ''; clear: both; } /* -- tables ---------------------------------------------------------------- */ table.docutils { margin-top: 10px; margin-bottom: 10px; border: 0; border-collapse: collapse; } table.align-center { margin-left: auto; margin-right: auto; } table.align-default { margin-left: auto; margin-right: auto; } table caption span.caption-number { font-style: italic; } table caption span.caption-text { } table.docutils td, table.docutils th { padding: 1px 8px 1px 5px; border-top: 0; border-left: 0; border-right: 0; border-bottom: 1px solid #aaa; } table.footnote td, table.footnote th { border: 0 !important; } th { text-align: left; padding-right: 5px; } table.citation { border-left: solid 1px gray; margin-left: 1px; } table.citation td { border-bottom: none; } th > :first-child, td > :first-child { margin-top: 0px; } th > :last-child, td > :last-child { margin-bottom: 0px; } /* -- figures --------------------------------------------------------------- */ div.figure { margin: 0.5em; padding: 0.5em; } div.figure p.caption { padding: 0.3em; } div.figure p.caption span.caption-number { font-style: italic; } div.figure p.caption span.caption-text { } /* -- field list styles ----------------------------------------------------- */ table.field-list td, table.field-list th { border: 0 !important; } .field-list ul { margin: 0; padding-left: 1em; } .field-list p { margin: 0; } .field-name { -moz-hyphens: manual; -ms-hyphens: manual; -webkit-hyphens: manual; hyphens: manual; } /* -- hlist styles ---------------------------------------------------------- */ table.hlist { margin: 1em 0; } table.hlist td { vertical-align: top; } /* -- other body styles ----------------------------------------------------- */ ol.arabic { list-style: decimal; } ol.loweralpha { list-style: lower-alpha; } ol.upperalpha { list-style: upper-alpha; } ol.lowerroman { list-style: lower-roman; } ol.upperroman { list-style: upper-roman; } :not(li) > ol > li:first-child > :first-child, :not(li) > ul > li:first-child > :first-child { margin-top: 0px; } :not(li) > ol > li:last-child > :last-child, :not(li) > ul > li:last-child > :last-child { margin-bottom: 0px; } ol.simple ol p, ol.simple ul p, ul.simple ol p, ul.simple ul p { margin-top: 0; } ol.simple > li:not(:first-child) > p, ul.simple > li:not(:first-child) > p { margin-top: 0; } ol.simple p, ul.simple p { margin-bottom: 0; } dl.footnote > dt, dl.citation > dt { float: left; margin-right: 0.5em; } dl.footnote > dd, dl.citation > dd { margin-bottom: 0em; } dl.footnote > dd:after, dl.citation > dd:after { content: ""; clear: both; } dl.field-list { display: grid; grid-template-columns: fit-content(30%) auto; } dl.field-list > dt { font-weight: bold; word-break: break-word; padding-left: 0.5em; padding-right: 5px; } dl.field-list > dt:after { content: ":"; } dl.field-list > dd { padding-left: 0.5em; margin-top: 0em; margin-left: 0em; margin-bottom: 0em; } dl { margin-bottom: 15px; } dd > :first-child { margin-top: 0px; } dd ul, dd table { margin-bottom: 10px; } dd { margin-top: 3px; margin-bottom: 10px; margin-left: 30px; } dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; } dt:target, span.highlighted { background-color: #fbe54e; } rect.highlighted { fill: #fbe54e; } dl.glossary dt { font-weight: bold; font-size: 1.1em; } .optional { font-size: 1.3em; } .sig-paren { font-size: larger; } .versionmodified { font-style: italic; } .system-message { background-color: #fda; padding: 5px; border: 3px solid red; } .footnote:target { background-color: #ffa; } .line-block { display: block; margin-top: 1em; margin-bottom: 1em; } .line-block .line-block { margin-top: 0; margin-bottom: 0; margin-left: 1.5em; } .guilabel, .menuselection { font-family: sans-serif; } .accelerator { text-decoration: underline; } .classifier { font-style: oblique; } .classifier:before { font-style: normal; margin: 0.5em; content: ":"; } abbr, acronym { border-bottom: dotted 1px; cursor: help; } /* -- code displays --------------------------------------------------------- */ pre { overflow: auto; overflow-y: hidden; /* fixes display issues on Chrome browsers */ } pre, div[class*="highlight-"] { clear: both; } span.pre { -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; } div[class*="highlight-"] { margin: 1em 0; } td.linenos pre { border: 0; background-color: transparent; color: #aaa; } table.highlighttable { display: block; } table.highlighttable tbody { display: block; } table.highlighttable tr { display: flex; } table.highlighttable td { margin: 0; padding: 0; } table.highlighttable td.linenos { padding-right: 0.5em; } table.highlighttable td.code { flex: 1; overflow: hidden; } .highlight .hll { display: block; } div.highlight pre, table.highlighttable pre { margin: 0; } div.code-block-caption + div { margin-top: 0; } div.code-block-caption { margin-top: 1em; padding: 2px 5px; font-size: small; } div.code-block-caption code { background-color: transparent; } table.highlighttable td.linenos, div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ user-select: none; } div.code-block-caption span.caption-number { padding: 0.1em 0.3em; font-style: italic; } div.code-block-caption span.caption-text { } div.literal-block-wrapper { margin: 1em 0; } code.descname { background-color: transparent; font-weight: bold; font-size: 1.2em; } code.descclassname { background-color: transparent; } code.xref, a code { background-color: transparent; font-weight: bold; } h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { background-color: transparent; } .viewcode-link { float: right; } .viewcode-back { float: right; font-family: sans-serif; } div.viewcode-block:target { margin: -1px -10px; padding: 0 10px; } /* -- math display ---------------------------------------------------------- */ img.math { vertical-align: middle; } div.body div.math p { text-align: center; } span.eqno { float: right; } span.eqno a.headerlink { position: absolute; z-index: 1; } div.math:hover a.headerlink { visibility: visible; } /* -- printout stylesheet --------------------------------------------------- */ @media print { div.document, div.documentwrapper, div.bodywrapper { margin: 0 !important; width: 100%; } div.sphinxsidebar, div.related, div.footer, #top-link { display: none; } } ================================================ FILE: docs-aside/_static/css/badge_only.css ================================================ .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} ================================================ FILE: docs-aside/_static/css/theme.css ================================================ html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} ================================================ FILE: docs-aside/_static/custom.css ================================================ /* This file intentionally left blank. */ ================================================ FILE: docs-aside/_static/doctools.js ================================================ /* * doctools.js * ~~~~~~~~~~~ * * Sphinx JavaScript utilities for all documentation. * * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ /** * select a different prefix for underscore */ $u = _.noConflict(); /** * make the code below compatible with browsers without * an installed firebug like debugger if (!window.console || !console.firebug) { var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; window.console = {}; for (var i = 0; i < names.length; ++i) window.console[names[i]] = function() {}; } */ /** * small helper function to urldecode strings */ jQuery.urldecode = function(x) { return decodeURIComponent(x).replace(/\+/g, ' '); }; /** * small helper function to urlencode strings */ jQuery.urlencode = encodeURIComponent; /** * This function returns the parsed url parameters of the * current request. Multiple values per key are supported, * it will always return arrays of strings for the value parts. */ jQuery.getQueryParameters = function(s) { if (typeof s === 'undefined') s = document.location.search; var parts = s.substr(s.indexOf('?') + 1).split('&'); var result = {}; for (var i = 0; i < parts.length; i++) { var tmp = parts[i].split('=', 2); var key = jQuery.urldecode(tmp[0]); var value = jQuery.urldecode(tmp[1]); if (key in result) result[key].push(value); else result[key] = [value]; } return result; }; /** * highlight a given string on a jquery object by wrapping it in * span elements with the given class name. */ jQuery.fn.highlightText = function(text, className) { function highlight(node, addItems) { if (node.nodeType === 3) { var val = node.nodeValue; var pos = val.toLowerCase().indexOf(text); if (pos >= 0 && !jQuery(node.parentNode).hasClass(className) && !jQuery(node.parentNode).hasClass("nohighlight")) { var span; var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); if (isInSVG) { span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); } else { span = document.createElement("span"); span.className = className; } span.appendChild(document.createTextNode(val.substr(pos, text.length))); node.parentNode.insertBefore(span, node.parentNode.insertBefore( document.createTextNode(val.substr(pos + text.length)), node.nextSibling)); node.nodeValue = val.substr(0, pos); if (isInSVG) { var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); var bbox = node.parentElement.getBBox(); rect.x.baseVal.value = bbox.x; rect.y.baseVal.value = bbox.y; rect.width.baseVal.value = bbox.width; rect.height.baseVal.value = bbox.height; rect.setAttribute('class', className); addItems.push({ "parent": node.parentNode, "target": rect}); } } } else if (!jQuery(node).is("button, select, textarea")) { jQuery.each(node.childNodes, function() { highlight(this, addItems); }); } } var addItems = []; var result = this.each(function() { highlight(this, addItems); }); for (var i = 0; i < addItems.length; ++i) { jQuery(addItems[i].parent).before(addItems[i].target); } return result; }; /* * backward compatibility for jQuery.browser * This will be supported until firefox bug is fixed. */ if (!jQuery.browser) { jQuery.uaMatch = function(ua) { ua = ua.toLowerCase(); var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; return { browser: match[ 1 ] || "", version: match[ 2 ] || "0" }; }; jQuery.browser = {}; jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; } /** * Small JavaScript module for the documentation. */ var Documentation = { init : function() { this.fixFirefoxAnchorBug(); this.highlightSearchWords(); this.initIndexTable(); if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { this.initOnKeyListeners(); } }, /** * i18n support */ TRANSLATIONS : {}, PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, LOCALE : 'unknown', // gettext and ngettext don't access this so that the functions // can safely bound to a different name (_ = Documentation.gettext) gettext : function(string) { var translated = Documentation.TRANSLATIONS[string]; if (typeof translated === 'undefined') return string; return (typeof translated === 'string') ? translated : translated[0]; }, ngettext : function(singular, plural, n) { var translated = Documentation.TRANSLATIONS[singular]; if (typeof translated === 'undefined') return (n == 1) ? singular : plural; return translated[Documentation.PLURALEXPR(n)]; }, addTranslations : function(catalog) { for (var key in catalog.messages) this.TRANSLATIONS[key] = catalog.messages[key]; this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); this.LOCALE = catalog.locale; }, /** * add context elements like header anchor links */ addContextElements : function() { $('div[id] > :header:first').each(function() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this headline')). appendTo(this); }); $('dt[id]').each(function() { $('\u00B6'). attr('href', '#' + this.id). attr('title', _('Permalink to this definition')). appendTo(this); }); }, /** * workaround a firefox stupidity * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 */ fixFirefoxAnchorBug : function() { if (document.location.hash && $.browser.mozilla) window.setTimeout(function() { document.location.href += ''; }, 10); }, /** * highlight the search words provided in the url in the text */ highlightSearchWords : function() { var params = $.getQueryParameters(); var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; if (terms.length) { var body = $('div.body'); if (!body.length) { body = $('body'); } window.setTimeout(function() { $.each(terms, function() { body.highlightText(this.toLowerCase(), 'highlighted'); }); }, 10); $('') .appendTo($('#searchbox')); } }, /** * init the domain index toggle buttons */ initIndexTable : function() { var togglers = $('img.toggler').click(function() { var src = $(this).attr('src'); var idnum = $(this).attr('id').substr(7); $('tr.cg-' + idnum).toggle(); if (src.substr(-9) === 'minus.png') $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); else $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); }).css('display', ''); if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { togglers.click(); } }, /** * helper function to hide the search marks again */ hideSearchWords : function() { $('#searchbox .highlight-link').fadeOut(300); $('span.highlighted').removeClass('highlighted'); }, /** * make the url absolute */ makeURL : function(relativeURL) { return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; }, /** * get the current relative url */ getCurrentURL : function() { var path = document.location.pathname; var parts = path.split(/\//); $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { if (this === '..') parts.pop(); }); var url = parts.join('/'); return path.substring(url.lastIndexOf('/') + 1, path.length - 1); }, initOnKeyListeners: function() { $(document).keydown(function(event) { var activeElementType = document.activeElement.tagName; // don't navigate when in search box or textarea if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { switch (event.keyCode) { case 37: // left var prevHref = $('link[rel="prev"]').prop('href'); if (prevHref) { window.location.href = prevHref; return false; } case 39: // right var nextHref = $('link[rel="next"]').prop('href'); if (nextHref) { window.location.href = nextHref; return false; } } } }); } }; // quick alias for translations _ = Documentation.gettext; $(document).ready(function() { Documentation.init(); }); ================================================ FILE: docs-aside/_static/documentation_options.js ================================================ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), VERSION: '0.7', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', FILE_SUFFIX: '.html', LINK_SUFFIX: '.html', HAS_SOURCE: true, SOURCELINK_SUFFIX: '.txt', NAVIGATION_WITH_KEYS: false }; ================================================ FILE: docs-aside/_static/graphviz.css ================================================ /* * graphviz.css * ~~~~~~~~~~~~ * * Sphinx stylesheet -- graphviz extension. * * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ img.graphviz { border: 0; max-width: 100%; } object.graphviz { max-width: 100%; } ================================================ FILE: docs-aside/_static/jquery-3.4.1.js ================================================ /*! * jQuery JavaScript Library v3.4.1 * https://jquery.com/ * * Includes Sizzle.js * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * * Date: 2019-05-01T21:04Z */ ( function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { // For CommonJS and CommonJS-like environments where a proper `window` // is present, execute the factory and get jQuery. // For environments that do not have a `window` with a `document` // (such as Node.js), expose a factory as module.exports. // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); // See ticket #14549 for more info. module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } // Pass this if window is not defined yet } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { // Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 // throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode // arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common // enough that all such attempts are guarded in a try block. "use strict"; var arr = []; var document = window.document; var getProto = Object.getPrototypeOf; var slice = arr.slice; var concat = arr.concat; var push = arr.push; var indexOf = arr.indexOf; var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); var support = {}; var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML elements // (i.e., `typeof document.createElement( "object" ) === "function"`). // We don't want to classify *any* DOM node as a function. return typeof obj === "function" && typeof obj.nodeType !== "number"; }; var isWindow = function isWindow( obj ) { return obj != null && obj === obj.window; }; var preservedScriptAttributes = { type: true, src: true, nonce: true, noModule: true }; function DOMEval( code, node, doc ) { doc = doc || document; var i, val, script = doc.createElement( "script" ); script.text = code; if ( node ) { for ( i in preservedScriptAttributes ) { // Support: Firefox 64+, Edge 18+ // Some browsers don't support the "nonce" property on scripts. // On the other hand, just using `getAttribute` is not enough as // the `nonce` attribute is reset to an empty string whenever it // becomes browsing-context connected. // See https://github.com/whatwg/html/issues/2369 // See https://html.spec.whatwg.org/#nonce-attributes // The `node.getAttribute` check was added for the sake of // `jQuery.globalEval` so that it can fake a nonce-containing node // via an object. val = node[ i ] || node.getAttribute && node.getAttribute( i ); if ( val ) { script.setAttribute( i, val ); } } } doc.head.appendChild( script ).parentNode.removeChild( script ); } function toType( obj ) { if ( obj == null ) { return obj + ""; } // Support: Android <=2.3 only (functionish RegExp) return typeof obj === "object" || typeof obj === "function" ? class2type[ toString.call( obj ) ] || "object" : typeof obj; } /* global Symbol */ // Defining this global in .eslintrc.json would create a danger of using the global // unguarded in another place, it seems safer to define global only for this module var version = "3.4.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); }, // Support: Android <=4.0 only // Make sure we trim BOM and NBSP rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; jQuery.fn = jQuery.prototype = { // The current version of jQuery being used jquery: version, constructor: jQuery, // The default length of a jQuery object is 0 length: 0, toArray: function() { return slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { // Return all the elements in a clean array if ( num == null ) { return slice.call( this ); } // Return just the one element from the set return num < 0 ? this[ num + this.length ] : this[ num ]; }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. each: function( callback ) { return jQuery.each( this, callback ); }, map: function( callback ) { return this.pushStack( jQuery.map( this, function( elem, i ) { return callback.call( elem, i, elem ); } ) ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ) ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); }, end: function() { return this.prevObject || this.constructor(); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: arr.sort, splice: arr.splice }; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( ( options = arguments[ i ] ) != null ) { // Extend the base object for ( name in options ) { copy = options[ name ]; // Prevent Object.prototype pollution // Prevent never-ending loop if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { src = target[ name ]; // Ensure proper type for the source value if ( copyIsArray && !Array.isArray( src ) ) { clone = []; } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { clone = {}; } else { clone = src; } copyIsArray = false; // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend( { // Unique for each copy of jQuery on the page expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), // Assume jQuery is ready without the ready module isReady: true, error: function( msg ) { throw new Error( msg ); }, noop: function() {}, isPlainObject: function( obj ) { var proto, Ctor; // Detect obvious negatives // Use toString instead of jQuery.type to catch host objects if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } proto = getProto( obj ); // Objects with no prototype (e.g., `Object.create( null )`) are plain if ( !proto ) { return true; } // Objects with prototype are plain iff they were constructed by a global Object function Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; }, // Evaluates a script in a global context globalEval: function( code, options ) { DOMEval( code, { nonce: options && options.nonce } ); }, each: function( obj, callback ) { var length, i = 0; if ( isArrayLike( obj ) ) { length = obj.length; for ( ; i < length; i++ ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } else { for ( i in obj ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } return obj; }, // Support: Android <=4.0 only trim: function( text ) { return text == null ? "" : ( text + "" ).replace( rtrim, "" ); }, // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; if ( arr != null ) { if ( isArrayLike( Object( arr ) ) ) { jQuery.merge( ret, typeof arr === "string" ? [ arr ] : arr ); } else { push.call( ret, arr ); } } return ret; }, inArray: function( elem, arr, i ) { return arr == null ? -1 : indexOf.call( arr, elem, i ); }, // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit merge: function( first, second ) { var len = +second.length, j = 0, i = first.length; for ( ; j < len; j++ ) { first[ i++ ] = second[ j ]; } first.length = i; return first; }, grep: function( elems, callback, invert ) { var callbackInverse, matches = [], i = 0, length = elems.length, callbackExpect = !invert; // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { callbackInverse = !callback( elems[ i ], i ); if ( callbackInverse !== callbackExpect ) { matches.push( elems[ i ] ); } } return matches; }, // arg is for internal usage only map: function( elems, callback, arg ) { var length, value, i = 0, ret = []; // Go through the array, translating each of the items to their new values if ( isArrayLike( elems ) ) { length = elems.length; for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } // Go through every key on the object, } else { for ( i in elems ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } } // Flatten any nested arrays return concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support } ); if ( typeof Symbol === "function" ) { jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; } // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), function( i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); function isArrayLike( obj ) { // Support: real iOS 8.2 only (not reproducible in simulator) // `in` check used to prevent JIT error (gh-2145) // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, type = toType( obj ); if ( isFunction( obj ) || isWindow( obj ) ) { return false; } return type === "array" || length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj; } var Sizzle = /*! * Sizzle CSS Selector Engine v2.3.4 * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://js.foundation/ * * Date: 2019-04-08 */ (function( window ) { var i, support, Expr, getText, isXML, tokenize, compile, select, outermostContext, sortInput, hasDuplicate, // Local document vars setDocument, document, docElem, documentIsHTML, rbuggyQSA, rbuggyMatches, matches, contains, // Instance-specific data expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), nonnativeSelectorCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; }, // Instance methods hasOwn = ({}).hasOwnProperty, arr = [], pop = arr.pop, push_native = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf as it's faster than native // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { if ( list[i] === elem ) { return i; } } return -1; }, booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { "ID": new RegExp( "^#(" + identifier + ")" ), "CLASS": new RegExp( "^\\.(" + identifier + ")" ), "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, rnative = /^[^{]+\{\s*\[native \w/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, // CSS escapes // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), funescape = function( _, escaped, escapedWhitespace ) { var high = "0x" + escaped - 0x10000; // NaN means non-codepoint // Support: Firefox<24 // Workaround erroneous numeric interpretation of +"0x" return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint String.fromCharCode( high + 0x10000 ) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, // CSS string/identifier serialization // https://drafts.csswg.org/cssom/#common-serializing-idioms rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, fcssescape = function( ch, asCodePoint ) { if ( asCodePoint ) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if ( ch === "\0" ) { return "\uFFFD"; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped return "\\" + ch; }, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }, inDisabledFieldset = addCombinator( function( elem ) { return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; }, { dir: "parentNode", next: "legend" } ); // Optimize for push.apply( _, NodeList ) try { push.apply( (arr = slice.call( preferredDoc.childNodes )), preferredDoc.childNodes ); // Support: Android<4.0 // Detect silently failing push.apply arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { push_native.apply( target, slice.call(els) ); } : // Support: IE<9 // Otherwise append directly function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length while ( (target[j++] = els[i++]) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; } // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // ID selector if ( (m = match[1]) ) { // Document context if ( nodeType === 9 ) { if ( (elem = context.getElementById( m )) ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } // Element context } else { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( newContext && (elem = newContext.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Type selector } else if ( match[2] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll if ( support.qsa && !nonnativeSelectorCache[ selector + " " ] && (!rbuggyQSA || !rbuggyQSA.test( selector )) && // Support: IE 8 only // Exclude object elements (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { newSelector = selector; newContext = context; // qSA considers elements outside a scoping root when evaluating child or // descendant combinators, which is not what we want. // In such cases, we work around the behavior by prefixing every selector in the // list with an ID selector referencing the scope context. // Thanks to Andrew Dupont for this technique. if ( nodeType === 1 && rdescend.test( selector ) ) { // Capture the context ID, setting it first if necessary if ( (nid = context.getAttribute( "id" )) ) { nid = nid.replace( rcssescape, fcssescape ); } else { context.setAttribute( "id", (nid = expando) ); } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { groups[i] = "#" + nid + " " + toSelector( groups[i] ); } newSelector = groups.join( "," ); // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; } try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch ( qsaError ) { nonnativeSelectorCache( selector, true ); } finally { if ( nid === expando ) { context.removeAttribute( "id" ); } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); } /** * Create key-value caches of limited size * @returns {function(string, object)} Returns the Object data after storing it on itself with * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) * deleting the oldest entry */ function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } return (cache[ key + " " ] = value); } return cache; } /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } /** * Support testing using an element * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { var el = document.createElement("fieldset"); try { return !!fn( el ); } catch (e) { return false; } finally { // Remove from its parent by default if ( el.parentNode ) { el.parentNode.removeChild( el ); } // release memory in IE el = null; } } /** * Adds the same handler for all of the specified attrs * @param {String} attrs Pipe-separated list of attributes * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { var arr = attrs.split("|"), i = arr.length; while ( i-- ) { Expr.attrHandle[ arr[i] ] = handler; } } /** * Checks document order of two siblings * @param {Element} a * @param {Element} b * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b */ function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes if ( diff ) { return diff; } // Check if b follows a if ( cur ) { while ( (cur = cur.nextSibling) ) { if ( cur === b ) { return -1; } } } return a ? 1 : -1; } /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === type; }; } /** * Returns a function to use in pseudos for buttons * @param {String} type */ function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && elem.type === type; }; } /** * Returns a function to use in pseudos for :enabled/:disabled * @param {Boolean} disabled true for :disabled; false for :enabled */ function createDisabledPseudo( disabled ) { // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable return function( elem ) { // Only certain elements can match :enabled or :disabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled if ( "form" in elem ) { // Check for inherited disabledness on relevant non-disabled elements: // * listed form-associated elements in a disabled fieldset // https://html.spec.whatwg.org/multipage/forms.html#category-listed // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled // * option elements in a disabled optgroup // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled // All such elements have a "form" property. if ( elem.parentNode && elem.disabled === false ) { // Option elements defer to a parent optgroup if present if ( "label" in elem ) { if ( "label" in elem.parentNode ) { return elem.parentNode.disabled === disabled; } else { return elem.disabled === disabled; } } // Support: IE 6 - 11 // Use the isDisabled shortcut property to check for disabled fieldset ancestors return elem.isDisabled === disabled || // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; // Try to winnow out elements that can't be disabled before trusting the disabled property. // Some victims get caught in our net (label, legend, menu, track), but it shouldn't // even exist on them, let alone have a boolean value. } else if ( "label" in elem ) { return elem.disabled === disabled; } // Remaining elements are neither :enabled nor :disabled return false; }; } /** * Returns a function to use in pseudos for positionals * @param {Function} fn */ function createPositionalPseudo( fn ) { return markFunction(function( argument ) { argument = +argument; return markFunction(function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { if ( seed[ (j = matchIndexes[i]) ] ) { seed[j] = !(matches[j] = seed[j]); } } }); }); } /** * Checks a node for validity as a Sizzle context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience support = Sizzle.support = {}; /** * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { var namespace = elem.namespaceURI, docElem = (elem.ownerDocument || elem).documentElement; // Support: IE <=8 // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes // https://bugs.jquery.com/ticket/4833 return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); }; /** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML( document ); // Support: IE 9-11, Edge // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) if ( preferredDoc !== document && (subWindow = document.defaultView) && subWindow.top !== subWindow ) { // Support: IE 11, Edge if ( subWindow.addEventListener ) { subWindow.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only } else if ( subWindow.attachEvent ) { subWindow.attachEvent( "onunload", unloadHandler ); } } /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert(function( el ) { el.className = "i"; return !el.getAttribute("className"); }); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert(function( el ) { el.appendChild( document.createComment("") ); return !el.getElementsByTagName("*").length; }); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test support.getById = assert(function( el ) { docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; }); // ID filter and find if ( support.getById ) { Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute("id") === attrId; }; }; Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return node && node.value === attrId; }; }; // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); if ( elem ) { // Verify the id attribute node = elem.getAttributeNode("id"); if ( node && node.value === id ) { return [ elem ]; } // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; while ( (elem = elems[i++]) ) { node = elem.getAttributeNode("id"); if ( node && node.value === id ) { return [ elem ]; } } } return []; } }; } // Tag Expr.find["TAG"] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); // DocumentFragment nodes don't have gEBTN } else if ( support.qsa ) { return context.querySelectorAll( tag ); } } : function( tag, context ) { var elem, tmp = [], i = 0, // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { while ( (elem = results[i++]) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } } return tmp; } return results; }; // Class Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; /* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { // Build QSA regex // Regex strategy adopted from Diego Perini assert(function( el ) { // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // https://bugs.jquery.com/ticket/12359 docElem.appendChild( el ).innerHTML = "" + ""; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section if ( el.querySelectorAll("[msallowcapture^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly if ( !el.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { rbuggyQSA.push("~="); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !el.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibling-combinator selector` fails if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { rbuggyQSA.push(".#.+[+~]"); } }); assert(function( el ) { el.innerHTML = "" + ""; // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment var input = document.createElement("input"); input.setAttribute( "type", "hidden" ); el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute if ( el.querySelectorAll("[name=d]").length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests if ( el.querySelectorAll(":enabled").length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: IE9-11+ // IE's :disabled selector does not pick up the children of disabled fieldsets docElem.appendChild( el ).disabled = true; if ( el.querySelectorAll(":disabled").length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Opera 10-11 does not throw on post-comma invalid pseudos el.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); } if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector) )) ) { assert(function( el ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( el, "*" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); }); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully self-exclusive // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // Choose the first element that is related to our preferred document if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { return -1; } if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { return 1; } // Maintain original order return sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison cur = a; while ( (cur = cur.parentNode) ) { ap.unshift( cur ); } cur = b; while ( (cur = cur.parentNode) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[i] === bp[i] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[i], bp[i] ) : // Otherwise nodes in our document sort first ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; }; return document; }; Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); }; Sizzle.matchesSelector = function( elem, expr ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } if ( support.matchesSelector && documentIsHTML && !nonnativeSelectorCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch (e) { nonnativeSelectorCache( expr, true ); } } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { // Set document vars if needed if ( ( context.ownerDocument || context ) !== document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; }; Sizzle.escape = function( sel ) { return (sel + "").replace( rcssescape, fcssescape ); }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Document sorting and removing duplicates * @param {ArrayLike} results */ Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { while ( (elem = results[i++]) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } // Clear input after sorting to release objects // See https://github.com/jquery/sizzle/pull/225 sortInput = null; return results; }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // If no nodeType, this is expected to be an array while ( (node = elem[i++]) ) { // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } // Do not include comment or processing instruction nodes return ret; }; Expr = Sizzle.selectors = { // Can be adjusted by the user cacheLength: 50, createPseudo: markFunction, match: matchExpr, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }, preFilter: { "ATTR": function( match ) { match[1] = match[1].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); if ( match[2] === "~=" ) { match[3] = " " + match[3] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[1] = match[1].toLowerCase(); if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument if ( !match[3] ) { Sizzle.error( match[0] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); // other types prohibit arguments } else if ( match[3] ) { Sizzle.error( match[0] ); } return match; }, "PSEUDO": function( match ) { var excess, unquoted = !match[6] && match[2]; if ( matchExpr["CHILD"].test( match[0] ) ) { return null; } // Accept quoted arguments as-is if ( match[3] ) { match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && // Get excess from tokenize (recursively) (excess = tokenize( unquoted, true )) && // advance to the next closing parenthesis (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { // excess is a negative index match[0] = match[0].slice( 0, excess ); match[2] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) return match.slice( 0, 3 ); } }, filter: { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; }, "CLASS": function( className ) { var pattern = classCache[ className + " " ]; return pattern || (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && classCache( className, function( elem ) { return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); }); }, "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } result += ""; return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; }; }, "CHILD": function( type, what, argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; return first === 1 && last === 0 ? // Shortcut for :nth-*(n) function( elem ) { return !!elem.parentNode; } : function( elem, context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), useCache = !xml && !ofType, diff = false; if ( parent ) { // :(first|last|only)-(child|of-type) if ( simple ) { while ( dir ) { node = elem; while ( (node = node[ dir ]) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { return false; } } // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } return true; } start = [ forward ? parent.firstChild : parent.lastChild ]; // non-xml :nth-child(...) stores cache data on `parent` if ( forward && useCache ) { // Seek `elem` from a previously-cached index // ...in a gzip-friendly way node = parent; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; while ( (node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start (diff = nodeIndex = 0) || start.pop()) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } } else { // Use previously-cached element index if available if ( useCache ) { // ...in a gzip-friendly way node = elem; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { // Use the same loop as above to seek `elem` from the start while ( (node = ++nodeIndex && node && node[ dir ] || (diff = nodeIndex = 0) || start.pop()) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { // Cache the index of each encountered element if ( useCache ) { outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); uniqueCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { break; } } } } } // Incorporate the offset, then check against cycle size diff -= last; return diff === first || ( diff % first === 0 && diff / first >= 0 ); } }; }, "PSEUDO": function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Sizzle does if ( fn[ expando ] ) { return fn( argument ); } // But maintain support for old signatures if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? markFunction(function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { idx = indexOf( seed, matched[i] ); seed[ idx ] = !( matches[ idx ] = matched[i] ); } }) : function( elem ) { return fn( elem, 0, args ); }; } return fn; } }, pseudos: { // Potentially complex pseudos "not": markFunction(function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? markFunction(function( seed, matches, context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { if ( (elem = unmatched[i]) ) { seed[i] = !(matches[i] = elem); } } }) : function( elem, context, xml ) { input[0] = elem; matcher( input, null, xml, results ); // Don't keep the element (issue #299) input[0] = null; return !results.pop(); }; }), "has": markFunction(function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; }), "contains": markFunction(function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; }), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test(lang || "") ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { if ( (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); return false; }; }), // Miscellaneous "target": function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, "root": function( elem ) { return elem === docElem; }, "focus": function( elem ) { return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); }, // Boolean properties "enabled": createDisabledPseudo( false ), "disabled": createDisabledPseudo( true ), "checked": function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); }, "selected": function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, // Contents "empty": function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { if ( elem.nodeType < 6 ) { return false; } } return true; }, "parent": function( elem ) { return !Expr.pseudos["empty"]( elem ); }, // Element/input types "header": function( elem ) { return rheader.test( elem.nodeName ); }, "input": function( elem ) { return rinputs.test( elem.nodeName ); }, "button": function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, "text": function( elem ) { var attr; return elem.nodeName.toLowerCase() === "input" && elem.type === "text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection "first": createPositionalPseudo(function() { return [ 0 ]; }), "last": createPositionalPseudo(function( matchIndexes, length ) { return [ length - 1 ]; }), "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; }), "even": createPositionalPseudo(function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "odd": createPositionalPseudo(function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument > length ? length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; }), "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; }) } }; Expr.pseudos["nth"] = Expr.pseudos["eq"]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { Expr.pseudos[ i ] = createInputPseudo( i ); } for ( i in { submit: true, reset: true } ) { Expr.pseudos[ i ] = createButtonPseudo( i ); } // Easy API for creating new setFilters function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); tokenize = Sizzle.tokenize = function( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); } soFar = selector; groups = []; preFilters = Expr.preFilter; while ( soFar ) { // Comma and first run if ( !matched || (match = rcomma.exec( soFar )) ) { if ( match ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[0].length ) || soFar; } groups.push( (tokens = []) ); } matched = false; // Combinators if ( (match = rcombinators.exec( soFar )) ) { matched = match.shift(); tokens.push({ value: matched, // Cast descendant combinators to space type: match[0].replace( rtrim, " " ) }); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || (match = preFilters[ type ]( match ))) ) { matched = match.shift(); tokens.push({ value: matched, type: type, matches: match }); soFar = soFar.slice( matched.length ); } } if ( !matched ) { break; } } // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens return parseOnly ? soFar.length : soFar ? Sizzle.error( selector ) : // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; function toSelector( tokens ) { var i = 0, len = tokens.length, selector = ""; for ( ; i < len; i++ ) { selector += tokens[i].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, skip = combinator.next, key = skip || dir, checkNonElements = base && key === "parentNode", doneName = done++; return combinator.first ? // Check against closest ancestor/preceding element function( elem, context, xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } return false; } : // Check against all ancestor/preceding elements function( elem, context, xml ) { var oldCache, uniqueCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; } } } } else { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || (elem[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); if ( skip && skip === elem.nodeName.toLowerCase() ) { elem = elem[ dir ] || elem; } else if ( (oldCache = uniqueCache[ key ]) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements return (newCache[ 2 ] = oldCache[ 2 ]); } else { // Reuse newcache so results back-propagate to previous elements uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { return true; } } } } } return false; }; } function elementMatcher( matchers ) { return matchers.length > 1 ? function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { if ( !matchers[i]( elem, context, xml ) ) { return false; } } return true; } : matchers[0]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { Sizzle( selector, contexts[i], results ); } return results; } function condense( unmatched, map, filter, context, xml ) { var elem, newUnmatched = [], i = 0, len = unmatched.length, mapped = map != null; for ( ; i < len; i++ ) { if ( (elem = unmatched[i]) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { map.push( i ); } } } } return newUnmatched; } function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } return markFunction(function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } // Apply postFilter if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( (elem = temp[i]) ) { matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); } } } if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) ) { // Restore matcherIn since elem is not yet a final match temp.push( (matcherIn[i] = elem) ); } } postFinder( null, (matcherOut = []), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) && (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { postFinder( null, results, matcherOut, xml ); } else { push.apply( results, matcherOut ); } } }); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, leadingRelative = Expr.relative[ tokens[0].type ], implicitRelative = leadingRelative || Expr.relative[" "], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) matchContext = addCombinator( function( elem ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( (checkContext = context).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { if ( (matcher = Expr.relative[ tokens[i].type ]) ) { matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; } else { matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[j].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } return elementMatcher( matchers ); } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, matchedCount = 0, i = "0", unmatched = seed && [], setMatched = [], contextBackup = outermostContext, // We must always have either seed elements or outermost context elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), len = elems.length; if ( outermost ) { outermostContext = context === document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id for ( ; i !== len && (elem = elems[i]) != null; i++ ) { if ( byElement && elem ) { j = 0; if ( !context && elem.ownerDocument !== document ) { setDocument( elem ); xml = !documentIsHTML; } while ( (matcher = elementMatchers[j++]) ) { if ( matcher( elem, context || document, xml) ) { results.push( elem ); break; } } if ( outermost ) { dirruns = dirrunsUnique; } } // Track unmatched elements for set filters if ( bySet ) { // They will have gone through all possible matchers if ( (elem = !matcher && elem) ) { matchedCount--; } // Lengthen the array for every element, matched or not if ( seed ) { unmatched.push( elem ); } } } // `i` is now the count of elements visited above, and adding it to `matchedCount` // makes the latter nonnegative. matchedCount += i; // Apply set filters to unmatched elements // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` // equals `i`), unless we didn't visit _any_ elements in the above loop because we have // no element matchers and no seed. // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that // case, which will result in a "00" `matchedCount` that differs from `i` but is also // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; while ( (matcher = setMatchers[j++]) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { if ( !(unmatched[i] || setMatched[i]) ) { setMatched[i] = pop.call( results ); } } } // Discard index placeholder values to get only actual matches setMatched = condense( setMatched ); } // Add matches to results push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { Sizzle.uniqueSort( results ); } } // Override manipulation of globals by nested matchers if ( outermost ) { dirruns = dirrunsUnique; outermostContext = contextBackup; } return unmatched; }; return bySet ? markFunction( superMatcher ) : superMatcher; } compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { cached = matcherFromTokens( match[i] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { elementMatchers.push( cached ); } } // Cache the compiled function cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; }; /** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( (selector = compiled.selector || selector) ); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees us context) if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID tokens = match[0] = match[0].slice( 0 ); if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; if ( !context ) { return results; // Precompiled matchers will still verify ancestry, so step up a level } else if ( compiled ) { context = context.parentNode; } selector = selector.slice( tokens.shift().value.length ); } // Fetch a seed set for right-to-left matching i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[i]; // Abort if we hit a combinator if ( Expr.relative[ (type = token.type) ] ) { break; } if ( (find = Expr.find[ type ]) ) { // Search, expanding context for leading sibling combinators if ( (seed = find( token.matches[0].replace( runescape, funescape ), rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context )) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above ( compiled || compile( selector, match ) )( seed, context, !documentIsHTML, results, !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }; // One-time assignments // Sort stability support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* support.sortDetached = assert(function( el ) { // Should return 1, but returns 4 (following) return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; }); // Support: IE<8 // Prevent attribute/property "interpolation" // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !assert(function( el ) { el.innerHTML = ""; return el.firstChild.getAttribute("href") === "#" ; }) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } }); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert(function( el ) { el.innerHTML = ""; el.firstChild.setAttribute( "value", "" ); return el.firstChild.getAttribute( "value" ) === ""; }) ) { addHandle( "value", function( elem, name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } }); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies if ( !assert(function( el ) { return el.getAttribute("disabled") == null; }) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : (val = elem.getAttributeNode( name )) && val.specified ? val.value : null; } }); } return Sizzle; })( window ); jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; // Deprecated jQuery.expr[ ":" ] = jQuery.expr.pseudos; jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; jQuery.escapeSelector = Sizzle.escape; var dir = function( elem, dir, until ) { var matched = [], truncate = until !== undefined; while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { if ( elem.nodeType === 1 ) { if ( truncate && jQuery( elem ).is( until ) ) { break; } matched.push( elem ); } } return matched; }; var siblings = function( n, elem ) { var matched = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { matched.push( n ); } } return matched; }; var rneedsContext = jQuery.expr.match.needsContext; function nodeName( elem, name ) { return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); }; var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); // Implement the identical functionality for filter and not function winnow( elements, qualifier, not ) { if ( isFunction( qualifier ) ) { return jQuery.grep( elements, function( elem, i ) { return !!qualifier.call( elem, i, elem ) !== not; } ); } // Single element if ( qualifier.nodeType ) { return jQuery.grep( elements, function( elem ) { return ( elem === qualifier ) !== not; } ); } // Arraylike of elements (jQuery, arguments, Array) if ( typeof qualifier !== "string" ) { return jQuery.grep( elements, function( elem ) { return ( indexOf.call( qualifier, elem ) > -1 ) !== not; } ); } // Filtered directly for both simple and complex selectors return jQuery.filter( qualifier, elements, not ); } jQuery.filter = function( expr, elems, not ) { var elem = elems[ 0 ]; if ( not ) { expr = ":not(" + expr + ")"; } if ( elems.length === 1 && elem.nodeType === 1 ) { return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; } return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { return elem.nodeType === 1; } ) ); }; jQuery.fn.extend( { find: function( selector ) { var i, ret, len = this.length, self = this; if ( typeof selector !== "string" ) { return this.pushStack( jQuery( selector ).filter( function() { for ( i = 0; i < len; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } } ) ); } ret = this.pushStack( [] ); for ( i = 0; i < len; i++ ) { jQuery.find( selector, self[ i ], ret ); } return len > 1 ? jQuery.uniqueSort( ret ) : ret; }, filter: function( selector ) { return this.pushStack( winnow( this, selector || [], false ) ); }, not: function( selector ) { return this.pushStack( winnow( this, selector || [], true ) ); }, is: function( selector ) { return !!winnow( this, // If this is a positional/relative selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". typeof selector === "string" && rneedsContext.test( selector ) ? jQuery( selector ) : selector || [], false ).length; } } ); // Initialize a jQuery object // A central reference to the root jQuery(document) var rootjQuery, // A simple way to check for HTML strings // Prioritize #id over to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) // Shortcut simple #id case for speed rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, init = jQuery.fn.init = function( selector, context, root ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Method init() accepts an alternate rootjQuery // so migrate can support jQuery.sub (gh-2101) root = root || rootjQuery; // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } // Match html or make sure no context is specified for #id if ( match && ( match[ 1 ] || !context ) ) { // HANDLE: $(html) -> $(array) if ( match[ 1 ] ) { context = context instanceof jQuery ? context[ 0 ] : context; // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[ 1 ], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible if ( isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else { elem = document.getElementById( match[ 2 ] ); if ( elem ) { // Inject the element directly into the jQuery object this[ 0 ] = elem; this.length = 1; } return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || root ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { this[ 0 ] = selector; this.length = 1; return this; // HANDLE: $(function) // Shortcut for document ready } else if ( isFunction( selector ) ) { return root.ready !== undefined ? root.ready( selector ) : // Execute immediately if ready is not present selector( jQuery ); } return jQuery.makeArray( selector, this ); }; // Give the init function the jQuery prototype for later instantiation init.prototype = jQuery.fn; // Initialize central reference rootjQuery = jQuery( document ); var rparentsprev = /^(?:parents|prev(?:Until|All))/, // Methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend( { has: function( target ) { var targets = jQuery( target, this ), l = targets.length; return this.filter( function() { var i = 0; for ( ; i < l; i++ ) { if ( jQuery.contains( this, targets[ i ] ) ) { return true; } } } ); }, closest: function( selectors, context ) { var cur, i = 0, l = this.length, matched = [], targets = typeof selectors !== "string" && jQuery( selectors ); // Positional selectors never match, since there's no _selection_ context if ( !rneedsContext.test( selectors ) ) { for ( ; i < l; i++ ) { for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { // Always skip document fragments if ( cur.nodeType < 11 && ( targets ? targets.index( cur ) > -1 : // Don't pass non-elements to Sizzle cur.nodeType === 1 && jQuery.find.matchesSelector( cur, selectors ) ) ) { matched.push( cur ); break; } } } } return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); }, // Determine the position of an element within the set index: function( elem ) { // No argument, return index in parent if ( !elem ) { return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; } // Index in selector if ( typeof elem === "string" ) { return indexOf.call( jQuery( elem ), this[ 0 ] ); } // Locate the position of the desired element return indexOf.call( this, // If it receives a jQuery object, the first element is used elem.jquery ? elem[ 0 ] : elem ); }, add: function( selector, context ) { return this.pushStack( jQuery.uniqueSort( jQuery.merge( this.get(), jQuery( selector, context ) ) ) ); }, addBack: function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); } } ); function sibling( cur, dir ) { while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} return cur; } jQuery.each( { parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { return sibling( elem, "nextSibling" ); }, prev: function( elem ) { return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { return dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return siblings( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return siblings( elem.firstChild ); }, contents: function( elem ) { if ( typeof elem.contentDocument !== "undefined" ) { return elem.contentDocument; } // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only // Treat the template element as a regular one in browsers that // don't support it. if ( nodeName( elem, "template" ) ) { elem = elem.content || elem; } return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var matched = jQuery.map( this, fn, until ); if ( name.slice( -5 ) !== "Until" ) { selector = until; } if ( selector && typeof selector === "string" ) { matched = jQuery.filter( selector, matched ); } if ( this.length > 1 ) { // Remove duplicates if ( !guaranteedUnique[ name ] ) { jQuery.uniqueSort( matched ); } // Reverse order for parents* and prev-derivatives if ( rparentsprev.test( name ) ) { matched.reverse(); } } return this.pushStack( matched ); }; } ); var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); // Convert String-formatted options into Object-formatted ones function createOptions( options ) { var object = {}; jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { object[ flag ] = true; } ); return object; } /* * Create a callback list using the following parameters: * * options: an optional list of space-separated options that will change how * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) options = typeof options === "string" ? createOptions( options ) : jQuery.extend( {}, options ); var // Flag to know if list is currently firing firing, // Last fire value for non-forgettable lists memory, // Flag to know if list was already fired fired, // Flag to prevent firing locked, // Actual callback list list = [], // Queue of execution data for repeatable lists queue = [], // Index of currently firing callback (modified by add/remove as needed) firingIndex = -1, // Fire callbacks fire = function() { // Enforce single-firing locked = locked || options.once; // Execute callbacks for all pending executions, // respecting firingIndex overrides and runtime changes fired = firing = true; for ( ; queue.length; firingIndex = -1 ) { memory = queue.shift(); while ( ++firingIndex < list.length ) { // Run callback and check for early termination if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && options.stopOnFalse ) { // Jump to end and forget the data so .add doesn't re-fire firingIndex = list.length; memory = false; } } } // Forget the data if we're done with it if ( !options.memory ) { memory = false; } firing = false; // Clean up if we're done firing for good if ( locked ) { // Keep an empty list if we have data for future add calls if ( memory ) { list = []; // Otherwise, this object is spent } else { list = ""; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { // If we have memory from a past run, we should fire after adding if ( memory && !firing ) { firingIndex = list.length - 1; queue.push( memory ); } ( function add( args ) { jQuery.each( args, function( _, arg ) { if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); } } ); } )( arguments ); if ( memory && !firing ) { fire(); } } return this; }, // Remove a callback from the list remove: function() { jQuery.each( arguments, function( _, arg ) { var index; while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes if ( index <= firingIndex ) { firingIndex--; } } } ); return this; }, // Check if a given callback is in the list. // If no argument is given, return whether or not list has callbacks attached. has: function( fn ) { return fn ? jQuery.inArray( fn, list ) > -1 : list.length > 0; }, // Remove all callbacks from the list empty: function() { if ( list ) { list = []; } return this; }, // Disable .fire and .add // Abort any current/pending executions // Clear all callbacks and values disable: function() { locked = queue = []; list = memory = ""; return this; }, disabled: function() { return !list; }, // Disable .fire // Also disable .add unless we have memory (since it would have no effect) // Abort any pending executions lock: function() { locked = queue = []; if ( !memory && !firing ) { list = memory = ""; } return this; }, locked: function() { return !!locked; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( !locked ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; queue.push( args ); if ( !firing ) { fire(); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!fired; } }; return self; }; function Identity( v ) { return v; } function Thrower( ex ) { throw ex; } function adoptValue( value, resolve, reject, noValue ) { var method; try { // Check for promise aspect first to privilege synchronous behavior if ( value && isFunction( ( method = value.promise ) ) ) { method.call( value ).done( resolve ).fail( reject ); // Other thenables } else if ( value && isFunction( ( method = value.then ) ) ) { method.call( value, resolve, reject ); // Other non-thenables } else { // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: // * false: [ value ].slice( 0 ) => resolve( value ) // * true: [ value ].slice( 1 ) => resolve() resolve.apply( undefined, [ value ].slice( noValue ) ); } // For Promises/A+, convert exceptions into rejections // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in // Deferred#then to conditionally suppress rejection. } catch ( value ) { // Support: Android 4.0 only // Strict mode functions invoked without .call/.apply get global-object context reject.apply( undefined, [ value ] ); } } jQuery.extend( { Deferred: function( func ) { var tuples = [ // action, add listener, callbacks, // ... .then handlers, argument index, [final state] [ "notify", "progress", jQuery.Callbacks( "memory" ), jQuery.Callbacks( "memory" ), 2 ], [ "resolve", "done", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 0, "resolved" ], [ "reject", "fail", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 1, "rejected" ] ], state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, "catch": function( fn ) { return promise.then( null, fn ); }, // Keep pipe for back-compat pipe: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred( function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; // deferred.progress(function() { bind to newDefer or newDefer.notify }) // deferred.done(function() { bind to newDefer or newDefer.resolve }) // deferred.fail(function() { bind to newDefer or newDefer.reject }) deferred[ tuple[ 1 ] ]( function() { var returned = fn && fn.apply( this, arguments ); if ( returned && isFunction( returned.promise ) ) { returned.promise() .progress( newDefer.notify ) .done( newDefer.resolve ) .fail( newDefer.reject ); } else { newDefer[ tuple[ 0 ] + "With" ]( this, fn ? [ returned ] : arguments ); } } ); } ); fns = null; } ).promise(); }, then: function( onFulfilled, onRejected, onProgress ) { var maxDepth = 0; function resolve( depth, deferred, handler, special ) { return function() { var that = this, args = arguments, mightThrow = function() { var returned, then; // Support: Promises/A+ section 2.3.3.3.3 // https://promisesaplus.com/#point-59 // Ignore double-resolution attempts if ( depth < maxDepth ) { return; } returned = handler.apply( that, args ); // Support: Promises/A+ section 2.3.1 // https://promisesaplus.com/#point-48 if ( returned === deferred.promise() ) { throw new TypeError( "Thenable self-resolution" ); } // Support: Promises/A+ sections 2.3.3.1, 3.5 // https://promisesaplus.com/#point-54 // https://promisesaplus.com/#point-75 // Retrieve `then` only once then = returned && // Support: Promises/A+ section 2.3.4 // https://promisesaplus.com/#point-64 // Only check objects and functions for thenability ( typeof returned === "object" || typeof returned === "function" ) && returned.then; // Handle a returned thenable if ( isFunction( then ) ) { // Special processors (notify) just wait for resolution if ( special ) { then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ) ); // Normal processors (resolve) also hook into progress } else { // ...and disregard older resolution values maxDepth++; then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ), resolve( maxDepth, deferred, Identity, deferred.notifyWith ) ); } // Handle all other returned values } else { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Identity ) { that = undefined; args = [ returned ]; } // Process the value(s) // Default process is resolve ( special || deferred.resolveWith )( that, args ); } }, // Only normal processors (resolve) catch and reject exceptions process = special ? mightThrow : function() { try { mightThrow(); } catch ( e ) { if ( jQuery.Deferred.exceptionHook ) { jQuery.Deferred.exceptionHook( e, process.stackTrace ); } // Support: Promises/A+ section 2.3.3.3.4.1 // https://promisesaplus.com/#point-61 // Ignore post-resolution exceptions if ( depth + 1 >= maxDepth ) { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Thrower ) { that = undefined; args = [ e ]; } deferred.rejectWith( that, args ); } } }; // Support: Promises/A+ section 2.3.3.3.1 // https://promisesaplus.com/#point-57 // Re-resolve promises immediately to dodge false rejection from // subsequent errors if ( depth ) { process(); } else { // Call an optional hook to record the stack, in case of exception // since it's otherwise lost when execution goes async if ( jQuery.Deferred.getStackHook ) { process.stackTrace = jQuery.Deferred.getStackHook(); } window.setTimeout( process ); } }; } return jQuery.Deferred( function( newDefer ) { // progress_handlers.add( ... ) tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith ) ); // fulfilled_handlers.add( ... ) tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, isFunction( onFulfilled ) ? onFulfilled : Identity ) ); // rejected_handlers.add( ... ) tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, isFunction( onRejected ) ? onRejected : Thrower ) ); } ).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } }, deferred = {}; // Add list-specific methods jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 5 ]; // promise.progress = list.add // promise.done = list.add // promise.fail = list.add promise[ tuple[ 1 ] ] = list.add; // Handle state if ( stateString ) { list.add( function() { // state = "resolved" (i.e., fulfilled) // state = "rejected" state = stateString; }, // rejected_callbacks.disable // fulfilled_callbacks.disable tuples[ 3 - i ][ 2 ].disable, // rejected_handlers.disable // fulfilled_handlers.disable tuples[ 3 - i ][ 3 ].disable, // progress_callbacks.lock tuples[ 0 ][ 2 ].lock, // progress_handlers.lock tuples[ 0 ][ 3 ].lock ); } // progress_handlers.fire // fulfilled_handlers.fire // rejected_handlers.fire list.add( tuple[ 3 ].fire ); // deferred.notify = function() { deferred.notifyWith(...) } // deferred.resolve = function() { deferred.resolveWith(...) } // deferred.reject = function() { deferred.rejectWith(...) } deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; // deferred.notifyWith = list.fireWith // deferred.resolveWith = list.fireWith // deferred.rejectWith = list.fireWith deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } ); // Make the deferred a promise promise.promise( deferred ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( singleValue ) { var // count of uncompleted subordinates remaining = arguments.length, // count of unprocessed arguments i = remaining, // subordinate fulfillment data resolveContexts = Array( i ), resolveValues = slice.call( arguments ), // the master Deferred master = jQuery.Deferred(), // subordinate callback factory updateFunc = function( i ) { return function( value ) { resolveContexts[ i ] = this; resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; if ( !( --remaining ) ) { master.resolveWith( resolveContexts, resolveValues ); } }; }; // Single- and empty arguments are adopted like Promise.resolve if ( remaining <= 1 ) { adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, !remaining ); // Use .then() to unwrap secondary thenables (cf. gh-3000) if ( master.state() === "pending" || isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { return master.then(); } } // Multiple arguments are aggregated like Promise.all array elements while ( i-- ) { adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } return master.promise(); } } ); // These usually indicate a programmer mistake during development, // warn about them ASAP rather than swallowing them by default. var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; jQuery.Deferred.exceptionHook = function( error, stack ) { // Support: IE 8 - 9 only // Console exists when dev tools are open, which can happen at any time if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); } }; jQuery.readyException = function( error ) { window.setTimeout( function() { throw error; } ); }; // The deferred used on DOM ready var readyList = jQuery.Deferred(); jQuery.fn.ready = function( fn ) { readyList .then( fn ) // Wrap jQuery.readyException in a function so that the lookup // happens at the time of error handling instead of callback // registration. .catch( function( error ) { jQuery.readyException( error ); } ); return this; }; jQuery.extend( { // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Handle when the DOM is ready ready: function( wait ) { // Abort if there are pending holds or we're already ready if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); } } ); jQuery.ready.then = readyList.then; // The ready event handler and self cleanup method function completed() { document.removeEventListener( "DOMContentLoaded", completed ); window.removeEventListener( "load", completed ); jQuery.ready(); } // Catch cases where $(document).ready() is called // after the browser event has already occurred. // Support: IE <=9 - 10 only // Older IE sometimes signals "interactive" too soon if ( document.readyState === "complete" || ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { // Handle it asynchronously to allow scripts the opportunity to delay ready window.setTimeout( jQuery.ready ); } else { // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed ); // A fallback to window.onload, that will always work window.addEventListener( "load", completed ); } // Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { var i = 0, len = elems.length, bulk = key == null; // Sets many values if ( toType( key ) === "object" ) { chainable = true; for ( i in key ) { access( elems, fn, i, key[ i ], true, emptyGet, raw ); } // Sets one value } else if ( value !== undefined ) { chainable = true; if ( !isFunction( value ) ) { raw = true; } if ( bulk ) { // Bulk operations run against the entire set if ( raw ) { fn.call( elems, value ); fn = null; // ...except when executing function values } else { bulk = fn; fn = function( elem, key, value ) { return bulk.call( jQuery( elem ), value ); }; } } if ( fn ) { for ( ; i < len; i++ ) { fn( elems[ i ], key, raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) ) ); } } } if ( chainable ) { return elems; } // Gets if ( bulk ) { return fn.call( elems ); } return len ? fn( elems[ 0 ], key ) : emptyGet; }; // Matches dashed string for camelizing var rmsPrefix = /^-ms-/, rdashAlpha = /-([a-z])/g; // Used by camelCase as callback to replace() function fcamelCase( all, letter ) { return letter.toUpperCase(); } // Convert dashed to camelCase; used by the css and data modules // Support: IE <=9 - 11, Edge 12 - 15 // Microsoft forgot to hump their vendor prefix (#9572) function camelCase( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); } var acceptData = function( owner ) { // Accepts only: // - Node // - Node.ELEMENT_NODE // - Node.DOCUMENT_NODE // - Object // - Any return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); }; function Data() { this.expando = jQuery.expando + Data.uid++; } Data.uid = 1; Data.prototype = { cache: function( owner ) { // Check if the owner object already has a cache var value = owner[ this.expando ]; // If not, create one if ( !value ) { value = {}; // We can accept data for non-element nodes in modern browsers, // but we should not, see #8335. // Always return an empty object. if ( acceptData( owner ) ) { // If it is a node unlikely to be stringify-ed or looped over // use plain assignment if ( owner.nodeType ) { owner[ this.expando ] = value; // Otherwise secure it in a non-enumerable property // configurable must be true to allow the property to be // deleted when data is removed } else { Object.defineProperty( owner, this.expando, { value: value, configurable: true } ); } } } return value; }, set: function( owner, data, value ) { var prop, cache = this.cache( owner ); // Handle: [ owner, key, value ] args // Always use camelCase key (gh-2257) if ( typeof data === "string" ) { cache[ camelCase( data ) ] = value; // Handle: [ owner, { properties } ] args } else { // Copy the properties one-by-one to the cache object for ( prop in data ) { cache[ camelCase( prop ) ] = data[ prop ]; } } return cache; }, get: function( owner, key ) { return key === undefined ? this.cache( owner ) : // Always use camelCase key (gh-2257) owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; }, access: function( owner, key, value ) { // In cases where either: // // 1. No key was specified // 2. A string key was specified, but no value provided // // Take the "read" path and allow the get method to determine // which value to return, respectively either: // // 1. The entire cache object // 2. The data stored at the key // if ( key === undefined || ( ( key && typeof key === "string" ) && value === undefined ) ) { return this.get( owner, key ); } // When the key is not a string, or both a key and value // are specified, set or extend (existing objects) with either: // // 1. An object of properties // 2. A key and value // this.set( owner, key, value ); // Since the "set" path can have two possible entry points // return the expected data based on which path was taken[*] return value !== undefined ? value : key; }, remove: function( owner, key ) { var i, cache = owner[ this.expando ]; if ( cache === undefined ) { return; } if ( key !== undefined ) { // Support array or space separated string of keys if ( Array.isArray( key ) ) { // If key is an array of keys... // We always set camelCase keys, so remove that. key = key.map( camelCase ); } else { key = camelCase( key ); // If a key with the spaces exists, use it. // Otherwise, create an array by matching non-whitespace key = key in cache ? [ key ] : ( key.match( rnothtmlwhite ) || [] ); } i = key.length; while ( i-- ) { delete cache[ key[ i ] ]; } } // Remove the expando if there's no more data if ( key === undefined || jQuery.isEmptyObject( cache ) ) { // Support: Chrome <=35 - 45 // Webkit & Blink performance suffers when deleting properties // from DOM nodes, so set to undefined instead // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) if ( owner.nodeType ) { owner[ this.expando ] = undefined; } else { delete owner[ this.expando ]; } } }, hasData: function( owner ) { var cache = owner[ this.expando ]; return cache !== undefined && !jQuery.isEmptyObject( cache ); } }; var dataPriv = new Data(); var dataUser = new Data(); // Implementation Summary // // 1. Enforce API surface and semantic compatibility with 1.9.x branch // 2. Improve the module's maintainability by reducing the storage // paths to a single mechanism. // 3. Use the same single mechanism to support "private" and "user" data. // 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) // 5. Avoid exposing implementation details on user objects (eg. expando properties) // 6. Provide a clear path for implementation upgrade to WeakMap in 2014 var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, rmultiDash = /[A-Z]/g; function getData( data ) { if ( data === "true" ) { return true; } if ( data === "false" ) { return false; } if ( data === "null" ) { return null; } // Only convert to a number if it doesn't change the string if ( data === +data + "" ) { return +data; } if ( rbrace.test( data ) ) { return JSON.parse( data ); } return data; } function dataAttr( elem, key, data ) { var name; // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = getData( data ); } catch ( e ) {} // Make sure we set the data so it isn't changed later dataUser.set( elem, key, data ); } else { data = undefined; } } return data; } jQuery.extend( { hasData: function( elem ) { return dataUser.hasData( elem ) || dataPriv.hasData( elem ); }, data: function( elem, name, data ) { return dataUser.access( elem, name, data ); }, removeData: function( elem, name ) { dataUser.remove( elem, name ); }, // TODO: Now that all calls to _data and _removeData have been replaced // with direct calls to dataPriv methods, these can be deprecated. _data: function( elem, name, data ) { return dataPriv.access( elem, name, data ); }, _removeData: function( elem, name ) { dataPriv.remove( elem, name ); } } ); jQuery.fn.extend( { data: function( key, value ) { var i, name, data, elem = this[ 0 ], attrs = elem && elem.attributes; // Gets all values if ( key === undefined ) { if ( this.length ) { data = dataUser.get( elem ); if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { i = attrs.length; while ( i-- ) { // Support: IE 11 only // The attrs elements can be null (#14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { name = camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } } dataPriv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { return this.each( function() { dataUser.set( this, key ); } ); } return access( this, function( value ) { var data; // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the // `value` parameter was not undefined. An empty jQuery object // will result in `undefined` for elem = this[ 0 ] which will // throw an exception if an attempt to read a data cache is made. if ( elem && value === undefined ) { // Attempt to get data from the cache // The key will always be camelCased in Data data = dataUser.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs data = dataAttr( elem, key ); if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn't exist. return; } // Set the data... this.each( function() { // We always store the camelCased key dataUser.set( this, key, value ); } ); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each( function() { dataUser.remove( this, key ); } ); } } ); jQuery.extend( { queue: function( elem, type, data ) { var queue; if ( elem ) { type = ( type || "fx" ) + "queue"; queue = dataPriv.get( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !queue || Array.isArray( data ) ) { queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); } else { queue.push( data ); } } return queue || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), startLength = queue.length, fn = queue.shift(), hooks = jQuery._queueHooks( elem, type ), next = function() { jQuery.dequeue( elem, type ); }; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); startLength--; } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } // Clear up the last queue stop function delete hooks.stop; fn.call( elem, next, hooks ); } if ( !startLength && hooks ) { hooks.empty.fire(); } }, // Not public - generate a queueHooks object, or return the current one _queueHooks: function( elem, type ) { var key = type + "queueHooks"; return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { empty: jQuery.Callbacks( "once memory" ).add( function() { dataPriv.remove( elem, [ type + "queue", key ] ); } ) } ); } } ); jQuery.fn.extend( { queue: function( type, data ) { var setter = 2; if ( typeof type !== "string" ) { data = type; type = "fx"; setter--; } if ( arguments.length < setter ) { return jQuery.queue( this[ 0 ], type ); } return data === undefined ? this : this.each( function() { var queue = jQuery.queue( this, type, data ); // Ensure a hooks for this queue jQuery._queueHooks( this, type ); if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { jQuery.dequeue( this, type ); } } ); }, dequeue: function( type ) { return this.each( function() { jQuery.dequeue( this, type ); } ); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, obj ) { var tmp, count = 1, defer = jQuery.Deferred(), elements = this, i = this.length, resolve = function() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } }; if ( typeof type !== "string" ) { obj = type; type = undefined; } type = type || "fx"; while ( i-- ) { tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); if ( tmp && tmp.empty ) { count++; tmp.empty.add( resolve ); } } resolve(); return defer.promise( obj ); } } ); var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; var documentElement = document.documentElement; var isAttached = function( elem ) { return jQuery.contains( elem.ownerDocument, elem ); }, composed = { composed: true }; // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only // Check attachment across shadow DOM boundaries when possible (gh-3504) // Support: iOS 10.0-10.2 only // Early iOS 10 versions support `attachShadow` but not `getRootNode`, // leading to errors. We need to check for `getRootNode`. if ( documentElement.getRootNode ) { isAttached = function( elem ) { return jQuery.contains( elem.ownerDocument, elem ) || elem.getRootNode( composed ) === elem.ownerDocument; }; } var isHiddenWithinTree = function( elem, el ) { // isHiddenWithinTree might be called from jQuery#filter function; // in that case, element will be second argument elem = el || elem; // Inline style trumps all return elem.style.display === "none" || elem.style.display === "" && // Otherwise, check computed style // Support: Firefox <=43 - 45 // Disconnected elements can have computed display: none, so first confirm that elem is // in the document. isAttached( elem ) && jQuery.css( elem, "display" ) === "none"; }; var swap = function( elem, options, callback, args ) { var ret, name, old = {}; // Remember the old values, and insert the new ones for ( name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } ret = callback.apply( elem, args || [] ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } return ret; }; function adjustCSS( elem, prop, valueParts, tween ) { var adjusted, scale, maxIterations = 20, currentValue = tween ? function() { return tween.cur(); } : function() { return jQuery.css( elem, prop, "" ); }, initial = currentValue(), unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches initialInUnit = elem.nodeType && ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { // Support: Firefox <=54 // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) initial = initial / 2; // Trust units reported by jQuery.css unit = unit || initialInUnit[ 3 ]; // Iteratively approximate from a nonzero starting point initialInUnit = +initial || 1; while ( maxIterations-- ) { // Evaluate and update our best guess (doubling guesses that zero out). // Finish if the scale equals or crosses 1 (making the old*new product non-positive). jQuery.style( elem, prop, initialInUnit + unit ); if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { maxIterations = 0; } initialInUnit = initialInUnit / scale; } initialInUnit = initialInUnit * 2; jQuery.style( elem, prop, initialInUnit + unit ); // Make sure we update the tween properties later on valueParts = valueParts || []; } if ( valueParts ) { initialInUnit = +initialInUnit || +initial || 0; // Apply relative offset (+=/-=) if specified adjusted = valueParts[ 1 ] ? initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : +valueParts[ 2 ]; if ( tween ) { tween.unit = unit; tween.start = initialInUnit; tween.end = adjusted; } } return adjusted; } var defaultDisplayMap = {}; function getDefaultDisplay( elem ) { var temp, doc = elem.ownerDocument, nodeName = elem.nodeName, display = defaultDisplayMap[ nodeName ]; if ( display ) { return display; } temp = doc.body.appendChild( doc.createElement( nodeName ) ); display = jQuery.css( temp, "display" ); temp.parentNode.removeChild( temp ); if ( display === "none" ) { display = "block"; } defaultDisplayMap[ nodeName ] = display; return display; } function showHide( elements, show ) { var display, elem, values = [], index = 0, length = elements.length; // Determine new display value for elements that need to change for ( ; index < length; index++ ) { elem = elements[ index ]; if ( !elem.style ) { continue; } display = elem.style.display; if ( show ) { // Since we force visibility upon cascade-hidden elements, an immediate (and slow) // check is required in this first loop unless we have a nonempty display value (either // inline or about-to-be-restored) if ( display === "none" ) { values[ index ] = dataPriv.get( elem, "display" ) || null; if ( !values[ index ] ) { elem.style.display = ""; } } if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { values[ index ] = getDefaultDisplay( elem ); } } else { if ( display !== "none" ) { values[ index ] = "none"; // Remember what we're overwriting dataPriv.set( elem, "display", display ); } } } // Set the display of the elements in a second loop to avoid constant reflow for ( index = 0; index < length; index++ ) { if ( values[ index ] != null ) { elements[ index ].style.display = values[ index ]; } } return elements; } jQuery.fn.extend( { show: function() { return showHide( this, true ); }, hide: function() { return showHide( this ); }, toggle: function( state ) { if ( typeof state === "boolean" ) { return state ? this.show() : this.hide(); } return this.each( function() { if ( isHiddenWithinTree( this ) ) { jQuery( this ).show(); } else { jQuery( this ).hide(); } } ); } } ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); // We have to close these tags to support XHTML (#13200) var wrapMap = { // Support: IE <=9 only option: [ 1, "" ], // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten // this by omitting or other required elements. thead: [ 1, "", "
" ], col: [ 2, "", "
" ], tr: [ 2, "", "
" ], td: [ 3, "", "
" ], _default: [ 0, "", "" ] }; // Support: IE <=9 only wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; function getAll( context, tag ) { // Support: IE <=9 - 11 only // Use typeof to avoid zero-argument method invocation on host objects (#15151) var ret; if ( typeof context.getElementsByTagName !== "undefined" ) { ret = context.getElementsByTagName( tag || "*" ); } else if ( typeof context.querySelectorAll !== "undefined" ) { ret = context.querySelectorAll( tag || "*" ); } else { ret = []; } if ( tag === undefined || tag && nodeName( context, tag ) ) { return jQuery.merge( [ context ], ret ); } return ret; } // Mark scripts as having already been evaluated function setGlobalEval( elems, refElements ) { var i = 0, l = elems.length; for ( ; i < l; i++ ) { dataPriv.set( elems[ i ], "globalEval", !refElements || dataPriv.get( refElements[ i ], "globalEval" ) ); } } var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { var elem, tmp, tag, wrap, attached, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, l = elems.length; for ( ; i < l; i++ ) { elem = elems[ i ]; if ( elem || elem === 0 ) { // Add nodes directly if ( toType( elem ) === "object" ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // Convert non-html into a text node } else if ( !rhtml.test( elem ) ) { nodes.push( context.createTextNode( elem ) ); // Convert html into DOM nodes } else { tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); // Deserialize a standard representation tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; // Descend through wrappers to the right content j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; } // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; // Ensure the created nodes are orphaned (#12392) tmp.textContent = ""; } } } // Remove wrapper from fragment fragment.textContent = ""; i = 0; while ( ( elem = nodes[ i++ ] ) ) { // Skip elements already in the context collection (trac-4087) if ( selection && jQuery.inArray( elem, selection ) > -1 ) { if ( ignored ) { ignored.push( elem ); } continue; } attached = isAttached( elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history if ( attached ) { setGlobalEval( tmp ); } // Capture executables if ( scripts ) { j = 0; while ( ( elem = tmp[ j++ ] ) ) { if ( rscriptType.test( elem.type || "" ) ) { scripts.push( elem ); } } } } return fragment; } ( function() { var fragment = document.createDocumentFragment(), div = fragment.appendChild( document.createElement( "div" ) ), input = document.createElement( "input" ); // Support: Android 4.0 - 4.3 only // Check state lost if the name is set (#11217) // Support: Windows Web Apps (WWA) // `name` and `type` must use .setAttribute for WWA (#14901) input.setAttribute( "type", "radio" ); input.setAttribute( "checked", "checked" ); input.setAttribute( "name", "t" ); div.appendChild( input ); // Support: Android <=4.1 only // Older WebKit doesn't clone checked state correctly in fragments support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; // Support: IE <=11 only // Make sure textarea (and checkbox) defaultValue is properly cloned div.innerHTML = ""; support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; } )(); var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, rtypenamespace = /^([^.]*)(?:\.(.+)|)/; function returnTrue() { return true; } function returnFalse() { return false; } // Support: IE <=9 - 11+ // focus() and blur() are asynchronous, except when they are no-op. // So expect focus to be synchronous when the element is already active, // and blur to be synchronous when the element is not already active. // (focus and blur are always synchronous in other supported browsers, // this just defines when we can count on it). function expectSync( elem, type ) { return ( elem === safeActiveElement() ) === ( type === "focus" ); } // Support: IE <=9 only // Accessing document.activeElement can throw unexpectedly // https://bugs.jquery.com/ticket/13393 function safeActiveElement() { try { return document.activeElement; } catch ( err ) { } } function on( elem, types, selector, data, fn, one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { on( elem, type, selector, data, types[ type ], one ); } return elem; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return elem; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return elem.each( function() { jQuery.event.add( this, types, fn, data, selector ); } ); } /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { global: {}, add: function( elem, types, handler, data, selector ) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); // Don't attach events to noData or text/comment nodes (but allow plain objects) if ( !elemData ) { return; } // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Ensure that invalid selectors throw exceptions at attach time // Evaluate against documentElement in case elem is a non-element node (e.g., document) if ( selector ) { jQuery.find.matchesSelector( documentElement, selector ); } // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { events = elemData.events = {}; } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } // Handle multiple events separated by a space types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers handleObj = jQuery.extend( { type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join( "." ) }, handleObjIn ); // Init the event handler queue if we're the first if ( !( handlers = events[ type ] ) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } }, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); if ( !elemData || !( events = elemData.events ) ) { return; } // Once for each type.namespace in types; type may be omitted types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector ? special.delegateType : special.bindType ) || type; handlers = events[ type ] || []; tmp = tmp[ 2 ] && new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !tmp || tmp.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { handlers.splice( j, 1 ); if ( handleObj.selector ) { handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( origCount && !handlers.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove data and the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { dataPriv.remove( elem, "handle events" ); } }, dispatch: function( nativeEvent ) { // Make a writable jQuery.Event from the native event object var event = jQuery.event.fix( nativeEvent ); var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[ 0 ] = event; for ( i = 1; i < arguments.length; i++ ) { args[ i ] = arguments[ i ]; } event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us i = 0; while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { // If the event is namespaced, then each handler is only invoked if it is // specially universal or its namespaces are a superset of the event's. if ( !event.rnamespace || handleObj.namespace === false || event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || handleObj.handler ).apply( matched.elem, args ); if ( ret !== undefined ) { if ( ( event.result = ret ) === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; }, handlers: function( event, handlers ) { var i, handleObj, sel, matchedHandlers, matchedSelectors, handlerQueue = [], delegateCount = handlers.delegateCount, cur = event.target; // Find delegate handlers if ( delegateCount && // Support: IE <=9 // Black-hole SVG instance trees (trac-13180) cur.nodeType && // Support: Firefox <=42 // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click // Support: IE 11 only // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) !( event.type === "click" && event.button >= 1 ) ) { for ( ; cur !== this; cur = cur.parentNode || this ) { // Don't check non-elements (#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { matchedHandlers = []; matchedSelectors = {}; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matchedSelectors[ sel ] === undefined ) { matchedSelectors[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) > -1 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( matchedSelectors[ sel ] ) { matchedHandlers.push( handleObj ); } } if ( matchedHandlers.length ) { handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); } } } } // Add the remaining (directly-bound) handlers cur = this; if ( delegateCount < handlers.length ) { handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); } return handlerQueue; }, addProp: function( name, hook ) { Object.defineProperty( jQuery.Event.prototype, name, { enumerable: true, configurable: true, get: isFunction( hook ) ? function() { if ( this.originalEvent ) { return hook( this.originalEvent ); } } : function() { if ( this.originalEvent ) { return this.originalEvent[ name ]; } }, set: function( value ) { Object.defineProperty( this, name, { enumerable: true, configurable: true, writable: true, value: value } ); } } ); }, fix: function( originalEvent ) { return originalEvent[ jQuery.expando ] ? originalEvent : new jQuery.Event( originalEvent ); }, special: { load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, click: { // Utilize native event to ensure correct state for checkable inputs setup: function( data ) { // For mutual compressibility with _default, replace `this` access with a local var. // `|| data` is dead code meant only to preserve the variable through minification. var el = this || data; // Claim the first handler if ( rcheckableType.test( el.type ) && el.click && nodeName( el, "input" ) ) { // dataPriv.set( el, "click", ... ) leverageNative( el, "click", returnTrue ); } // Return false to allow normal processing in the caller return false; }, trigger: function( data ) { // For mutual compressibility with _default, replace `this` access with a local var. // `|| data` is dead code meant only to preserve the variable through minification. var el = this || data; // Force setup before triggering a click if ( rcheckableType.test( el.type ) && el.click && nodeName( el, "input" ) ) { leverageNative( el, "click" ); } // Return non-false to allow normal event-path propagation return true; }, // For cross-browser consistency, suppress native .click() on links // Also prevent it if we're currently inside a leveraged native-event stack _default: function( event ) { var target = event.target; return rcheckableType.test( target.type ) && target.click && nodeName( target, "input" ) && dataPriv.get( target, "click" ) || nodeName( target, "a" ); } }, beforeunload: { postDispatch: function( event ) { // Support: Firefox 20+ // Firefox doesn't alert if the returnValue field is not set. if ( event.result !== undefined && event.originalEvent ) { event.originalEvent.returnValue = event.result; } } } } }; // Ensure the presence of an event listener that handles manually-triggered // synthetic events by interrupting progress until reinvoked in response to // *native* events that it fires directly, ensuring that state changes have // already occurred before other listeners are invoked. function leverageNative( el, type, expectSync ) { // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add if ( !expectSync ) { if ( dataPriv.get( el, type ) === undefined ) { jQuery.event.add( el, type, returnTrue ); } return; } // Register the controller as a special universal handler for all event namespaces dataPriv.set( el, type, false ); jQuery.event.add( el, type, { namespace: false, handler: function( event ) { var notAsync, result, saved = dataPriv.get( this, type ); if ( ( event.isTrigger & 1 ) && this[ type ] ) { // Interrupt processing of the outer synthetic .trigger()ed event // Saved data should be false in such cases, but might be a leftover capture object // from an async native handler (gh-4350) if ( !saved.length ) { // Store arguments for use when handling the inner native event // There will always be at least one argument (an event object), so this array // will not be confused with a leftover capture object. saved = slice.call( arguments ); dataPriv.set( this, type, saved ); // Trigger the native event and capture its result // Support: IE <=9 - 11+ // focus() and blur() are asynchronous notAsync = expectSync( this, type ); this[ type ](); result = dataPriv.get( this, type ); if ( saved !== result || notAsync ) { dataPriv.set( this, type, false ); } else { result = {}; } if ( saved !== result ) { // Cancel the outer synthetic event event.stopImmediatePropagation(); event.preventDefault(); return result.value; } // If this is an inner synthetic event for an event with a bubbling surrogate // (focus or blur), assume that the surrogate already propagated from triggering the // native event and prevent that from happening again here. // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the // bubbling surrogate propagates *after* the non-bubbling base), but that seems // less bad than duplication. } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { event.stopPropagation(); } // If this is a native event triggered above, everything is now in order // Fire an inner synthetic event with the original arguments } else if ( saved.length ) { // ...and capture the result dataPriv.set( this, type, { value: jQuery.event.trigger( // Support: IE <=9 - 11+ // Extend with the prototype to reset the above stopImmediatePropagation() jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), saved.slice( 1 ), this ) } ); // Abort handling of the native event event.stopImmediatePropagation(); } } } ); } jQuery.removeEvent = function( elem, type, handle ) { // This "if" is needed for plain objects if ( elem.removeEventListener ) { elem.removeEventListener( type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !( this instanceof jQuery.Event ) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && // Support: Android <=2.3 only src.returnValue === false ? returnTrue : returnFalse; // Create target properties // Support: Safari <=6 - 7 only // Target should not be a text node (#504, #13143) this.target = ( src.target && src.target.nodeType === 3 ) ? src.target.parentNode : src.target; this.currentTarget = src.currentTarget; this.relatedTarget = src.relatedTarget; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || Date.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { constructor: jQuery.Event, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, isSimulated: false, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if ( e && !this.isSimulated ) { e.preventDefault(); } }, stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopPropagation(); } }, stopImmediatePropagation: function() { var e = this.originalEvent; this.isImmediatePropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopImmediatePropagation(); } this.stopPropagation(); } }; // Includes all common event props including KeyEvent and MouseEvent specific props jQuery.each( { altKey: true, bubbles: true, cancelable: true, changedTouches: true, ctrlKey: true, detail: true, eventPhase: true, metaKey: true, pageX: true, pageY: true, shiftKey: true, view: true, "char": true, code: true, charCode: true, key: true, keyCode: true, button: true, buttons: true, clientX: true, clientY: true, offsetX: true, offsetY: true, pointerId: true, pointerType: true, screenX: true, screenY: true, targetTouches: true, toElement: true, touches: true, which: function( event ) { var button = event.button; // Add which for key events if ( event.which == null && rkeyEvent.test( event.type ) ) { return event.charCode != null ? event.charCode : event.keyCode; } // Add which for click: 1 === left; 2 === middle; 3 === right if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { if ( button & 1 ) { return 1; } if ( button & 2 ) { return 3; } if ( button & 4 ) { return 2; } return 0; } return event.which; } }, jQuery.event.addProp ); jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { jQuery.event.special[ type ] = { // Utilize native event if possible so blur/focus sequence is correct setup: function() { // Claim the first handler // dataPriv.set( this, "focus", ... ) // dataPriv.set( this, "blur", ... ) leverageNative( this, type, expectSync ); // Return false to allow normal processing in the caller return false; }, trigger: function() { // Force setup before trigger leverageNative( this, type ); // Return non-false to allow normal event-path propagation return true; }, delegateType: delegateType }; } ); // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout // // Support: Safari 7 only // Safari sends mouseenter too often; see: // https://bugs.chromium.org/p/chromium/issues/detail?id=470258 // for the description of the bug (it existed in older Chrome versions as well). jQuery.each( { mouseenter: "mouseover", mouseleave: "mouseout", pointerenter: "pointerover", pointerleave: "pointerout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var ret, target = this, related = event.relatedTarget, handleObj = event.handleObj; // For mouseenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; } ); jQuery.fn.extend( { on: function( types, selector, data, fn ) { return on( this, types, selector, data, fn ); }, one: function( types, selector, data, fn ) { return on( this, types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { var handleObj, type; if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each( function() { jQuery.event.remove( this, types, fn, selector ); } ); } } ); var /* eslint-disable max-len */ // See https://github.com/eslint/eslint/issues/3229 rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, /* eslint-enable */ // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ rnoInnerhtml = /\s*$/g; // Prefer a tbody over its parent table for containing new rows function manipulationTarget( elem, content ) { if ( nodeName( elem, "table" ) && nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { return jQuery( elem ).children( "tbody" )[ 0 ] || elem; } return elem; } // Replace/restore the type attribute of script elements for safe DOM manipulation function disableScript( elem ) { elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; return elem; } function restoreScript( elem ) { if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { elem.type = elem.type.slice( 5 ); } else { elem.removeAttribute( "type" ); } return elem; } function cloneCopyEvent( src, dest ) { var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; } // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { pdataOld = dataPriv.access( src ); pdataCur = dataPriv.set( dest, pdataOld ); events = pdataOld.events; if ( events ) { delete pdataCur.handle; pdataCur.events = {}; for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type, events[ type ][ i ] ); } } } } // 2. Copy user data if ( dataUser.hasData( src ) ) { udataOld = dataUser.access( src ); udataCur = jQuery.extend( {}, udataOld ); dataUser.set( dest, udataCur ); } } // Fix IE bugs, see support tests function fixInput( src, dest ) { var nodeName = dest.nodeName.toLowerCase(); // Fails to persist the checked state of a cloned checkbox or radio button. if ( nodeName === "input" && rcheckableType.test( src.type ) ) { dest.checked = src.checked; // Fails to return the selected option to the default selected state when cloning options } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } } function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays args = concat.apply( [], args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, l = collection.length, iNoClone = l - 1, value = args[ 0 ], valueIsFunction = isFunction( value ); // We can't cloneNode fragments that contain checked, in WebKit if ( valueIsFunction || ( l > 1 && typeof value === "string" && !support.checkClone && rchecked.test( value ) ) ) { return collection.each( function( index ) { var self = collection.eq( index ); if ( valueIsFunction ) { args[ 0 ] = value.call( this, index, self.html() ); } domManip( self, args, callback, ignored ); } ); } if ( l ) { fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { fragment = first; } // Require either new content or an interest in ignored elements to invoke the callback if ( first || ignored ) { scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); hasScripts = scripts.length; // Use the original fragment for the last item // instead of the first because it can end up // being emptied incorrectly in certain situations (#8070). for ( ; i < l; i++ ) { node = fragment; if ( i !== iNoClone ) { node = jQuery.clone( node, true, true ); // Keep references to cloned scripts for later restoration if ( hasScripts ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( scripts, getAll( node, "script" ) ); } } callback.call( collection[ i ], node, i ); } if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; // Reenable scripts jQuery.map( scripts, restoreScript ); // Evaluate executable scripts on first document insertion for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && !dataPriv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present if ( jQuery._evalUrl && !node.noModule ) { jQuery._evalUrl( node.src, { nonce: node.nonce || node.getAttribute( "nonce" ) } ); } } else { DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } } } } } return collection; } function remove( elem, selector, keepData ) { var node, nodes = selector ? jQuery.filter( selector, elem ) : elem, i = 0; for ( ; ( node = nodes[ i ] ) != null; i++ ) { if ( !keepData && node.nodeType === 1 ) { jQuery.cleanData( getAll( node ) ); } if ( node.parentNode ) { if ( keepData && isAttached( node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); } } return elem; } jQuery.extend( { htmlPrefilter: function( html ) { return html.replace( rxhtmlTag, "<$1>" ); }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), inPage = isAttached( elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); srcElements = getAll( elem ); for ( i = 0, l = srcElements.length; i < l; i++ ) { fixInput( srcElements[ i ], destElements[ i ] ); } } // Copy the events from the original to the clone if ( dataAndEvents ) { if ( deepDataAndEvents ) { srcElements = srcElements || getAll( elem ); destElements = destElements || getAll( clone ); for ( i = 0, l = srcElements.length; i < l; i++ ) { cloneCopyEvent( srcElements[ i ], destElements[ i ] ); } } else { cloneCopyEvent( elem, clone ); } } // Preserve script evaluation history destElements = getAll( clone, "script" ); if ( destElements.length > 0 ) { setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); } // Return the cloned set return clone; }, cleanData: function( elems ) { var data, elem, type, special = jQuery.event.special, i = 0; for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { if ( acceptData( elem ) ) { if ( ( data = elem[ dataPriv.expando ] ) ) { if ( data.events ) { for ( type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } } // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataPriv.expando ] = undefined; } if ( elem[ dataUser.expando ] ) { // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataUser.expando ] = undefined; } } } } } ); jQuery.fn.extend( { detach: function( selector ) { return remove( this, selector, true ); }, remove: function( selector ) { return remove( this, selector ); }, text: function( value ) { return access( this, function( value ) { return value === undefined ? jQuery.text( this ) : this.empty().each( function() { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { this.textContent = value; } } ); }, null, value, arguments.length ); }, append: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.appendChild( elem ); } } ); }, prepend: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.insertBefore( elem, target.firstChild ); } } ); }, before: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this ); } } ); }, after: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this.nextSibling ); } } ); }, empty: function() { var elem, i = 0; for ( ; ( elem = this[ i ] ) != null; i++ ) { if ( elem.nodeType === 1 ) { // Prevent memory leaks jQuery.cleanData( getAll( elem, false ) ); // Remove any remaining nodes elem.textContent = ""; } } return this; }, clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? false : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function() { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); } ); }, html: function( value ) { return access( this, function( value ) { var elem = this[ 0 ] || {}, i = 0, l = this.length; if ( value === undefined && elem.nodeType === 1 ) { return elem.innerHTML; } // See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { value = jQuery.htmlPrefilter( value ); try { for ( ; i < l; i++ ) { elem = this[ i ] || {}; // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch ( e ) {} } if ( elem ) { this.empty().append( value ); } }, null, value, arguments.length ); }, replaceWith: function() { var ignored = []; // Make the changes, replacing each non-ignored context element with the new content return domManip( this, arguments, function( elem ) { var parent = this.parentNode; if ( jQuery.inArray( this, ignored ) < 0 ) { jQuery.cleanData( getAll( this ) ); if ( parent ) { parent.replaceChild( elem, this ); } } // Force callback invocation }, ignored ); } } ); jQuery.each( { appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var elems, ret = [], insert = jQuery( selector ), last = insert.length - 1, i = 0; for ( ; i <= last; i++ ) { elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems ); // Support: Android <=4.0 only, PhantomJS 1 only // .get() because push.apply(_, arraylike) throws on ancient WebKit push.apply( ret, elems.get() ); } return this.pushStack( ret ); }; } ); var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var getStyles = function( elem ) { // Support: IE <=11 only, Firefox <=30 (#15098, #14150) // IE throws on elements created in popups // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" var view = elem.ownerDocument.defaultView; if ( !view || !view.opener ) { view = window; } return view.getComputedStyle( elem ); }; var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); ( function() { // Executing both pixelPosition & boxSizingReliable tests require only one layout // so they're executed at the same time to save the second computation. function computeStyleTests() { // This is a singleton, we need to execute it only once if ( !div ) { return; } container.style.cssText = "position:absolute;left:-11111px;width:60px;" + "margin-top:1px;padding:0;border:0"; div.style.cssText = "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + "margin:auto;border:1px;padding:1px;" + "width:60%;top:1%"; documentElement.appendChild( container ).appendChild( div ); var divStyle = window.getComputedStyle( div ); pixelPositionVal = divStyle.top !== "1%"; // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 // Some styles come back with percentage values, even though they shouldn't div.style.right = "60%"; pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; // Support: IE 9 - 11 only // Detect misreporting of content dimensions for box-sizing:border-box elements boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; // Support: IE 9 only // Detect overflow:scroll screwiness (gh-3699) // Support: Chrome <=64 // Don't get tricked when zoom affects offsetWidth (gh-4029) div.style.position = "absolute"; scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; documentElement.removeChild( container ); // Nullify the div so it wouldn't be stored in the memory and // it will also be a sign that checks already performed div = null; } function roundPixelMeasures( measure ) { return Math.round( parseFloat( measure ) ); } var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, reliableMarginLeftVal, container = document.createElement( "div" ), div = document.createElement( "div" ); // Finish early in limited (non-browser) environments if ( !div.style ) { return; } // Support: IE <=9 - 11 only // Style of cloned element affects source element cloned (#8908) div.style.backgroundClip = "content-box"; div.cloneNode( true ).style.backgroundClip = ""; support.clearCloneStyle = div.style.backgroundClip === "content-box"; jQuery.extend( support, { boxSizingReliable: function() { computeStyleTests(); return boxSizingReliableVal; }, pixelBoxStyles: function() { computeStyleTests(); return pixelBoxStylesVal; }, pixelPosition: function() { computeStyleTests(); return pixelPositionVal; }, reliableMarginLeft: function() { computeStyleTests(); return reliableMarginLeftVal; }, scrollboxSize: function() { computeStyleTests(); return scrollboxSizeVal; } } ); } )(); function curCSS( elem, name, computed ) { var width, minWidth, maxWidth, ret, // Support: Firefox 51+ // Retrieving style before computed somehow // fixes an issue with getting wrong values // on detached elements style = elem.style; computed = computed || getStyles( elem ); // getPropertyValue is needed for: // .css('filter') (IE 9 only, #12537) // .css('--customProperty) (#3144) if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; if ( ret === "" && !isAttached( elem ) ) { ret = jQuery.style( elem, name ); } // A tribute to the "awesome hack by Dean Edwards" // Android Browser returns percentage for some values, // but width seems to be reliably pixels. // This is against the CSSOM draft spec: // https://drafts.csswg.org/cssom/#resolved-values if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { // Remember the original values width = style.width; minWidth = style.minWidth; maxWidth = style.maxWidth; // Put in the new values to get a computed value out style.minWidth = style.maxWidth = style.width = ret; ret = computed.width; // Revert the changed values style.width = width; style.minWidth = minWidth; style.maxWidth = maxWidth; } } return ret !== undefined ? // Support: IE <=9 - 11 only // IE returns zIndex value as an integer. ret + "" : ret; } function addGetHookIf( conditionFn, hookFn ) { // Define the hook, we'll check on the first run if it's really needed. return { get: function() { if ( conditionFn() ) { // Hook not needed (or it's not possible to use it due // to missing dependency), remove it. delete this.get; return; } // Hook needed; redefine it so that the support test is not executed again. return ( this.get = hookFn ).apply( this, arguments ); } }; } var cssPrefixes = [ "Webkit", "Moz", "ms" ], emptyStyle = document.createElement( "div" ).style, vendorProps = {}; // Return a vendor-prefixed property or undefined function vendorPropName( name ) { // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; while ( i-- ) { name = cssPrefixes[ i ] + capName; if ( name in emptyStyle ) { return name; } } } // Return a potentially-mapped jQuery.cssProps or vendor prefixed property function finalPropName( name ) { var final = jQuery.cssProps[ name ] || vendorProps[ name ]; if ( final ) { return final; } if ( name in emptyStyle ) { return name; } return vendorProps[ name ] = vendorPropName( name ) || name; } var // Swappable if display is none or starts with table // except "table", "table-cell", or "table-caption" // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, rcustomProp = /^--/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { letterSpacing: "0", fontWeight: "400" }; function setPositiveNumber( elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point var matches = rcssNum.exec( value ); return matches ? // Guard against undefined "subtract", e.g., when used as in cssHooks Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : value; } function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { var i = dimension === "width" ? 1 : 0, extra = 0, delta = 0; // Adjustment may not be necessary if ( box === ( isBorderBox ? "border" : "content" ) ) { return 0; } for ( ; i < 4; i += 2 ) { // Both box models exclude margin if ( box === "margin" ) { delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } // If we get here with a content-box, we're seeking "padding" or "border" or "margin" if ( !isBorderBox ) { // Add padding delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); // For "border" or "margin", add border if ( box !== "padding" ) { delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); // But still keep track of it otherwise } else { extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } // If we get here with a border-box (content + padding + border), we're seeking "content" or // "padding" or "margin" } else { // For "content", subtract padding if ( box === "content" ) { delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } // For "content" or "padding", subtract border if ( box !== "margin" ) { delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } // Account for positive content-box scroll gutter when requested by providing computedVal if ( !isBorderBox && computedVal >= 0 ) { // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border // Assuming integer scroll gutter, subtract the rest and round down delta += Math.max( 0, Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - computedVal - delta - extra - 0.5 // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter // Use an explicit zero to avoid NaN (gh-3964) ) ) || 0; } return delta; } function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style var styles = getStyles( elem ), // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). // Fake content-box until we know it's needed to know the true value. boxSizingNeeded = !support.boxSizingReliable() || extra, isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", valueIsBorderBox = isBorderBox, val = curCSS( elem, dimension, styles ), offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); // Support: Firefox <=54 // Return a confounding non-pixel value or feign ignorance, as appropriate. if ( rnumnonpx.test( val ) ) { if ( !extra ) { return val; } val = "auto"; } // Fall back to offsetWidth/offsetHeight when value is "auto" // This happens for inline elements with no explicit setting (gh-3571) // Support: Android <=4.1 - 4.3 only // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) // Support: IE 9-11 only // Also use offsetWidth/offsetHeight for when box sizing is unreliable // We use getClientRects() to check for hidden/disconnected. // In those cases, the computed value can be trusted to be border-box if ( ( !support.boxSizingReliable() && isBorderBox || val === "auto" || !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && elem.getClientRects().length ) { isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; // Where available, offsetWidth/offsetHeight approximate border box dimensions. // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the // retrieved value as a content box dimension. valueIsBorderBox = offsetProp in elem; if ( valueIsBorderBox ) { val = elem[ offsetProp ]; } } // Normalize "" and auto val = parseFloat( val ) || 0; // Adjust for the element's box model return ( val + boxModelAdjustment( elem, dimension, extra || ( isBorderBox ? "border" : "content" ), valueIsBorderBox, styles, // Provide the current computed size to request scroll gutter calculation (gh-3589) val ) ) + "px"; } jQuery.extend( { // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity var ret = curCSS( elem, "opacity" ); return ret === "" ? "1" : ret; } } } }, // Don't automatically add "px" to these possibly-unitless properties cssNumber: { "animationIterationCount": true, "columnCount": true, "fillOpacity": true, "flexGrow": true, "flexShrink": true, "fontWeight": true, "gridArea": true, "gridColumn": true, "gridColumnEnd": true, "gridColumnStart": true, "gridRow": true, "gridRowEnd": true, "gridRowStart": true, "lineHeight": true, "opacity": true, "order": true, "orphans": true, "widows": true, "zIndex": true, "zoom": true }, // Add in properties whose names you wish to fix before // setting or getting the value cssProps: {}, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; } // Make sure that we're working with the right name var ret, type, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ), style = elem.style; // Make sure that we're working with the right name. We don't // want to query the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Gets hook for the prefixed version, then unprefixed version hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // Check if we're setting a value if ( value !== undefined ) { type = typeof value; // Convert "+=" or "-=" to relative numbers (#7345) if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { value = adjustCSS( elem, name, ret ); // Fixes bug #9237 type = "number"; } // Make sure that null and NaN values aren't set (#7116) if ( value == null || value !== value ) { return; } // If a number was passed in, add the unit (except for certain CSS properties) // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append // "px" to a few hardcoded values. if ( type === "number" && !isCustomProp ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } // background-* props affect original clone's values if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { style[ name ] = "inherit"; } // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !( "set" in hooks ) || ( value = hooks.set( elem, value, extra ) ) !== undefined ) { if ( isCustomProp ) { style.setProperty( name, value ); } else { style[ name ] = value; } } } else { // If a hook was provided get the non-computed value from there if ( hooks && "get" in hooks && ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { return ret; } // Otherwise just get the value from the style object return style[ name ]; } }, css: function( elem, name, extra, styles ) { var val, num, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ); // Make sure that we're working with the right name. We don't // want to modify the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Try prefixed name followed by the unprefixed name hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // If a hook was provided get the computed value from there if ( hooks && "get" in hooks ) { val = hooks.get( elem, true, extra ); } // Otherwise, if a way to get the computed value exists, use that if ( val === undefined ) { val = curCSS( elem, name, styles ); } // Convert "normal" to computed value if ( val === "normal" && name in cssNormalTransform ) { val = cssNormalTransform[ name ]; } // Make numeric if forced or a qualifier was provided and val looks numeric if ( extra === "" || extra ) { num = parseFloat( val ); return extra === true || isFinite( num ) ? num || 0 : val; } return val; } } ); jQuery.each( [ "height", "width" ], function( i, dimension ) { jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { // Certain elements can have dimension info if we invisibly show them // but it must have a current display style that would benefit return rdisplayswap.test( jQuery.css( elem, "display" ) ) && // Support: Safari 8+ // Table columns in Safari have non-zero offsetWidth & zero // getBoundingClientRect().width unless display is changed. // Support: IE <=11 only // Running getBoundingClientRect on a disconnected node // in IE throws an error. ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? swap( elem, cssShow, function() { return getWidthOrHeight( elem, dimension, extra ); } ) : getWidthOrHeight( elem, dimension, extra ); } }, set: function( elem, value, extra ) { var matches, styles = getStyles( elem ), // Only read styles.position if the test has a chance to fail // to avoid forcing a reflow. scrollboxSizeBuggy = !support.scrollboxSize() && styles.position === "absolute", // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) boxSizingNeeded = scrollboxSizeBuggy || extra, isBorderBox = boxSizingNeeded && jQuery.css( elem, "boxSizing", false, styles ) === "border-box", subtract = extra ? boxModelAdjustment( elem, dimension, extra, isBorderBox, styles ) : 0; // Account for unreliable border-box dimensions by comparing offset* to computed and // faking a content-box to get border and padding (gh-3699) if ( isBorderBox && scrollboxSizeBuggy ) { subtract -= Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - parseFloat( styles[ dimension ] ) - boxModelAdjustment( elem, dimension, "border", false, styles ) - 0.5 ); } // Convert to pixels if value adjustment is needed if ( subtract && ( matches = rcssNum.exec( value ) ) && ( matches[ 3 ] || "px" ) !== "px" ) { elem.style[ dimension ] = value; value = jQuery.css( elem, dimension ); } return setPositiveNumber( elem, value, subtract ); } }; } ); jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, function( elem, computed ) { if ( computed ) { return ( parseFloat( curCSS( elem, "marginLeft" ) ) || elem.getBoundingClientRect().left - swap( elem, { marginLeft: 0 }, function() { return elem.getBoundingClientRect().left; } ) ) + "px"; } } ); // These hooks are used by animate to expand properties jQuery.each( { margin: "", padding: "", border: "Width" }, function( prefix, suffix ) { jQuery.cssHooks[ prefix + suffix ] = { expand: function( value ) { var i = 0, expanded = {}, // Assumes a single number if not a string parts = typeof value === "string" ? value.split( " " ) : [ value ]; for ( ; i < 4; i++ ) { expanded[ prefix + cssExpand[ i ] + suffix ] = parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; } return expanded; } }; if ( prefix !== "margin" ) { jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; } } ); jQuery.fn.extend( { css: function( name, value ) { return access( this, function( elem, name, value ) { var styles, len, map = {}, i = 0; if ( Array.isArray( name ) ) { styles = getStyles( elem ); len = name.length; for ( ; i < len; i++ ) { map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); } return map; } return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }, name, value, arguments.length > 1 ); } } ); function Tween( elem, options, prop, end, easing ) { return new Tween.prototype.init( elem, options, prop, end, easing ); } jQuery.Tween = Tween; Tween.prototype = { constructor: Tween, init: function( elem, options, prop, end, easing, unit ) { this.elem = elem; this.prop = prop; this.easing = easing || jQuery.easing._default; this.options = options; this.start = this.now = this.cur(); this.end = end; this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); }, cur: function() { var hooks = Tween.propHooks[ this.prop ]; return hooks && hooks.get ? hooks.get( this ) : Tween.propHooks._default.get( this ); }, run: function( percent ) { var eased, hooks = Tween.propHooks[ this.prop ]; if ( this.options.duration ) { this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration ); } else { this.pos = eased = percent; } this.now = ( this.end - this.start ) * eased + this.start; if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } if ( hooks && hooks.set ) { hooks.set( this ); } else { Tween.propHooks._default.set( this ); } return this; } }; Tween.prototype.init.prototype = Tween.prototype; Tween.propHooks = { _default: { get: function( tween ) { var result; // Use a property on the element directly when it is not a DOM element, // or when there is no matching style property that exists. if ( tween.elem.nodeType !== 1 || tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { return tween.elem[ tween.prop ]; } // Passing an empty string as a 3rd parameter to .css will automatically // attempt a parseFloat and fallback to a string if the parse fails. // Simple values such as "10px" are parsed to Float; // complex values such as "rotate(1rad)" are returned as-is. result = jQuery.css( tween.elem, tween.prop, "" ); // Empty strings, null, undefined and "auto" are converted to 0. return !result || result === "auto" ? 0 : result; }, set: function( tween ) { // Use step hook for back compat. // Use cssHook if its there. // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); } else if ( tween.elem.nodeType === 1 && ( jQuery.cssHooks[ tween.prop ] || tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; } } } }; // Support: IE <=9 only // Panic based approach to setting things on disconnected nodes Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { set: function( tween ) { if ( tween.elem.nodeType && tween.elem.parentNode ) { tween.elem[ tween.prop ] = tween.now; } } }; jQuery.easing = { linear: function( p ) { return p; }, swing: function( p ) { return 0.5 - Math.cos( p * Math.PI ) / 2; }, _default: "swing" }; jQuery.fx = Tween.prototype.init; // Back compat <1.8 extension point jQuery.fx.step = {}; var fxNow, inProgress, rfxtypes = /^(?:toggle|show|hide)$/, rrun = /queueHooks$/; function schedule() { if ( inProgress ) { if ( document.hidden === false && window.requestAnimationFrame ) { window.requestAnimationFrame( schedule ); } else { window.setTimeout( schedule, jQuery.fx.interval ); } jQuery.fx.tick(); } } // Animations created synchronously will run synchronously function createFxNow() { window.setTimeout( function() { fxNow = undefined; } ); return ( fxNow = Date.now() ); } // Generate parameters to create a standard animation function genFx( type, includeWidth ) { var which, i = 0, attrs = { height: type }; // If we include width, step value is 1 to do all cssExpand values, // otherwise step value is 2 to skip over Left and Right includeWidth = includeWidth ? 1 : 0; for ( ; i < 4; i += 2 - includeWidth ) { which = cssExpand[ i ]; attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; } if ( includeWidth ) { attrs.opacity = attrs.width = type; } return attrs; } function createTween( value, prop, animation ) { var tween, collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), index = 0, length = collection.length; for ( ; index < length; index++ ) { if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { // We're done with this property return tween; } } } function defaultPrefilter( elem, props, opts ) { var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, isBox = "width" in props || "height" in props, anim = this, orig = {}, style = elem.style, hidden = elem.nodeType && isHiddenWithinTree( elem ), dataShow = dataPriv.get( elem, "fxshow" ); // Queue-skipping animations hijack the fx hooks if ( !opts.queue ) { hooks = jQuery._queueHooks( elem, "fx" ); if ( hooks.unqueued == null ) { hooks.unqueued = 0; oldfire = hooks.empty.fire; hooks.empty.fire = function() { if ( !hooks.unqueued ) { oldfire(); } }; } hooks.unqueued++; anim.always( function() { // Ensure the complete handler is called before this completes anim.always( function() { hooks.unqueued--; if ( !jQuery.queue( elem, "fx" ).length ) { hooks.empty.fire(); } } ); } ); } // Detect show/hide animations for ( prop in props ) { value = props[ prop ]; if ( rfxtypes.test( value ) ) { delete props[ prop ]; toggle = toggle || value === "toggle"; if ( value === ( hidden ? "hide" : "show" ) ) { // Pretend to be hidden if this is a "show" and // there is still data from a stopped show/hide if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { hidden = true; // Ignore all other no-op show/hide data } else { continue; } } orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); } } // Bail out if this is a no-op like .hide().hide() propTween = !jQuery.isEmptyObject( props ); if ( !propTween && jQuery.isEmptyObject( orig ) ) { return; } // Restrict "overflow" and "display" styles during box animations if ( isBox && elem.nodeType === 1 ) { // Support: IE <=9 - 11, Edge 12 - 15 // Record all 3 overflow attributes because IE does not infer the shorthand // from identically-valued overflowX and overflowY and Edge just mirrors // the overflowX value there. opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; // Identify a display type, preferring old show/hide data over the CSS cascade restoreDisplay = dataShow && dataShow.display; if ( restoreDisplay == null ) { restoreDisplay = dataPriv.get( elem, "display" ); } display = jQuery.css( elem, "display" ); if ( display === "none" ) { if ( restoreDisplay ) { display = restoreDisplay; } else { // Get nonempty value(s) by temporarily forcing visibility showHide( [ elem ], true ); restoreDisplay = elem.style.display || restoreDisplay; display = jQuery.css( elem, "display" ); showHide( [ elem ] ); } } // Animate inline elements as inline-block if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { if ( jQuery.css( elem, "float" ) === "none" ) { // Restore the original display value at the end of pure show/hide animations if ( !propTween ) { anim.done( function() { style.display = restoreDisplay; } ); if ( restoreDisplay == null ) { display = style.display; restoreDisplay = display === "none" ? "" : display; } } style.display = "inline-block"; } } } if ( opts.overflow ) { style.overflow = "hidden"; anim.always( function() { style.overflow = opts.overflow[ 0 ]; style.overflowX = opts.overflow[ 1 ]; style.overflowY = opts.overflow[ 2 ]; } ); } // Implement show/hide animations propTween = false; for ( prop in orig ) { // General show/hide setup for this element animation if ( !propTween ) { if ( dataShow ) { if ( "hidden" in dataShow ) { hidden = dataShow.hidden; } } else { dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); } // Store hidden/visible for toggle so `.stop().toggle()` "reverses" if ( toggle ) { dataShow.hidden = !hidden; } // Show elements before animating them if ( hidden ) { showHide( [ elem ], true ); } /* eslint-disable no-loop-func */ anim.done( function() { /* eslint-enable no-loop-func */ // The final step of a "hide" animation is actually hiding the element if ( !hidden ) { showHide( [ elem ] ); } dataPriv.remove( elem, "fxshow" ); for ( prop in orig ) { jQuery.style( elem, prop, orig[ prop ] ); } } ); } // Per-property setup propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); if ( !( prop in dataShow ) ) { dataShow[ prop ] = propTween.start; if ( hidden ) { propTween.end = propTween.start; propTween.start = 0; } } } } function propFilter( props, specialEasing ) { var index, name, easing, value, hooks; // camelCase, specialEasing and expand cssHook pass for ( index in props ) { name = camelCase( index ); easing = specialEasing[ name ]; value = props[ index ]; if ( Array.isArray( value ) ) { easing = value[ 1 ]; value = props[ index ] = value[ 0 ]; } if ( index !== name ) { props[ name ] = value; delete props[ index ]; } hooks = jQuery.cssHooks[ name ]; if ( hooks && "expand" in hooks ) { value = hooks.expand( value ); delete props[ name ]; // Not quite $.extend, this won't overwrite existing keys. // Reusing 'index' because we have the correct "name" for ( index in value ) { if ( !( index in props ) ) { props[ index ] = value[ index ]; specialEasing[ index ] = easing; } } } else { specialEasing[ name ] = easing; } } } function Animation( elem, properties, options ) { var result, stopped, index = 0, length = Animation.prefilters.length, deferred = jQuery.Deferred().always( function() { // Don't match elem in the :animated selector delete tick.elem; } ), tick = function() { if ( stopped ) { return false; } var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), // Support: Android 2.3 only // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, length = animation.tweens.length; for ( ; index < length; index++ ) { animation.tweens[ index ].run( percent ); } deferred.notifyWith( elem, [ animation, percent, remaining ] ); // If there's more to do, yield if ( percent < 1 && length ) { return remaining; } // If this was an empty animation, synthesize a final progress notification if ( !length ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); } // Resolve the animation and report its conclusion deferred.resolveWith( elem, [ animation ] ); return false; }, animation = deferred.promise( { elem: elem, props: jQuery.extend( {}, properties ), opts: jQuery.extend( true, { specialEasing: {}, easing: jQuery.easing._default }, options ), originalProperties: properties, originalOptions: options, startTime: fxNow || createFxNow(), duration: options.duration, tweens: [], createTween: function( prop, end ) { var tween = jQuery.Tween( elem, animation.opts, prop, end, animation.opts.specialEasing[ prop ] || animation.opts.easing ); animation.tweens.push( tween ); return tween; }, stop: function( gotoEnd ) { var index = 0, // If we are going to the end, we want to run all the tweens // otherwise we skip this part length = gotoEnd ? animation.tweens.length : 0; if ( stopped ) { return this; } stopped = true; for ( ; index < length; index++ ) { animation.tweens[ index ].run( 1 ); } // Resolve when we played the last frame; otherwise, reject if ( gotoEnd ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); deferred.resolveWith( elem, [ animation, gotoEnd ] ); } else { deferred.rejectWith( elem, [ animation, gotoEnd ] ); } return this; } } ), props = animation.props; propFilter( props, animation.opts.specialEasing ); for ( ; index < length; index++ ) { result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); if ( result ) { if ( isFunction( result.stop ) ) { jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = result.stop.bind( result ); } return result; } } jQuery.map( props, createTween, animation ); if ( isFunction( animation.opts.start ) ) { animation.opts.start.call( elem, animation ); } // Attach callbacks from options animation .progress( animation.opts.progress ) .done( animation.opts.done, animation.opts.complete ) .fail( animation.opts.fail ) .always( animation.opts.always ); jQuery.fx.timer( jQuery.extend( tick, { elem: elem, anim: animation, queue: animation.opts.queue } ) ); return animation; } jQuery.Animation = jQuery.extend( Animation, { tweeners: { "*": [ function( prop, value ) { var tween = this.createTween( prop, value ); adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); return tween; } ] }, tweener: function( props, callback ) { if ( isFunction( props ) ) { callback = props; props = [ "*" ]; } else { props = props.match( rnothtmlwhite ); } var prop, index = 0, length = props.length; for ( ; index < length; index++ ) { prop = props[ index ]; Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; Animation.tweeners[ prop ].unshift( callback ); } }, prefilters: [ defaultPrefilter ], prefilter: function( callback, prepend ) { if ( prepend ) { Animation.prefilters.unshift( callback ); } else { Animation.prefilters.push( callback ); } } } ); jQuery.speed = function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !isFunction( easing ) && easing }; // Go to the end state if fx are off if ( jQuery.fx.off ) { opt.duration = 0; } else { if ( typeof opt.duration !== "number" ) { if ( opt.duration in jQuery.fx.speeds ) { opt.duration = jQuery.fx.speeds[ opt.duration ]; } else { opt.duration = jQuery.fx.speeds._default; } } } // Normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing opt.old = opt.complete; opt.complete = function() { if ( isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } }; return opt; }; jQuery.fn.extend( { fadeTo: function( speed, to, easing, callback ) { // Show any hidden elements after setting opacity to 0 return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() // Animate to the value specified .end().animate( { opacity: to }, speed, easing, callback ); }, animate: function( prop, speed, easing, callback ) { var empty = jQuery.isEmptyObject( prop ), optall = jQuery.speed( speed, easing, callback ), doAnimation = function() { // Operate on a copy of prop so per-property easing won't be lost var anim = Animation( this, jQuery.extend( {}, prop ), optall ); // Empty animations, or finishing resolves immediately if ( empty || dataPriv.get( this, "finish" ) ) { anim.stop( true ); } }; doAnimation.finish = doAnimation; return empty || optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, stop: function( type, clearQueue, gotoEnd ) { var stopQueue = function( hooks ) { var stop = hooks.stop; delete hooks.stop; stop( gotoEnd ); }; if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; type = undefined; } if ( clearQueue && type !== false ) { this.queue( type || "fx", [] ); } return this.each( function() { var dequeue = true, index = type != null && type + "queueHooks", timers = jQuery.timers, data = dataPriv.get( this ); if ( index ) { if ( data[ index ] && data[ index ].stop ) { stopQueue( data[ index ] ); } } else { for ( index in data ) { if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { stopQueue( data[ index ] ); } } } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && ( type == null || timers[ index ].queue === type ) ) { timers[ index ].anim.stop( gotoEnd ); dequeue = false; timers.splice( index, 1 ); } } // Start the next in the queue if the last step wasn't forced. // Timers currently will call their complete callbacks, which // will dequeue but only if they were gotoEnd. if ( dequeue || !gotoEnd ) { jQuery.dequeue( this, type ); } } ); }, finish: function( type ) { if ( type !== false ) { type = type || "fx"; } return this.each( function() { var index, data = dataPriv.get( this ), queue = data[ type + "queue" ], hooks = data[ type + "queueHooks" ], timers = jQuery.timers, length = queue ? queue.length : 0; // Enable finishing flag on private data data.finish = true; // Empty the queue first jQuery.queue( this, type, [] ); if ( hooks && hooks.stop ) { hooks.stop.call( this, true ); } // Look for any active animations, and finish them for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && timers[ index ].queue === type ) { timers[ index ].anim.stop( true ); timers.splice( index, 1 ); } } // Look for any animations in the old queue and finish them for ( index = 0; index < length; index++ ) { if ( queue[ index ] && queue[ index ].finish ) { queue[ index ].finish.call( this ); } } // Turn off finishing flag delete data.finish; } ); } } ); jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? cssFn.apply( this, arguments ) : this.animate( genFx( name, true ), speed, easing, callback ); }; } ); // Generate shortcuts for custom animations jQuery.each( { slideDown: genFx( "show" ), slideUp: genFx( "hide" ), slideToggle: genFx( "toggle" ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; } ); jQuery.timers = []; jQuery.fx.tick = function() { var timer, i = 0, timers = jQuery.timers; fxNow = Date.now(); for ( ; i < timers.length; i++ ) { timer = timers[ i ]; // Run the timer and safely remove it when done (allowing for external removal) if ( !timer() && timers[ i ] === timer ) { timers.splice( i--, 1 ); } } if ( !timers.length ) { jQuery.fx.stop(); } fxNow = undefined; }; jQuery.fx.timer = function( timer ) { jQuery.timers.push( timer ); jQuery.fx.start(); }; jQuery.fx.interval = 13; jQuery.fx.start = function() { if ( inProgress ) { return; } inProgress = true; schedule(); }; jQuery.fx.stop = function() { inProgress = null; }; jQuery.fx.speeds = { slow: 600, fast: 200, // Default speed _default: 400 }; // Based off of the plugin by Clint Helfers, with permission. // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ jQuery.fn.delay = function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; return this.queue( type, function( next, hooks ) { var timeout = window.setTimeout( next, time ); hooks.stop = function() { window.clearTimeout( timeout ); }; } ); }; ( function() { var input = document.createElement( "input" ), select = document.createElement( "select" ), opt = select.appendChild( document.createElement( "option" ) ); input.type = "checkbox"; // Support: Android <=4.3 only // Default value for a checkbox should be "on" support.checkOn = input.value !== ""; // Support: IE <=11 only // Must access selectedIndex to make default options select support.optSelected = opt.selected; // Support: IE <=11 only // An input loses its value after becoming a radio input = document.createElement( "input" ); input.value = "t"; input.type = "radio"; support.radioValue = input.value === "t"; } )(); var boolHook, attrHandle = jQuery.expr.attrHandle; jQuery.fn.extend( { attr: function( name, value ) { return access( this, jQuery.attr, name, value, arguments.length > 1 ); }, removeAttr: function( name ) { return this.each( function() { jQuery.removeAttr( this, name ); } ); } } ); jQuery.extend( { attr: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set attributes on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } // Fallback to prop when attributes are not supported if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } // Attribute hooks are determined by the lowercase version // Grab necessary hook if one is defined if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { hooks = jQuery.attrHooks[ name.toLowerCase() ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return; } if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } elem.setAttribute( name, value + "" ); return value; } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } ret = jQuery.find.attr( elem, name ); // Non-existent attributes return null, we normalize to undefined return ret == null ? undefined : ret; }, attrHooks: { type: { set: function( elem, value ) { if ( !support.radioValue && value === "radio" && nodeName( elem, "input" ) ) { var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } } }, removeAttr: function( elem, value ) { var name, i = 0, // Attribute names can contain non-HTML whitespace characters // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 attrNames = value && value.match( rnothtmlwhite ); if ( attrNames && elem.nodeType === 1 ) { while ( ( name = attrNames[ i++ ] ) ) { elem.removeAttribute( name ); } } } } ); // Hooks for boolean attributes boolHook = { set: function( elem, value, name ) { if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { elem.setAttribute( name, name ); } return name; } }; jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { var ret, handle, lowercaseName = name.toLowerCase(); if ( !isXML ) { // Avoid an infinite loop by temporarily removing this function from the getter handle = attrHandle[ lowercaseName ]; attrHandle[ lowercaseName ] = ret; ret = getter( elem, name, isXML ) != null ? lowercaseName : null; attrHandle[ lowercaseName ] = handle; } return ret; }; } ); var rfocusable = /^(?:input|select|textarea|button)$/i, rclickable = /^(?:a|area)$/i; jQuery.fn.extend( { prop: function( name, value ) { return access( this, jQuery.prop, name, value, arguments.length > 1 ); }, removeProp: function( name ) { return this.each( function() { delete this[ jQuery.propFix[ name ] || name ]; } ); } } ); jQuery.extend( { prop: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set properties on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } return ( elem[ name ] = value ); } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } return elem[ name ]; }, propHooks: { tabIndex: { get: function( elem ) { // Support: IE <=9 - 11 only // elem.tabIndex doesn't always return the // correct value when it hasn't been explicitly set // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ // Use proper attribute retrieval(#12072) var tabindex = jQuery.find.attr( elem, "tabindex" ); if ( tabindex ) { return parseInt( tabindex, 10 ); } if ( rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ) { return 0; } return -1; } } }, propFix: { "for": "htmlFor", "class": "className" } } ); // Support: IE <=11 only // Accessing the selectedIndex property // forces the browser to respect setting selected // on the option // The getter ensures a default option is selected // when in an optgroup // eslint rule "no-unused-expressions" is disabled for this code // since it considers such accessions noop if ( !support.optSelected ) { jQuery.propHooks.selected = { get: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent && parent.parentNode ) { parent.parentNode.selectedIndex; } return null; }, set: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } } }; } jQuery.each( [ "tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable" ], function() { jQuery.propFix[ this.toLowerCase() ] = this; } ); // Strip and collapse whitespace according to HTML spec // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace function stripAndCollapse( value ) { var tokens = value.match( rnothtmlwhite ) || []; return tokens.join( " " ); } function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; } function classesToArray( value ) { if ( Array.isArray( value ) ) { return value; } if ( typeof value === "string" ) { return value.match( rnothtmlwhite ) || []; } return []; } jQuery.fn.extend( { addClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); } ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { if ( cur.indexOf( " " + clazz + " " ) < 0 ) { cur += clazz + " "; } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, removeClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); } ); } if ( !arguments.length ) { return this.attr( "class", "" ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); // This expression is here for better compressibility (see addClass) cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { // Remove *all* instances while ( cur.indexOf( " " + clazz + " " ) > -1 ) { cur = cur.replace( " " + clazz + " ", " " ); } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isValidValue = type === "string" || Array.isArray( value ); if ( typeof stateVal === "boolean" && isValidValue ) { return stateVal ? this.addClass( value ) : this.removeClass( value ); } if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( value.call( this, i, getClass( this ), stateVal ), stateVal ); } ); } return this.each( function() { var className, i, self, classNames; if ( isValidValue ) { // Toggle individual class names i = 0; self = jQuery( this ); classNames = classesToArray( value ); while ( ( className = classNames[ i++ ] ) ) { // Check each className given, space separated list if ( self.hasClass( className ) ) { self.removeClass( className ); } else { self.addClass( className ); } } // Toggle whole class name } else if ( value === undefined || type === "boolean" ) { className = getClass( this ); if ( className ) { // Store className if set dataPriv.set( this, "__className__", className ); } // If the element has a class name or if we're passed `false`, // then remove the whole classname (if there was one, the above saved it). // Otherwise bring back whatever was previously saved (if anything), // falling back to the empty string if nothing was stored. if ( this.setAttribute ) { this.setAttribute( "class", className || value === false ? "" : dataPriv.get( this, "__className__" ) || "" ); } } } ); }, hasClass: function( selector ) { var className, elem, i = 0; className = " " + selector + " "; while ( ( elem = this[ i++ ] ) ) { if ( elem.nodeType === 1 && ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { return true; } } return false; } } ); var rreturn = /\r/g; jQuery.fn.extend( { val: function( value ) { var hooks, ret, valueIsFunction, elem = this[ 0 ]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && ( ret = hooks.get( elem, "value" ) ) !== undefined ) { return ret; } ret = elem.value; // Handle most common string cases if ( typeof ret === "string" ) { return ret.replace( rreturn, "" ); } // Handle cases where value is null/undef or number return ret == null ? "" : ret; } return; } valueIsFunction = isFunction( value ); return this.each( function( i ) { var val; if ( this.nodeType !== 1 ) { return; } if ( valueIsFunction ) { val = value.call( this, i, jQuery( this ).val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( Array.isArray( val ) ) { val = jQuery.map( val, function( value ) { return value == null ? "" : value + ""; } ); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } } ); } } ); jQuery.extend( { valHooks: { option: { get: function( elem ) { var val = jQuery.find.attr( elem, "value" ); return val != null ? val : // Support: IE <=10 - 11 only // option.text throws exceptions (#14686, #14858) // Strip and collapse whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace stripAndCollapse( jQuery.text( elem ) ); } }, select: { get: function( elem ) { var value, option, i, options = elem.options, index = elem.selectedIndex, one = elem.type === "select-one", values = one ? null : [], max = one ? index + 1 : options.length; if ( index < 0 ) { i = max; } else { i = one ? index : 0; } // Loop through all the selected options for ( ; i < max; i++ ) { option = options[ i ]; // Support: IE <=9 only // IE8-9 doesn't update selected after form reset (#2551) if ( ( option.selected || i === index ) && // Don't return options that are disabled or in a disabled optgroup !option.disabled && ( !option.parentNode.disabled || !nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } return values; }, set: function( elem, value ) { var optionSet, option, options = elem.options, values = jQuery.makeArray( value ), i = options.length; while ( i-- ) { option = options[ i ]; /* eslint-disable no-cond-assign */ if ( option.selected = jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) { optionSet = true; } /* eslint-enable no-cond-assign */ } // Force browsers to behave consistently when non-matching value is set if ( !optionSet ) { elem.selectedIndex = -1; } return values; } } } } ); // Radios and checkboxes getter/setter jQuery.each( [ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { set: function( elem, value ) { if ( Array.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); } } }; if ( !support.checkOn ) { jQuery.valHooks[ this ].get = function( elem ) { return elem.getAttribute( "value" ) === null ? "on" : elem.value; }; } } ); // Return jQuery for attributes-only inclusion support.focusin = "onfocusin" in window; var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, stopPropagationCallback = function( e ) { e.stopPropagation(); }; jQuery.extend( jQuery.event, { trigger: function( event, data, elem, onlyHandlers ) { var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, eventPath = [ elem || document ], type = hasOwn.call( event, "type" ) ? event.type : event, namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; cur = lastElement = tmp = elem = elem || document; // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "." ) > -1 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split( "." ); type = namespaces.shift(); namespaces.sort(); } ontype = type.indexOf( ":" ) < 0 && "on" + type; // Caller can pass in a jQuery.Event object, Object, or just an event type string event = event[ jQuery.expando ] ? event : new jQuery.Event( type, typeof event === "object" && event ); // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) event.isTrigger = onlyHandlers ? 2 : 3; event.namespace = namespaces.join( "." ); event.rnamespace = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : null; // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list data = data == null ? [ event ] : jQuery.makeArray( data, [ event ] ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { cur = cur.parentNode; } for ( ; cur; cur = cur.parentNode ) { eventPath.push( cur ); tmp = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( tmp === ( elem.ownerDocument || document ) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } } // Fire handlers on the event path i = 0; while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { lastElement = cur; event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Native handler handle = ontype && cur[ ontype ]; if ( handle && handle.apply && acceptData( cur ) ) { event.result = handle.apply( cur, data ); if ( event.result === false ) { event.preventDefault(); } } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( ( !special._default || special._default.apply( eventPath.pop(), data ) === false ) && acceptData( elem ) ) { // Call a native DOM method on the target with the same name as the event. // Don't do default actions on window, that's where global variables be (#6170) if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method tmp = elem[ ontype ]; if ( tmp ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; if ( event.isPropagationStopped() ) { lastElement.addEventListener( type, stopPropagationCallback ); } elem[ type ](); if ( event.isPropagationStopped() ) { lastElement.removeEventListener( type, stopPropagationCallback ); } jQuery.event.triggered = undefined; if ( tmp ) { elem[ ontype ] = tmp; } } } } return event.result; }, // Piggyback on a donor event to simulate a different one // Used only for `focus(in | out)` events simulate: function( type, elem, event ) { var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true } ); jQuery.event.trigger( e, null, elem ); } } ); jQuery.fn.extend( { trigger: function( type, data ) { return this.each( function() { jQuery.event.trigger( type, data, this ); } ); }, triggerHandler: function( type, data ) { var elem = this[ 0 ]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } } } ); // Support: Firefox <=44 // Firefox doesn't have focus(in | out) events // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 // // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 // focus(in | out) events fire after focus & blur events, // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 if ( !support.focusin ) { jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler on the document while someone wants focusin/focusout var handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); }; jQuery.event.special[ fix ] = { setup: function() { var doc = this.ownerDocument || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { doc.addEventListener( orig, handler, true ); } dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { var doc = this.ownerDocument || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { doc.removeEventListener( orig, handler, true ); dataPriv.remove( doc, fix ); } else { dataPriv.access( doc, fix, attaches ); } } }; } ); } var location = window.location; var nonce = Date.now(); var rquery = ( /\?/ ); // Cross-browser xml parsing jQuery.parseXML = function( data ) { var xml; if ( !data || typeof data !== "string" ) { return null; } // Support: IE 9 - 11 only // IE throws on parseFromString with invalid input. try { xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); } catch ( e ) { xml = undefined; } if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }; var rbracket = /\[\]$/, rCRLF = /\r?\n/g, rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, rsubmittable = /^(?:input|select|textarea|keygen)/i; function buildParams( prefix, obj, traditional, add ) { var name; if ( Array.isArray( obj ) ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // Item is non-scalar (array or object), encode its numeric index. buildParams( prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", v, traditional, add ); } } ); } else if ( !traditional && toType( obj ) === "object" ) { // Serialize object item. for ( name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // Serialize an array of form elements or a set of // key/values into a query string jQuery.param = function( a, traditional ) { var prefix, s = [], add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value var value = isFunction( valueOrFunction ) ? valueOrFunction() : valueOrFunction; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value == null ? "" : value ); }; if ( a == null ) { return ""; } // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); } ); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ); }; jQuery.fn.extend( { serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map( function() { // Can add propHook for "elements" to filter or add form elements var elements = jQuery.prop( this, "elements" ); return elements ? jQuery.makeArray( elements ) : this; } ) .filter( function() { var type = this.type; // Use .is( ":disabled" ) so that fieldset[disabled] works return this.name && !jQuery( this ).is( ":disabled" ) && rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && ( this.checked || !rcheckableType.test( type ) ); } ) .map( function( i, elem ) { var val = jQuery( this ).val(); if ( val == null ) { return null; } if ( Array.isArray( val ) ) { return jQuery.map( val, function( val ) { return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ); } return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ).get(); } } ); var r20 = /%20/g, rhash = /#.*$/, rantiCache = /([?&])_=[^&]*/, rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, // #7653, #8125, #8152: local protocol detection rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}, // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = "*/".concat( "*" ), // Anchor tag for parsing the document origin originAnchor = document.createElement( "a" ); originAnchor.href = location.href; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport function addToPrefiltersOrTransports( structure ) { // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } var dataType, i = 0, dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; if ( isFunction( func ) ) { // For each dataType in the dataTypeExpression while ( ( dataType = dataTypes[ i++ ] ) ) { // Prepend if requested if ( dataType[ 0 ] === "+" ) { dataType = dataType.slice( 1 ) || "*"; ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); // Otherwise append } else { ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); } } } }; } // Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { var inspected = {}, seekingTransport = ( structure === transports ); function inspect( dataType ) { var selected; inspected[ dataType ] = true; jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) { options.dataTypes.unshift( dataTypeOrTransport ); inspect( dataTypeOrTransport ); return false; } else if ( seekingTransport ) { return !( selected = dataTypeOrTransport ); } } ); return selected; } return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); } // A special extend for ajax options // that takes "flat" options (not to be deep extended) // Fixes #9887 function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; for ( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } } if ( deep ) { jQuery.extend( true, target, deep ); } return target; } /* Handles responses to an ajax request: * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jqXHR, responses ) { var ct, type, finalDataType, firstDataType, contents = s.contents, dataTypes = s.dataTypes; // Remove auto dataType and get content-type in the process while ( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } /* Chain conversions given the request and the original response * Also sets the responseXXX fields on the jqXHR instance */ function ajaxConvert( s, response, jqXHR, isSuccess ) { var conv2, current, conv, tmp, prev, converters = {}, // Work with a copy of dataTypes in case we need to modify it for conversion dataTypes = s.dataTypes.slice(); // Create converters map with lowercased keys if ( dataTypes[ 1 ] ) { for ( conv in s.converters ) { converters[ conv.toLowerCase() ] = s.converters[ conv ]; } } current = dataTypes.shift(); // Convert to each sequential dataType while ( current ) { if ( s.responseFields[ current ] ) { jqXHR[ s.responseFields[ current ] ] = response; } // Apply the dataFilter if provided if ( !prev && isSuccess && s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } prev = current; current = dataTypes.shift(); if ( current ) { // There's only work to do if current dataType is non-auto if ( current === "*" ) { current = prev; // Convert response if prev dataType is non-auto and differs from current } else if ( prev !== "*" && prev !== current ) { // Seek a direct converter conv = converters[ prev + " " + current ] || converters[ "* " + current ]; // If none found, seek a pair if ( !conv ) { for ( conv2 in converters ) { // If conv2 outputs current tmp = conv2.split( " " ); if ( tmp[ 1 ] === current ) { // If prev can be converted to accepted input conv = converters[ prev + " " + tmp[ 0 ] ] || converters[ "* " + tmp[ 0 ] ]; if ( conv ) { // Condense equivalence converters if ( conv === true ) { conv = converters[ conv2 ]; // Otherwise, insert the intermediate dataType } else if ( converters[ conv2 ] !== true ) { current = tmp[ 0 ]; dataTypes.unshift( tmp[ 1 ] ); } break; } } } } // Apply converter (if not an equivalence) if ( conv !== true ) { // Unless errors are allowed to bubble, catch and return them if ( conv && s.throws ) { response = conv( response ); } else { try { response = conv( response ); } catch ( e ) { return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; } } } } } } return { state: "success", data: response }; } jQuery.extend( { // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {}, ajaxSettings: { url: location.href, type: "GET", isLocal: rlocalProtocol.test( location.protocol ), global: true, processData: true, async: true, contentType: "application/x-www-form-urlencoded; charset=UTF-8", /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, throws: false, traditional: false, headers: {}, */ accepts: { "*": allTypes, text: "text/plain", html: "text/html", xml: "application/xml, text/xml", json: "application/json, text/javascript" }, contents: { xml: /\bxml\b/, html: /\bhtml/, json: /\bjson\b/ }, responseFields: { xml: "responseXML", text: "responseText", json: "responseJSON" }, // Data converters // Keys separate source (or catchall "*") and destination types with a single space converters: { // Convert anything to text "* text": String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": JSON.parse, // Parse text as xml "text xml": jQuery.parseXML }, // For options that shouldn't be deep extended: // you can add your own custom options here if // and when you create one that shouldn't be // deep extended (see ajaxExtend) flatOptions: { url: true, context: true } }, // Creates a full fledged settings object into target // with both ajaxSettings and settings fields. // If target is omitted, writes into ajaxSettings. ajaxSetup: function( target, settings ) { return settings ? // Building a settings object ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : // Extending ajaxSettings ajaxExtend( jQuery.ajaxSettings, target ); }, ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), ajaxTransport: addToPrefiltersOrTransports( transports ), // Main method ajax: function( url, options ) { // If url is an object, simulate pre-1.5 signature if ( typeof url === "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var transport, // URL without anti-cache param cacheURL, // Response headers responseHeadersString, responseHeaders, // timeout handle timeoutTimer, // Url cleanup var urlAnchor, // Request state (becomes false upon send and true upon completion) completed, // To know if global events are to be dispatched fireGlobals, // Loop variable i, // uncached part of the url uncached, // Create the final options object s = jQuery.ajaxSetup( {}, options ), // Callbacks context callbackContext = s.context || s, // Context for global events is callbackContext if it is a DOM node or jQuery collection globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, requestHeadersNames = {}, // Default abort message strAbort = "canceled", // Fake xhr jqXHR = { readyState: 0, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( completed ) { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[ 1 ].toLowerCase() + " " ] = ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) .concat( match[ 2 ] ); } } match = responseHeaders[ key.toLowerCase() + " " ]; } return match == null ? null : match.join( ", " ); }, // Raw string getAllResponseHeaders: function() { return completed ? responseHeadersString : null; }, // Caches the header setRequestHeader: function( name, value ) { if ( completed == null ) { name = requestHeadersNames[ name.toLowerCase() ] = requestHeadersNames[ name.toLowerCase() ] || name; requestHeaders[ name ] = value; } return this; }, // Overrides response content-type header overrideMimeType: function( type ) { if ( completed == null ) { s.mimeType = type; } return this; }, // Status-dependent callbacks statusCode: function( map ) { var code; if ( map ) { if ( completed ) { // Execute the appropriate callbacks jqXHR.always( map[ jqXHR.status ] ); } else { // Lazy-add the new callbacks in a way that preserves old ones for ( code in map ) { statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } } } return this; }, // Cancel the request abort: function( statusText ) { var finalText = statusText || strAbort; if ( transport ) { transport.abort( finalText ); } done( 0, finalText ); return this; } }; // Attach deferreds deferred.promise( jqXHR ); // Add protocol if not provided (prefilters might expect it) // Handle falsy url in the settings object (#10093: consistency with old signature) // We also use the url parameter if available s.url = ( ( url || s.url || location.href ) + "" ) .replace( rprotocol, location.protocol + "//" ); // Alias method option to type as per ticket #12004 s.type = options.method || options.type || s.method || s.type; // Extract dataTypes list s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; // A cross-domain request is in order when the origin doesn't match the current origin. if ( s.crossDomain == null ) { urlAnchor = document.createElement( "a" ); // Support: IE <=8 - 11, Edge 12 - 15 // IE throws exception on accessing the href property if url is malformed, // e.g. http://example.com:80x/ try { urlAnchor.href = s.url; // Support: IE <=8 - 11 only // Anchor's host property isn't correctly set when s.url is relative urlAnchor.href = urlAnchor.href; s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== urlAnchor.protocol + "//" + urlAnchor.host; } catch ( e ) { // If there is an error parsing the URL, assume it is crossDomain, // it can be rejected by the transport if it is invalid s.crossDomain = true; } } // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); } // Apply prefilters inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // If request was aborted inside a prefilter, stop there if ( completed ) { return jqXHR; } // We can fire global events as of now if asked to // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) fireGlobals = jQuery.event && s.global; // Watch for a new set of requests if ( fireGlobals && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content s.hasContent = !rnoContent.test( s.type ); // Save the URL in case we're toying with the If-Modified-Since // and/or If-None-Match header later on // Remove hash to simplify url manipulation cacheURL = s.url.replace( rhash, "" ); // More options handling for requests with no content if ( !s.hasContent ) { // Remember the hash so we can put it back uncached = s.url.slice( cacheURL.length ); // If data is available and should be processed, append data to url if ( s.data && ( s.processData || typeof s.data === "string" ) ) { cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry delete s.data; } // Add or update anti-cache param if needed if ( s.cache === false ) { cacheURL = cacheURL.replace( rantiCache, "$1" ); uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; } // Put hash and anti-cache on the URL that will be requested (gh-1732) s.url = cacheURL + uncached; // Change '%20' to '+' if this is encoded form body content (gh-2658) } else if ( s.data && s.processData && ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { s.data = s.data.replace( r20, "+" ); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( jQuery.lastModified[ cacheURL ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); } if ( jQuery.etag[ cacheURL ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); } } // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } // Set the Accepts header for the server, depending on the dataType jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : s.accepts[ "*" ] ); // Check for headers option for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { // Abort if not done already and return return jqXHR.abort(); } // Aborting is no longer a cancellation strAbort = "abort"; // Install callbacks on deferreds completeDeferred.add( s.complete ); jqXHR.done( s.success ); jqXHR.fail( s.error ); // Get transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1; // Send global event if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // If request was aborted inside ajaxSend, stop there if ( completed ) { return jqXHR; } // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = window.setTimeout( function() { jqXHR.abort( "timeout" ); }, s.timeout ); } try { completed = false; transport.send( requestHeaders, done ); } catch ( e ) { // Rethrow post-completion exceptions if ( completed ) { throw e; } // Propagate others as results done( -1, e ); } } // Callback for when everything is done function done( status, nativeStatusText, responses, headers ) { var isSuccess, success, error, response, modified, statusText = nativeStatusText; // Ignore repeat invocations if ( completed ) { return; } completed = true; // Clear timeout if it exists if ( timeoutTimer ) { window.clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jqXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; // Determine if successful isSuccess = status >= 200 && status < 300 || status === 304; // Get response data if ( responses ) { response = ajaxHandleResponses( s, jqXHR, responses ); } // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); // If successful, handle type chaining if ( isSuccess ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { modified = jqXHR.getResponseHeader( "Last-Modified" ); if ( modified ) { jQuery.lastModified[ cacheURL ] = modified; } modified = jqXHR.getResponseHeader( "etag" ); if ( modified ) { jQuery.etag[ cacheURL ] = modified; } } // if no content if ( status === 204 || s.type === "HEAD" ) { statusText = "nocontent"; // if not modified } else if ( status === 304 ) { statusText = "notmodified"; // If we have data, let's convert it } else { statusText = response.state; success = response.data; error = response.error; isSuccess = !error; } } else { // Extract error from statusText and normalize for non-aborts error = statusText; if ( status || !statusText ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } // Set data for the fake xhr object jqXHR.status = status; jqXHR.statusText = ( nativeStatusText || statusText ) + ""; // Success/Error if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } // Status-dependent callbacks jqXHR.statusCode( statusCode ); statusCode = undefined; if ( fireGlobals ) { globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", [ jqXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } } return jqXHR; }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, getScript: function( url, callback ) { return jQuery.get( url, undefined, callback, "script" ); } } ); jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted if ( isFunction( data ) ) { type = type || callback; callback = data; data = undefined; } // The url can be an options object (which then must have .url) return jQuery.ajax( jQuery.extend( { url: url, type: method, dataType: type, data: data, success: callback }, jQuery.isPlainObject( url ) && url ) ); }; } ); jQuery._evalUrl = function( url, options ) { return jQuery.ajax( { url: url, // Make this explicit, since user can override this through ajaxSetup (#11264) type: "GET", dataType: "script", cache: true, async: false, global: false, // Only evaluate the response if it is successful (gh-4126) // dataFilter is not invoked for failure responses, so using it instead // of the default converter is kludgy but it works. converters: { "text script": function() {} }, dataFilter: function( response ) { jQuery.globalEval( response, options ); } } ); }; jQuery.fn.extend( { wrapAll: function( html ) { var wrap; if ( this[ 0 ] ) { if ( isFunction( html ) ) { html = html.call( this[ 0 ] ); } // The elements to wrap the target around wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); if ( this[ 0 ].parentNode ) { wrap.insertBefore( this[ 0 ] ); } wrap.map( function() { var elem = this; while ( elem.firstElementChild ) { elem = elem.firstElementChild; } return elem; } ).append( this ); } return this; }, wrapInner: function( html ) { if ( isFunction( html ) ) { return this.each( function( i ) { jQuery( this ).wrapInner( html.call( this, i ) ); } ); } return this.each( function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } } ); }, wrap: function( html ) { var htmlIsFunction = isFunction( html ); return this.each( function( i ) { jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); } ); }, unwrap: function( selector ) { this.parent( selector ).not( "body" ).each( function() { jQuery( this ).replaceWith( this.childNodes ); } ); return this; } } ); jQuery.expr.pseudos.hidden = function( elem ) { return !jQuery.expr.pseudos.visible( elem ); }; jQuery.expr.pseudos.visible = function( elem ) { return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); }; jQuery.ajaxSettings.xhr = function() { try { return new window.XMLHttpRequest(); } catch ( e ) {} }; var xhrSuccessStatus = { // File protocol always yields status code 0, assume 200 0: 200, // Support: IE <=9 only // #1450: sometimes IE returns 1223 when it should be 204 1223: 204 }, xhrSupported = jQuery.ajaxSettings.xhr(); support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); support.ajax = xhrSupported = !!xhrSupported; jQuery.ajaxTransport( function( options ) { var callback, errorCallback; // Cross domain only allowed if supported through XMLHttpRequest if ( support.cors || xhrSupported && !options.crossDomain ) { return { send: function( headers, complete ) { var i, xhr = options.xhr(); xhr.open( options.type, options.url, options.async, options.username, options.password ); // Apply custom fields if provided if ( options.xhrFields ) { for ( i in options.xhrFields ) { xhr[ i ] = options.xhrFields[ i ]; } } // Override mime type if needed if ( options.mimeType && xhr.overrideMimeType ) { xhr.overrideMimeType( options.mimeType ); } // X-Requested-With header // For cross-domain requests, seeing as conditions for a preflight are // akin to a jigsaw puzzle, we simply never set it to be sure. // (it can always be set on a per-request basis or even using ajaxSetup) // For same-domain requests, won't change header if already provided. if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { headers[ "X-Requested-With" ] = "XMLHttpRequest"; } // Set headers for ( i in headers ) { xhr.setRequestHeader( i, headers[ i ] ); } // Callback callback = function( type ) { return function() { if ( callback ) { callback = errorCallback = xhr.onload = xhr.onerror = xhr.onabort = xhr.ontimeout = xhr.onreadystatechange = null; if ( type === "abort" ) { xhr.abort(); } else if ( type === "error" ) { // Support: IE <=9 only // On a manual native abort, IE9 throws // errors on any property access that is not readyState if ( typeof xhr.status !== "number" ) { complete( 0, "error" ); } else { complete( // File: protocol always yields status 0; see #8605, #14207 xhr.status, xhr.statusText ); } } else { complete( xhrSuccessStatus[ xhr.status ] || xhr.status, xhr.statusText, // Support: IE <=9 only // IE9 has no XHR2 but throws on binary (trac-11426) // For XHR2 non-text, let the caller handle it (gh-2498) ( xhr.responseType || "text" ) !== "text" || typeof xhr.responseText !== "string" ? { binary: xhr.response } : { text: xhr.responseText }, xhr.getAllResponseHeaders() ); } } }; }; // Listen to events xhr.onload = callback(); errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); // Support: IE 9 only // Use onreadystatechange to replace onabort // to handle uncaught aborts if ( xhr.onabort !== undefined ) { xhr.onabort = errorCallback; } else { xhr.onreadystatechange = function() { // Check readyState before timeout as it changes if ( xhr.readyState === 4 ) { // Allow onerror to be called first, // but that will not handle a native abort // Also, save errorCallback to a variable // as xhr.onerror cannot be accessed window.setTimeout( function() { if ( callback ) { errorCallback(); } } ); } }; } // Create the abort callback callback = callback( "abort" ); try { // Do send the request (this may raise an exception) xhr.send( options.hasContent && options.data || null ); } catch ( e ) { // #14683: Only rethrow if this hasn't been notified as an error yet if ( callback ) { throw e; } } }, abort: function() { if ( callback ) { callback(); } } }; } } ); // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) jQuery.ajaxPrefilter( function( s ) { if ( s.crossDomain ) { s.contents.script = false; } } ); // Install script dataType jQuery.ajaxSetup( { accepts: { script: "text/javascript, application/javascript, " + "application/ecmascript, application/x-ecmascript" }, contents: { script: /\b(?:java|ecma)script\b/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } } ); // Handle cache's special case and crossDomain jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; } } ); // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { // This transport only deals with cross domain or forced-by-attrs requests if ( s.crossDomain || s.scriptAttrs ) { var script, callback; return { send: function( _, complete ) { script = jQuery( "

Block library

The block diagrams comprise blocks which belong to one of a number of different categories. These come from the package bdsim.blocks, roboticstoolbox.blocks, machinevisiontoolbox.blocks.

Icons, if shown to the left of the black header bar, are as used with bdedit.

Inheritance diagram of bdsim.components.SourceBlock, bdsim.components.SinkBlock, bdsim.graphics.GraphicsBlock, bdsim.components.FunctionBlock, bdsim.components.TransferBlock, bdsim.components.SubsystemBlock

Source blocks

Source blocks:

  • have outputs but no inputs

  • have no state variables

  • are a subclass of SourceBlockBlock

class bdsim.blocks.sources.Constant(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

CONSTANT

inputs

outputs

states

0

1

0

float, A(N,)

__init__(value=None, **blockargs)[source]

Constant value.

Parameters
Returns

a CONSTANT block

Return type

Constant instance

This block has only one output port, but the value can be any Python type, for example float, list or Numpy ndarray.

class bdsim.blocks.sources.Piecewise(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

PIECEWISE

inputs

outputs

states

0

1

0

float

__init__(*seq, **blockargs)[source]

Piecewise constant signal.

Parameters
  • seq (list of 2-tuples) – sequence of time, value pairs

  • blockargs (dict) – common Block options

Returns

a PIECEWISE block

Return type

Piecewise instance

Outputs a piecewise constant function of time. This is described as a series of 2-tuples (time, value). The output value is taken from the active tuple, that is, the latest one in the list whose time is no greater than simulation time.

Note

  • The tuples must be order by monotonically increasing time.

  • There is no default initial value, the list should contain a tuple with time zero otherwise the output will be undefined.

Note

The block declares an event for the start of each segment.

Seealso

declare_events()

class bdsim.blocks.sources.Ramp(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

RAMP

inputs

outputs

states

0

1

0

float

__init__(T=1, off=0, slope=1, **blockargs)[source]

Ramp signal.

Parameters
  • T (float, optional) – time of ramp start, defaults to 1

  • off (float, optional) – initial value, defaults to 0

  • slope (float, optional) – gradient of slope, defaults to 1

  • blockargs (dict) – common Block options

Returns

a RAMP block

Return type

Ramp

Output a ramp signal that starts increasing from the value off when time equals T linearly with time, with a gradient of slope.

Note

The block declares an event for the ramp start time.

Seealso

:method:`declare_event`

class bdsim.blocks.sources.Step(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

STEP

inputs

outputs

states

0

1

0

float

__init__(T=1, off=0, on=1, **blockargs)[source]

Step signal.

Parameters
  • T (float, optional) – time of step, defaults to 1

  • off (float, optional) – initial value, defaults to 0

  • on (float, optional) – final value, defaults to 1

  • blockargs (dict) – common Block options

Returns

a STEP block

Return type

Step

Output a step signal that transitions from the value off to on when time equals T.

Note

The block declares an event for the step time.

Seealso

declare_events()

class bdsim.blocks.sources.Time(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

TIME

inputs

outputs

states

0

1

0

float

__init__(value=None, **blockargs)[source]

Simulation time.

Parameters

blockargs (dict) – common Block options

Returns

a TIME block

Return type

Time instance

The block has only one output port which is the current simulation time.

class bdsim.blocks.sources.WaveForm(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

WAVEFORM

inputs

outputs

states

0

1

0

float

__init__(wave='square', freq=1, unit='Hz', phase=0, amplitude=1, offset=0, min=None, max=None, duty=0.5, **blockargs)[source]

Waveform as function of time.

Parameters
  • wave (str, optional) – type of waveform to generate, one of: ‘sine’, ‘square’, ‘triangle’, defaults to ‘square’

  • freq (float, optional) – frequency, defaults to 1

  • unit (str, optional) – frequency unit, one of: ‘rad/s’, ‘Hz’, defaults to ‘Hz’

  • amplitude (float, optional) – amplitude, defaults to 1

  • offset (float, optional) – signal offset, defaults to 0

  • phase (float, optional) – Initial phase of signal in the range [0,1], defaults to 0

  • min (float, optional) – minimum value, defaults to 0

  • max (float, optional) – maximum value, defaults to 1

  • duty (float, optional) – duty cycle for square wave in range [0,1], defaults to 0.5

  • blockargs (dict) – common Block options

Returns

a WAVEFORM block

Return type

WaveForm instance

Create a waveform generator block.

Examples:

WAVEFORM(wave='sine', freq=2)   # 2Hz sine wave varying from -1 to 1
WAVEFORM(wave='square', freq=2, unit='rad/s') # 2rad/s square wave varying from -1 to 1

The minimum and maximum values of the waveform are given by default in terms of amplitude and offset. The signals are symmetric about the offset value. For example:

WAVEFORM(wave='sine') varies between -1 and +1
WAVEFORM(wave='sine', amplitude=2) varies between -2 and +2
WAVEFORM(wave='sine', offset=1) varies between 0 and +2
WAVEFORM(wave='sine', amplitude=2, offset=1) varies between -1 and +3

Alternatively we can specify the minimum and maximum values which override amplitude and offset:

WAVEFORM(wave='triangle', min=0, max=5) varies between 0 and +5

At time 0 the sine and triangle wave are zero and increasing, and the square wave has its first rise. We can specify a phase shift with a number in the range [0,1] where 1 corresponds to one cycle.

Note

For discontinuous signals (square, triangle) the block declares events for every discontinuity.

:seealso declare_events()

Sink blocks

Sink blocks:

  • have inputs but no outputs

  • have no state variables

  • are a subclass of SinkBlockBlock

  • that perform graphics are a subclass of GraphicsBlockSinkBlockBlock

class bdsim.blocks.sinks.Null(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SinkBlock

NULL

inputs

outputs

states

N

0

0

any

__init__(nin=1, **blockargs)[source]

Discard signal.

Parameters
  • nin (int, optional) – number of input ports, defaults to 1

  • blockargs (dict) – common Block options

Returns

A NULL block

Return type

Null instance

Create a sink block with arbitrary number of input ports that discards all data. Useful for testing.

class bdsim.blocks.sinks.Print(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SinkBlock

PRINT

inputs

outputs

states

1

0

0

any

__init__(fmt=None, file=None, **blockargs)[source]

Print signal.

Parameters
  • fmt (str, optional) – Format string, defaults to None

  • file (file object, optional) – file to write data to, defaults to None

  • blockargs (dict) – common Block options

Returns

A PRINT block

Return type

Print instance

Creates a console print block which displays the value of a signal at each simulation time step. The display format is like:

PRINT(print.0 @ t=0.100) [-1.0 0.2]

and includes the block name, time, and the formatted value.

The numerical formatting of the signal is controlled by fmt:

  • if not provided, str() is used to format the signal

  • if provided:
    • a scalar is formatted by the fmt.format()

    • a NumPy array is formatted by fmt.format() applied to every element

Examples:

pr = bd.PRINT()     # create PRINT block
bd.connect(x, inputs=pr)   # its input comes from x

bd.PRINT(x)         # create PRINT block with input from x

bd.PRINT(x, name='X')  # block name appears in the printed text

bd.PRINT(x, fmt="{:.1f}") # print with explicit format

Note

  • By default writes to stdout

  • The output is cleaner if progress bar printing is disabled.

class bdsim.blocks.sinks.Stop(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SinkBlock

STOP

inputs

outputs

states

1

0

0

any

__init__(func=None, **blockargs)[source]

Conditional simulation stop.

Parameters
  • func (callable, optional) – evaluate stop condition, defaults to None

  • blockargs (dict) – common Block options

Returns

A STOP block

Return type

Stop instance

Conditionally stop the simulation if the input is:

  • bool type and True

  • numeric type and > 0

If func is provided, then it is applied to the block input and if it returns True the simulation is stopped.

class bdsim.blocks.sinks.Watch(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SinkBlock

WATCH

inputs

outputs

states

N

0

0

1

__init__(**blockargs)[source]

Watch a signal.

Parameters

blockargs (dict) – common Block options

Returns

A NULL block

Return type

Null instance

Causes the input signal to be logged during the simulation run. Equivalent to adding it as the watch= argument to bdsim.run.

Seealso

:method:`BDSim.run`

Function blocks

Function blocks:

  • have inputs and outputs

  • have no state variables

  • are a subclass of FunctionBlockBlock

class bdsim.blocks.functions.Clip(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

CLIP

inputs

outputs

states

1

1

0

float, A(N,)

float, A(N,)

__init__(min=- inf, max=inf, **blockargs)[source]

Signal clipping.

Parameters
  • min (float or array_like, optional) – Minimum value, defaults to -math.inf

  • max (float or array_like, optional) – Maximum value, defaults to math.inf

  • blockargs (dict) – common Block options

Returns

A CLIP block

Return type

Clip instance

The input signal is clipped to the range from minimum to maximum inclusive.

The signal can be a 1D-array in which case each element is clipped. The minimum and maximum values can be:

  • a scalar, in which case the same value applies to every element of the input vector , or

  • a 1D-array, of the same shape as the input vector that applies elementwise to the input vector.

For example:

clip = bd.CLIP()
class bdsim.blocks.functions.Function(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

FUNCTION

inputs

outputs

states

nin

nout

0

any

any

__init__(func=None, nin=1, nout=1, persistent=False, args=None, kwargs=None, **blockargs)[source]

Python function.

Parameters
  • func (callable or sequence of callables, optional) – A function or lambda, or list thereof, defaults to None

  • nin (int, optional) – number of inputs, defaults to 1

  • nout (int, optional) – number of outputs, defaults to 1

  • persistent (bool, optional) – pass in a reference to a dictionary instance to hold persistent state, defaults to False

  • args (list, optional) – extra positional arguments passed to the function, defaults to []

  • kwargs (dict, optional) – extra keyword arguments passed to the function, defaults to {}

  • blockargs (dict, optional) – common Block options

Returns

A FUNCTION block

Return type

A Function instance

Inputs to the block are passed as separate arguments to the function. Programmatic ositional or keyword arguments can also be passed to the function.

A block with one output port that sums its two input ports is:

FUNCTION(lambda u1, u2: u1+u2, nin=2)

A block with a function that takes two inputs and has two additional arguments:

def myfun(u1, u2, param1, param2):
    pass

FUNCTION(myfun, nin=2, args=(p1,p2))

If we need access to persistent (static) data, to keep some state:

def myfun(u1, u2, param1, param2, state):
    pass

FUNCTION(myfun, nin=2, args=(p1,p2), persistent=True)

where a dictionary is passed in as the last argument and which is kept from call to call.

A block with a function that takes two inputs and additional keyword arguments:

def myfun(u1, u2, param1=1, param2=2, param3=3, param4=4):
    pass

FUNCTION(myfun, nin=2, kwargs=dict(param2=7, param3=8))

A block with two inputs and two outputs, the outputs are defined by two lambda functions with the same inputs:

FUNCTION( [ lambda x, y: x_t, lambda x, y: x* y])

A block with two inputs and two outputs, the outputs are defined by a single function which returns a list:

def myfun(u1, u2):
    return [ u1+u2, u1*u2 ]

FUNCTION( myfun, nin=2, nout=2)

For example:

func = bd.FUNCTION(myfun, args)

If inputs are specified then connections are automatically made and are assigned to sequential input ports:

func = bd.FUNCTION(myfun, block1, block2, args)

is equivalent to:

func = bd.FUNCTION(myfun, args)
bd.connect(block1, func[0])
bd.connect(block2, func[1])
class bdsim.blocks.functions.Gain(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

GAIN

inputs

outputs

states

1

1

0

float, A(N,), A(N,M)

float, A(N,), A(N,M)

__init__(K=1, premul=False, **blockargs)[source]

Gain block.

Parameters
  • K (array_like) – The gain value, defaults to 1

  • premul (bool, optional) – premultiply by constant, default is postmultiply, defaults to False

  • blockargs (dict) – common Block options

Returns

A GAIN block

Return type

Gain instance

Scale the input signal. If the input is \(u\) the output is \(u K\).

Either or both the input and gain can be Numpy arrays and Numpy will compute the appropriate product \(u K\).

If \(u\) and K are both NumPy arrays the @ operator is used and \(u\) is postmultiplied by the gain. To premultiply by the gain, to compute \(K u\) use the premul option.

For example:

gain = bd.GAIN(constant)
class bdsim.blocks.functions.Interpolate(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

INTERPOLATE

inputs

outputs

states

0 or 1

1

0

float

any

__init__(x=None, y=None, xy=None, time=False, kind='linear', **blockargs)[source]

Interpolate signal.

Parameters
  • x (array_like, shape (N,) optional) – x-values of function, defaults to None

  • y (array_like, optional) – y-values of function, defaults to None

  • xy (array_like, optional) – combined x- and y-values of function, defaults to None

  • time (bool, optional) – x new is simulation time, defaults to False

  • kind (str, optional) – interpolation method, defaults to ‘linear’

  • blockargs (dict) – common Block options

Returns

An INTERPOLATE block

Return type

An Interpolate instance

Interpolate the input signal using to a piecewise function.

A simple triangle function with domain [0,10] and range [0,1] can be defined by:

INTERPOLATE(x=(0,5,10), y=(0,1,0))

We might also express this as a list of 2D-coordinats:

INTERPOLATE(xy=[(0,0), (5,1), (10,0)])

The data can also be expressed as Numpy arrays. If that is the case, the interpolation function can be vector valued. x has a shape of (N,1) and y has a shape of (N,M). Alternatively xy has a shape of (N,M+1) and the first column is the x-data.

The input to the interpolator comes from:

  • Input port 0

  • Simulation time, if time=True. In this case the block has no input ports and is a Source not a Function.

class bdsim.blocks.functions.Prod(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

PROD

inputs

outputs

states

len(ops)

1

0

float, A(N,), A(N,M)

float, A(N,), A(N,M)

__init__(ops='**', matrix=False, **blockargs)[source]

Product junction.

Parameters
  • ops (str, optional) – operations associated with input ports, accepted characters: * or /, defaults to ‘**’

  • inputs (Block or Plug) – Optional incoming connections

  • matrix (bool, optional) – Arguments are matrices, defaults to False

  • blockargs (dict) – common Block options

Returns

A PROD block

Return type

Prod instance

Multiply or divide input signals according to the ops string. The number of input ports is the length of this string.

For example:

prod = PROD('*/*')

is a 3-input product junction which computes port0 / port 1 * port2.

Implicit PROD blocks are created by:

sum = block1  block2

which will create a summation block named “_prod.N”.

Note

  • The inputs can be scalars or NumPy arrays.

  • By default the * and / operators are used.

  • The option matrix will instead use @ and @ np.linalg.inv(). - the shapes of matrices must conform. - only square matrices are supported.

class bdsim.blocks.functions.Sum(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

SUM

inputs

outputs

states

len(signs)

1

0

float, A(N,), A(N,M)

float, A(N,), A(N,M)

__init__(signs='++', angles=False, **blockargs)[source]

Summing junction.

Parameters
Returns

A SUM block

Return type

Sum instance

Add or subtract input signals according to the signs string. The number of input ports is the length of this string.

For example:

sum = bd.SUM('+-+')

is a 3-input summing junction which computes port0 - port1 + port2.

Implicit SUM blocks are created by:

sum = block1 + block2

which will create a summation block named “_sum.N”.

Note

The signals must be compatible, all scalars, or all arrays of the same shape.

Transfer blocks

Transfer blocks:

  • have inputs and outputs

  • have state variables

  • are a subclass of TransferBlockBlock

class bdsim.blocks.transfers.Integrator(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

INTEGRATOR

inputs

outputs

states

1

1

N

float, A(N,)

float, A(N,)

__init__(x0=0, min=None, max=None, **blockargs)[source]

Integrator.

Parameters
  • x0 (array_like, optional) – Initial state, defaults to 0

  • min (float or array_like, optional) – Minimum value of state, defaults to None

  • max (float or array_like, optional) – Maximum value of state, defaults to None

  • blockargs (dict) – common Block options

Returns

an INTEGRATOR block

Return type

Integrator instance

Output is the time integral of the input. The state can be a scalar or a vector. The initial state, and type, is given by x0. The shape of the input signal must match x0.

The minimum and maximum values can be:

  • a scalar, in which case the same value applies to every element of the state vector, or

  • a vector, of the same shape as x0 that applies elementwise to the state.

class bdsim.blocks.transfers.LTI_SISO(*args, bd=None, **kwargs)[source]

Bases: bdsim.blocks.transfers.LTI_SS

LTI_SISO

inputs

outputs

states

1

1

n

float

float

__init__(N=1, D=[1, 1], x0=None, **blockargs)[source]

SISO LTI dynamics.

Parameters
  • N (array_like, optional) – numerator coefficients, defaults to 1

  • D (array_like, optional) – denominator coefficients, defaults to [1,1]

  • x0 (array_like, optional) – initial states, defaults to None

  • blockargs (dict) – common Block options

Returns

A SCOPE block

Return type

LTI_SISO instance

Implements the dynamics of a single-input single-output (SISO) linear time invariant (LTI) system described by numerator and denominator polynomial coefficients.

Coefficients are given in the order from highest order to zeroth order, ie. \(2s^2 - 4s +3\) is [2, -4, 3].

Only proper transfer functions, where order of numerator is less than denominator are allowed.

The order of the states in x0 is consistent with controller canonical form.

Examples:

LTI_SISO(N=[1, 2], D=[2, 3, -4])

is the transfer function \(\frac{s+2}{2s^2+3s-4}\).

class bdsim.blocks.transfers.LTI_SS(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

LTI_SS

inputs

outputs

states

1

1

nc

float, A(nb,)

float, A(nc,)

__init__(A=None, B=None, C=None, x0=None, **blockargs)[source]

State-space LTI dynamics.

Parameters
  • N (array_like, optional) – numerator coefficients, defaults to 1

  • D (array_like, optional) – denominator coefficients, defaults to [1,1]

  • x0 (array_like, optional) – initial states, defaults to None

  • blockargs (dict) – common Block options

Returns

A SCOPE block

Return type

LTI_SISO instance

Implements the dynamics of a single-input single-output (SISO) linear time invariant (LTI) system described by numerator and denominator polynomial coefficients.

Coefficients are given in the order from highest order to zeroth order, ie. \(2s^2 - 4s +3\) is [2, -4, 3].

Only proper transfer functions, where order of numerator is less than denominator are allowed.

The order of the states in x0 is consistent with controller canonical form.

Examples:

LTI_SISO(N=[1,2], D=[2, 3, -4])

is the transfer function \(\frac{s+2}{2s^2+3s-4}\).

class bdsim.blocks.transfers.PoseIntegrator(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

POSEINTEGRATOR

inputs

outputs

states

1

1

N

A(N,)

A(N,)

__init__(x0=None, **blockargs)[source]

Pose integrator

Parameters
Returns

an INTEGRATOR block

Return type

Integrator instance

This block integrates spatial velocity over time. The block input is a spatial velocity as a 6-vector \((v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)\) and the output is pose as an SE3 instance.

Note

State is a velocity twist.

Warning

NOT WORKING YET

Discrete-time blocks

Transfer blocks:

  • have inputs and outputs

  • have state variables

  • are a subclass of TransferBlockBlock

class bdsim.blocks.discrete.DIntegrator(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.ClockedBlock

DINTEGRATOR

inputs

outputs

states

1

1

N

float, A(N,)

float, A(N,)

__init__(clock, x0=0, gain=1.0, min=None, max=None, **blockargs)[source]

Discrete-time integrator.

Parameters
  • clock (Clock) – clock source

  • x0 (array_like, optional) – Initial state, defaults to 0

  • min (float or array_like, optional) – Minimum value of state, defaults to None

  • max (float or array_like, optional) – Maximum value of state, defaults to None

  • blockargs (dict) – common Block options

Returns

an INTEGRATOR block

Return type

Integrator instance

Create a discrete-time integrator block.

Output is the time integral of the input. The state can be a scalar or a vector, this is given by the type of x0.

The minimum and maximum values can be:

  • a scalar, in which case the same value applies to every element of the state vector, or

  • a vector, of the same shape as x0 that applies elementwise to the state.

next()[source]
class bdsim.blocks.discrete.DPoseIntegrator(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.ClockedBlock

DPOSEINTEGRATOR

inputs

outputs

states

1

1

N

A(6,)

SE3

__init__(clock, x0=None, **blockargs)[source]

Discrete-time spatial velocity integrator.

Parameters
  • clock (Clock) – clock source

  • x0 (SE3, optional) – Initial pose, defaults to null

  • blockargs (dict) – common Block options

Returns

an DPOSEINTEGRATOR block

Return type

Integrator instance

This block integrates spatial velocity over time. The block input is a spatial velocity as a 6-vector \((v_x, v_y, v_z, \omega_x, \omega_y, \omega_z)\) and the output is pose as an SE3 instance.

Note

State is a velocity twist.

next()[source]
class bdsim.blocks.discrete.ZOH(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.ClockedBlock

ZOH

inputs

outputs

states

1

1

N

float, A(N,)

float, A(N,)

__init__(clock, x0=0, **blockargs)[source]

Zero-order hold.

Parameters
  • clock (Clock) – clock source

  • x0 (array_like, optional) – Initial value of the hold, defaults to 0

  • blockargs (dict) – common Block options

Returns

a ZOH block

Return type

Integrator instance

Output is the input at the previous clock time. The state can be a scalar or a vector, this is given by the type of x0.

Note

If input is not a scalar, x0 must have the shape of the input signal.

next()[source]

Linear algebra blocks

Linear algebra blocks:

  • have inputs and outputs

  • have no state variables

  • are a subclass of FunctionBlockBlock

class bdsim.blocks.linalg.Cond(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

COND

inputs

outputs

states

1

1

0

A(N,M)

float

__init__(**blockargs)[source]

Compute the matrix condition number.

Parameters

blockargs (dict) – common Block options

Returns

A COND block

Return type

Cond instance

Seealso

numpy.linalg.cond()

class bdsim.blocks.linalg.Det(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

DET

inputs

outputs

states

1

1

0

A(N,N)

float

__init__(**blockargs)[source]

Matrix determinant

Parameters

blockargs (dict) – common Block options

Returns

A DET block

Return type

Det instance

Compute the matrix determinant.

Seealso

numpy.linalg.inv()

class bdsim.blocks.linalg.Flatten(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

FLATTEN

inputs

outputs

states

1

1

0

A(N,M )

A(NM,)

__init__(order='C', **blockargs)[source]

Flatten a multi-dimensional array.

Parameters
  • order (str) – flattening order, either “C” or “F”, defaults to “C”

  • blockargs (dict) – common Block options

Returns

A FLATTEN block

Return type

Flatten instance

Flattens the incoming array in either row major (‘C’) or column major (‘F’) order.

Seealso

numpy.flatten()

class bdsim.blocks.linalg.Inverse(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

INVERSE

inputs

outputs

states

1

2

0

A(M,N)

A(N,M) float

__init__(pinv=False, **blockargs)[source]

Matrix inverse.

Parameters
  • pinv (bool, optional) – force pseudo inverse, defaults to False

  • blockargs (dict) – common Block options

Returns

An INVERSE block

Return type

Inverse instance

Compute inverse of the 2D-array input signal. If the matrix is square the inverse is computed unless the pinv flag is True. For a non-square matrix the pseudo-inverse is used. The condition number is output on the second port.

Seealso

numpy.linalg.inv() numpy.linalg.pinv() numpy.linalg.cond()

onames = ('inv', 'cond')
class bdsim.blocks.linalg.Norm(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

NORM

inputs

outputs

states

1

1

0

A(N,) A(N,M)

float

__init__(ord=None, axis=None, **blockargs)[source]

Array norm.

Parameters
  • axis (int, optional) – specifies the axis along which to compute the vector norms, defaults to None.

  • ord (int or str) – Order of the norm, default to None.

  • blockargs (dict) – common Block options

Returns

A NORM block

Return type

Norm instance

Computes the specified norm for a 1D- or 2D-array.

Seealso

numpy.linalg.norm()

class bdsim.blocks.linalg.Slice1(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

SLICE1

inputs

outputs

states

1

1

0

A(N)

A(M)

__init__(index, **blockargs)[source]

Slice out subarray of 1D-array.

Parameters
Returns

A SLICE1 block

Return type

Slice1 instance

Compute a 1D slice of input 1D array.

If index is None it means all elements.

If index is a list, perform NumPy fancy indexing, returning the specified elements

Example:

SLICE1(index=[2,3]) # return elements 2 and 3 as a 1D array
SLICE1(index=[2])   # return element 2 as a 1D array
SLICE1(index=2)     # return element 2 as a NumPy scalar

If index is a tuple, it must have three elements. It describes a Python slice (start, stop, step) where any element can be None

  • start=None means start at first element

  • stop=None means finish at last element

  • step=None means step by one

rows=None is equivalent to rows=(None, None, None).

Example:

SLICE1(index=(None,None,2))  # return every second element
SLICE1(index=(None,None,-1)) # reverse the elements
Seealso

Slice1

class bdsim.blocks.linalg.Slice2(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

SLICE2

inputs

outputs

states

1

1

0

A(N,M)

A(K,L)

__init__(rows=None, cols=None, **blockargs)[source]

Slice out subarray of 2D-array.

Parameters
  • rows (tuple(3) or list) – row selection, defaults to None

  • cols (tuple(3) or list) – column selection, defaults to None

  • blockargs (dict) – common Block options

Returns

A SLICE2 block

Return type

Slice2 instance

Compute a 2D slice of input 2D array.

If rows or cols is None it means all rows or columns respectively.

If rows or cols is a list, perform NumPy fancy indexing, returning the specified rows or columns

Example:

SLICE2(rows=[2,3])  # return rows 2 and 3, all columns
SLICE2(cols=[4,1])  # return columns 4 and 1, all rows
SLICE2(rows=[2,3], cols=[4,1]) # return elements [2,4] and [3,1] as a 1D array

If a single row or column is selected, the result will be a 1D array

If rows or cols is a tuple, it must have three elements. It describes a Python slice (start, stop, step) where any element can be None

  • start=None means start at first element

  • stop=None means finish at last element

  • step=None means step by one

rows=None is equivalent to rows=(None, None, None).

Example:

SLICE2(rows=(None,None,2))  # return every second row
SLICE2(cols=(None,None,-1)) # reverse the columns

The list and tuple notation can be mixed, for example, one for rows and one for columns.

Seealso

Slice1 Index

class bdsim.blocks.linalg.Transpose(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

TRANSPOSE

inputs

outputs

states

1

1

0

A(M,N)

A(N,M)

__init__(**blockargs)[source]

Matrix transpose.

Parameters

blockargs (dict) – common Block options

Returns

A TRANSPOSE block

Return type

Transpose instance

Compute transpose of the 2D-array input signal.

Note

  • An input 1D-array of shape (N,) is turned into a 2D-array column vector with shape (N,1).

  • An input 2D-array column vector of shape (N,1) becomes a 2D-array

row vector with shape (1,N).

Seealso

numpy.transpose()

Connection blocks

Connection blocks are in two categories:

  1. Signal manipulation:
    • have inputs and outputs

    • have no state variables

    • are a subclass of FunctionBlockBlock

  2. Subsystem support
    • have inputs or outputs

    • have no state variables

    • are a subclass of SubsysytemBlockBlock

class bdsim.blocks.connections.DeMux(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

DEMUX

inputs

outputs

states

1

nout

0

float, A(nout,)

float

__init__(nout=1, **kwargs)[source]

Demultiplex signals.

Parameters
Returns

A DEMUX block

Return type

DeMux instance

This block has a single input port and nout output ports. A 1D-array input signal (with nout elements) is routed element-wise to individual scalar output ports.

class bdsim.blocks.connections.Dict(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

DICT

inputs

outputs

states

N

1

0

any

dict

__init__(item, **kwargs)[source]

Create a dictionary signal.

Parameters
Returns

A DICT block

Return type

Dict instance

Inputs are assigned to a dictionary signal, using the corresponding names from keys. For example:

DICT(['x', 'xd', 'xdd'])

expects three inputs and assigns them to dictionary items x, xd, xdd of the output dictionary respectively.

A dictionary signal can serve a similar purpose to a “bus” in Simulink(R).

This is somewhat like a multiplexer Mux but allows for named heterogeneous data.

Seealso

Item Mux

class bdsim.blocks.connections.InPort(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SubsystemBlock

INPORT

inputs

outputs

states

0

nout

0

any

__init__(nout=1, **kwargs)[source]

Input ports for a subsystem.

Parameters
  • nout (int, optional) – Number of output ports, defaults to 1

  • kwargs (dict) – common Block options

Returns

An INPORT block

Return type

InPort instance

This block connects a subsystem to a parent block diagram. Inputs to the parent-level SubSystem block appear as the outputs of this block.

Note

Only one INPORT block can appear in a block diagram but it can have multiple ports. This is different to Simulink(R) which would require multiple single-port input blocks.

class bdsim.blocks.connections.Index(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

INDEX

inputs

outputs

states

1

1

0

ndarray

ndarray

__init__(index=[], **kwargs)[source]

Index an iterable signal.

Parameters
  • index (list, slice or str, optional) – elements of input array, defaults to []

  • kwargs (dict) – common Block options

Returns

An INDEX block

Return type

Index instance

The specified element(s) of the input iterable (list, string, etc.) are output. The index can be an integer, sequence of integers, a Python slice object, or a string with Python slice notation, eg. "::-1".

Seealso

Slice1 Slice2

class bdsim.blocks.connections.Item(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

ITEM

inputs

outputs

states

1

1

0

dict

any

__init__(item, **kwargs)[source]

Selector item from a dictionary signal.

Parameters
Returns

An ITEM block

Return type

Item instance

For a dictionary type input signal, select one item as the output signal. For example:

ITEM('xd')

selects the xd item from the dictionary signal input to the block.

A dictionary signal can serve a similar purpose to a “bus” in Simulink(R).

This is somewhat like a demultiplexer DeMux but allows for named heterogeneous data.

Seealso

Dict

class bdsim.blocks.connections.Mux(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

MUX

inputs

outputs

states

nin

1

0

float, A(N,)

A(M,) A(M,)

__init__(nin=1, **kwargs)[source]

Multiplex signals.

Parameters
  • nin (int, optional) – Number of input ports, defaults to 1

  • kwargs (dict) – common Block options

Returns

A MUX block

Return type

Mux instance

This block takes a number of scalar or 1D-array signals and concatenates them into a single 1-D array signal. For example:

MUX(2, inputs=(func1[2], sum3))

multiplexes the outputs of blocks func1 (port 2) and sum3 into a single output vector as a 1D-array.

Seealso

Dict

class bdsim.blocks.connections.OutPort(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SubsystemBlock

OUTPORT

inputs

outputs

states

nin

0

0

any

__init__(nin=1, **kwargs)[source]

Output ports for a subsystem.

Parameters
  • nin (int, optional) – Number of input ports, defaults to 1

  • kwargs (dict) – common Block options

Returns

A OUTPORT block

Return type

OutPort instance

This block connects a subsystem to a parent block diagram. The the inputs of this block become the outputs of the parent-level SubSystem block.

Note

Only one OUTPORT block can appear in a block diagram but it can have multiple ports. This is different to Simulink(R) which would require multiple single-port output blocks.

class bdsim.blocks.connections.SubSystem(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SubsystemBlock

SUBSYSTEM

inputs

outputs

states

ss.in.nout

ss.out.nin

0

any

any

__init__(subsys, nin=1, nout=1, **kwargs)[source]

Instantiate a subsystem.

Parameters
  • subsys (str or BlockDiagram) – Subsystem as either a filename or a BlockDiagram instance

  • nin (int, optional) – Number of input ports, defaults to 1

  • nout (int, optional) – Number of output ports, defaults to 1

  • kwargs (dict) – common Block options

Raises
  • ImportError – DESCRIPTION

  • ValueError – DESCRIPTION

Returns

A SUBSYSTEM block

Return type

SubSystem instance

This block represents a subsystem in a block diagram. The definition of the subsystem can be:

  • the name of a module which is imported and must contain only only BlockDiagram instance, or

  • a BlockDiagram instance

The referenced block diagram must contain one or both of:

  • one InPort block, which has outputs but no inputs. These outputs are connected to the inputs to the enclosing SubSystem block.

  • one OutPort block, which has inputs but no outputs. These inputs are connected to the outputs to the enclosing SubSystem block.

  • The referenced block diagram is treated like a macro and copied into the parent block diagram at compile time. The SubSystem, InPort and OutPort blocks are eliminated, that is, all hierarchical structure is lost.

  • The same subsystem can be used multiple times, its blocks and wires

    will be cloned. Subsystems can also include subsystems.

  • The number of input and output ports is not specified, they are computed from the number of ports on the InPort and OutPort blocks within the subsystem.

External Toolbox blocksets

These blocks are defined within external Toolboxes or packages.

Robot blocks

These blocks are defined within the Robotics Toolbox for Python.

Arm robots

class roboticstoolbox.blocks.arm.ArmPlot(*args, bd=None, **kwargs)[source]

Bases: bdsim.graphics.GraphicsBlock

ARMPLOT

inputs

outputs

states

1

0

0

ndarray

__init__(robot=None, *inputs, q0=None, backend=None, **blockargs)[source]
Parameters
  • *inputs (Block or Plug) – Optional incoming connections

  • robot (Robot subclass) – Robot model

  • backend (str, optional) – RTB backend name, defaults to ‘pyplot’

  • blockargs (dict) – common Block options

Returns

An ARMPLOT block

Return type

ArmPlot instance

Create a robot animation.

Notes:

  • Uses RTB plot method

Example of vehicle display (animated). The label at the top is the block name.

class roboticstoolbox.blocks.arm.CTraj(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

CTRAJ

inputs

outputs

states

0

1

0

float

__init__(T1, T2, T, lspb=True, **blockargs)[source]

[summary]

Parameters
  • T1 (SE3) – initial pose

  • T2 (SE3) – final pose

  • T (float) – motion time

  • lspb (bool) – Use LSPB motion profile along the path

  • blockargs (dict) – common Block options

Returns

CTRAJ block

Return type

CTraj instance

Create a Cartesian motion block.

The block outputs a pose that varies smoothly from T1 to T2 over the course of T seconds.

If T is not given it defaults to the simulation time.

If lspb is True then an LSPB motion profile is used along the path to provide initial acceleration and final deceleration. Otherwise, motion is at constant velocity.

Seealso

:method:`SE3.interp`

class roboticstoolbox.blocks.arm.CirclePath(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

CIRCLEPATH

inputs

outputs

states

0 or 1

1

0

float

float

__init__(radius=1, centre=0, 0, 0, pose=None, frequency=1, unit='rps', phase=0, **blockargs)[source]
Parameters
  • radius (float) – radius of circle, defaults to 1

  • centre (array_like(3)) – center of circle, defaults to [0,0,0]

  • pose (SE3) – SE3 pose of output, defaults to None

  • frequency (float) – rotational frequency, defaults to 1

  • unit (str) – unit for frequency, one of: ‘rps’ [default], ‘rad’

  • phase (float) – phase

  • blockargs (dict) – common Block options

Returns

TRAJ block

Return type

Traj instance

Create a circular motion block.

The block outputs the coordinates of a point moving in a circle of radius r centred at centre and parallel to the xy-plane.

By default the output is a 3-vector \((x, y, z)\) but if pose is an SE3 instance the output is a copy of that pose with its translation set to the coordinate of the moving point. This is the motion of a frame with fixed orientation following a circular path.

class roboticstoolbox.blocks.arm.Delta2Tr(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

DELTA2TR

inputs

outputs

states

1

1

0

ndarray(6)

SE3

__init__(**blockargs)[source]
Parameters

blockargs (dict) – common Block options

Returns

a DELTA2TR block

Return type

Delta2Tr instance

Delta to SE(3)

The block has one input port:

  1. delta as an ndarray(6,n)

and one output port:

  1. T as an SE3

Seealso

spatialmath.base.delta2tr()

class roboticstoolbox.blocks.arm.FDyn(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

FDYN

inputs

outputs

states

1

3

0

ndarray

ndarray, ndarray, ndarray

__init__(robot, q0=None, **blockargs)[source]
Parameters
  • robot (Robot subclass) – Robot model

  • q0 (array_like(n)) – Initial joint configuration

  • blockargs (dict) – common Block options

Returns

a FORWARD_DYNAMICS block

Return type

Foward_Dynamics instance

Robot arm forward dynamics model.

The block has one input port:

  1. Joint force/torque as an ndarray.

and three output ports:

  1. joint configuration

  2. joint velocity

  3. joint acceleration

class roboticstoolbox.blocks.arm.FDyn_X(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

FDYN_X

inputs

outputs

states

1

3

0

ndarray

ndarray, ndarray, ndarray

__init__(robot, q0=None, gravcomp=False, velcomp=False, representation='rpy/xyz', **blockargs)[source]
Parameters
  • robot (Robot subclass) – Robot model

  • end (Link or str) – Link to compute pose of, defaults to end-effector

  • blockargs (dict) – common Block options

Returns

a FDYN_X block

Return type

FDyn_X instance

Robot arm forward dynamics model.

The block has one input port:

  1. Applied end-effector wrench as an ndarray.

and three output ports:

  1. task space pose

  2. task space velocity

  3. task space acceleration

class roboticstoolbox.blocks.arm.FKine(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

FKINE

inputs

outputs

states

1

1

0

ndarray

SE3

__init__(robot=None, args={}, **blockargs)[source]
Parameters
  • *inputs (Block or Plug) – Optional incoming connections

  • robot (Robot subclass, optional) – Robot model, defaults to None

  • args (dict, optional) – Options for fkine, defaults to {}

  • blockargs (dict) – common Block options

Returns

a FORWARD_KINEMATICS block

Return type

Foward_Kinematics instance

Robot arm forward kinematic model.

Block ports

input q

Joint configuration vector as an ndarray.

output T

End-effector pose as an SE(3) object

class roboticstoolbox.blocks.arm.Gravload(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

GRAVLOAD

inputs

outputs

states

1

1

0

ndarray

ndarray

__init__(robot, gravity=None, **blockargs)[source]
Parameters
  • robot (Robot subclass) – Robot model

  • gravity (float) – gravitational acceleration

  • blockargs (dict) – common Block options

Returns

a GRAVLOAD block

Return type

Gravload instance

Robot arm gravity torque.

The block has one input port:

  1. Joint configuration vector as an ndarray.

and one output port:

  1. joint torque/force due to gravity

class roboticstoolbox.blocks.arm.Gravload_X(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

GRAVLOAD_X

inputs

outputs

states

1

1

0

ndarray

ndarray

__init__(robot, gravity=None, **blockargs)[source]
Parameters
  • robot (Robot subclass) – Robot model

  • gravity (float) – gravitational acceleration

  • blockargs (dict) – common Block options

Returns

a GRAVLOAD block

Return type

Gravload instance

Robot arm gravity torque.

The block has one input port:

  1. Joint configuration vector as an ndarray.

and one output port:

  1. joint torque/force due to gravity

class roboticstoolbox.blocks.arm.IDyn(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

IDYN

inputs

outputs

states

3

1

0

ndarray, ndarray, ndarray

ndarray

__init__(robot, gravity=None, **blockargs)[source]
Parameters
  • robot (Robot subclass) – Robot model

  • gravity (float) – gravitational acceleration

  • blockargs (dict) – common Block options

Returns

an INVERSE_DYNAMICS block

Return type

Inverse_Dynamics instance

Robot arm forward dynamics model.

The block has three input port:

  1. Joint configuration vector as an ndarray.

  2. Joint velocity vector as an ndarray.

  3. Joint acceleration vector as an ndarray.

and one output port:

  1. joint torque/force

class roboticstoolbox.blocks.arm.IKine(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

IKINE

inputs

outputs

states

1

1

0

SE3

ndarray

__init__(robot=None, q0=None, useprevious=True, ik=None, **blockargs)[source]
Parameters
  • robot (Robot subclass, optional) – Robot model, defaults to None

  • q0 (array_like(n), optional) – Initial joint angles, defaults to None

  • useprevious (bool, optional) – Use previous IK solution as q0, defaults to True

  • ik (callable f(T)) – Specify an IK function, defaults to ‘ikine_LM’

  • blockargs (dict) – common Block options

Returns

an INVERSE_KINEMATICS block

Return type

Inverse_Kinematics instance

Robot arm inverse kinematic model.

The block has one input port:

  1. End-effector pose as an SE(3) object

and one output port:

  1. Joint configuration vector as an ndarray.

class roboticstoolbox.blocks.arm.Inertia(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

INERTIA

inputs

outputs

states

1

1

0

ndarray

ndarray

__init__(robot, gravity=None, **blockargs)[source]
Parameters
Returns

an INERTIA block

Return type

Inertia instance

Robot arm inertia matrix.

The block has one input port:

  1. Joint configuration vector as an ndarray.

and one output port:

  1. Joint-space inertia matrix \(\mat{M}(q)\)

class roboticstoolbox.blocks.arm.Inertia_X(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

INERTIA_X

inputs

outputs

states

1

1

0

ndarray

ndarray

__init__(robot, representation=None, pinv=False, **blockargs)[source]
Parameters
Returns

an INERTIA_X block

Return type

Inertia_X instance

Robot arm task-space inertia matrix.

The block has one input port:

  1. Joint configuration vector as an ndarray.

and one output port:

  1. Task-space inertia matrix \(\mat{M}_x(q)\)

class roboticstoolbox.blocks.arm.JTraj(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

JTRAJ

inputs

outputs

states

0

3

0

ndarray(n)

__init__(q0, qf, qd0=None, qdf=None, T=None, **blockargs)[source]

Compute a joint-space trajectory

Parameters
  • q0 (array_like(n)) – initial joint coordinate

  • qf (array_like(n)) – final joint coordinate

  • T (array_like or int, optional) – time vector or number of steps, defaults to None

  • qd0 (array_like(n), optional) – initial velocity, defaults to None

  • qdf (array_like(n), optional) – final velocity, defaults to None

  • blockargs (dict) – common Block options

Returns

TRAJ block

Return type

Traj instance

  • tg = jtraj(q0, qf, N) is a joint space trajectory where the joint

coordinates vary from q0 (M) to qf (M). A quintic (5th order) polynomial is used with default zero boundary conditions for velocity and acceleration. Time is assumed to vary from 0 to 1 in N steps.

  • tg = jtraj(q0, qf, t) as above but t is a uniformly-spaced time

vector

The return value is an object that contains position, velocity and acceleration data.

Notes:

  • The time vector, if given, scales the velocity and acceleration outputs

assuming that the time vector starts at zero and increases linearly.

Seealso

ctraj(), qplot(), jtraj()

class roboticstoolbox.blocks.arm.Jacobian(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

JACOBIAN

inputs

outputs

states

1

1

0

ndarray

ndarray

__init__(robot, frame='0', inverse=False, pinv=False, transpose=False, **blockargs)[source]
Parameters
  • robot (Robot subclass) – Robot model

  • frame (str, optional) – Frame to compute Jacobian for, one of: ‘0’ [default], ‘e’

  • inverse (bool, optional) – output inverse of Jacobian, defaults to False

  • pinv (bool, optional) – output pseudo-inverse of Jacobian, defaults to False

  • transpose (bool, optional) – output transpose of Jacobian, defaults to False

  • blockargs (dict) – common Block options

Returns

a JACOBIAN block

Return type

Jacobian instance

Robot arm Jacobian.

The block has one input port:

  1. Joint configuration vector as an ndarray.

and one output port:

  1. Jacobian matrix as an ndarray(6,n)

class roboticstoolbox.blocks.arm.LSPB(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SourceBlock

LSPB

inputs

outputs

states

0

3

0

float

__init__(q0, qf, V=None, T=None, **blockargs)[source]

Compute a joint-space trajectory

Parameters
  • q0 (array_like(n)) – initial joint coordinate

  • qf (array_like(n)) – final joint coordinate

  • T (array_like or int, optional) – time vector or number of steps, defaults to None

  • blockargs (dict) – common Block options

Returns

LSPB block

Return type

LSPB instance

  • tg = jtraj(q0, qf, N) is a joint space trajectory where the joint

coordinates vary from q0 (M) to qf (M). A quintic (5th order) polynomial is used with default zero boundary conditions for velocity and acceleration. Time is assumed to vary from 0 to 1 in N steps.

  • tg = jtraj(q0, qf, t) as above but t is a uniformly-spaced time

vector

The return value is an object that contains position, velocity and acceleration data.

Notes:

  • The time vector, if given, scales the velocity and acceleration outputs

assuming that the time vector starts at zero and increases linearly.

Seealso

ctraj(), qplot(), jtraj()

class roboticstoolbox.blocks.arm.Point2Tr(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

POINT2TR

inputs

outputs

states

1

1

0

ndarray(3)

SE3

__init__(T, **blockargs)[source]
Parameters
Returns

a POINT2TR block

Return type

Point2Tr instance

The block has one input port:

  1. a 3D point as an ndarray(3)

and one output port:

  1. T as an SE3 with its position part replaced by the input

Seealso

spatialmath.base.delta2tr()

class roboticstoolbox.blocks.arm.TR2T(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

TR2T

inputs

outputs

states

1

3

0

SE3

float

__init__(**blockargs)[source]
Parameters
Returns

a POINT2TR block

Return type

Point2Tr instance

The block has one input port:

  1. a 3D point as an ndarray(3)

and one output port:

  1. T as an SE3 with its position part replaced by the input

Seealso

spatialmath.base.delta2tr()

class roboticstoolbox.blocks.arm.Tr2Delta(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

TR2DELTA

inputs

outputs

states

2

1

0

SE3, SE3

ndarray(6)

__init__(**blockargs)[source]
Parameters

blockargs (dict) – common Block options

Returns

a TR2DELTA block

Return type

Tr2Delta instance

Difference between T1 and T2 as a 6-vector

The block has two input port:

  1. T1 as an SE3.

  2. T2 as an SE3.

and one output port:

  1. delta as an ndarray(6,n)

Seealso

spatialmath.base.tr2delta()

class roboticstoolbox.blocks.arm.Traj(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

TRAJ

inputs

outputs

states

0 or 1

1

0

float

float

__init__(y0=0, yf=1, T=None, time=False, traj='lspb', **blockargs)[source]
Parameters
  • y0 (array_like(m), optional) – initial value, defaults to 0

  • yf (array_like(m), optional) – final value, defaults to 1

  • T (array_like or int, optional) – time vector or number of steps, defaults to None

  • time (bool, optional) – x is simulation time, defaults to False

  • traj (str, optional) – trajectory type, one of: ‘lspb’ [default], ‘tpoly’

  • blockargs (dict) – common Block options

Returns

TRAJ block

Return type

Traj instance

Create a trajectory block.

A block that generates a trajectory using a trapezoidal or quintic polynomial profile.

Mobile robots

class roboticstoolbox.blocks.mobile.Bicycle(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

BICYCLE

inputs

outputs

states

2

1

3

float

ndarray(3)

__init__(L=1, speed_max=inf, accel_max=inf, steer_max=1.413716694115407, x0=None, **blockargs)[source]

Create a vehicle model with Bicycle kinematics.

Parameters
  • L (float, optional) – Wheelbase, defaults to 1

  • speed_max (float, optional) – Velocity limit, defaults to 1

  • accel_max (float, optional) – maximum acceleration, defaults to math.inf

  • steer_max (float, optional) – maximum steering angle, defaults to math.pi*0.45

  • x0 (array_like, optional) – Inital state, defaults to None

  • blockargs (dict) – common Block options

Returns

a BICYCLE block

Return type

Bicycle instance

Bicycle kinematic model with state \([x, y, \theta]\).

Block ports

input v

Vehicle speed (metres/sec). The velocity limit vlim is applied to the magnitude of this input.

input γ

Steering wheel angle (radians). The steering limit slim is applied to the magnitude of this input.

output q

configuration (x, y, θ)

Seealso

mobile.Bicycle DiffSteer

class roboticstoolbox.blocks.mobile.DiffSteer(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

DIFFSTEER

inputs

outputs

states

2

3

3

float

float

__init__(w=1, R=1, speed_max=inf, accel_max=inf, steer_max=None, a=0, x0=None, **blockargs)[source]

Create a differential steer vehicle model

Parameters
  • w (float, optional) – vehicle width, defaults to 1

  • R (float, optional) – Wheel radius, defaults to 1

  • speed_max (float, optional) – Velocity limit, defaults to 1

  • accel_max (float, optional) – maximum acceleration, defaults to math.inf

  • steer_max (float, optional) – maximum steering rate, defaults to 1

  • x0 (array_like, optional) – Inital state, defaults to None

  • blockargs (dict :return: a DIFFSTEER block) – common Block options

Return type

DifSteer instance

Unicycle kinematic model with state \([x, y, heta]\), with with inputs given as wheel angular velocity.

Block ports

input ωL

Left-wheel angular velocity (radians/sec).

input ωR

Right-wheel angular velocity (radians/sec).

output q

configuration (x, y, θ)

Note

Wheel velocity is defined such that if both are positive the vehicle moves forward.

Seealso

Bicycle Unicycle

class roboticstoolbox.blocks.mobile.Unicycle(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

UNICYCLE

inputs

outputs

states

2

1

3

float

float

__init__(w=1, speed_max=inf, accel_max=inf, steer_max=None, a=0, x0=None, **blockargs)[source]

Create a vehicle model with Unicycle kinematics.

Parameters
  • w (float, optional) – vehicle width, defaults to 1

  • speed_max (float, optional) – Velocity limit, defaults to 1

  • accel_max (float, optional) – maximum acceleration, defaults to math.inf

  • steer_max (float, optional) – maximum steering rate, defaults to 1

  • x0 (array_like, optional) – Inital state, defaults to None

  • blockargs (dict :return: a UNICYCLE block) – common Block options

Return type

Unicycle instance

Unicycle kinematic model with state \([x, y, \theta]\).

Block ports

input v

Vehicle speed (metres/sec). The velocity limit vlim is applied to the magnitude of this input.

input ω

Angular velocity (radians/sec). The steering limit slim is applied to the magnitude of this input.

output q

configuration (x, y, θ)

Seealso

Bicycle DiffSteer

class roboticstoolbox.blocks.mobile.VehiclePlot(*args, bd=None, **kwargs)[source]

Bases: bdsim.graphics.GraphicsBlock

VEHICLEPLOT

inputs

outputs

states

1

0

0

ndarray

__init__(animation=None, path=None, labels=['X', 'Y'], square=True, init=None, scale=True, **blockargs)[source]

Create a vehicle animation

Parameters
  • animation (VehicleAnimation subclass, optional) – Graphical animation of vehicle, defaults to None

  • path (str or dict, optional) – linestyle to plot path taken by vehicle, defaults to None

  • labels (array_like(2) or list) – axis labels (xlabel, ylabel), defaults to [“X”,”Y”]

  • square (bool, optional) – Set aspect ratio to 1, defaults to True

  • init (callable, optional) – initialize graphics, defaults to None

  • blockargs (dict :return: A VEHICLEPLOT block) – common Block options

Return type

VehiclePlot instance

Create a vehicle animation similar to the figure below.

Block ports

input q

configuration (x, y, θ)

Notes:

  • The init function is called after the axes are initialized and can be used to draw application specific detail on the plot. In the example below, this is the dot and star.

  • A dynamic trail, showing path to date can be animated if the option path is set to a linestyle.

example of generated graphic

Example of vehicle display (animated). The label at the top is the block name.

Multi rotor flying robots

class roboticstoolbox.blocks.uav.MultiRotor(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.TransferBlock

MULTIROTOR

inputs

outputs

states

1

1

16

A(4,)

dict

__init__(model, groundcheck=True, speedcheck=True, x0=None, **blockargs)[source]

Create a a multi-rotor dynamic model block.

Parameters
  • model (dict) – Vehicle geometric and inertial parameters

  • groundcheck (bool) – Prevent vehicle moving below ground, defaults to True

  • speedcheck (bool) – Check for zero rotor speed, defaults to True

  • x0 (float, optional) – Initial state, defaults to None

  • blockargs (dict) – common Block options

Returns

a MULTIROTOR block

Return type

MultiRotor instance

Block ports

input ω

a vector of input rotor speeds in (radians/sec). These are, looking down, clockwise from the front rotor which lies on the x-axis.

output x

a dictionary signal with the following items:

  • x pose in the world frame as \([x, y, z, \theta_Y, \theta_P, \theta_R]\)

  • vb translational velocity in the world frame (metres/sec)

  • w angular rates in the world frame as yaw-pitch-roll rates (radians/second)

  • a1s longitudinal flapping angles (radians)

  • b1s lateral flapping angles (radians)

Based on MATLAB code developed by Pauline Pounds 2004.

class roboticstoolbox.blocks.uav.MultiRotorMixer(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.FunctionBlock

MULTIROTORMIXER

inputs

outputs

states

4

1

0

float

__init__(maxw=1000, minw=5, **blockargs)[source]

Create a block that displays/animates a multi-rotor flying vehicle.

Parameters
  • maxw (float) – maximum rotor speed in rad/s, defaults to 1000

  • minw (float) – minimum rotor speed in rad/s, defaults to 5

  • blockargs (dict) – common Block options

Returns

a MULTIROTORMIXER block

Return type

MultiRotorMixer instance

Block ports

input 𝛕r

roll torque

input 𝛕p

pitch torque

input 𝛕y

yaw torque

input T

total thrust

output ω

1D array of rotor speeds

Derived from Simulink model by Pauline Pounds 2004

class roboticstoolbox.blocks.uav.MultiRotorPlot(*args, bd=None, **kwargs)[source]

Bases: bdsim.graphics.GraphicsBlock

MULTIROTORPLOT

inputs

outputs

states

1

0

0

dict

__init__(model, scale=[- 2, 2, - 2, 2, 10], flapscale=1, projection='ortho', **blockargs)[source]

Create a block that displays/animates a multi-rotor flying vehicle.

Parameters
  • model (dict) – A dictionary of vehicle geometric and inertial properties

  • scale (array_like, optional) – dimensions of workspace: xmin, xmax, ymin, ymax, zmin, zmax, defaults to [-2,2,-2,2,10]

  • flapscale (float) – exagerate flapping angle by this factor, defaults to 1

  • projection (str) – 3D projection, one of: ‘ortho’ [default], ‘perspective’

  • blockargs (dict) – common Block options

Returns

a MULTIROTORPLOT block

Return type

MultiRotorPlot instance

Block ports

input y

a dictionary signal that includes the item:

  • x pose in the world frame as \([x, y, z, heta_Y, heta_P, heta_R]\)

  • X pose in the world frame as \([x, y, z, heta_Y, heta_P, heta_R]\)

  • a1s

  • b1s

example of generated graphic

Example of quad-rotor display.

Written by Pauline Pounds 2004

Vision blocks

These blocks are defined within the Machine Vision Toolbox for Python.

================================================ FILE: docs-aside/bdsim.html ================================================ Overview — Block diagram simulation 0.7 documentation

Overview

Getting started

We first sketch the dynamic system we want to simulate as a block diagram, for example this simple first-order system

_images/bd1-sketch.png

which we can express concisely with bdsim as (see bdsim/examples/eg1.py)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import bdsim

sim = bdsim.BDSim()  # create simulator
bd = sim.blockdiagram()  # create an empty block diagram

# define the blocks
demand = bd.STEP(T=1, name='demand')
sum = bd.SUM('+-')
gain = bd.GAIN(10)
plant = bd.LTI_SISO(0.5, [2, 1], name='plant')
scope = bd.SCOPE(styles=['k', 'r--'], movie='eg1.mp4')

# connect the blocks
bd.connect(demand, sum[0], scope[1])
bd.connect(plant, sum[1])
bd.connect(sum, gain)
bd.connect(gain, plant)
bd.connect(plant, scope[0])

bd.compile()   # check the diagram
bd.report()    # list all blocks and wires

out = sim.run(bd, 5)  # simulate for 5s

print(out)

# sim.savefig(scope, 'scope0') # save scope figure as scope0.pdf
sim.done(bd, block=True)  # keep figures open on screen

which is just 16 lines of executable code.

The red block annotations on the hand-drawn diagram are used as the names of the variables holding references to the block instance. The blocks can also have user-assigned names, see lines 8 and 11, which are used in diagnostics and as labels in plots.

After the blocks are created their input and output ports need to be connected. In bdsim all wires are point to point, a one-to-many connection is implemented by many wires, for example:

bd.connect(source, dest1, dest2, ...)

creates individual wires from source -> dest1, source -> dest2 and so on. Ports are designated using Python indexing notation, for example block[2] is port 2 (the third port) of block. Whether it is an input or output port depends on context. In the example above an index on the first argument refers to an output port, while on the second (or subsequent) arguments it refers to an input port. If a block has only a single input or output port then no index is required, 0 is assumed.

A group of ports can be denoted using slice notation, for example:

bd.connect(source[2:5], dest[3:6)

will connect source[2] -> dest[3], source[3] -> dest[4], source[4] -> dest[5]. The number of wires in each slice must be consistent. You could even do a cross over by connecting source[2:5] to dest[6:3:-1].

Line 20 assembles all the blocks and wires, instantiates subsystems, checks connectivity to create a flat wire list, and then builds the dataflow execution plan.

Line 21 generates a report, in tabular form, showing a summary of the block diagram:

Blocks::

┌───┬─────────┬─────┬──────┬────────┬─────────┬───────┐
│id │    name │ nin │ nout │ nstate │ ndstate │ type  │
├───┼─────────┼─────┼──────┼────────┼─────────┼───────┤
│ 0 │  demand │   0 │    1 │      0 │       0 │ step  │
│ 1 │   sum.0 │   2 │    1 │      0 │       0 │ sum   │
│ 2 │  gain.0 │   1 │    1 │      0 │       0 │ gain  │
│ 3 │   plant │   1 │    1 │      1 │       0 │ LTI   │
│ 4 │ scope.0 │   2 │    0 │      0 │       0 │ scope │
└───┴─────────┴─────┴──────┴────────┴─────────┴───────┘

Wires::

┌───┬──────┬──────┬──────────────────────────┬─────────┐
│id │ from │  to  │       description        │  type   │
├───┼──────┼──────┼──────────────────────────┼─────────┤
│ 0 │ 0[0] │ 1[0] │ demand[0] --> sum.0[0]   │ int     │
│ 1 │ 0[0] │ 4[1] │ demand[0] --> scope.0[1] │ int     │
│ 2 │ 3[0] │ 1[1] │ plant[0] --> sum.0[1]    │ float64 │
│ 3 │ 1[0] │ 2[0] │ sum.0[0] --> gain.0[0]   │ float64 │
│ 4 │ 2[0] │ 3[0] │ gain.0[0] --> plant[0]   │ float64 │
│ 5 │ 3[0] │ 4[0] │ plant[0] --> scope.0[0]  │ float64 │
└───┴──────┴──────┴──────────────────────────┴─────────┘
_images/Figure_1.png

The simulation results are returned in a simple container object:

>>> out
results:
t           | ndarray (67,)
x           | ndarray (67, 1)
xnames      | list

where

  • t the time vector: ndarray, shape=(M,)

  • x is the state vector: ndarray, shape=(M,N), one row per timestep

  • xnames is a list of the names of the states corresponding to columns of x, eg. “plant.x0”

To record additional simulation variables we “watch” them. This can be specified by wiring the signal to a WATCH block, or more conveniently by an additional option to run:

out = sim.run(bd, 5, watch=[plant,demand])  # simulate for 5s

and now the result out has additional elements:

>>> out
results:
t           | ndarray (67,)
x           | ndarray (67, 1)
xnames      | list
y0          | ndarray (67,)
y1          | ndarray (67,)
ynames      | list

where

  • y0 is the time history of the first watched signal

  • y1 is the time history of the second watched signal

  • ynames is a list of the names of the states corresponding to columns of x, eg. “plant[0]”

Line 27 saves the content of the scope to be saved in the file called scope0.pdf.

Line 28 blocks the script until all figure windows are closed, or the script is killed with SIGINT.

Line 29 saves the scope graphics as a PDF file.

Line 30 blocks until the last figure is dismissed.

A list of available blocks can be obtained by:

>>> sim.blocks()
   73  blocks loaded
   bdsim.blocks.functions..................: Sum Prod Gain Clip Function Interpolate
   bdsim.blocks.sources....................: Constant Time WaveForm Piecewise Step Ramp
   bdsim.blocks.sinks......................: Print Stop Null Watch
   bdsim.blocks.transfers..................: Integrator PoseIntegrator LTI_SS LTI_SISO
   bdsim.blocks.discrete...................: ZOH DIntegrator DPoseIntegrator
   bdsim.blocks.linalg.....................: Inverse Transpose Norm Flatten Slice2 Slice1 Det Cond
   bdsim.blocks.displays...................: Scope ScopeXY ScopeXY1
   bdsim.blocks.connections................: Item Dict Mux DeMux Index SubSystem InPort OutPort
   roboticstoolbox.blocks.arm..............: FKine IKine Jacobian Tr2Delta Delta2Tr Point2Tr TR2T FDyn IDyn Gravload
   ........................................: Inertia Inertia_X FDyn_X ArmPlot Traj JTraj LSPB CTraj CirclePath
   roboticstoolbox.blocks.mobile...........: Bicycle Unicycle DiffSteer VehiclePlot
   roboticstoolbox.blocks.uav..............: MultiRotor MultiRotorMixer MultiRotorPlot
   machinevisiontoolbox.blocks.camera......: Camera Visjac_p EstPose_p ImagePlane

More details can be found at:

Using operator overloading

Wiring, and some simple arithmetic blocks like GAIN, SUM and PROD can be implicitly generated by overloaded Python operators. This strikes a nice balance between block diagram coding and Pythonic programming.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import bdsim

sim = bdsim.BDSim()  # create simulator
bd = sim.blockdiagram()  # create an empty block diagram

# define the blocks
demand = bd.STEP(T=1, name='demand')
plant = bd.LTI_SISO(0.5, [2, 1], name='plant')
scope = bd.SCOPE(styles=['k', 'r--'], movie='eg1.mp4')

# connect the blocks
scope[0] = plant
scope[1] = demand
plant[0] = 10 * (demand - plant)

bd.compile()   # check the diagram
bd.report()    # list all blocks and wires

out = sim.run(bd, 5)  # simulate for 5s
# out = sim.run(bd, 5 watch=[plant,demand])  # simulate for 5s
print(out)

# sim.savefig(scope, 'scope0') # save scope figure as scope0.pdf
sim.done(bd, block=True)  # keep figures open on screen

This requires fewer lines of code and the code is more readable. Importantly, it results in in exactly the same block diagram in terms of blocks and wires:

┌───┬──────┬──────┬──────────────────────────────┬─────────┐
│id │ from │  to  │         description          │  type   │
├───┼──────┼──────┼──────────────────────────────┼─────────┤
│ 0 │ 1[0] │ 2[0] │ plant[0] --> scope.0[0]      │ float64 │
│ 1 │ 0[0] │ 2[1] │ demand[0] --> scope.0[1]     │ int     │
│ 2 │ 0[0] │ 3[0] │ demand[0] --> _sum.0[0]      │ int     │
│ 3 │ 1[0] │ 3[1] │ plant[0] --> _sum.0[1]       │ float64 │
│ 4 │ 3[0] │ 4[0] │ _sum.0[0] --> _gain.0(10)[0] │ float64 │
│ 5 │ 4[0] │ 1[0] │ _gain.0(10)[0] --> plant[0]  │ float64 │
└───┴──────┴──────┴──────────────────────────────┴─────────┘

The implicitly created blocks have names prefixed with an underscore.

================================================ FILE: docs-aside/genindex.html ================================================ Index — Block diagram simulation 0.7 documentation
  • »
  • Index

Index

_ | A | B | C | D | F | G | I | J | L | M | N | O | P | R | S | T | U | V | W | Z

_

A

B

C

D

F

G

I

J

L

M

N

O

P

R

S

T

U

V

W

Z


© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/index.html ================================================ Block diagrams for Python — Block diagram simulation 0.7 documentation

Block diagrams for Python

# define the blocks
demand = bd.STEP(T=1, name='demand')
sum = bd.SUM('+-')
gain = bd.GAIN(10)
plant = bd.LTI_SISO(0.5, [2, 1])
scope = bd.SCOPE(styles=['k', 'r--'])

# connect the blocks
bd.connect(demand, sum[0], scope[1])
bd.connect(plant, sum[1])
bd.connect(sum, gain)
bd.connect(gain, plant)
bd.connect(plant, scope[0])

This Python package enables modelling and simulation of dynamic systems conceptualized in block diagram form, but represented in terms of Python class and method calls.

Unlike Simulink® or LabView®, we write Python code rather than drawing boxes and wires. Wires can communicate any Python type such as scalars, strings, lists, dictionaries, numpy arrays, other objects, and even functions.

================================================ FILE: docs-aside/internals.html ================================================ Supporting classes — Block diagram simulation 0.7 documentation

Supporting classes

Inheritance diagram of bdsim.components

BDSim class

This class describes the run-time environment for executing a block diagram.

class bdsim.BDSim(packages=None, **kwargs)[source]

Bases: object

__init__(packages=None, **kwargs)[source]
Parameters
  • sysargs (bool, optional) – process options from sys.argv, defaults to True

  • graphics (bool, optional) – enable graphics, defaults to True

  • animation (bool, optional) – enable animation, defaults to False

  • progress (bool, optional) – enable progress bar, defaults to True

  • debug (str, optional) – debug options, defaults to None

  • backend (str, optional) – matplotlib backend, defaults to ‘Qt5Agg’’

  • tiles (str, optional) – figure tile layout on monitor, defaults to ‘3x4’

Raises

ImportError – syntax error in block

Returns

parent object for blockdiagram simulation

Return type

BDSim

If sysargs is True, process command line arguments and passed options. Command line arguments have precedence.

Command line switch

Argument

Default

Behaviour

++nographics, +g

graphics

True

enable graphical display

++animation, +a

animation

True

update graphics at each time step

–nographics, -g

graphics

True

disable graphical display

–animation, -a

animation

True

don’t update graphics at each time step

–noprogress, -p

progress

True

do not display simulation progress bar

–backend BE

backend

‘Qt5Agg’

matplotlib backend

–tiles RxC, -t RxC

tiles

‘3x4’

arrangement of figure tiles on the display

–shape WxH

shape

None

window size, default matplotlib size

–altscreen

altscreen

True

use secondary monitor if it exists

–verbose, -v

verbose

False

be verbose

–debug F, -d F

debug

‘’

debug flag string

Note

animation and graphics options are coupled. If graphics=False, all graphics is suppressed. If graphics=True then graphics are shown and the behaviour depends on animation. animation=False shows graphs at the end of the simulation, while ``animation=True` will animate the graphs during simulation.

Seealso

set_options()

blockdiagram(name='main')[source]

Instantiate a new block diagram object.

Parameters

name (str, optional) – diagram name, defaults to ‘main’

Returns

parent object for blockdiagram

Return type

BlockDiagram

This object describes the connectivity of a set of blocks and wires.

It is an instantiation of the BlockDiagram class with a factory method for every dynamically loaded block which returns an instance of the block. These factory methods have names which are all upper case, for example, the method .GAIN invokes the constructor for the Gain class.

Seealso

BlockDiagram()

blocks()[source]

List all loaded blocks.

Example:

73  blocks loaded
bdsim.blocks.functions..................: Sum Prod Gain Clip Function Interpolate 
bdsim.blocks.sources....................: Constant Time WaveForm Piecewise Step Ramp 
bdsim.blocks.sinks......................: Print Stop Null Watch 
bdsim.blocks.transfers..................: Integrator PoseIntegrator LTI_SS LTI_SISO 
bdsim.blocks.discrete...................: ZOH DIntegrator DPoseIntegrator 
bdsim.blocks.linalg.....................: Inverse Transpose Norm Flatten Slice2 Slice1 Det Cond 
bdsim.blocks.displays...................: Scope ScopeXY ScopeXY1 
bdsim.blocks.connections................: Item Dict Mux DeMux Index SubSystem InPort OutPort 
roboticstoolbox.blocks.arm..............: FKine IKine Jacobian Tr2Delta Delta2Tr Point2Tr TR2T FDyn IDyn Gravload 
........................................: Inertia Inertia_X FDyn_X ArmPlot Traj JTraj LSPB CTraj CirclePath 
roboticstoolbox.blocks.mobile...........: Bicycle Unicycle DiffSteer VehiclePlot 
roboticstoolbox.blocks.uav..............: MultiRotor MultiRotorMixer MultiRotorPlot 
machinevisiontoolbox.blocks.camera......: Camera Visjac_p EstPose_p ImagePlane 
closefigs()[source]
done(bd, **kwargs)[source]
get_options(sysargs=True, **kwargs)[source]
load_blocks(verbose=True)[source]

Dynamically load all block definitions.

Raises

ImportError – module could not be imported

Returns

dictionary of block metadata

Return type

dict of dict

Reads blocks from .py files found in bdsim/bdsim/blocks, folders given by colon separated list in envariable BDSIMPATH, and the command line option packages.

The result is a dict indexed by the upper-case block name with elements: - path to the folder holding the Python file defining the block - classname - blockname, upper case version of classname - url of online documentation for the block - package containing the block - doc is the docstring from the class constructor

options = None
progress(t=None)[source]

Update progress bar

Parameters

t (float, optional) – current simulation time, defaults to None

Update progress bar as a percentage of the maximum simulation time, given as an argument to run.

Seealso

run() progress_done()

progress_done()[source]

Clean up progress bar

run(bd, T=10.0, dt=0.1, solver='RK45', solver_args={}, debug='', block=False, checkfinite=True, minstepsize=1e-12, watch=[])[source]

Run the block diagram

Parameters
  • T (float, optional) – maximum integration time, defaults to 10.0

  • dt (float, optional) – maximum time step, defaults to 0.1

  • solver (str, optional) – integration method, defaults to RK45

  • block (bool) – matplotlib block at end of run, default False

  • checkfinite (bool) – error if inf or nan on any wire, default True

  • minstepsize (float) – minimum step length, default 1e-6

  • watch (list) – list of input ports to log

  • solver_args (dict) – arguments passed to scipy.integrate

Returns

time history of signals and states

Return type

Sim class

Assumes that the network has been compiled.

Results are returned in a class with attributes:

  • t the time vector: ndarray, shape=(M,)

  • x is the state vector: ndarray, shape=(M,N)

  • xnames is a list of the names of the states corresponding to columns of x, eg. “plant.x0”,

    defined for the block using the snames argument

  • yN for a watched input where N is the index of the port mentioned in the watch argument

  • ynames is a list of the names of the input ports being watched, same order as in watch argument

If there are no dynamic elements in the diagram, ie. no states, then x and xnames are not present.

The watch argument is a list of one or more input ports whose value during simulation will be recorded. The elements of the list can be:

  • a Block reference, which is interpretted as input port 0

  • a Plug reference, ie. a block with an index or attribute

  • a string of the form “block[i]” which is port i of the block named block.

The debug string comprises single letter flags:

  • ‘p’ debug network value propagation

  • ‘s’ debug state vector

  • ‘d’ debug state derivative

Note

Simulation stops if the step time falls below minsteplength which typically indicates that the solver is struggling with a very harsh non-linearity.

run_interval(bd, t0, T, x0, state)[source]

Integrate system over interval

Parameters
  • bd (BlockDiagram) – the system blockdiagram

  • t0 (float) – initial time

  • tf (float) – final time

  • x0 (ndarray(n)) – initial state vector

  • simstate (SimState) – simulation state object

Returns

final state vector xf

Return type

ndarray(n)

The system is integrated from from x0 to xf over the interval t0 to tf.

savefig(block, filename=None, format='pdf', **kwargs)[source]
savefigs(bd, format='pdf', **kwargs)[source]
set_options(**options)[source]

Set simulation options at run time

The options are the same as those for the constructor.

Example:

sim = bdsim.BDsim()
sim.set_options(graphics=False)
Seealso

__init__()

showgraph(bd, **kwargs)[source]
class bdsim.Struct(name='Struct', **kwargs)[source]

Bases: collections.UserDict

A dict like object that allows items to be added by attribute or by key.

For example:

>>> d = Struct('thing')
>>> d.a = 1
>>> d['b'] = 2
>>> d.a
1
>>> d['a']
1
>>> d.b
2
>>> str(d)
"thing {'a': 1, 'b': 2}"
__init__(name='Struct', **kwargs)[source]

Initialize self. See help(type(self)) for accurate signature.

add(name, value)[source]
class bdsim.BDSimState[source]

Bases: object

Variables
  • x (np.ndarray) – state vector

  • T (float) – maximum simulation time (seconds)

  • t (float) – current simulation time (seconds)

  • fignum (int) – number of next matplotlib figure to create

  • stop (Block subclass) – reference to block wanting to stop simulation, else None

  • checkfinite (bool) – halt simulation if any wire has inf or nan

  • graphics (bool) – enable graphics

__init__()[source]

Initialize self. See help(type(self)) for accurate signature.

declare_event(block, t)[source]

BlockDiagram class

This class describes a block diagram, a collection of blocks and wires that can be “executed”.

bdsim.blockdiagram

alias of bdsim.blockdiagram

Components

Wire

class bdsim.Wire(start=None, end=None, name=None)[source]

Bases: object

Create a wire.

Parameters
  • start (Plug, optional) – Plug at the start of a wire, defaults to None

  • end (Plug, optional) – Plug at the end of a wire, defaults to None

  • name (str, optional) – Name of wire, defaults to None

Returns

A wire object

Return type

Wire

A Wire object connects two block ports. A Wire has a reference to the start and end ports.

A wire records all the connections defined by the user. At compile time wires are used to build inter-block references.

Between two blocks, a wire can connect one or more ports, ie. it can connect a set of output ports on one block to a same sized set of input ports on another block.

property fullname

Display wire connection details.

Returns

Wire name

Return type

str

String format:

d2goal[0] --> Kv[0]
property info

Interactive display of wire properties.

Displays all attributes of the wire for debugging purposes.

send(value, sinks=True)[source]

Send a value to the port at end of this wire.

Parameters

value (float, numpy.ndarray, etc.) – A port value

The value is sent to the input port connected to the end of this wire.

Plug

class bdsim.Plug(block, port=0, type=None)[source]

Bases: object

Create a plug.

Parameters
  • block (Block) – The block being plugged into

  • port (int, optional) – The port on the block, defaults to 0

  • type (str, optional) – ‘start’ or ‘end’, defaults to None

Returns

Plug object

Return type

Plug

Plugs are the interface between a wire and block and have information about port number and wire end. Plugs are on the end of each wire, and connect a Wire to a specific port on a Block.

The type argument indicates if the Plug is at:
  • the start of a wire, ie. the port is an output port

  • the end of a wire, ie. the port is an input port

A plug can specify a set of ports on a block.

__add__(other)[source]

Overloaded + operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be added

  • other (Block or Plug) – A signal (block or plug) to be added

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the + operator when the left operand is a Plug and the right operand is a Plug, Block or constant:

result = X[i] + Y
result = X[i] + Y[j]
result = X[i] + C

where X and Y are blocks and C is a Python or NumPy constant.

Create a SUM("++") block named _sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Seealso

Plug.__radd__() Block.__add__()

__init__(block, port=0, type=None)[source]

Initialize self. See help(type(self)) for accurate signature.

__mul__(other)[source]

Overloaded * operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be multiplied

  • other (Block or Plug) – A signal (block or plug) to be multiplied

Returns

PROD or GAIN block

Return type

Block subclass

This method is implicitly invoked by the * operator when the left operand is a Plug and the right operand is a Plug, Block or constant:

result = X[i] * Y
result = X[i] * Y[j]
result = X[i] * C

where X and Y are blocks and C is a Python or NumPy constant.

Create a PROD("**") block named _prod.N whose inputs are the left and right operands.

For the third case, create a GAIN(C) block named _gain.N.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Plug.__rmul__() Block.__mul__()

__neg__()[source]

Overloaded unary minus operator for implicit block creation.

Parameters

self (Plug) – A signal (plug) to be negated

Returns

GAIN block

Return type

Block subclass

This method is implicitly invoked by the - operator for unary minus when the operand is a Plug:

result = -X[i]

where X is a block.

Create a GAIN(-1) block named _gain.N whose input is the operand.

Seealso

Block.__neg__()

__radd__(other)[source]

Overloaded + operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be added

  • other (Block or Plug) – A signal (block or plug) to be added

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the + operator when the right operand is a Plug and the left operand is a Plug, Block or constant:

result = X + Y[j]
result = X[i] + Y[j]
result = C + Y[j]

where X and Y are blocks and C is a Python or NumPy constant.

Create a SUM("++") block named ``_sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Note

The inputs to the summing junction are reversed: right then left operand.

Seealso

Plug.__add__() Block.__radd__()

__repr__()[source]

Display plug details.

Returns

Plug description

Return type

str

String format:

bicycle.0[1]
__rmul__(other)[source]

Overloaded * operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be multiplied

  • other (Block or Plug) – A signal (block or plug) to be multiplied

Returns

PROD or GAIN block

Return type

Block subclass

This method is implicitly invoked by the * operator when the right operand is a Plug and the left operand is a Plug, Block or constant:

result = X * Y[j]
result = X[i] * Y[j]
result = C * Y[j]

where X and Y are blocks and C is a Python or NumPy constant.

For the first two cases, a PROD("**") block named _prod.N whose inputs are the left and right operands.

For the third case, create a GAIN(C) block named _gain.N.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Plug.__mul__() Block.__rmul__()

__rshift__(right)[source]

Overloaded >> operator for implicit wiring.

Parameters
  • left (Plug) – A plug to be wired from

  • right (Block or Plug) – A block or plug to be wired to

Returns

right

Return type

Block or Plug

Implements implicit wiring, where the left-hand operator is a Plug, for example:

a = bike[2] >> bd.GAIN(3)

will connect port 2 of bike to the input of the GAIN block.

Note that:

a = bike[2] >> func[1]

will connect port 2 of bike to port 1 of func, and port 1 of func will be assigned to a. To specify a different outport port on func we need to use parentheses:

a = (bike[2] >> func[1])[0]

which will connect port 2 of bike to port 1 of func, and port 0 of func will be assigned to a.

Seealso

Block.__mul__

__rsub__(other)[source]

Overloaded - operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be added (minuend)

  • other (Block or Plug) – A signal (block or plug) to be subtracted (subtrahend)

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the - operator when the left operand is a Plug and the right operand is a Plug, Block or constant:

result = X - Y[j]
result = X[i] - Y[j]
result = C - Y[j]

where X and Y are blocks and C is a Python or NumPy constant.

Create a SUM("+-") block named _sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Note

The inputs to the summing junction are reversed: right then left operand.

Seealso

Plug.__sub__() Block.__rsub__()

__rtruediv__(other)[source]

Overloaded / operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be multiplied (dividend)

  • other (Block or Plug) – A signal (block or plug) to be divided (divisor)

Returns

PROD block

Return type

Block subclass

This method is implicitly invoked by the / operator when the right operand is a Plug and the left operand is a Plug, Block or constant:

result = X / Y[j]
result = X[i] / Y[j]
result = C / Y[j]

where X and Y are blocks and C is a Python or NumPy constant.

For the first two cases, a PROD("*/") block named _prod.N whose inputs are the left and right operands. For the third case, a new CONSTANT block named _const.N is also created.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Plug.__truediv__() Block.__rtruediv__()

__sub__(other)[source]

Overloaded - operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be added (minuend)

  • other (Block or Plug) – A signal (block or plug) to be subtracted (subtrahend)

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the - operator when the left operand is a Plug and the right operand is a Plug, Block or constant:

result = X[i] - Y
result = X[i] - Y[j]
result = X[i] - C

where X and Y are blocks and C is a Python or NumPy constant.

Create a SUM("+-") block named _sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Seealso

Plug.__rsub__() Block.__sub__()

__truediv__(other)[source]

Overloaded / operator for implicit block creation.

Parameters
  • self (Plug) – A signal (plug) to be multiplied (dividend)

  • other (Block or Plug) – A signal (block or plug) to be divided (divisor)

Returns

PROD or GAIN block

Return type

Block subclass

This method is implicitly invoked by the / operator when the left operand is a Plug and the right operand is a Plug, Block or constant:

result = X[i] / Y
result = X[i] / Y[j]
result = X[i] / C

where X and Y are blocks and C is a Python or NumPy constant.

Create a PROD("**") block named _prod.N whose inputs are the left and right operands.

For the third case, create a GAIN(1/C) block named _gain.N.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Plug.__rtruediv__() Block.__truediv__()

property isslice

Test if port number is a slice.

Returns

Whether the port is a slice

Return type

bool

Returns True if the port is a slice, eg. [0:3], and False for a simple index, eg. [2].

property portlist

Return port numbers.

Returns

Port numbers

Return type

list of int

If the port is a simple index, eg. [2] returns [2].

If the port is a slice, eg. [0:3], returns [0, 1, 2]. For the case [2:] the upper bound is the maximum number of input or output ports of the block.

property width

Return number of ports connected.

Returns

Number of ports

Return type

int

If the port is a simple index, eg. [2] returns 1.

If the port is a slice, eg. [0:3], returns 3.

Blocks

class bdsim.Block(*args, bd=None, **kwargs)[source]

Bases: object

__add__(other)[source]

Overloaded + operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be added

  • other (Block or Plug) – A signal (block or plug) to be added

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the + operator when the right operand is a Block and the left operand is a Plug, Block or constant:

result = X + Y
result = X + Y[j]
result = X + C

where X and Y are blocks and C is a Python or NumPy constant.

Creates a SUM("++") block named ``_sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Note

The inputs to the summing junction are reversed: right then left operand.

Seealso

Block.__radd__() Plug.__add__()

__getitem__(port)[source]

Convert a block slice reference to a plug.

Parameters

port (int) – Port number

Returns

A port plug

Return type

Plug

Invoked whenever a block is referenced as a slice, for example:

c = bd.CONSTANT(1)

bd.connect(x, c[0])
bd.connect(c[0], x)

In both cases c[0] is converted to a Plug by this method.

__init__(name=None, nin=None, nout=None, inputs=None, type=None, inames=None, onames=None, snames=None, pos=None, bd=None, blockclass=None, verbose=False, **kwargs)[source]

Construct a new block object.

Parameters
  • name (str, optional) – Name of the block, defaults to None

  • nin (int, optional) – Number of inputs, defaults to None

  • nout (int, optional) – Number of outputs, defaults to None

  • inputs (Block, Plug or list of Block or Plug) – Optional incoming connections

  • inames (list of str, optional) – Names of input ports, defaults to None

  • onames (list of str, optional) – Names of output ports, defaults to None

  • snames (list of str, optional) – Names of states, defaults to None

  • pos (2-element tuple or list, optional) – Position of block on the canvas, defaults to None

  • bd (BlockDiagram, optional) – Parent block diagram, defaults to None

  • verbose (bool, optional) – enable diagnostic prints, defaults to False

  • kwargs (dict) – Unused arguments

Returns

A Block superclass

Return type

Block

A block object is the superclass of all blocks in the simulation environment.

This is the top-level initializer, and handles most options passed to the superclass initializer for each block in the library.

__mul__(other)[source]

Overloaded * operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be multiplied

  • other (Block or Plug) – A signal (block or plug) to be multiplied

Returns

PROD or GAIN block

Return type

Block subclass

This method is implicitly invoked by the * operator when the left operand is a Block and the right operand is a Plug, Block or constant:

result = X * Y
result = X * Y[j]
result = X * C

where X and Y are blocks and C is a Python or NumPy constant.

Create a PROD("**") block named _prod.N whose inputs are the left and right operands.

For the third case, create a GAIN(C) block named _gain.N.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Block.__rmul__() Plug.__mul__()

__neg__()[source]

Overloaded unary minus operator for implicit block creation.

Parameters

self (Block) – A signal (block) to be negated

Returns

GAIN block

Return type

Block subclass

This method is implicitly invoked by the - operator for unary minus when the operand is a Block:

result = -X

where X is a block.

Creates a GAIN(-1) block named _gain.N whose input is the operand.

Seealso

Plug.__neg__()

static __new__(cls, *args, bd=None, **kwargs)[source]

Construct a new Block object.

Parameters
  • cls (class type) – The class to construct

  • *args

    positional args passed to constructor

  • **kwargs

    keyword args passed to constructor

Returns

new Block instance

Return type

Block instance

__radd__(other)[source]

Overloaded + operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be added

  • other (Block or Plug) – A signal (block or plug) to be added

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the + operator when the right operand is a Block and the left operand is a Plug, Block or constant:

result = X + Y[j]
result = X[i] + Y[j]
result = C + Y[j]

where X and Y are blocks and C is a Python or NumPy constant.

Creates a SUM("++") block named ``_sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Note

The inputs to the summing junction are reversed: right then left operand.

Seealso

Block.__add__() Plug._r_add__()

__repr__()[source]

Return repr(self).

__rmul__(other)[source]

Overloaded * operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be multiplied

  • other (Block or Plug) – A signal (block or plug) to be multiplied

Returns

PROD or GAIN block

Return type

Block subclass

This method is implicitly invoked by the * operator when the right operand is a Block and the left operand is a Plug, Block or constant:

result = X * Y
result = X[i] * Y
result = C * Y

where X and Y are blocks and C is a Python or NumPy constant.

For the first two cases, a PROD("**") block named _prod.N whose inputs are the left and right operands.

For the third case, create a GAIN(C) block named _gain.N.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Block.__mul__() Plug.__rmul__()

__rshift__(right)[source]

Operator for implicit wiring.

Parameters
  • left (Block) – A block to be wired from

  • right (Block or Plug) – A block or plugto be wired to

Returns

right

Return type

Block or Plug

Implements implicit wiring, for example:

a = bd.CONSTANT(1) >> bd.GAIN(2)

will connect the output of the CONSTANT block to the input of the GAIN block. The result will be GAIN block, whose output in this case will be assigned to a.

Note that:

a = bd.CONSTANT(1) >> func[1]

will connect port 0 of CONSTANT to port 1 of func, and port 1 of func will be assigned to a. To specify a different outport port on func we need to use parentheses:

a = (bd.CONSTANT(1) >> func[1])[0]

which will connect port 0 of CONSTANT ` to port 1 of func, and port 0 of func will be assigned to a.

Seealso

Plug.__rshift__

__rsub__(other)[source]

Overloaded - operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be added (minuend)

  • other (Block or Plug) – A signal (block or plug) to be subtracted (subtrahend)

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the - operator when the left operand is a Block and the right operand is a Plug, Block or constant:

result = X - Y
result = X[i] - Y
result = C - Y

where X and Y are blocks and C is a Python or NumPy constant.

Creates a SUM("+-") block named _sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Note

The inputs to the summing junction are reversed: right then left operand.

Seealso

Block.__sub__() Plug.__rsub__()

__rtruediv__(other)[source]

Overloaded / operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be multiplied (dividend)

  • other (Block or Plug) – A signal (block or plug) to be divided (divisor)

Returns

PROD block

Return type

Block subclass

This method is implicitly invoked by the / operator when the right operand is a Block and the left operand is a Plug, Block or constant:

result = X / Y
result = X[i] / Y
result = C / Y

where X and Y are blocks and C is a Python or NumPy constant.

For the first two cases, a PROD("*/") block named _prod.N whose inputs are the left and right operands. For the third case, a new CONSTANT block named _const.N is also created.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Block.__truediv__() Plug.__rtruediv__()

__setattr__(name, value)[source]

Convert a LHS block name reference to a wire.

Parameters
  • name – Port name

  • value (Block or Plug) – the RHS

Used to create a wired connection by assignment, for example:

c = bd.CONSTANT(1, inames=['u'])

c.u = x

Ths method is invoked to create a wire from x to port ‘u’ of the constant block c.

Notes:

  • this overloaded method handles all instances of setattr and implements normal functionality as well, only creating a wire if name is a known port name.

__setitem__(port, src)[source]

Convert a LHS block slice reference to a wire.

Parameters
  • port (int) – Port number

  • src (Block or Plug) – the RHS

Used to create a wired connection by assignment, for example:

X[0] = Y

where X and Y are blocks. This method is implicitly invoked and creates a wire from Y to input port 0 of X.

Note

The square brackets on the left-hand-side is critical, and X = Y will simply overwrite the reference to X.

__str__()[source]

Return str(self).

__sub__(other)[source]

Overloaded - operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be added (minuend)

  • other (Block or Plug) – A signal (block or plug) to be subtracted (subtrahend)

Returns

SUM block

Return type

Block subclass

This method is implicitly invoked by the - operator when the left operand is a Block and the right operand is a Plug, Block or constant:

result = X - Y
result = X - Y[j]
result = X - C

where X and Y are blocks and C is a Python or NumPy constant.

Creates a SUM("+-") block named _sum.N whose inputs are the left and right operands. For the third case, a new CONSTANT(C) block named _const.N is also created.

Seealso

Block.__rsub__() Plug.__sub__()

__truediv__(other)[source]

Overloaded / operator for implicit block creation.

Parameters
  • self (Block) – A signal (block) to be multiplied (dividend)

  • other (Block or Plug) – A signal (block or plug) to be divided (divisor)

Returns

PROD or GAIN block

Return type

Block subclass

This method is implicitly invoked by the / operator when the left operand is a Block and the right operand is a Plug, Block or constant:

result = X / Y
result = X / Y[j]
result = X / C

where X and Y are blocks and C is a Python or NumPy constant.

Create a PROD("**") block named _prod.N whose inputs are the left and right operands.

For the third case, create a GAIN(1/C) block named _gain.N.

Note

Signals are assumed to be scalars, but if C is a NumPy array then the option matrix is set to True.

Seealso

Block.__rtruediv__() Plug.__truediv__()

add_inport(w)[source]
add_outport(w)[source]
add_param(param, handler=None)[source]
check()[source]
done(**kwargs)[source]
property info

Interactive display of block properties.

Displays all attributes of the block for debugging purposes.

inport_names(names)[source]

Set the names of block input ports.

Parameters

names (list of str) – List of port names

Invoked by the inames argument to the Block constructor.

The names can include LaTeX math markup. The LaTeX version is used where appropriate, but the port names are a de-LaTeXd version of the given string with backslash, caret, braces and dollar signs removed.

property isclocked
property isgraphics
outport_names(names)[source]

Set the names of block output ports.

Parameters

names (list of str) – List of port names

Invoked by the onames argument to the Block constructor.

The names can include LaTeX math markup. The LaTeX version is used where appropriate, but the port names are a de-LaTeXd version of the given string with backslash, caret, braces and dollar signs removed.

reset()[source]
savefig(*pos, **kwargs)[source]
set_param(name, newvalue)[source]
setinput(port, value)[source]

Receive input from a wire

Parameters
  • self – Block to be updated

  • port (int) – Input port to be updated

  • value – Input value

setinputs(*pos)[source]
sourcename(port)[source]

Get the name of output port driving this input port.

Parameters

port (int) – Input port

Returns

Port name

Return type

str

Return the name of the output port that drives the specified input port. The name can be:

  • a LaTeX string if provided

  • block name with port number given in square brackets. The block name will the one optionally assigned by the user using the name keyword, otherwise a systematic default name.

Seealso

outport_names

start(**kwargs)[source]
state_names(names)[source]
step(**kwargs)[source]
varinputs = False
varoutputs = False

Source block

class bdsim.SourceBlock(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.Block

A SourceBlock is a subclass of Block that represents a block that has outputs but no inputs. Its output is a function of parameters and time.

__init__(**blockargs)[source]

Create a source block.

Parameters

blockargs (dict) – common Block options

Returns

source block base class

Return type

SourceBlock

This is the parent class of all source blocks.

__module__ = 'bdsim.components'
blockclass = 'source'

Sink block

class bdsim.SinkBlock(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.Block

A SinkBlock is a subclass of Block that represents a block that has inputs but no outputs. Typically used to save data to a variable, file or graphics.

__init__(**blockargs)[source]

Create a sink block.

Parameters

blockargs (dict) – common Block options

Returns

sink block base class

Return type

SinkBlock

This is the parent class of all sink blocks.

__module__ = 'bdsim.components'
blockclass = 'sink'

Function block

class bdsim.FunctionBlock(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.Block

A FunctionBlock is a subclass of Block that represents a block that has inputs and outputs but no state variables. Typically used to describe operations such as gain, summation or various mappings.

__init__(**blockargs)[source]

Create a function block.

Parameters

blockargs (dict) – common Block options

Returns

function block base class

Return type

FunctionBlock

This is the parent class of all function blocks.

__module__ = 'bdsim.components'
blockclass = 'function'

Transfer function block

class bdsim.TransferBlock(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.Block

A TransferBlock is a subclass of Block that represents a block with inputs outputs and states. Typically used to describe a continuous time dynamic system, either linear or nonlinear.

__init__(nstates=1, **blockargs)[source]

Create a transfer function block.

Parameters

blockargs (dict) – common Block options

Returns

transfer function block base class

Return type

TransferBlock

This is the parent class of all transfer function blocks.

__module__ = 'bdsim.components'
blockclass = 'transfer'
check()[source]
getstate0()[source]
reset()[source]
setstate(x)[source]

Subsystem block

class bdsim.SubsystemBlock(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.Block

A SubSystem s a subclass of Block that represents a block that has inputs and outputs but no state variables. Typically used to describe operations such as gain, summation or various mappings.

__init__(**blockargs)[source]

Create a subsystem block.

Parameters

blockargs (dict) – common Block options

Returns

subsystem block base class

Return type

SubsystemBlock

This is the parent class of all subsystem blocks.

__module__ = 'bdsim.components'
blockclass = 'subsystem'

Graphics block

class bdsim.GraphicsBlock(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.SinkBlock

A GraphicsBlock is a subclass of SinkBlock that represents a block that has inputs but no outputs and creates/updates a graphical display.

__init__(movie=None, **blockargs)[source]

Create a graphical display block.

Parameters
  • movie (str, optional) – Save animation in this file in MP4 format, defaults to None

  • blockargs (dict) – common Block options

Returns

transfer function block base class

Return type

TransferBlock

This is the parent class of all graphic display blocks.

__module__ = 'bdsim.graphics'
blockclass = 'graphics'
create_figure(state)[source]
done(state=None, block=False, **kwargs)[source]
savefig(filename=None, format='pdf', **kwargs)[source]

Save the figure as an image file

Parameters
  • fname (str) – Name of file to save graphics to

  • **kwargs – Options passed to savefig

The file format is taken from the file extension and can be jpeg, png or pdf.

start()[source]
step(state=None)[source]

Discrete-time systems

class bdsim.ClockedBlock(*args, bd=None, **kwargs)[source]

Bases: bdsim.components.Block

A ClockedBlock is a subclass of Block that represents a block with inputs outputs and discrete states. Typically used to describe a discrete time dynamic system, either linear or nonlinear.

__init__(clock=None, **blockargs)[source]

Create a clocked block.

Parameters

blockargs (dict) – common Block options

Returns

clocked block base class

Return type

ClockedBlock

This is the parent class of all clocked blocks.

blockclass = 'clocked'
check()[source]
getstate0()[source]
reset()[source]
setstate(x)[source]
class bdsim.Clock(arg, unit='s', offset=0, name=None)[source]

Bases: object

__init__(arg, unit='s', offset=0, name=None)[source]

Initialize self. See help(type(self)) for accurate signature.

add_block(block)[source]
getstate()[source]
getstate0()[source]
next_event(state=None)[source]
savestate(t)[source]
setstate()[source]
start(state=None)[source]
time(i)[source]
class bdsim.PriorityQ[source]

Bases: object

__init__()[source]

Initialize self. See help(type(self)) for accurate signature.

pop(dt=0)[source]
pop_until(t)[source]
push(value)[source]
================================================ FILE: docs-aside/modules.html ================================================ bdsim — Block diagram simulation 0.7 documentation

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/py-modindex.html ================================================ Python Module Index — Block diagram simulation 0.7 documentation
  • »
  • Python Module Index

Python Module Index

b | m | r
 
b
bdsim
    bdsim.blocks.connections
    bdsim.blocks.discrete
    bdsim.blocks.functions
    bdsim.blocks.linalg
    bdsim.blocks.sinks
    bdsim.blocks.sources
    bdsim.blocks.transfers
 
m
machinevisiontoolbox
    machinevisiontoolbox.blocks
 
r
roboticstoolbox
    roboticstoolbox.blocks.arm
    roboticstoolbox.blocks.mobile
    roboticstoolbox.blocks.uav

© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/search.html ================================================ Search — Block diagram simulation 0.7 documentation
  • »
  • Search


© Copyright 2020, Peter Corke. Last updated on 29-Dec-2021.

Built with Sphinx using a theme provided by Read the Docs.
================================================ FILE: docs-aside/searchindex.js ================================================ Search.setIndex({docnames:["bdsim","bdsim.blocks","index","internals","modules"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["bdsim.rst","bdsim.blocks.rst","index.rst","internals.rst","modules.rst"],objects:{"bdsim.BDSim":{__init__:[3,1,1,""],blockdiagram:[3,1,1,""],blocks:[3,1,1,""],closefigs:[3,1,1,""],done:[3,1,1,""],get_options:[3,1,1,""],load_blocks:[3,1,1,""],options:[3,2,1,""],progress:[3,1,1,""],progress_done:[3,1,1,""],run:[3,1,1,""],run_interval:[3,1,1,""],savefig:[3,1,1,""],savefigs:[3,1,1,""],set_options:[3,1,1,""],showgraph:[3,1,1,""]},"bdsim.BDSimState":{__init__:[3,1,1,""],declare_event:[3,1,1,""]},"bdsim.Block":{__add__:[3,1,1,""],__getitem__:[3,1,1,""],__init__:[3,1,1,""],__mul__:[3,1,1,""],__neg__:[3,1,1,""],__new__:[3,1,1,""],__radd__:[3,1,1,""],__repr__:[3,1,1,""],__rmul__:[3,1,1,""],__rshift__:[3,1,1,""],__rsub__:[3,1,1,""],__rtruediv__:[3,1,1,""],__setattr__:[3,1,1,""],__setitem__:[3,1,1,""],__str__:[3,1,1,""],__sub__:[3,1,1,""],__truediv__:[3,1,1,""],add_inport:[3,1,1,""],add_outport:[3,1,1,""],add_param:[3,1,1,""],check:[3,1,1,""],done:[3,1,1,""],info:[3,1,1,""],inport_names:[3,1,1,""],isclocked:[3,1,1,""],isgraphics:[3,1,1,""],outport_names:[3,1,1,""],reset:[3,1,1,""],savefig:[3,1,1,""],set_param:[3,1,1,""],setinput:[3,1,1,""],setinputs:[3,1,1,""],sourcename:[3,1,1,""],start:[3,1,1,""],state_names:[3,1,1,""],step:[3,1,1,""],varinputs:[3,2,1,""],varoutputs:[3,2,1,""]},"bdsim.Clock":{__init__:[3,1,1,""],add_block:[3,1,1,""],getstate0:[3,1,1,""],getstate:[3,1,1,""],next_event:[3,1,1,""],savestate:[3,1,1,""],setstate:[3,1,1,""],start:[3,1,1,""],time:[3,1,1,""]},"bdsim.ClockedBlock":{__init__:[3,1,1,""],blockclass:[3,2,1,""],check:[3,1,1,""],getstate0:[3,1,1,""],reset:[3,1,1,""],setstate:[3,1,1,""]},"bdsim.FunctionBlock":{__init__:[3,1,1,""],__module__:[3,2,1,""],blockclass:[3,2,1,""]},"bdsim.GraphicsBlock":{__init__:[3,1,1,""],__module__:[3,2,1,""],blockclass:[3,2,1,""],create_figure:[3,1,1,""],done:[3,1,1,""],savefig:[3,1,1,""],start:[3,1,1,""],step:[3,1,1,""]},"bdsim.Plug":{__add__:[3,1,1,""],__init__:[3,1,1,""],__mul__:[3,1,1,""],__neg__:[3,1,1,""],__radd__:[3,1,1,""],__repr__:[3,1,1,""],__rmul__:[3,1,1,""],__rshift__:[3,1,1,""],__rsub__:[3,1,1,""],__rtruediv__:[3,1,1,""],__sub__:[3,1,1,""],__truediv__:[3,1,1,""],isslice:[3,1,1,""],portlist:[3,1,1,""],width:[3,1,1,""]},"bdsim.PriorityQ":{__init__:[3,1,1,""],pop:[3,1,1,""],pop_until:[3,1,1,""],push:[3,1,1,""]},"bdsim.SinkBlock":{__init__:[3,1,1,""],__module__:[3,2,1,""],blockclass:[3,2,1,""]},"bdsim.SourceBlock":{__init__:[3,1,1,""],__module__:[3,2,1,""],blockclass:[3,2,1,""]},"bdsim.Struct":{__init__:[3,1,1,""],add:[3,1,1,""]},"bdsim.SubsystemBlock":{__init__:[3,1,1,""],__module__:[3,2,1,""],blockclass:[3,2,1,""]},"bdsim.TransferBlock":{__init__:[3,1,1,""],__module__:[3,2,1,""],blockclass:[3,2,1,""],check:[3,1,1,""],getstate0:[3,1,1,""],reset:[3,1,1,""],setstate:[3,1,1,""]},"bdsim.Wire":{fullname:[3,1,1,""],info:[3,1,1,""],send:[3,1,1,""]},"bdsim.blocks":{connections:[1,3,0,"-"],discrete:[1,3,0,"-"],functions:[1,3,0,"-"],linalg:[1,3,0,"-"],sinks:[1,3,0,"-"],sources:[1,3,0,"-"],transfers:[1,3,0,"-"]},"bdsim.blocks.connections":{DeMux:[1,0,1,""],Dict:[1,0,1,""],InPort:[1,0,1,""],Index:[1,0,1,""],Item:[1,0,1,""],Mux:[1,0,1,""],OutPort:[1,0,1,""],SubSystem:[1,0,1,""]},"bdsim.blocks.connections.DeMux":{__init__:[1,1,1,""]},"bdsim.blocks.connections.Dict":{__init__:[1,1,1,""]},"bdsim.blocks.connections.InPort":{__init__:[1,1,1,""]},"bdsim.blocks.connections.Index":{__init__:[1,1,1,""]},"bdsim.blocks.connections.Item":{__init__:[1,1,1,""]},"bdsim.blocks.connections.Mux":{__init__:[1,1,1,""]},"bdsim.blocks.connections.OutPort":{__init__:[1,1,1,""]},"bdsim.blocks.connections.SubSystem":{__init__:[1,1,1,""]},"bdsim.blocks.discrete":{DIntegrator:[1,0,1,""],DPoseIntegrator:[1,0,1,""],ZOH:[1,0,1,""]},"bdsim.blocks.discrete.DIntegrator":{__init__:[1,1,1,""],next:[1,1,1,""]},"bdsim.blocks.discrete.DPoseIntegrator":{__init__:[1,1,1,""],next:[1,1,1,""]},"bdsim.blocks.discrete.ZOH":{__init__:[1,1,1,""],next:[1,1,1,""]},"bdsim.blocks.functions":{Clip:[1,0,1,""],Function:[1,0,1,""],Gain:[1,0,1,""],Interpolate:[1,0,1,""],Prod:[1,0,1,""],Sum:[1,0,1,""]},"bdsim.blocks.functions.Clip":{__init__:[1,1,1,""]},"bdsim.blocks.functions.Function":{__init__:[1,1,1,""]},"bdsim.blocks.functions.Gain":{__init__:[1,1,1,""]},"bdsim.blocks.functions.Interpolate":{__init__:[1,1,1,""]},"bdsim.blocks.functions.Prod":{__init__:[1,1,1,""]},"bdsim.blocks.functions.Sum":{__init__:[1,1,1,""]},"bdsim.blocks.linalg":{Cond:[1,0,1,""],Det:[1,0,1,""],Flatten:[1,0,1,""],Inverse:[1,0,1,""],Norm:[1,0,1,""],Slice1:[1,0,1,""],Slice2:[1,0,1,""],Transpose:[1,0,1,""]},"bdsim.blocks.linalg.Cond":{__init__:[1,1,1,""]},"bdsim.blocks.linalg.Det":{__init__:[1,1,1,""]},"bdsim.blocks.linalg.Flatten":{__init__:[1,1,1,""]},"bdsim.blocks.linalg.Inverse":{__init__:[1,1,1,""],onames:[1,2,1,""]},"bdsim.blocks.linalg.Norm":{__init__:[1,1,1,""]},"bdsim.blocks.linalg.Slice1":{__init__:[1,1,1,""]},"bdsim.blocks.linalg.Slice2":{__init__:[1,1,1,""]},"bdsim.blocks.linalg.Transpose":{__init__:[1,1,1,""]},"bdsim.blocks.sinks":{Null:[1,0,1,""],Print:[1,0,1,""],Stop:[1,0,1,""],Watch:[1,0,1,""]},"bdsim.blocks.sinks.Null":{__init__:[1,1,1,""]},"bdsim.blocks.sinks.Print":{__init__:[1,1,1,""]},"bdsim.blocks.sinks.Stop":{__init__:[1,1,1,""]},"bdsim.blocks.sinks.Watch":{__init__:[1,1,1,""]},"bdsim.blocks.sources":{Constant:[1,0,1,""],Piecewise:[1,0,1,""],Ramp:[1,0,1,""],Step:[1,0,1,""],Time:[1,0,1,""],WaveForm:[1,0,1,""]},"bdsim.blocks.sources.Constant":{__init__:[1,1,1,""]},"bdsim.blocks.sources.Piecewise":{__init__:[1,1,1,""]},"bdsim.blocks.sources.Ramp":{__init__:[1,1,1,""]},"bdsim.blocks.sources.Step":{__init__:[1,1,1,""]},"bdsim.blocks.sources.Time":{__init__:[1,1,1,""]},"bdsim.blocks.sources.WaveForm":{__init__:[1,1,1,""]},"bdsim.blocks.transfers":{Integrator:[1,0,1,""],LTI_SISO:[1,0,1,""],LTI_SS:[1,0,1,""],PoseIntegrator:[1,0,1,""]},"bdsim.blocks.transfers.Integrator":{__init__:[1,1,1,""]},"bdsim.blocks.transfers.LTI_SISO":{__init__:[1,1,1,""]},"bdsim.blocks.transfers.LTI_SS":{__init__:[1,1,1,""]},"bdsim.blocks.transfers.PoseIntegrator":{__init__:[1,1,1,""]},"roboticstoolbox.blocks":{arm:[1,3,0,"-"],mobile:[1,3,0,"-"],uav:[1,3,0,"-"]},"roboticstoolbox.blocks.arm":{ArmPlot:[1,0,1,""],CTraj:[1,0,1,""],CirclePath:[1,0,1,""],Delta2Tr:[1,0,1,""],FDyn:[1,0,1,""],FDyn_X:[1,0,1,""],FKine:[1,0,1,""],Gravload:[1,0,1,""],Gravload_X:[1,0,1,""],IDyn:[1,0,1,""],IKine:[1,0,1,""],Inertia:[1,0,1,""],Inertia_X:[1,0,1,""],JTraj:[1,0,1,""],Jacobian:[1,0,1,""],LSPB:[1,0,1,""],Point2Tr:[1,0,1,""],TR2T:[1,0,1,""],Tr2Delta:[1,0,1,""],Traj:[1,0,1,""]},"roboticstoolbox.blocks.arm.ArmPlot":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.CTraj":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.CirclePath":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Delta2Tr":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.FDyn":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.FDyn_X":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.FKine":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Gravload":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Gravload_X":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.IDyn":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.IKine":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Inertia":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Inertia_X":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.JTraj":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Jacobian":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.LSPB":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Point2Tr":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.TR2T":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Tr2Delta":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.arm.Traj":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.mobile":{Bicycle:[1,0,1,""],DiffSteer:[1,0,1,""],Unicycle:[1,0,1,""],VehiclePlot:[1,0,1,""]},"roboticstoolbox.blocks.mobile.Bicycle":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.mobile.DiffSteer":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.mobile.Unicycle":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.mobile.VehiclePlot":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.uav":{MultiRotor:[1,0,1,""],MultiRotorMixer:[1,0,1,""],MultiRotorPlot:[1,0,1,""]},"roboticstoolbox.blocks.uav.MultiRotor":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.uav.MultiRotorMixer":{__init__:[1,1,1,""]},"roboticstoolbox.blocks.uav.MultiRotorPlot":{__init__:[1,1,1,""]},bdsim:{BDSim:[3,0,1,""],BDSimState:[3,0,1,""],Block:[3,0,1,""],Clock:[3,0,1,""],ClockedBlock:[3,0,1,""],FunctionBlock:[3,0,1,""],GraphicsBlock:[3,0,1,""],Plug:[3,0,1,""],PriorityQ:[3,0,1,""],SinkBlock:[3,0,1,""],SourceBlock:[3,0,1,""],Struct:[3,0,1,""],SubsystemBlock:[3,0,1,""],TransferBlock:[3,0,1,""],Wire:[3,0,1,""],blockdiagram:[3,2,1,""]},machinevisiontoolbox:{blocks:[1,3,0,"-"]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","attribute","Python attribute"],"3":["py","module","Python module"]},objtypes:{"0":"py:class","1":"py:method","2":"py:attribute","3":"py:module"},terms:{"100":1,"1000":1,"123":[],"125":[],"2004":1,"2hz":1,"2rad":1,"3x4":3,"413716694115407":1,"5th":1,"\u03b3":1,"\u03b8":1,"\u03c9":1,"\u03c9l":1,"\u03c9r":1,"\ud835\uded5p":1,"\ud835\uded5p":[],"\ud835\uded5r":1,"\ud835\uded5r":[],"\ud835\uded5y":1,"\ud835\uded5y":[],"case":[1,3],"class":[1,2],"default":[1,3],"final":[1,3],"float":[1,3],"function":[0,2],"import":[0,1,3],"int":[0,1,3],"new":[1,3],"null":[0,1,3],"return":[0,1,3],"static":[1,3],"switch":3,"true":[0,1,3],"while":[0,3],Adding:0,For:[1,3],Its:3,LHS:3,NOT:1,RHS:3,The:[0,1,3],There:1,These:[1,3],Ths:3,Use:1,Used:3,Useful:1,Uses:1,Using:[2,4],__add__:3,__array_ufunc__:[],__dict__:[],__doc__:[],__getitem__:3,__init__:[1,3],__module__:3,__mul__:3,__neg__:3,__new__:3,__radd__:3,__repr__:3,__rmul__:3,__rshift__:3,__rsub__:3,__rtruediv__:3,__setattr__:3,__setitem__:3,__str__:3,__sub__:3,__truediv__:3,__weakref__:[],_autoconst:[],_autogain:[],_const:3,_eval:[],_fixnam:[],_gain:[0,3],_latex_remov:[],_prod:[1,3],_r_add__:3,_sum:[0,1,3],a1s:1,abc:3,about:[1,3],abov:[0,1],accel_max:1,acceler:1,accept:1,access:1,accord:1,accur:3,activ:1,add:[1,3],add_block:3,add_inport:3,add_outport:3,add_param:3,added:3,adding:1,addit:[0,1],after:[0,1],algebra:2,alia:3,all:[0,1,3],allow:[1,3],along:1,also:[0,1,3],altern:1,altscreen:3,amplitud:1,angl:1,angular:1,ani:[1,2,3],anim:[1,3],annot:0,anoth:3,appear:1,appli:1,applic:1,appropri:[1,3],arbitrari:1,arg:[1,3],argument:[0,1,3],argv:3,arithmet:0,arm:[0,3],armplot:[0,1,3],arrai:[1,2,3],arrang:3,array_lik:1,aspect:1,assembl:0,assign:[0,1,3],associ:1,assum:[0,1,3],attribut:3,automat:1,avail:0,axes:1,axi:1,b1s:1,backend:[1,3],backslash:3,balanc:0,bar:[1,3],base:[1,3],bdedit:1,bdsim:[0,1,2],bdsimpath:3,bdsimstat:3,becom:1,been:3,behaviour:3,being:3,belong:1,below:[1,3],between:[0,1,3],bicycl:[0,1,3],bike:3,black:1,blob:[],block1:1,block2:1,block:0,blockarg:[1,3],blockclass:3,blockdiagram:[0,1,2],blocknam:3,blockset:2,bool:[1,3],both:[1,3],bound:3,boundari:1,box:2,brace:3,bracket:3,build:[0,3],bus:1,call:[0,1,2],callabl:1,camera:[0,3],can:[0,1,2,3],canon:1,canva:3,caret:3,cartesian:1,categori:1,caus:1,center:1,centr:1,charact:1,check:[0,1,3],checkfinit:3,circl:1,circlepath:[0,1,3],circular:1,classnam:3,clean:3,cleaner:1,clip:[0,1,3],clock:[1,3],clockedblock:[1,3],clockwis:1,clone:1,close:0,closefig:3,cls:3,code:[0,1,2],coeffici:1,col:1,collect:3,colon:3,column:[0,1,3],com:[],combin:1,come:1,command:3,common:[1,3],commun:2,compat:1,compil:[0,1,3],compon:[1,2],compris:[1,3],comput:1,concaten:1,conceptu:2,concis:0,cond:[0,1,3],condit:1,condition:1,configur:1,conform:1,connect:[0,2,3],consist:[0,1],consol:1,constant:[0,1,3],construct:3,constructor:3,contain:[0,1,3],content:0,context:0,continu:3,control:1,conveni:0,convert:3,coordin:1,coordinat:1,copi:1,correspond:[0,1,3],could:[0,3],coupl:3,cours:1,creat:[0,1,3],create_figur:3,creation:3,critic:3,cross:0,ctraj:[0,1,3],current:[1,3],cycl:1,d2goal:3,data:[1,3],dataflow:0,date:1,debug:3,deceler:1,declar:1,declare_ev:[1,3],def:1,defin:[0,1,2,3],definit:[1,3],delta2tr:[0,1,3],delta:1,demand:[0,2],demultiplex:1,demux:[0,1,3],denomin:1,denot:0,depend:[0,3],deriv:[1,3],describ:[1,3],descript:[0,1,3],design:0,dest1:0,dest2:0,dest:0,det:[0,1,3],detail:[0,1,3],determin:1,develop:1,diagnost:[0,3],diagram:[0,1,3],dict:[0,1,3],dictionari:[1,2,3],differ:[1,3],differenti:1,diffsteer:[0,1,3],difsteer:1,dimens:1,dimension:1,dintegr:[0,1,3],disabl:[1,3],discard:1,discontinu:1,discret:[0,2],dismiss:0,displai:[0,1,3],divid:[1,3],dividend:3,divis:[],divisor:3,doc:3,docstr:3,document:[2,3],dollar:3,domain:1,don:3,done:[0,3],dot:1,down:1,dposeintegr:[0,1,3],draw:[1,2],drawn:0,drive:3,due:1,dure:[1,3],duti:1,dynam:[0,1,2,3],each:[0,1,3],effector:1,eg1:0,either:[1,3],element:[0,1,3],elementwis:1,elimin:1,els:3,empti:0,enabl:[2,3],enclos:1,end:[1,3],envari:3,environ:3,equal:1,equival:1,error:3,estpose_p:[0,3],etc:[1,3],evalu:1,even:[0,2],event:1,everi:[1,3],exactli:0,exager:1,exampl:[0,1,3],execut:[0,3],exist:3,expect:1,explicit:1,express:[0,1],extens:3,extern:2,extra:1,factor:1,factori:3,fall:3,fals:[1,3],fanci:1,fdyn:[0,1,3],fdyn_x:[0,1,3],fewer:0,fignum:3,figur:[0,1,3],file:[0,1,3],filenam:[1,3],finish:1,first:[0,1,3],fix:1,fkine:[0,1,3],flag:[1,3],flap:1,flapscal:1,flat:0,flatten:[0,1,3],float64:0,fmt:1,fname:3,folder:3,follow:1,forc:1,form:[0,1,2,3],format:[1,3],forward:1,forward_dynam:1,forward_kinemat:1,found:[0,3],foward_dynam:1,foward_kinemat:1,frac:1,frame:1,freq:1,frequenc:1,from:[0,1,3],front:1,fullnam:3,func1:1,func:[1,3],functionblock:[1,3],gain:[0,1,2,3],gener:[0,1],geometr:1,get:[2,3,4],get_opt:3,getstat:3,getstate0:3,github:1,given:[1,3],gradient:1,graph:3,graphic:[0,1],graphicsblock:[1,3],gravcomp:1,gravit:1,graviti:1,gravload:[0,1,3],gravload_x:1,greater:1,ground:1,groundcheck:1,group:0,halt:3,hand:[0,3],handl:3,handler:3,harsh:3,has:[0,1,3],have:[0,1,3],header:1,help:3,heta:1,heta_i:1,heta_p:1,heta_r:1,heterogen:1,hierarch:1,highest:1,highlight:1,histori:[0,3],hold:[0,1,3],html:1,http:1,icon:1,idyn:[0,1,3],ikin:[0,1,3],ikine_lm:1,imag:3,imageplan:[0,3],implement:[0,1,3],implicit:[1,3],implicitli:[0,3],importantli:0,importerror:[1,3],inam:3,includ:[1,3],inclus:1,incom:[1,3],increas:1,index:[0,1,3],indic:3,individu:[0,1],inerti:1,inertia:[0,1,3],inertia_x:[0,1,3],inf:[1,3],info:3,inform:3,init:1,initi:[1,3],inport:[0,1,3],inport_nam:3,input:[0,1,3],instanc:[0,1,3],instanti:[0,1,3],instead:1,integ:1,integr:[0,1,3],inter:3,interact:3,interfac:3,interp:1,interpol:[0,1,3],interpret:3,interv:3,inv:1,invari:1,invers:[0,1,3],inverse_dynam:1,inverse_kinemat:1,invok:3,isclock:3,isgraph:3,isslic:3,item:[0,1,3],iter:[1,3],its:1,jacobian:[0,1,3],joint:1,jpeg:3,jtraj:[0,1,3],junction:[1,3],just:0,keep:[0,1],kei:[1,3],kept:1,keyword:[1,3],kill:0,kind:1,kinemat:1,known:3,kwarg:[1,3],label:[0,1],labview:2,lambda:1,last:[0,1],later:1,latest:1,latex:3,latexd:3,layout:3,left:[1,3],len:1,length:[1,3],less:1,letter:3,level:[1,3],librari:[0,2,3],lies:1,like:[0,1,3],limit:1,linalg:[0,1,3],line:[0,3],linear:[2,3],linearli:1,linestyl:1,link:1,list:[0,1,2,3],load:[0,3],load_block:3,log:[1,3],longitudin:1,look:1,lost:1,lspb:[0,1,3],lti:[0,1],lti_siso:[0,1,2,3],lti_ss:[0,1,3],machin:1,machinevisiontoolbox:[0,1,3],macro:1,made:1,magnitud:1,main:3,major:1,mani:0,manipul:1,map:3,mappingproxi:[],markup:3,master:[],mat:1,match:1,math:[1,3],matlab:1,matplotlib:3,matric:1,matrix:[1,3],max:1,maximum:[1,3],maxw:1,mean:1,mention:3,metadata:3,method:[1,2,3],metr:1,might:1,min:1,minimum:[1,3],minsteplength:3,minsteps:3,minu:3,minuend:3,minw:1,mix:1,mobil:[0,3],model:[1,2],modul:[1,3],monitor:3,monoton:1,more:[0,3],most:3,motion:1,move:1,movi:[0,3],mp4:[0,3],multipl:1,multiplex:1,multipli:[1,3],multirotor:[0,1,3],multirotormix:[0,1,3],multirotorplot:[0,1,3],must:[0,1],mutablemap:3,mux:[0,1,3],myfun:1,name:[0,1,2,3],nan:3,ndarrai:[0,1,3],ndstate:0,need:[0,1,3],negat:3,network:3,newvalu:3,next:[1,3],next_ev:3,nice:0,nin:[0,1,3],nograph:3,non:[1,3],none:[1,3],nonlinear:3,noprogress:3,norm:[0,1,3],normal:3,notat:[0,1],note:[1,3],nout:[0,1,3],now:0,nstate:[0,3],number:[0,1,3],numer:1,numpi:[1,2,3],object:[0,1,2,3],obtain:0,off:1,offset:[1,3],omega_i:1,omega_x:1,omega_z:1,onam:[1,3],one:[0,1,3],onli:[0,1,3],onlin:3,ope:[],open:0,oper:[1,2,3,4],operand:3,ops:1,option:[0,1,3],ord:1,order:[0,1,3],orient:1,ortho:1,osit:1,other:[2,3],otherwis:[1,3],out:[0,1],outport:[0,1,3],outport_nam:3,output:[0,1,3],over:[0,1,3],overload:[2,3,4],overrid:1,overview:[2,4],overwrit:3,packag:[1,2,3],page:0,pair:1,parallel:1,param1:1,param2:1,param3:1,param4:1,param:3,paramet:[1,3],parent:[1,3],parenthes:3,part:1,pass:[1,3],path:[1,3],paulin:1,payload:1,pdf:[0,3],per:0,percentag:3,perform:1,persist:1,perspect:1,petercork:1,phase:1,piecewis:[0,1,3],pinv:1,pitch:1,plan:0,plane:1,plant:[0,2,3],plot:[0,1],plug:1,plugto:3,png:3,point2tr:[0,1,3],point:[0,1],polynomi:1,pop:3,pop_until:3,port0:1,port1:1,port2:1,port:[0,1,3],portlist:3,pos:3,pose:1,poseintegr:[0,1,3],posit:[1,3],postmultipli:1,pound:1,preced:3,prefix:0,premul:1,premultipli:1,present:3,prevent:1,previou:1,print:[0,1,3],priorityq:3,process:3,prod:[0,1,3],product:1,profil:1,program:0,programmat:1,progress:[1,3],progress_don:3,project:1,propag:3,proper:1,properti:[1,3],provid:[1,3],pseudo:1,purpos:[1,3],push:3,pyplot:1,python:[0,1,3],qd0:1,qdf:1,qplot:1,qt5agg:3,quad:1,quintic:1,rad:1,radian:1,radiu:1,rais:[1,3],ramp:[0,1,3],rand:[],rang:1,rate:1,rather:2,ratio:1,read:3,readabl:0,receiv:3,record:[0,3],red:0,refer:[0,1,3],referenc:[1,3],remov:3,replac:1,report:0,repr:3,repres:[1,2,3],represent:1,requir:[0,1],reset:3,respect:1,result:[0,1,3],revers:[1,3],right:[1,3],rise:1,rk45:3,roboticstoolbox:[0,1,3],roll:1,rotat:1,rout:1,row:[0,1],rps:1,rpy:1,rtb:1,rtype:[],run:[0,1,3],run_interv:3,rxc:3,same:[0,1,3],save:[0,3],savefig:[0,3],savest:3,scalar:[1,2,3],scale:1,scipi:3,scope0:0,scope:[0,1,2,3],scopexi:[0,3],scopexy1:[0,3],screen:0,script:0,se3:1,sec:1,second:[0,1,3],secondari:3,see:[0,3],seealso:[1,3],segment:1,select:1,selector:1,self:3,send:3,sent:3,separ:[1,3],seq:1,sequenc:1,sequenti:1,seri:1,serv:1,set:[1,3],set_opt:3,set_param:3,setattr:3,setinput:3,setstat:3,shape:[0,1,3],shift:1,should:1,show:[0,1,3],showgraph:3,shown:[1,3],side:3,sigint:0,sign:[1,3],signal:[0,1,3],signatur:3,sim:[0,3],similar:1,simpl:[0,1,3],simpli:3,simstat:3,simul:[0,1,2,3],simulink:[1,2],sine:1,singl:[0,1,3],sink:[0,2],sinkblock:[1,3],siso:1,size:3,sketch:0,slice1:[0,1,3],slice2:[0,1,3],slice:[0,1,3],slim:1,slope:1,smoothli:1,sname:3,solut:1,solver:3,solver_arg:3,some:[0,1],somewhat:1,sourc:[0,2],sourceblock:[1,3],sourcenam:3,space:1,spatial:1,spatialmath:1,specif:[1,3],specifi:[0,1,3],speed:1,speed_max:1,speedcheck:1,squar:[1,3],src:3,star:1,start:[1,2,3,4],state:[0,1,3],state_nam:3,staticmethod:[],stdout:1,steer:1,steer_max:1,step:[0,1,2,3],stop:[0,1,3],str:[1,3],strike:0,string:[1,2,3],struct:3,structur:1,struggl:3,style:[0,2],subarrai:1,subclass:[1,3],subsequ:0,subsi:1,subsystem:[0,1],subsystemblock:[1,3],subsysytemblock:1,subtract:[1,3],subtrahend:3,sum3:1,sum:[0,1,2,3],summari:[0,1],summat:[1,3],superclass:3,support:[1,2],suppress:3,symmetr:1,syntax:3,sys:3,sysarg:3,system:[0,1,2],systemat:3,tabular:0,take:1,taken:[1,3],task:1,term:[0,1,2],test:[1,3],text:1,than:[1,2],thei:1,them:[0,1],thereof:1,theta:1,theta_i:1,theta_p:1,theta_r:1,thi:[0,1,2,3],thing:3,third:[0,3],those:3,three:1,thrust:1,tile:3,time:[0,2],timestep:0,toolbox:2,top:[1,3],torqu:1,total:1,tpoli:1,tr2delta:[0,1,3],tr2t:[0,1,3],trail:1,traj:[0,1,3],trajectori:1,transfer:[0,2],transferblock:[1,3],transform:1,transit:1,translat:1,transpos:[0,1,3],trapezoid:1,treat:1,triangl:1,tupl:[1,3],turn:1,twist:1,two:[1,3],type:[0,1,2,3],typic:3,uav:[0,1,3],unari:3,undefin:1,underscor:0,unicycl:[0,1,3],uniformli:1,unit:[1,3],unless:1,unlik:2,until:0,unus:3,updat:3,upper:3,url:3,use:[1,3],used:[0,1,3],useprevi:1,user:[0,3],userdict:3,using:[0,1,3],v_x:1,v_y:1,v_z:1,valu:[1,3],valueerror:1,vari:1,variabl:[0,1,3],varinput:3,variou:3,varoutput:3,vector:[0,1,3],vehicl:1,vehicleanim:1,vehicleplot:[0,1,3],velcomp:1,veloc:1,verbos:3,veri:3,version:3,visjac_p:[0,3],vlim:1,want:[0,3],watch:[0,1,3],wave:1,waveform:[0,1,3],weak:[],well:3,wheel:1,wheelbas:1,when:[1,3],whenev:3,where:[0,1,3],whether:[0,3],which:[0,1,3],whose:[1,3],width:[1,3],wiki:0,window:[0,3],wire:[0,1,2],wise:1,within:1,work:1,workspac:1,world:1,would:1,wrap:1,wrench:1,write:[1,2],written:1,wxh:3,x_t:1,xdd:1,xlabel:1,xmax:1,xmin:1,xname:[0,3],xyz:1,yaw:1,yet:1,ylabel:1,ymax:1,ymin:1,yname:[0,3],you:0,zero:1,zeroth:1,zmax:1,zmin:1,zoh:[0,1,3]},titles:["Overview","Block library","Block diagrams for Python","Supporting classes","bdsim"],titleterms:{"class":3,"function":[1,3],Using:0,algebra:1,arm:1,bdsim:[3,4],block:[1,2,3],blockdiagram:3,blockset:1,compon:3,connect:1,diagram:2,discret:[1,3],extern:1,fly:1,get:0,graphic:3,librari:1,linear:1,mobil:1,multi:1,oper:0,overload:0,overview:0,plug:3,python:2,robot:1,rotor:1,simulink:[],sink:[1,3],sourc:[1,3],start:0,subsystem:3,support:3,system:3,time:[1,3],todo:1,toolbox:1,transfer:[1,3],vision:1,wire:3}}) ================================================ FILE: examples/README.md ================================================ # Running the examples From the command line: ``` % pip install bdsim ``` Then you can run the examples from the command line, for example ``` % ./eg1.py ``` which will display a graph in a new figure window. Close the figure to allow the program to exit. Examples provided in this folder include: - `eg1.py` the example given above - `waveform.py` two signal generators connected to two scopes - `vanderpol.py` a classic non-linear oscillator - `sine+sampler.py` a sine wave generator connected to a scope via a ZOH - `subsys.py` a block diagram with a subystem - `RVC2` is a folder holding some examples from Chapter four of [_Robotics, Vision & Control (2017)_](https://petercorke.com/rvc/home): - `rvc4_2.py` Fig 4.2 - a car-like vehicle with bicycle kinematics driven by a rectangular pulse steering signal - `rvc4_4.py` Fig 4.4 - a car-like vehicle driving to a point - `rvc4_6.py` Fig 4.6 - a car-like vehicle driving to/along a line - `rvc4_11.py` Fig 4.11 a car-like vehicle driving to a pose All `bdsim` programs support a number of [command line switches](https://github.com/petercorke/bdsim/wiki/Runtime-options). More examples can be found in the [support package for the book _Robotics, Vision & Control (2023) 3e_](https://github.com/petercorke/RVC3-python/tree/main/RVC3/models). ================================================ FILE: examples/RVC2/README.md ================================================ # Running the examples Examples from Chapter four of [_Robotics, Vision & Control (2017)_](https://petercorke.com/rvc/home): - `rvc4_2.py` Fig 4.2 - a car-like vehicle with bicycle kinematics driven by a rectangular pulse steering signal - `rvc4_4.py` Fig 4.4 - a car-like vehicle driving to a point - `rvc4_6.py` Fig 4.6 - a car-like vehicle driving to/along a line - `rvc4_11.py` Fig 4.11 a car-like vehicle driving to a pose Figs 4.8 (pure pursuit) and Fig 4.21 (quadrotor control) are yet to be done. ================================================ FILE: examples/RVC2/rvc4_11.py ================================================ #!/usr/bin/env python3 # run with command line -a switch to show animation import bdsim from math import pi, sqrt, atan, atan2 sim = bdsim.BDSim() bd = sim.blockdiagram() # parameters xg = [5, 5, pi / 2] Krho = bd.GAIN(1, name="Krho") Kalpha = bd.GAIN(5, name="Kalpha") Kbeta = bd.GAIN(-2, name="Kbeta") xg = [5, 5, pi / 2] x0 = [5, 2, 0] x0 = [5, 9, 0] x0 = [8, 5, pi / 2] # annotate the graphics def background_graphics(ax): ax.plot(*xg[:2], "*") ax.plot(*x0[:2], "o") # convert x,y,theta state to polar form def polar(x, dict): rho = sqrt(x[0] ** 2 + x[1] ** 2) if not "direction" in dict: # direction not yet set, set it beta = -atan2(-x[1], -x[0]) alpha = -x[2] - beta print("alpha", alpha) if -pi / 2 <= alpha <= pi / 2: dict["direction"] = 1 else: dict["direction"] = -1 print("set direction to ", dict["direction"]) if dict["direction"] == -1: beta = -atan2(x[1], x[0]) else: beta = -atan2(-x[1], -x[0]) alpha = -x[2] - beta # clip alpha if alpha > pi / 2: alpha = pi / 2 elif alpha < -pi / 2: alpha = -pi / 2 return [ dict["direction"], rho, alpha, beta, ] # constants goal0 = bd.CONSTANT([xg[0], xg[1], 0], name="goal_pos") goalh = bd.CONSTANT(xg[2], name="goal_heading") # stateful blocks bike = bd.BICYCLE(x0=x0, vlim=2, slim=1.3, name="vehicle") # functions fabs = bd.FUNCTION(lambda x: abs(x), name="abs") polar = bd.FUNCTION( polar, nout=4, persistent=True, name="polar", inames=("x",), onames=("direction", r"$\rho$", r"$\alpha$", r"$\beta"), ) stop = bd.STOP(lambda x: x < 0.01, name="close enough") steer_rate = bd.FUNCTION(lambda u: atan(u), name="atan") # arithmetic vprod = bd.PROD("**", name="vprod") wprod = bd.PROD("**/", name="aprod") xerror = bd.SUM("+-") heading_sum = bd.SUM("++") gsum = bd.SUM("++") # displays vplot = bd.VEHICLEPLOT(scale=[0, 10], size=0.7, shape="box", init=background_graphics) # , movie="rvc4_11.mp4") ascope = bd.SCOPE(name=r"$\alpha$") bscope = bd.SCOPE(name=r"$\beta$") # connections bd.connect(bike, vplot) bd.connect(bike, xerror[0]) bd.connect(goal0, xerror[1]) bd.connect(xerror, polar) bd.connect(polar[1], Krho, stop) # rho bd.connect(Krho, vprod[1]) bd.connect(polar[2], Kalpha, ascope) # alpha bd.connect(Kalpha, gsum[0]) bd.connect(polar[3], heading_sum[0]) # beta bd.connect(goalh, heading_sum[1]) bd.connect(heading_sum, Kbeta, bscope) bd.connect(polar[0], vprod[0], wprod[1]) bd.connect(vprod, fabs, bike.v) bd.connect(fabs, wprod[2]) bd.connect(wprod, steer_rate) bd.connect(steer_rate, bike.gamma) bd.connect(Kbeta, gsum[1]) bd.connect(gsum, wprod[0]) bd.compile() bd.report_summary() out = sim.run(bd, T=10) ================================================ FILE: examples/RVC2/rvc4_2.py ================================================ #!/usr/bin/env python3 # run with command line -a switch to show animation import bdsim sim = bdsim.BDSim(animation=True) bd = sim.blockdiagram() steer = bd.PIECEWISE((0, 0), (3, 0.5), (4, 0), (5, -0.5), (6, 0), name="steering") speed = bd.CONSTANT(1, name="speed") bike = bd.BICYCLE(x0=[0, 0, 0], name="bicycle") q = bd.DEMUX(3) tscope = bd.SCOPE(name="theta") vplot = bd.VEHICLEPLOT( scale=[0, 10, -5, 5], size=0.7, shape="box" ) # , movie='rvc4_2.mp4') bd.connect(bike, q, vplot) bd.connect(speed, bike[0]) bd.connect(steer, bike[1]) bd.connect(q[2], tscope) bd.compile() bd.report() out = sim.run(bd, 10, dt=0.05) ================================================ FILE: examples/RVC2/rvc4_4.py ================================================ #!/usr/bin/env python3 # run with command line -a switch to show animation import bdsim import math sim = bdsim.BDSim(animation=True) bd = sim.blockdiagram() def background_graphics(ax): ax.plot(5, 5, "*") ax.plot(5, 2, "o") goal = bd.CONSTANT([5, 5]) error = bd.SUM("+-", name="error") d2goal = bd.FUNCTION(lambda d: math.sqrt(d[0] ** 2 + d[1] ** 2)) h2goal = bd.FUNCTION(lambda d: math.atan2(d[1], d[0])) heading_error = bd.SUM("+-", angles=True) Kv = bd.GAIN(0.5) Kh = bd.GAIN(4) bike = bd.BICYCLE(x0=[5, 2, 0]) vplot = bd.VEHICLEPLOT(scale=[0, 10], size=0.7, init=background_graphics) # movie="rvc4_4.mp4") vscope = bd.SCOPE(name="velocity") hscope = bd.SCOPE(name="heading") xy = bd.SLICE1([0, 1]) theta = bd.SLICE1([2]) bd.connect(bike, xy, theta, vplot) bd.connect(goal, error[0]) bd.connect(xy, error[1]) bd.connect(error, d2goal, h2goal) bd.connect(d2goal, Kv) bd.connect(Kv, bike[0], vscope) bd.connect(h2goal, heading_error[0]) bd.connect(theta, heading_error[1]) bd.connect(heading_error, hscope) bd.connect(heading_error, Kh) bd.connect(Kh, bike[1]) bd.compile() bd.report() out = sim.run(bd) ================================================ FILE: examples/RVC2/rvc4_6.py ================================================ #!/usr/bin/env python3 # run with command line -a switch to show animation import bdsim import math import numpy as np sim = bdsim.BDSim(animation=True) bd = sim.blockdiagram() # x0 = [8, 5, math.pi/2] x0 = [5, 2, 0] L = [1, -2, 4] def plot_homline(ax, line, *args, xlim, ylim, **kwargs): if abs(line[1]) > abs(line[0]): y = (-line[2] - line[0] * xlim) / line[1] ax.plot(xlim, y, *args, **kwargs) else: x = (-line[2] - line[1] * ylim) / line[0] ax.plot(x, ylim, *args, **kwargs) def background_graphics(ax): plot_homline(ax, L, "r--", xlim=np.r_[0, 10], ylim=np.r_[0, 10]) ax.plot(x0[0], x0[1], "o") speed = bd.CONSTANT(0.5) slope = bd.CONSTANT(math.atan2(-L[0], L[1])) d2line = bd.FUNCTION( lambda u: (u[0] * L[0] + u[1] * L[1] + L[2]) / math.sqrt(L[0] ** 2 + L[1] ** 2) ) heading_error = bd.SUM("+-", angles=True) steer_sum = bd.SUM("+-") Kd = bd.GAIN(0.5) Kh = bd.GAIN(1) bike = bd.BICYCLE(x0=x0) vplot = bd.VEHICLEPLOT(scale=[0, 10], size=0.7, shape="box", init=background_graphics) # , movie="rvc4_6.mp4") hscope = bd.SCOPE(name="heading") xy = bd.INDEX([0, 1], name="xy") theta = bd.INDEX([2], name="theta") bd.connect(d2line, Kd) bd.connect(Kd, steer_sum[1]) bd.connect(steer_sum, bike.gamma) bd.connect(speed, bike.v) bd.connect(slope, heading_error[0]) bd.connect(theta, heading_error[1]) bd.connect(heading_error, Kh) bd.connect(Kh, steer_sum[0]) bd.connect(xy, d2line) bd.connect(bike, xy, theta, vplot) bd.connect(theta, hscope) bd.compile() bd.report_summary() out = sim.run(bd, 20) ================================================ FILE: examples/RVC2/rvc4_8.py ================================================ #!/usr/bin/env python3 # run with command line -a switch to show animation import bdsim import numpy as np import math import roboticstoolbox as rtb # parameters for the path look_ahead = 5 speed = 1 dt = 0.1 tacc = 1 # create the path path = np.array([[10, 10], [10, 60], [80, 80], [50, 10]]) robot_traj = rtb.mstraj(path[1:, :], qdmax=speed, q0=path[0, :], dt=0.1, tacc=tacc).q total_time = robot_traj.shape[0] * dt + look_ahead / speed sim = bdsim.BDSim(graphics=True) bd = sim.blockdiagram() def background_graphics(ax): ax.plot(path[:, 0], path[:, 1], "r", linewidth=3, alpha=0.7) def pure_pursuit(cp, R=None, traj=None): # find closest point on the path to current point d = np.linalg.norm(traj - cp, axis=1) # rely on implicit expansion i = np.argmin(d) # find all points on the path at least R away (k,) = np.where(d[i + 1 :] >= R) # find all points beyond horizon if len(k) == 0: # no such points, we must be near the end, goal is the end pstar = traj[-1, :] else: # many such points, take the first one k = k[0] # first point beyond look ahead distance pstar = traj[k + i, :] return pstar.flatten() speed = bd.CONSTANT(speed, name="speed") error = bd.SUM("+-", name="err") # d2goal = bd.FUNCTION(lambda d: math.sqrt(d[0]**2 + d[1]**2), name='d2goal') h2goal = bd.FUNCTION(lambda d: math.atan2(d[1], d[0]), name="h2goal") heading_error = bd.SUM("+-", mode="c", name="herr") Kh = bd.GAIN(0.5, name="Kh") bike = bd.BICYCLE(x0=[2, 2, 0]) vplot = bd.VEHICLEPLOT( scale=[0, 80, 0, 80], size=0.7, shape="box", init=background_graphics ) # , movie='rvc4_8.mp4') sscope = bd.SCOPE(name="steer angle") hscope = bd.SCOPE(name="heading angle") # stop = bd.STOP(lambda x: np.linalg.norm(x - np.r_[50,10]) < 0.1, name='close_enough') pp = bd.FUNCTION( pure_pursuit, fkwargs={"R": look_ahead, "traj": robot_traj}, name="pure_pursuit" ) xy = bd.INDEX([0, 1], name="xy") theta = bd.INDEX([2], name="theta") bd.connect(pp, error[0]) bd.connect(error, h2goal) # bd.connect(d2goal, stop) bd.connect(h2goal, heading_error[0]) bd.connect(theta, heading_error[1], hscope) bd.connect(heading_error, Kh) bd.connect(Kh, bike[1], sscope) bd.connect(speed, bike[0]) bd.connect(bike, xy, theta, vplot) bd.connect(xy, pp, error[1]) bd.compile() bd.report_summary() print("\nSimulating for ", total_time, " seconds") out = sim.run(bd, T=total_time) ================================================ FILE: examples/deriv.py ================================================ #!/usr/bin/env python ''' Demonstrate derivative block ''' import bdsim sim = bdsim.BDSim() bd = sim.blockdiagram() u = bd.WAVEFORM("sine", freq=1, unit="rad/s") du = bd.DERIV(0.1, y0=1) scope = bd.SCOPE(styles=['k','r--']) # connect bd.connect(u, scope[0], du) bd.connect(du, scope[1]) bd.compile() sim.report(bd) out = sim.run(bd, 10) ================================================ FILE: examples/eg1.bd ================================================ { "id": 140192902056064, "created_by": "corkep", "creation_time": 1632824708, "scene_width": 7207.200000000001, "scene_height": 3595.2000000000003, "blocks": [ { "id": 140596124728768, "block_type": "GAIN", "title": "Gain Block", "pos_x": -80.0, "pos_y": -120.0, "width": 100, "height": 100, "flipped": false, "inputsNum": 1, "outputsNum": 1, "inputs": [ { "id": 140596124784720, "index": 0, "multi_wire": true, "position": 1, "socket_type": 1 } ], "outputs": [ { "id": 140596124784768, "index": 0, "multi_wire": true, "position": 3, "socket_type": 2 } ], "parameters": [ [ "K", 10 ], [ "premul", false ] ] }, { "id": 140596130326608, "block_type": "SUM", "title": "Sum Block", "pos_x": -260.0, "pos_y": -120.0, "width": 100, "height": 100, "flipped": false, "inputsNum": 2, "outputsNum": 1, "inputs": [ { "id": 140596130326800, "index": 0, "multi_wire": true, "position": 1, "socket_type": 1 }, { "id": 140596130326656, "index": 1, "multi_wire": true, "position": 1, "socket_type": 1 } ], "outputs": [ { "id": 140596130326896, "index": 0, "multi_wire": true, "position": 3, "socket_type": 2 } ], "parameters": [ [ "signs", "+-" ], [ "mode", null ] ] }, { "id": 140596130327856, "block_type": "LTI_SISO", "title": "Lti_siso Block", "pos_x": 80.0, "pos_y": -120.0, "width": 100, "height": 100, "flipped": false, "inputsNum": 1, "outputsNum": 1, "inputs": [ { "id": 140596130327424, "index": 0, "multi_wire": true, "position": 1, "socket_type": 1 } ], "outputs": [ { "id": 140596130327472, "index": 0, "multi_wire": true, "position": 3, "socket_type": 2 } ], "parameters": [ [ "N", 0.5 ], [ "D", [ 2, 1 ] ], [ "x0", null ] ] }, { "id": 140596130327376, "block_type": "STEP", "title": "Step Block", "pos_x": -460.0, "pos_y": -120.0, "width": 100, "height": 100, "flipped": false, "inputsNum": 0, "outputsNum": 1, "inputs": [], "outputs": [ { "id": 140596130327712, "index": 0, "multi_wire": true, "position": 3, "socket_type": 2 } ], "parameters": [ [ "T", 1.0 ], [ "off", 0.0 ], [ "on", 1.0 ] ] }, { "id": 140596130328000, "block_type": "SCOPE", "title": "Scope Block", "pos_x": 280.0, "pos_y": 20.0, "width": 100, "height": 100, "flipped": false, "inputsNum": 2, "outputsNum": 0, "inputs": [ { "id": 140596130328480, "index": 0, "multi_wire": true, "position": 1, "socket_type": 1 }, { "id": 140596130400096, "index": 1, "multi_wire": true, "position": 1, "socket_type": 1 } ], "outputs": [], "parameters": [ [ "nin", 2 ], [ "vector", null ], [ "styles", null ], [ "stairs", false ], [ "scale", "auto" ], [ "labels", null ], [ "grid", true ], [ "watch", false ] ] } ], "wires": [ { "id": 140596130377056, "start_socket": 140596130326896, "end_socket": 140596124784720, "wire_type": 3, "custom_routing": false, "wire_coordinates": [] }, { "id": 140596130449008, "start_socket": 140596124784768, "end_socket": 140596130327424, "wire_type": 3, "custom_routing": false, "wire_coordinates": [] }, { "id": 140596130449632, "start_socket": 140596130327472, "end_socket": 140596130328480, "wire_type": 3, "custom_routing": true, "wire_coordinates": [ [ 280.0, 60.0 ], [ 240.0, 60.0 ], [ 240.0, -80.0 ], [ 180.0, -80.0 ] ] }, { "id": 140596130449680, "start_socket": 140596130327712, "end_socket": 140596130326800, "wire_type": 3, "custom_routing": false, "wire_coordinates": [] }, { "id": 140596130449920, "start_socket": 140596130327472, "end_socket": 140596130326656, "wire_type": 3, "custom_routing": true, "wire_coordinates": [ [ -260.0, -60.0 ], [ -300.0, -60.0 ], [ -300.0, 60.0 ], [ 240.0, 60.0 ], [ 240.0, -80.0 ], [ 180.0, -80.0 ] ] }, { "id": 140192961940304, "start_socket": 140596130327712, "end_socket": 140596130400096, "wire_type": 3, "custom_routing": true, "wire_coordinates": [ [ 280.0, 80.0 ], [ -330.0, 80.0 ], [ -330.0, -80.0 ], [ -360.0, -80.0 ] ] } ], "labels": [], "grouping_boxes": [] } ================================================ FILE: examples/eg1.py ================================================ #!/usr/bin/env python3 """ Example of continuous-time system Copyright (c) 2021- Peter Corke """ import bdsim sim = bdsim.BDSim(animation=True) # create simulator bd = sim.blockdiagram() # create an empty block diagram # define the blocks demand = bd.STEP(T=1, name="demand") sum = bd.SUM("+-") gain = bd.GAIN(10) plant = bd.LTI_SISO(0.5, [2, 1], name="plant") scope = bd.SCOPE(styles=["k", "r--"], loc="lower right") # , movie='eg1.mp4') # connect the blocks bd.connect(demand, sum[0], scope[1]) bd.connect(plant, sum[1]) bd.connect(sum, gain) bd.connect(gain, plant) bd.connect(plant, scope[0]) bd.compile() # check the diagram sim.report(bd) # , format="latex") sim.report(bd, "schedule") # bd.dotfile("eg1a.dot") out = sim.run(bd, T=5) # , watch=[demand, sum]) # simulate for 5s # out = sim.run(bd, watch=[plant, demand]) # simulate for 5s print(out) # scope.savefig() # save scope figure as scope0.pdf, need to set hold=False ================================================ FILE: examples/eg1_zoh.py ================================================ #!/usr/bin/env python3 import bdsim sim = bdsim.BDSim(animation=True) # create simulator bd = sim.blockdiagram() # create an empty block diagram # define the clocks clock1 = bd.clock(10, "Hz") # define the blocks demand = bd.STEP(T=1, name="demand") sum = bd.SUM("+-") gain = bd.GAIN(10) plant = bd.LTI_SISO(0.5, [2, 1], name="plant") scope = bd.SCOPE(styles=["k", "r--"]) zoh = bd.ZOH(clock=clock1) # connect the blocks bd.connect(demand, sum[0], scope[1]) bd.connect(plant, sum[1]) bd.connect(sum, gain) # bd.connect(gain, plant) bd.connect(gain, zoh) bd.connect(zoh, plant) bd.connect(plant, scope[0]) bd.compile() # check the diagram bd.report() bd.report_summary() # list all blocks and wires out = sim.run(bd, watch=[demand, sum]) # simulate for 5s # out = sim.run(bd, 5 watch=[plant,demand]) # simulate for 5s print(out) # sim.savefig(scope, 'scope0') # save scope figure as scope0.pdf ================================================ FILE: examples/pid.py ================================================ #!/usr/bin/env python ''' Example of PID control system Copyright (c) 2021- Peter Corke ''' import bdsim sim = bdsim.BDSim(animation=True) # create simulator bd = sim.blockdiagram() # create an empty block diagram # define the blocks dmd = bd.STEP(T=1, on=1, name="demand") controller = bd.PID(P=5, D=0, I=-5, I_band=0.3, name="PID") plant = bd.LTI_SISO(0.5, [2, 1], name="plant") scope = bd.SCOPE(styles=["k", "r--"]) # connect the blocks bd.connect(dmd, controller[1], scope[0]) bd.connect(controller, plant) bd.connect(plant, controller[0], scope[1]) bd.compile() sim.report(bd) out = sim.run(bd, 10) print(out) ================================================ FILE: examples/rt_step.py ================================================ #!/usr/bin/env python3 """ Example of real-time system using Arduino+Firmata Copyright (c) 2023- Peter Corke """ import bdsim from bdsim.blocks.IO.Firmata import * sim = bdsim.BDRealTime( toolboxes=False, graphics=True, animation=True ) # create RT model bd = sim.blockdiagram() # create an empty block diagram scope = bd.SCOPE(scale=[0, 1]) # define the blocks # demand = bd.WAVEFORM(wave="triangle", name="demand") pot = AnalogIn(pin=0) # led = AnalogOut(pin=6, scale=0.5, offset=0.5, bd=bd) led = AnalogOut(pin=6) # connect the blocks bd.connect(pot, led, scope) bd.compile() # check the diagram bd.report_summary() # list all blocks and wires out = sim.run(bd, T=10, dt=0.2, samples=False, watch=[pot]) # simulate for 5s print(out) print(out.y0) ================================================ FILE: examples/sine+sampler.py ================================================ #!/usr/bin/env python3 """ Example with one ZOH sampling a sine wave Copyright (c) 2021- Peter Corke """ import bdsim import time sim = bdsim.BDSim() bd = sim.blockdiagram() clock = bd.clock(5, "Hz") # define the blocks sine = bd.WAVEFORM("sine", freq=0.2, unit="Hz") zoh = bd.ZOH(clock) scope = bd.SCOPE(styles=[dict(color="b"), dict(color="r", drawstyle="steps")]) # connect the blocks bd.connect(sine, zoh, scope[0]) bd.connect(zoh, scope[1]) bd.compile() # check the diagram bd.report() # list all blocks and wires print(clock) out = sim.run(bd, 5) # simulate for 5s sim.done(bd, block=True) ================================================ FILE: examples/sine+sampler2.py ================================================ #!/usr/bin/env python3 """ Example with two ZOHs sampling a sine wave Copyright (c) 2021- Peter Corke """ import bdsim import time sim = bdsim.BDSim() bd = sim.blockdiagram() clock1 = bd.clock(5, "Hz") clock2 = bd.clock(10, "Hz") # define the blocks sine = bd.WAVEFORM("sine", freq=0.2, unit="Hz") zoh1 = bd.ZOH(clock1) zoh2 = bd.ZOH(clock2) scope = bd.SCOPE(styles=[dict(color="k", linestyle="--"), dict(color="r", drawstyle="steps"), dict(color="b", drawstyle="steps")]) # connect the blocks bd.connect(sine, zoh1, zoh2, scope[0]) bd.connect(zoh1, scope[1]) bd.connect(zoh2, scope[2]) bd.compile() # check the diagram bd.report() # list all blocks and wires print(clock1) print(clock2) out = sim.run(bd, 5) # simulate for 5s sim.done(bd, block=True) ================================================ FILE: examples/subsys.py ================================================ #!/usr/bin/env python3 import bdsim sim = bdsim.BDSim() # create simple subsystem as a blockdiagram ss = sim.blockdiagram(name="subsystem1") squarer = ss.FUNCTION(lambda x: x**2) sum = ss.SUM("++") inp = ss.INPORT(2) outp = ss.OUTPORT(1) ss.connect(inp[0], squarer) ss.connect(squarer, sum[0]) ss.connect(inp[1], sum[1]) ss.connect(sum, outp) # create main system as a blockdiagram main = sim.blockdiagram() x = main.WAVEFORM("sine", 1, "Hz") const = main.CONSTANT(1) scope = main.SCOPE() subsys = main.SUBSYSTEM(ss) # instantiate the subsystem main.connect(x, subsys[0]) main.connect(const, subsys[1]) main.connect(subsys, scope) main.compile(verbose=False) sim.report(main) sim.run(main, T=5) ================================================ FILE: examples/vanderpol.py ================================================ #!/usr/bin/env python3 """ Example of van der Pol oscillator Copyright (c) 2021- Peter Corke """ import bdsim sim = bdsim.BDSim(animation=True, globs=globals()) # create simulator bd = sim.blockdiagram() # create an empty block diagram λ = 0.3 # define the blocks x = bd.INTEGRATOR(x0=1) # integrator block y = bd.INTEGRATOR(x0=1) # integrator block scope = bd.SCOPEXY(scale=[-2, 2, -2, 2]) # we can write this two different ways ## 1. Using a 2-input function block, probably computationally efficient f = bd.FUNCTION(lambda x, y: -x + λ * (1 - x**2) * y, nin=2) bd.connect(x, scope[0], f[0]) bd.connect(y, x, scope[1], f[1]) bd.connect(f, y) ## 2. Using implicit block creation # x[0] = y # y[0] = -x + λ * (1 - x**2) * y # scope[0] = x # scope[1] = y bd.compile() # check the diagram bd.report_summary() # list all blocks and wires # probably needs a stiff integrator out = sim.run(bd, T=20, solver="BDF") # , watch=[demand, sum]) # simulate for 5s ================================================ FILE: examples/viewsim.py ================================================ import pickle from bdsim import BDStruct import sys sys.setrecursionlimit(20_000) with open("bd.out", "rb") as f: out = pickle.load(f) print(out) ================================================ FILE: examples/waveform.py ================================================ #!/usr/bin/env python3 """ Example with two waveform generators driving a scope Copyright (c) 2021- Peter Corke """ import bdsim sim = bdsim.BDSim(animation=True) # create simulator bd = sim.blockdiagram() wave1 = bd.WAVEFORM(wave="triangle", freq=1, phase=0.25) wave2 = bd.WAVEFORM(wave="square", freq=1, min=0, max=1) scope1 = bd.SCOPE() scope2 = bd.SCOPE() bd.connect(wave1, scope1) bd.connect(wave2, scope2) bd.compile() sim.report(bd) out = sim.run(bd, 4, dt=0.02) ================================================ FILE: figs/data_structures.ezdraw ================================================ AAAA_CurrentOSXVersion macOS Catalina: 1676.105000 AAAA_DKDDocumentVersion 9.0.0 : Mobile Friendly AAAA_EazyDrawVersion 9.7.0 AAAB_Build 9087 AAAB_Distribution Free Market AAA_DKDQuicklookData JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPDwgL0xlbmd0aCA1IDAgUiAvRmls dGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAGlmkmPHMcRhe/1K/JIHljKfblK8EUn CxrAB0EHe0RZtIekyaGgv+/vZS1dXd3VPaIgQJwJVmZkxvLiRSQ/mR/MJ2PHbN6Zb757 dubx2Tjz/Ggs/7XmR+ezqTaNtbZqPr81vxpvvten3nz3Y//Kmh+/M58G1395oz/W7x/f G+fCGKx3JrrRRRsykjQW66qJefTVlqBtf+znuLLHwB4+cY4Yw9fuYdgjNDu2muONPYZb 59Aeybex+ppv7DHfZfiEFWXCC3vkXMdcLLf/enusNvUeE+aAYRZTrj/cu4urfiw+eHO8 x117rH453uOuPYJ3Y8op3DjH3fhYfXv3HMT6gV9itmO0Jd04x117rPFx9xyDH3PIPpg/ yL0aSnSVaOk/hVLnn3xxPbWOjkxEu+yKIZWKK2NILhgyNmUX83JW9ESbfULR4dWXfbwN pLtvJtk22lovNnM6bv9PGX90LB/TGCrLXQhTopPBpY4uFTessqdVZlwso/XVIGpujJV4 vibKaYw+Fn1WytiyH9yFyDgUtZidvtI5ivOHslJKnL/z1udBa3eyfgfdZd5uudYVUYe0 J/PbpJcPTnd93+FtgVc7wes7WRAATKnkGSqW394Ui0FCc6bGOnLnJBT89sFwmQlUSNvk MG4GTgncwMfm4b355uHB4ZaHX82r5y///PzltXn4j/nbA9g6IfxVn2nv4NwYpHD1D7IY R2cx0Sp72shWn4VUxhiE65No4KtVdHJQiGFsRBfnXf14kp281k/iXVq9pv10ukU2e0gy l4iajddOMiJvclFfulxMTpl3W0Sr184+7NLJa5bCeOyoWseaCnl67KjgiMgcOeh1R739 8MvJTW5syTs+Fi7koaueXc4f27oK7I8EdzG4fizNFkOWXcgWybB+tUhO6x5ZR0XOUdin TQXGKY3N1ma8a2O2EoQRuHfGgw0utDCwjBCMlG+SB5FC0vMNq12LHClOErANhCJGSEvz uGSCF9GYsOT5cdDu1ttsQCr0Fq/LcITqcjAZA0YHzpGDiViPbCxZyNUjI+H2q4f30BTg vFYLANyCvnWpKw16AE5uFPdTl+qImVWxZJghnyverBaR2XiR0Ln0X+c0JZHumESeIcP6 lS9k/r5EjgAPA47Bf2l0LfMD14kVhzqKSsv8UIkSm/jB4grXsL8J4D2VN3YH5hLqECgj KVm8LNYGXTJsC03gmJMxWppdqLLtG8i18RgoNOJt172DwtSTDQAr1BR5jJ0FzL+x6+Xq F3isI9WydGPzVbEOma1VWCjtumLJKEcKn5NisH7190s81hlkIPisMBLqloFtYiXMsmGV LZLTV5cSTO/raG3CrNiMrKtIwhhTpGpYkseRViGAv8mChPiJXIObg6x5TA4m7izFX+ET LdgIpnb4TfgdCRwAcjG0BBeoSKaskzyG1LlCjah8z6d8wvbISJdEwDwho/jEULqMoCLU h9/6prvFL3CYzLaoBSptTK2SnKtaThiD66V/VYssV+ta99d+MWfu7jrK6Z5ZvsORp1h5 UEUOyzhbYZHFGyxYeY8dR4i1dT7e2GNhWFczXBf3yzkyyVRziyf1u3MM5FiCpoG30081 iwNKptpyk2yhBHMSB+JEsXnM66EPyePBRYYpT7JGilMzJYP42UZJjldkyUeiyHfwYz+2 c/Q/k2zeb5LBEkIlXNmPs4RCcKYDGXWjCEwXw+g7yea11BDOAhXgg0U23+2abL5vrwB7 K8xxQlVJXuj1h2CYHrK54CKAfGTSCWG4USS9V/OR5wUzZ3JmlYEwq+xkvgD/toG4WUwq CFxkG/NprSvUuauyk/nYiuxuUPJZpv22stl8klE2Q/9uL9uYtK+d7jaZdNnv7L4TUl5a YYeUV6J2juS7UQutrRSeHCBxdPa4y+9Ew15w8QWwJgZSaQkyRS2pYyEkCUEcnoHFWEh5 DxWEpMQh1QydjUJDD2qnwo/8D7ABIWiEwfZsUqZhoqrp9xYSoQzjgHbNELreeL7neZ4y NZE2ijmciI1So1xyswCnEQyJ0XACRTsHkMUlsdmqUEJjrqx9IcauSxMsm7TuKLCo1RVK 5S4nvf1WvilpN3qXxbvkGV6aPB30iuyORzOzkhgBA+HRTjZcSC6/kZvwTszdmVDoKloK yOUIS0sUdeiJGzzJlVUkE/MMAoASCdribzlXcyY6JBXSEvBF0scWIAsBMlKoezKEbXT9 j8NUV7YggT/piaha9NWizV6ESH0R16kokPdyFaOh/aFc5MmhtfnJodcWy6ElE2p0ODda 8nVpooal5hywvurVob2ifaM4RUpc6zAMy1lOvay+9Ohh1cJyGAtmHVuGs8N0pXkvWyTD +tUiOa3DekyjQlJxogDjttRPBtNkGsi9fATY+HsVDQTyRG7QWdganJNviOCE4fk7AsFz OQjkyJ3YkDhn9AFG058QTXFOUMn7kDHQgrgAnYJtcAogEFUgAFnfSQ4+pL5DspAlmFjP hIvV3H3jMBLh0GzrUvgvhVt5v+rVGTX6REec9Q6SBSLlXO9p8R5qexNxq2r5CimF7WBQ EUI4PMFebIO/kRDZZkr+PdbjyaEEsR2CUqewFN9joOaYAqwyom6V0VVE8I1bLGv5y1UG tlzZUVc7tmMjz+h/AzEOCfbnt4C1tru8i0YT1yvvwQ94fhXvglESLbKE9limkn+aH/Q+ gJaIAVOokKFGzcBGXTKskun30xf73wlxVgAhhDYMnKJVenbnIgLEmKBGIlftGUETac5p 0gRtHYQcRY3KRvuLR0XraM+gZjkyCc7UOwqPTeSKamIG0mdo82zrwQynObhKHbicNDlI Dhn46lKvTGizkTBykJoA1vYA3S8mqzZ5cQvIlpXMJBltEO8btQTl2KgVVD9NILrabEFm D2T0yjSfebN4lxc/GaiW+Zns+EUZMhwzO3kOPg1YAABUdkIEHLkmW74a1q8WyWndI3tB J+hdQCmogpMrK47LCU5HmEH6ADAFcYthEA4ztxL3AMeYreIoeYzEmkIdh1Ay6b6peaSa YA6HUEQVsJ2z6OLTDUUzFOJWQzJlPXBKDGoNRBwRcVmoVOQpstzgWep2ne3tHTk5Lx5O i89pxk+TPQ9zdNmA4xF0lGIVp0V9pj9nltZb8EV9pptijjLTjeXsy2oOr8vN41Fc2F+f bk3dxK0sQ0bTyB6QjbnSNB5dZi1wLSwHOwMEcDWMqA3n49F3H/738fOX59Pkjdsu49dp xLr89gZtFA3ShPElbLU5NQkaxu60RagdhRNeEnaz2I+/f3mxNof9GJMyfKeQMO/2l9pI T0JP1ZSIo/CXv6ROz2KQU5kSGlmuqePFKqsCYMpayJqvNiXjKXIJw1WIfw7+ijaig2ky LiW+qZq0XOfa/vX08fG/T++eN7PtY9dJH6CDPpKCXevl7dBH/Rdw1v4syfR8O0f/ybz6 493n12SVefV2UvuzefieofrwgyLGJksK92ktg2IojGN4wWuOuk0CgeEKnedR3VbYivfQ sIQBX9rEGUkHNZnABcMh/gRkSOU5Q6ax6axl1YGKuznjqDgUitAn1eTOFVNo6MEwWyN6 voWFnZvi1d+ffv+3efeBl4UPj29PmQOT4mXHN+jRjVeGfldi20aSkgk3F6O4dScXSCan K9UywhRHwbS3x+6U92leXJmH0W5Q5PYpGUFGokx4qPClJdr79R+vzRs69Verezd3m30s zO1P44tjV5Pz3H3TqwXma/EcrENlgmMQzlbV4eymUjBN+Re42YGPbsoDb8VtnEHk9cpN eVkRv0UPGZPq+U1ffauM2dztBU9C3VkMYihg+EhPAE5hwWQSuNHAf3LWQsU2eUAuqkxd vnvDJZhE65SwDsKvM2Q2ojGFlOjBiIIIE1CgH27CSxijGRU0jbP1skCysAn0Xj28LAxy 9E2GzRBeB7rNDLzok3in5riQqj6RmGV9tttll19dSsTSNFSQ4RjyApxqFGlJM9gKsDPS pVvpLM3hTaYflFCyXuv04EFz6xjGMlZX08lfUrexvHqxqnX0NzymarZbMrVi7numG4oZ 8AEMMfcpL71zw0LauPFAqCGvyxBj8S2MlumEJbNQhGkCcWXxOTU49Myql2yjaDUowaJW R9U/NNholajp3y1MLG8+8rx05gOL/zqsfPV4lM7RV/r7yMCgRB6gOdde9iIJpmVdlR3V zfKCknlUg4Yn6i9zbQib/EP4APqQUh5Dm/6Zh9bBfih2yNTzMojxegvFYr3DzfBChl16 AOMTKCAMYxnZk2fUy6O5sNwtYimGF+jWeTiM/X6MJXiTgx6AsqH3th70AHTV7yom4Zjd 8vvVmH7j71v97qo48h6YC8Eqw86K+y1SHy8timl4Ibe+9xhQ0EXxZnUnuF9T2ahaDPhg vrA8+JnLV4o8QUnLhZmYuzbl5lkxOITIi9J2UQfuVPfegwcmlCHD5pi7k9yQVz2v7EH0 dsWD8sGsGVgwgyLUeB+4qAOO64MavHIQqDxU7SgvTObPVLyrmS7L6jWqvwgS0hOrPy5t N9/OuRIDUtKhiQ2A6pd+ExJSFuioNFSheJy77YCQgJ63Xs6dmoZAJML7CItDFkIh+uH/ Xin6FgplbmRzdHJlYW0KZW5kb2JqCjUgMCBvYmoKMzQ0NQplbmRvYmoKMiAwIG9iago8 PCAvVHlwZSAvUGFnZSAvUGFyZW50IDMgMCBSIC9SZXNvdXJjZXMgNiAwIFIgL0NvbnRl bnRzIDQgMCBSIC9NZWRpYUJveCBbMCAwIDk5Mi4xMjYgODA1Ljg4OThdCj4+CmVuZG9i ago2IDAgb2JqCjw8IC9Qcm9jU2V0IFsgL1BERiAvVGV4dCBdIC9Db2xvclNwYWNlIDw8 IC9DczEgNyAwIFIgL0NzMiA4IDAgUiA+PiAvRm9udCA8PAovVFQxIDkgMCBSID4+ID4+ CmVuZG9iagoxMCAwIG9iago8PCAvTGVuZ3RoIDExIDAgUiAvTiAxIC9BbHRlcm5hdGUg L0RldmljZUdyYXkgL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngBpVcHWJPX Gj7/SMJK2FNG2MgyoOwZmQFkD0FUYhJIGCEGgoC4KMUK1i0OHBUtilK0WhEoLtTioG5Q 67hQSwWlFqu4sHrPCaDQ9rn3Ps/N/xz+93xnfOs9338AQF3IlUiycQBAjjhfGhLLTp6Z nMKk3QMKQBeoAkegyuXlSdjR0RFwChDnigXoPfH3sgtgSHLDAe01cey/9ih8QR4PzjoF WxE/j5cDAOYNAK2PJ5HmA6BoAeXmC/IlCIdCrJUVHxsAcSoACiqja6EYmIQIxAKpiMcM kXKLmCHcnBwu09nRmRktzU0XZf+D1WjR//PLyZYhu9HPBDaVvKy4cPh2hPaX8bmBCLtD fJjHDYobxY8LRImREPsDgJtJ8qfHQhwG8TxZVgIbYnuI69OlwQkQ+0J8WygLRXgaAIRO sTA+CWJjiMPE8yKjIPaEWMjLC0iB2AbiGqGAg/IEY0ZcFOVz4iGG+oin0txYNN8WANKb LwgMGpGT6Vm54cgGMyj/Lq8gDsnlNhcLA5CdUBfZlckNi4bYCuIXguwQNB/uQzGQ5Eej PWGfEijOjkR6/SGuEuTJ/YV9Sle+MB7lzBkAqlm+NB6thbZR49NFwRyIgyEuFEpDkRz6 Sz0hyZbzDMaE+k4qi0W+Qx9pwQJxAooh4sVSrjQoBGIYK1orSMS4QABywTz4lwfEoAcw QR4QgQI5ygBckAMbE1pgD1sInCWGTQpn5IEsKM+AuPfjOOqjFWiNBI7kgnQ4MxuuG5My AR+uH1mH9siFDfXQvn3yfXmj+hyhvgDjr4EMjgvBABwXQjQDdMslhdC+HNgPgFIZHMuA eLwWZ8gjZxAtt3XEBjSOtPSPasmFK/hyXSPrkJcjtgVAm8WgGI4h2+Sek7oki5wKmxcZ QfqQLLk2KZxRBBzkcm+5bEzrJ8+Rb/0ftc6Hto73fny8xmJ8GsYrH+6cDT0Uj8YnD1rz DtqdNbr6UzTlGtcYyGwkkqpVMZw5tXKLke/MUulcEe/K6sH/kLVP2RrT7jAhb1HjeSFn Cv9vvIC6KNcpVykPKDcBE75/oXRS+iC6S7kHnzsf7YkexwcUe8QcEfwrgj6OMWCEWTy5 BOUiGz4oL3+381PORvb5yw4YIdeLOMuW74IYlgMbyqxAntcQqJ8L85EHoy2DPEXccICM GZ+7ES3jTkB7SaseYHatPHUBMOvVms/Ltcij3Uk2pd5QaS9JF68xkEjm1JYMCySfRlEe BMsjX0aCUnvWIdYAaw+rnvWc9eDTDNYt1m+sTtYuOPKEWE8cJY4TzUQL0QGYsNdCnCaa 5aieaIXPtx/XTWT4yDmayHDEN94oo5GP+aOcGs/9cR7K4zUWLTR/LFOZoyd1PPdQfMcz BmXsf7NofEYnVoSR7MhPHcOc4cSgMWwZLgw2A2OYwseZ4Q+ROcOMEcHQhaOhDGtGIGPS x3iMnHFkBzrviGFjdeFTFUuGo2NMQP4JIQ+k8prFHfX3rz4yJ3iJKppo/KnC6PBkjmga qQljOsfiKmfIhJOVADWJwAJohxTGFZ12MawlzAlzUCVGVQgyEpslz+E/nATSmHQiObAy RQEmySZdSP9RjKqVN3xQrRqp3g6kHxz1JQNJd1THxnsAdx+JF6po/2z9+JMhoHpSralB VGv53nLvqIHUUGowYFKdkJw6hRoGsQealS8ohHcPAAJyJUVSUYYwn8mGtxwBkyPmOdoz nVlO8OuG7kxoDgDPY+R3IUyngyeTFozISPSiACV4n9IC+vCrag6/1g7QKzfgBb+ZQfAO EAXiQTKYA/0QwkxKYWRLwDJQDirBGrARbAU7wR5QBxrAYXAMtILT4AdwCVwFneAu/J70 gidgELwEwxiG0TA6ponpYyaYJWaHOWPumC8WhEVgsVgyloZlYGJMhpVgn2GV2DpsK7YL q8O+xZqx09gF7Bp2B+vB+rE/sLc4gavgWrgRboVPwd1xNh6Ox+Oz8Qx8Pl6Ml+Gr8M14 DV6PN+Kn8Ut4J96NP8GHCEAoEzqEKeFAuBMBRBSRQqQTUmIxUUFUETVEA6wB7cQNopsY IN6QVFKTZJIOMIuhZALJI+eTi8mV5FZyH9lIniVvkD3kIPmeQqcYUuwonhQOZSYlg7KA Uk6potRSjlLOwQrdS3lJpVJ1YH7cYN6SqZnUhdSV1O3Ug9RT1GvUh9QhGo2mT7Oj+dCi aFxaPq2ctoVWTztJu07rpb1WUFYwUXBWCFZIURArlCpUKexXOKFwXeGRwrCimqKloqdi lCJfsUhxteIexRbFK4q9isNK6krWSj5K8UqZSsuUNis1KJ1Tuqf0XFlZ2UzZQzlGWaS8 VHmz8iHl88o9ym9UNFRsVQJUUlVkKqtU9qqcUrmj8pxOp1vR/ekp9Hz6Knod/Qz9Af01 Q5PhyOAw+IwljGpGI+M646mqoqqlKlt1jmqxapXqEdUrqgNqimpWagFqXLXFatVqzWq3 1IbUNdWd1KPUc9RXqu9Xv6Dep0HTsNII0uBrlGns1jij8VCT0DTXDNDkaX6muUfznGav FlXLWoujlalVqfWN1mWtQW0N7WnaidqF2tXax7W7dQgdKx2OTrbOap3DOl06b3WNdNm6 At0Vug2613Vf6U3S89cT6FXoHdTr1Hurz9QP0s/SX6t/TP++AWlgaxBjsMBgh8E5g4FJ WpO8JvEmVUw6POknQ9zQ1jDWcKHhbsMOwyEjY6MQI4nRFqMzRgPGOsb+xpnGG4xPGPeb aJr4mohMNpicNHnM1GaymdnMzcyzzEFTQ9NQU5npLtPLpsNm1mYJZqVmB83umyuZu5un m28wbzMftDCxmGFRYnHA4idLRUt3S6HlJst2y1dW1lZJVsutjln1WetZc6yLrQ9Y37Oh 2/jZzLepsbk5mTrZfXLW5O2Tr9riti62Qttq2yt2uJ2rnchuu901e4q9h73Yvsb+loOK A9uhwOGAQ4+jjmOEY6njMcenUyympExZO6V9ynuWCysbft3uOmk4hTmVOrU4/eFs68xz rna+OZU+NXjqkqlNU59Ns5smmLZj2m0XTZcZLstd2lz+dHVzlbo2uPa7WbiluW1zu+Wu 5R7tvtL9vAfFY7rHEo9Wjzeerp75noc9f/dy8Mry2u/V523tLfDe4/3Qx8yH67PLp9uX 6Zvm+5Vvt5+pH9evxu9nf3N/vn+t/yP2ZHYmu579dDprunT60emvAjwDFgWcCiQCQwIr Ai8HaQQlBG0NehBsFpwRfCB4MMQlZGHIqVBKaHjo2tBbHCMOj1PHGQxzC1sUdjZcJTwu fGv4zxG2EdKIlhn4jLAZ62fci7SMFEceiwJRnKj1UfejraPnR38fQ42JjqmO+TXWKbYk tj1OM25u3P64l/HT41fH302wSZAltCWqJqYm1iW+SgpMWpfUPXPKzEUzLyUbJIuSm1Jo KYkptSlDs4JmbZzVm+qSWp7aNdt6duHsC3MM5mTPOT5XdS537pE0SlpS2v60d9wobg13 aB5n3rZ5g7wA3ibeE74/fwO/X+AjWCd4lO6Tvi69L8MnY31Gv9BPWCUcEAWItoqeZYZm 7sx8lRWVtTfrQ3ZS9sEchZy0nGaxhjhLfDbXOLcw95rETlIu6Z7vOX/j/EFpuLQ2D8ub ndeUrwX/weyQ2cg+l/UU+BZUF7xekLjgSKF6obiwo8i2aEXRo+Lg4q8Xkgt5C9tKTEuW lfQsYi/atRhbPG9x2xLzJWVLepeGLN23TGlZ1rIfS1ml60pffJb0WUuZUdnSsoefh3x+ oJxRLi2/tdxr+c4vyC9EX1xeMXXFlhXvK/gVFytZlVWV71byVl780unLzV9+WJW+6vJq 19U71lDXiNd0rfVbu2+d+rridQ/Xz1jfuIG5oWLDi41zN16omla1c5PSJtmm7s0Rm5u2 WGxZs+XdVuHWzurp1Qe3GW5bse3Vdv726zv8dzTsNNpZufPtV6Kvbu8K2dVYY1VTtZu6 u2D3r3sS97R/7f51Xa1BbWXtn3vFe7v3xe47W+dWV7ffcP/qA/gB2YH++tT6q98EftPU 4NCw66DOwcpD4JDs0ONv077tOhx+uO2I+5GG7yy/23ZU82hFI9ZY1Dh4THisuym56Vpz WHNbi1fL0e8dv9/batpafVz7+OoTSifKTnw4WXxy6JTk1MDpjNMP2+a23T0z88zNszFn L58LP3f+h+AfzrSz20+e9znfesHzQvNF94vHLrleauxw6Tj6o8uPRy+7Xm684nal6arH 1ZZr3tdOXPe7fvpG4I0fbnJuXuqM7LzWldB1+1bqre7b/Nt9d7LvPPup4Kfhu0vhJb7i vtr9qgeGD2r+NflfB7tdu4/3BPZ0/Bz3892HvIdPfsn75V1v2a/0X6semTyq63Pua+0P 7r/6eNbj3ieSJ8MD5b+p/7btqc3T7373/71jcOZg7zPpsw9/rHyu/3zvi2kv2oaihx68 zHk5/Kritf7rfW/c37S/TXr7aHjBO9q7zX9O/rPlffj7ex9yPnz4Ny1d8BwKZW5kc3Ry ZWFtCmVuZG9iagoxMSAwIG9iagozMzY3CmVuZG9iago3IDAgb2JqClsgL0lDQ0Jhc2Vk IDEwIDAgUiBdCmVuZG9iagoxMiAwIG9iago8PCAvTGVuZ3RoIDEzIDAgUiAvTiAzIC9B bHRlcm5hdGUgL0RldmljZVJHQiAvRmlsdGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0K eAGdlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFH hyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0 WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/ UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan 5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAw FJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrx s1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3I evtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZs XpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TM zAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicH GX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtc im7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOW Ay5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6B aQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6 CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfh G/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGY GBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCP YFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/Bi fCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIp sY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/ UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uR a5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppi iWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i9 9AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4q fJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6 iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnu zFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1Hv oT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+ 41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/kr Cz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3f dr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3sn sdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2 xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752 vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9Jf JFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksW d0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNc lrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34 ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000Il YYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdH ZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7r j20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrb lyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t 5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/f bX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo91 1TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn /Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93 fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9 jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17c Pel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy// lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTw a9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn8 8ngmbWbm3/eE8/sKZW5kc3RyZWFtCmVuZG9iagoxMyAwIG9iagoyNjEyCmVuZG9iago4 IDAgb2JqClsgL0lDQ0Jhc2VkIDEyIDAgUiBdCmVuZG9iagozIDAgb2JqCjw8IC9UeXBl IC9QYWdlcyAvTWVkaWFCb3ggWzAgMCA5OTIuMTI2IDgwNS44ODk4XSAvQ291bnQgMSAv S2lkcyBbIDIgMCBSIF0KPj4KZW5kb2JqCjE0IDAgb2JqCjw8IC9UeXBlIC9DYXRhbG9n IC9QYWdlcyAzIDAgUiA+PgplbmRvYmoKOSAwIG9iago8PCAvVHlwZSAvRm9udCAvU3Vi dHlwZSAvVHJ1ZVR5cGUgL0Jhc2VGb250IC9SRERZWUQrQXZlbmlyLUJvb2sgL0ZvbnRE ZXNjcmlwdG9yCjE1IDAgUiAvRW5jb2RpbmcgL01hY1JvbWFuRW5jb2RpbmcgL0ZpcnN0 Q2hhciAzMiAvTGFzdENoYXIgMTE5IC9XaWR0aHMgWyAyNzgKMCAwIDAgMCAwIDAgMCAw IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAg NjMwIDAgMAowIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTc0IDAgMCAwIDAgMCAwIDk0NCAw IDAgMCAwIDAgMCAwIDAgMCA1MTkgNjExIDQ4MiA2MTEKNTU2IDAgNjExIDAgMjQwIDAg NDgyIDI0MCAwIDU1NiA1OTMgNjExIDAgMzMyIDQyNiAzMzIgNTU2IDAgNzIyIF0gPj4K ZW5kb2JqCjE1IDAgb2JqCjw8IC9UeXBlIC9Gb250RGVzY3JpcHRvciAvRm9udE5hbWUg L1JERFlZRCtBdmVuaXItQm9vayAvRmxhZ3MgMzIgL0ZvbnRCQm94ClstMTY3IC0yODgg MTAwMCA5NDBdIC9JdGFsaWNBbmdsZSAwIC9Bc2NlbnQgMTAwMCAvRGVzY2VudCAtMzY2 IC9DYXBIZWlnaHQKNzA4IC9TdGVtViA3MiAvWEhlaWdodCA0NjggL1N0ZW1IIDY2IC9B dmdXaWR0aCA1MjcgL01heFdpZHRoIDEwMDAgL0ZvbnRGaWxlMgoxNiAwIFIgPj4KZW5k b2JqCjE2IDAgb2JqCjw8IC9MZW5ndGggMTcgMCBSIC9MZW5ndGgxIDE1ODE2IC9GaWx0 ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4AbV7C3BVx5lmn3N0dfWWrt5v6epKuhJC 7xfCMkhCCGFnjGIwSIwJODYYvHbAHj9wVVymaj22h9SuM5n4gWu3nMrGGLJba20qwUKu NdTExpCpcsjYgHG8gfXEGMjs4qp4MM5gab/v7/6vBM5s7c7USNXq7/zdpx9//6/uPnrw gYc2mwyzywRm4M777thh5CdUjOzonQ8/WG2fg37k1Vt23H2fe77dGO+bd9/76Bb7nJxj TO/w1s133GWfzVXkPVtBsM9eF/Larfc9uNM+Jx1GPn7v9jtdeegsnlvuu2On6998iOfq b91x32bk+LnxR/hTu2P7nz0oj+bGNcjHdjyw2dX3xjGejyYf2PqX/+vqqgFbB389pEJz lwmb20ySPBmTZoxvUl/GH08onhm6ZSZt28bs/n8wJcF5vvvtuh9Kuyceefb87MTM2eB8 8F9ATsU79gftBudnvse/sxOzE3iLPc3/KZwypslMg+4bv8mbBnsDgjeQrTCDptvUmWKT 0vQGxvUn1zyHzJp5z+YNNDGf8AYavN2MmB7XAPpINiETbkKH1csf21Y8PG02mRTj/dm0 eRyTZf6Syw+5/IzLZ11exfzBabPU5Rtd/ozLX3X5L11+yeXZzB+aNq0uX+Xy7S5/FYLF /i+5vIo5+ml1+UaXP+Pyl1x+yOW/dPmsy7OZS382X+qeV7l8u8svMUc/8h76l/eQn3H0 HJMl4xpw+ZjLX3X5JZe3Mhe+IEe/s46+XehY7gIzZPrNLWYUuTG5s+Umw3vP5PpvIn1m MrAkC2+eMqlj4//N8/79xJQ3++dTZrjiICQp2PiN5injLayuXr5teNLbhAd/IQgLokDB wuqRyaBu5Nbx2ET17urdK+/aXT1SvfWOuyaT6iRHwebdE63Vk2b1+Db8XTMenRyYKEvA zRMTi9FOEtvBK6i+ewIt3ONaQC6k1i9RKbTw5urJoH5s/Ovjk7uGyyYHhifKotHq5ZOH x8YnDw+XRScmUCs5MVKMmFJmxxzGmJMXoDzFtrIabaCJid272ebq8Vh08vDu3WW7MQ/3 POWZ6wkDjoCJ4xVMfPmUt2sM7yKLRctIiEVjUQxrYhhdpS68efX4cgwsOtFMXYaaFyJ/ 2Gzyl5hNwSRw2LT5d5pR7yPJG/yFkreZT4U2inwdaMv8r7t6XzM3ez82Ue9rJgv0Uf9v scC0HlarM6Bh03iuNuOOgod/wY9akT/WRHANMemaJ/sQwmjCUPBUPMKWmXSkDJOJv1kQ 9BzkEZNr8kw+UAGSMfeYe7zzgZ/0m+R3wlfTWtJ3ZS7JfjgnJ7I78k7u4fxbC14sHC5+ ouT20rayH6K29Amj9Q4sVczEYcVWTpmFrVOmGWlhzpSpP47UehCGNekqqKDUfXjQlOPJ vI53PFNrkppeh3nyTIkgWl4PqK29t7uzoNXr6l3i9S4JurvqYzXJ4fgSr7OjsCA/OYzf gqJINOIh/WZRt58ZLozk5KclNVdVNSd3hG/q6Rkpqa9LTj40c5f3zox5aNmyhyKLSjIr ItlFeZHU2vaFnSmjgyv6q7tj0bz8ril/25fP+//hyw6ZySZIxWLoZZopMk3+rmkMr8kE TYOBaQPw7gfIETBlmnIOkg1XQfouZ7FBKgHYSgSYMiolmwAMMGBAOlhSh7wSeWUr6o+Z Srx4EEsRXJ02USxSXSS3DwU7zEIUGLy2fHzKlLSWHQSTMpZMOMICEhaY5CUTB8k0vJuO 8gV4F52A52nGR5cl6LxQStlymm35ArrxNrS1e4Mhsw/rAG0yPsc+ykXh2L8gICWV/oGU FyEnoITMTojL00jywnkAqbdNwYsKzkOkXJEDPVh5aetJU2Xb2gJFeRhJ2loLIMWfELDz j03MgpMK3oPEuCIBYbMWlC1IDyMFGwZJiIMQByFOQmBOmAa8Y4DW6PR+ptNbrpS9Srms AEtmh5OmLDgHpZHOt+ocX7AgZB4F+AskmcgFLd6qLEjw4pxObauCEzq1j3VqpxS8LwPv 7exY4kMBmrxui1oQG8Q7Oyp9KkJBpVeQnwXKvt3x4Y6K9ev+6i9LOxtLU6v6O3ePvrvw 1oH6wf6bb4v0bLnt/aGcaEdsZLg/s7K1Jr2hoWIod8Fwx+LVWX7yhlsiQ4NtEHuEA8Yv guxnmCLv4jRksshKfg4AZblIxInKbI6DoUtRw9swZSJOxEE6BCEEk0IoKzGrkIQljwtV ZTmLops1T5YLSSikLE+DZVmmUKS4gOpxRnm4SwD1zUP3WegxHYhCHJgrumgpumgNCo5p UYlS7lHKrxVsZcRHUWkgoLQ/qpQLCmCfbNFWioOt7FTjgqWEzHmoyxUkTNma3WI8NCAF ts1U++IxUKSFp2BIBDwJM2xFtF5HeUoHhxWwHT+sQ7lbwaM6lEYV0XuUslP7aCS4H+vx AcBFJFmPMksNzJMyhLxrJIsWtsmLxPY92LasITe3YVnbg6Pv3PvYY/c+Oep9Jz/aXFrS XJM39I2NGzfOvAbTg59gEjKTHTzK2NW3EnMGQOZ2SABiFhi9zA/B22x4IBnR4wDPIMmI fkkq2bpUwEGIIO2hwVOGtVpXuPSsMa7gKQVHFBxWrp21IAS7HJgfcFgQ0wBDSOEQ2nTF dgDsQpIhHBaqhFzoOQksSrE9H7UMD5tFAKNI40hiW562JYH5XEGYgMsdF4AO0WmYnf4S vljGv0KAdQrTtNMmYD9hNJtmtiLtRAow3ky8msVXR6ET8uphAQfBw+Aq6D9QAdpIRWSv q0QjAbYreEbBYQVnBagyBtS9YJ4yZpCAQIbKyHhBBheYP1ce9yl4U8FHAsiyRO0nlB83 KLD8wMjeVMpHAuyyJB+fMhmYbupxcgTDsZy/iAeZ+N8o6LEgDGuQZuqQupECmCYubvZx aw8+15EVEZAvo0oZpbOkFEF5bdFlldRkAlb+BAOQOvPcoFPCXo6ZdSgKUmcRtlQCrihI IWCdBgU9IlmgXFHwFBWQw7hBwRU7MQ4Mc+brn6tKjCroU1BMV3Q/fZqdYW9nnvwGMfnt /IP/k7U/8feN/WrNT/yfrPnV2Bb/G1/+EKlfci6cb9pmP/Pe8M5ChLqCsWlEIa3Q3CnT CssasjHNrIQm6KSKADyuRFmuCcH+5mC9WsHxIuRFtNOX6CEgs7kgxugb2rAyMsMz2IAK mBWgcucCmrQ/EtC4CCdGQYxZQeyCA7GxTVcrjNlx02XOIvkInDAXDGgBhjYX8sATQ4UL Ey8V2iEiEqJrakPJAJK/YdrUI4bqorCFzK/x8DskX0KjwDxkxSBkaIspE7ASIfMzaO9b SGIyLqtOM9DGorFuJuo6o/a0DZqoPBID8e0CvF1gm3oOYK99CMw5AGHTKQW/xxCFspqA EvGWpaQjniyU33r87UEaQQrZ7gvRPaeG6u/LmmBQz5kydFRmqXsZhrH4eQWnJNAA5bRp tkUXFTSAWeg6jOimzexB2o8kpq/ElgQ4m2jXKu2o0o4q7bZKoy0JYck7zFEkjCpsGgH6 kFYiBSLDO1Xfzis4rSDB4A9U9H+nIMHcf1CGvWBBCBMvgDMWLgfmoBavA19kjU4DXFAm nbRMmscOG3eBHRcsX0LClwtgiXD1Sc6K/CvS6R3ATI4gSXEhgHd/XU0W4rNKn471xqAr VhOO9zBmq/DyC4tiLV53LMuL1bQgsFvid3q3lty8vD2vOp5f210b2XnrjQ1DT9QsbSkP BQNB4Fes6qta3Fx2w8TmBRdCeQ2xomhBanFjb1VNT8ajva3D+Y1Lmn5VdkNepCGvtTES 66zpG6xOoX6Pzn7m34CzhTzT4p3gzqJF9DsKReGeZcq0ADWLNqdBi7k7K6DiTsIQyPzO cIU50aUKNsqaqwYXUD8RriU0uJyEcjOn0s0kNFODnUrXk1BvCeXoPiS6Ww+U7TS23NRb 03+Sq2UVrlAEX7j7sVCnsf+qN+VWbfnwCyQpv8idGcd8WoDd4+wBN/YjoUYIohdA9Jwy 7wHYrw/PQYf3Ws2myqTgQSIDPmTgIcM28BwEcK/VcLYWQWsRW7IHYL99CMwEgChtnwR3 6HkPwH4kGegBa1tDphdgBZJP9T0AU3sESUZ6wMTwELMPvdgVrEDyRWMWYczehrquFj82 T9C8zkjnksBuDbJQ0uLHYwUx7/clLYONjcMd5eUdw42Ngy0lM7cN+TkL2zqLev90qK5u 6E97izrbFub4Qxe5hajoGI673G+f+V5xTWFa3dD67u71Q3VphTXFt9CRwnf83nvX+58m GnxOySqxMd92RvlwE9x0plgXsp1bTZqubAXfJYCjiELeIsdhD7Zjio8jPYMkluUSgKzh RgK+/KpSVingC3CATqwqKVaV8wQvn4R8jWEqTT5EBXVb0V01JoC9DhwA4laMgz4N0gB9 yAeC4YbjyMG7lfJKDr3NUhBWIaljYFwkA/xHBX+vYDsBh7xP4wwe7Erliwp+reAiZBJF IZjwEEw4lIGS+wqAvLAXZl7AyxoadHPPwzojCvYq2MeIgEU8wxRwUcEfMHRQ6OBy4OA4 D9TbTipH+nMKKotPApyz4huCrEXMOj7YKtyfoO4ayjIp56xQBzCzzknR3UqdPyhII2Dl mywIQU2LsA8psgNYaUpt8RfwTDKAaYBfqJtKI5Vv06RIw6cVfKEgjcD24EDiEOFt3WD/ XOQN7axTCfxAKdnGydd2AQynZH/Jmb6vi3RBwY8V7NcF2qcLtEJXYa/yPHFW8IllPiNQ x2+aCwyaO7IIdmRiOxDwWmpgEqH8uPL6ouV1SHzWF+ClrF+C369Z9jFeFD5SOhw3XlNG nVLwuQUhWNRqnIxU27YSXFutPHpLefSxgpAWnRKK1+LF6q2P6+3o6S3K8sLJ1p0VVXpF 3n+M3BiHqSkuhumJ3xjpiY239d0+WFMzeHtf23jM21Ja65V3LG9oWN5R7tWWNjbGPQ+G qLsbBsnz4o30Xw3wX2XWf/nV09DI6HX+i/pakPBfBbAnzTbwvN5/WbeFRT0kjkythnNG X3FXX3Foc/7LuTwh/F/9Vyr0sBDCXIhVLsQq4wQUwWbCa6UiEGzCGjRhDZpwztWEYuur nsXMeeMkHuBZWKaX1T31AIzowx74qf3qq/bA2u5XX7UHorRffdWzkKqXrYjRykTQgPNV z0KoXkYSWUo1FVaPUumVKJt9ACvVKx2ERzpmvRJPBHF4R527YqA9tBsM0o8huaacTvWx mPVGeZ5lFRmAuvWFnnClalGfgisGh6ask0oAX0e3mYkJuYh6EcYv5Yt1yEc5ng29Ltbq di7RiyHCChIRFiKvIjjHHfSElDnKHj2h95//3zzhzKVbrneE3rewStxDXcUeKsXUBWnT mESp9YRndYaHFeBOEvOZMrUQWndkPEYDCA9UChLdJXe/mHmP7AhZE7up40jcu2wUYwCP aY8v6i0TxhAFCTe2m7gFzyg4q+CwAJX5KjpGXOIlIrL5El2FgdAVTssiF4gDTGXvn8qQ AHiz6d3P4dJhFqB+ltSPQBGrpH6E9b8rq2T34nvVWq4goBy8omZzAgIslIT9fEUNaSEk WIpGFLxCQMmIK2UFhELqXFCru56A4jsOY7sVSbToaYAX7AMPj5wZfpSA7fVyA8CXsJPF LkW8E8OyYvjAYhuW0QUdQ5LmpiB5R5FE3PtUDM+pnbTmEe0aMp7zXWVXAGif8iLhv3t1 womZM87EuKiuGVBXBJ0cWxEAJy7ueEQn/olOfJ2deIBI1c3qpIK1dsKp8H855m6kR5B4 8ph0zeQPootjdvI0M8V26AkHcoTz5AuvKQjrzHstYPwQRVwQBWM83tfAOYQrEY2Kc5iv iRWe99Ox0er+5jI/KG/pj3X/SX7ngs2LuyYG6moH1nZ0r+mr9LKWrS5Z0FPRE1vSXNpe vaC7RWPQ+qW3tVWCt9S9HL8Iupdn+oOj0zgMwNkpb2lWUSmgadxVOE1bhVKSukAqT3iM cihYBxSsHnk/8n6raEvs1M8YAM54qYJDCqoJEsFnE1WqaZ5KdZDQMY8QJSE6bxvUR0Kf SaEWcj9mt0G8TbLboHwUNkGdsPBvwfGdQBJpOwAgY/qtBTzezMcpIIunTB/eh/RCfSvQ m7wf4OsIWbgQLtwqzN8gSUO/E+o03GGTibKjsPkpHt5Eeg8pcM7oObB5r3VGAY5zAts5 nZCM4jkFr1gQgpqFIeTucOQFmAnUIzUDVLd9egHCu0/Nebea870KGHdK489bwM1XLgbh PNUK2kGuypO6q+8hoJrdrZRRld5R2Y+gaJFYWoBRjTlhXm07xxQc1UOQKZxQSBejBGy5 T8EBKKXMaB3A3UjCzLeVegJW42MkUENQlF7ztn0IQ7P6sSPtRwTXD+bSq/WCZSuQpO7z AOShNPcKrSKnyC2oALp0NyLHHO4yhfKiggd19u8p2KzgYwXdBJyQYx73mwXYYrqDkm7l 0oiCo8ocujbp74Ay520FZIUUvY85CTimYFrBQRxzujr9WscBckSKpgWoH487Px5U4soX Ryje/PMS8ebdnRFcfMVq6uM7y1qX1tUOdpR5xS1DjXVLW8syKjb0dYw05fnVt3d2TgzV 1y5d11XQ2tqS7w/l9X5z1ci3mj+tG2yvKG1FdeQV7YMz/y7eWtGxrH5NW0Pt0vHOzvGh upT8WNktM231a0fbWtqgBDxC3ufjchVK+o84mOfOkJ+F8JMQH8aFR9jJuPg2Hw5GcHlr cLFgoJ8G+mlwqGLMYaTjSGeRUjagUg5ANVIb0gDSGNImpB1Iu5BS0CovurJhsrBqu/AC WBVCHXbgNg4vqQTYY33UGyCF5imVo+HlcTbtDT7BSXj9uTN+NHcGTv0SkkjfUnX1ryrY LgCtwUwGH7a15+Ung+vdXTjPKgxikc59lbm5lUzZQ0P+0wVlZQVIX96Jr1s88sz7O8Tw af5buAkhx16FUB9i/ICT27CYYgO+eQxrYFWseMJYW5kYUZDYjcEv2Tq3Kfi3WufnCh4Q gBsnNgq7wRfCBvGp6URahiT3cbfZEgZc7qpqWMEPLQiDyz7Y7mMZ8dKGwVSso4919LGO PtbRhxO1Nzwh9vVjde+zFnCLF0Lw7zb239PibwrARzd86QG9SPiDBengQxKcWhJGnIQR J2HESTzcDbDuSeRLNoaUhCElQbKSMKIkjCgJI0rCiJJMGPsMBh9J1n8cwMMRJFlcHmKL BN0N8IhSi0hl+9xqiC5+34IQOkpHR+l4WwPINIpS2rxDP48Eb55shUjApycJYQuTENbD GM+E7MhOYZCfIMnI1gPIGPB9jgV1AjgX9wYtvBufmwfvaR0lcc0LGbVTvMamIjSwrRYI 4IdwiVbPazh6WkFiX19HCjmDoyQAyH5vZzgUixdA7Hf/jzsveP6W3w8Ned67J2cuX4aY c8iIs4zZg4fAQH09xhTPgCpsHwAYQ/IRQhur1AOUBUZ4zwC8hAR2UMNDkD0nN2cB5j4E cfd1c5tUQ/YasretvRMD2wM1/AydLMNgTiM+qvC+Mw1/687oXqVPhNDyjK7Y6R+GyLtY kjNBzrIjO869IP3QpIJZ2WqB8l0BKhKpHEDqPAlwI5ozN8WsUWwlgPeqxVYCTkJczyGJ BLwFIN39VAAsH+xCKoYDDXbX/EeFEeh/uV6RHdCTsbVKeVspyxlZc+3WKogr4GZAuuoj YJ0X1Ig+qmC3gh8rOCpulIdEb+s4jmhn3GJIi4ldxdP6Gg+FpYh+1tuQhw825JC2sCAS i9g4ucIjij/le356zYLWovVrh3pWhlo6G4YC33svry4vr7ooc+P4zCkvuGV1Su3owKfe fXmLS2F+sP/0o1jjPBP3/yvPSCptDFwNyICXRyQwBVcx6GquIkg870xTkgtwVmmA87h6 9Y0Klio4q8AQJKJfd0k3t9buXk/UP4a+bGxLcWNs+5UDecZkTof7LOCO/586le+FHeH1 uSjJP31E3wNp5ibJVZt3Xj/viD6EsDAXvUtkec3pPPdeBWgAcRE18wBOb44giZheBpDl 5BWcKO4BBN1HdOd0GcAVu13lQZzKHUOSt91RJ4835HSThyeJrSUPRnBwR5VLJWDf3F+u ZNRuH6rwgNsBPhzEih7TA7wvANyLALY4imJuxtDcFwCuGIDFB0wdBl2nc8JZC+slg+KK 61HMq1BQL3NDZYsB+HYP9qIjSLyMIBvtIqIyH8LgqVuhXqwBd+nSTA84Lc3YGP76y4te iebcjRgiPO+0XFsMNpeUNOP6Aoc1o7yq6LFXFj28urj+viLOSwrdKCKH6ENHRmfO+jeI jrT4qf+cezC5/qKVEEBGnEHIJlPZSMCiagGwiPz0UWKur1yGuZOWOU35/7gMO6FSd07B zxT8tYLE1RfPGT1EAQuxbu6O7Bd4+ADJ3mBQ2P+VbsS413L2Ts65KRLzdI7FTgpWWnCN 4gWQS3E619yE8TxCzhxJTVyJkYpNCZmP23+7HPgiE4Am2l6QAfRqf9dVul78vE5rlvlZ nd6dRf4Fd2de5CsnhvwvAPjlm/HnNshjkZfCbyf44Ru/qEiCuytCiC3fSB2G0oiAnRF3 jXlk617WOnBQdkmROuF0ulh84ZmIupJISJpHmP+RXTKq8iM7NHNOo553BWB/A1MNb+Z8 7qhuQhfBKMmQLitHkwnIf9pQKXJXnTQk+FpUbipH9P1ufX+FBSEcKOTCk9P85sH7Of4X he3RUSS2KvAG/XBpXXtVeUFmZkl9T21kyBuqGuhclxcrjyQPhiqau4pnrghPoziH/Q54 2h78b95/NaoXdP5tQH3XmPgu7PAxR3dD+QPUZ+TTCN4nvmbZSDMOZ5mNetVyNJuN4nYs UTsDugHe8dMQLEWA7tZJQCoOajtxtdaJgKoTIR3C90Tk3MIFaZm3INdeUrbMXVKyAwgJ +ucVPWJ3HOmk41V7i5neOo1YKt+0YP2mTDE3VZMgHEbiKRBdPrwNJCodQ67lFuMz2b3b 49h6aRnE1wmonvz/Gd5fipEeh4BiTdMRWXv45sOD8no4fffgbhAwc8ruGjOEwyQPN4oI ZtnIIxDYp6ylD+HDmSx88YJdMUsOAByxD4H5rRqHExbwmCSCeMrtZd+2gsQPOArlZlHG dN761JB5F1P8O5kmxnFENeM1BR/rscUBBScVnDI1dp32KRgloPDuUcoFC+jC4hBpeDc7 XXeyvkg+x8YLPCCTNf9EQbdpt22dJpDjnWowlNt6Oa99DUDe2KngfQU7wUBZhRcAeGeM TsM4XvbkAvI8crkS50pIC1wGAesJrCdPBS9k937NMnCDJ18NksdZ4LGsR4CgAMrNmeEy QYFT32Oqx/xARurw4soBiWu4nCXCemHOuTn+8ioDjZ5XJpKt+5Gk3kpSyewPtHgej/l5 TRwnY47hPcrnj5W9JxWcEPZCpuoAyHT/fl5DyhcQhVWe/RSaJ0K99tQIX023ei347wF8 Y9NRWOQNZHfURmA7WlqWtQ9WdQ7V9GyI9uXfuCC/oSqvq24gozFeXtG+JNq+psjbXFGT EinNi1am56Qv66jvjeU0dsSr61PzKvNjVanZQVZ5c7S2pyZS38KVNFmwP0v8cVPqfeN1 PIa4f+dZ9Ke6XKwkyzUGsAlJ+HJYirFLhKbi2IgfzaEi+ZhDAAPAfxdIp8l5iZsXlnxK 00pgxMaCG2MAm5BckyyeMnloMv9DmxeiEXZRwuulxzXG3CQAYlCsIctXPsnLpMnKtFt5 RvAYNuwK94gI8IDYaB4aL0FeKN9ehqA5OXLJIqPZqcK2TcFfKJgggIsII5pMgy9Pw0aN X3KCoE1sRRWIPz+vz4ExysEMsVOSmz5cKuAUCtFiJNbdiX8V6fQWP5WWW5Idqc0O6rry GmsK77xz6CnvhZm/L43mhtNSboyklbfFvXj/E0+Asfw2yvsNTqiqvJX86CDT+ovHGZSD d2WYYOnc3vhVlF+3N57UePu4glkNse33LeqW3afuc27ZbZbnTkfmtu8860g1WdYt/3c8 YJlpZgOYWXeAc1Godl/Mbxx5PogLRMf6m8DA9UiyP7gJArMeSSzQI6rrvQQ0G9ecqj+l ij8ufg9SMg4zy9UJ4f8oIjgvdgaab8moHgHgW7LKrwg1Kl+14c61vjuCs1n37Ru+ScIX b96L8/a2GRXtdW53m9jbeo0zX8b7GwtkdzvzV9zdcp3wv1X+m1inbP8/vY5gJoTpJ+Eb VX7rmwXhzqbO7BAHDjBGQCtzVijc+fITcrbivmHeiwfRnUcUPK/ghILfCkA8hj5SP5xG nzixsGvC0zx5/XcWMHYNwcq5Q5udAE/LA84EObLHNfbZSMCRXRIK1Yf/D8TTDW7F6d0x MfHnPO/NtNvzHdQQavpxXb1ZBYmPuuVrEdY5pEVLCdjXD5SSo5TEwbBsUL5ySjOn7WQa /k3MzprnuLLmEwDbkGTNH7LUsHkC4PtIP0ISX/WRlMCggYEBPucOzTX1IzzIjB6yIISv 5EJo0x10PanF7ylIHJl+JBR8SX+c9mfKpKHhDCxwph3jFntDwmAmA8GMXDUF+E9ed5Xa BTAsVBsE8YhOeLSOYyWzxhn8WuDupBt0zfgPHFL0PhZLRs9vfAXEIVpSFBfRK/Ou/Qz7 9nv9x4Yf8++95fs3fdv/9k3f996becL7tqSF3q6ZXRBs7MPkZ/ZnCCn/2A/PQwNTh0hy MaawHIZyBezjTeZr+F/SVebr5lazGvNci1PHcag7fzxqoyAG6ObW4eGJieGmwYc3f2vb A81D27f/m/8D72ZlsAplbmRzdHJlYW0KZW5kb2JqCjE3IDAgb2JqCjgwNDUKZW5kb2Jq CjEgMCBvYmoKPDwgL1Byb2R1Y2VyIChtYWNPUyBWZXJzaW9uIDEwLjE1LjUgXChCdWls ZCAxOUYxMDFcKSBRdWFydHogUERGQ29udGV4dCkgL0NyZWF0aW9uRGF0ZQooRDoyMDIw MDcxNDA0NDI0NFowMCcwMCcpIC9Nb2REYXRlIChEOjIwMjAwNzE0MDQ0MjQ0WjAwJzAw JykgPj4KZW5kb2JqCnhyZWYKMCAxOAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMTkw MjQgMDAwMDAgbiAKMDAwMDAwMzU2MSAwMDAwMCBuIAowMDAwMDEwMDgyIDAwMDAwIG4g CjAwMDAwMDAwMjIgMDAwMDAgbiAKMDAwMDAwMzU0MSAwMDAwMCBuIAowMDAwMDAzNjc0 IDAwMDAwIG4gCjAwMDAwMDcyNzQgMDAwMDAgbiAKMDAwMDAxMDA0NiAwMDAwMCBuIAow MDAwMDEwMjI0IDAwMDAwIG4gCjAwMDAwMDM3ODIgMDAwMDAgbiAKMDAwMDAwNzI1MyAw MDAwMCBuIAowMDAwMDA3MzEwIDAwMDAwIG4gCjAwMDAwMTAwMjUgMDAwMDAgbiAKMDAw MDAxMDE3NCAwMDAwMCBuIAowMDAwMDEwNjE1IDAwMDAwIG4gCjAwMDAwMTA4NjcgMDAw MDAgbiAKMDAwMDAxOTAwMyAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDE4IC9Sb290 IDE0IDAgUiAvSW5mbyAxIDAgUiAvSUQgWyA8OTY2OTg5MzJlYTNiM2Y4ZjViOWFhYjJh NDk0OWQ4NjY+Cjw5NjY5ODkzMmVhM2IzZjhmNWI5YWFiMmE0OTQ5ZDg2Nj4gXSA+Pgpz dGFydHhyZWYKMTkxODkKJSVFT0YK ArchiveMatchStore Archive_Stores Class_Store DKDLock Match_Hashes BinIndex 14 BinMatches NHashBins 16 Class_Store DKDGraphicStyle Match_Hashes BinIndex 8 BinMatches DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0 GreenPlus 0 Opacity 1 OpacityPlus 1 Red 0 RedPlus 0 LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.8 BluePlus 0.837427 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.8 GreenPlus 0.837438 Opacity 1 OpacityPlus 1 Red 0.8 RedPlus 0.837418 LineWidth 2.63623046875 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.8 BluePlus 0.837427 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.8 GreenPlus 0.837438 Opacity 1 OpacityPlus 1 Red 0.8 RedPlus 0.837418 LineWidth 2.640625 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0 GreenPlus 0 Opacity 1 OpacityPlus 1 Red 0 RedPlus 0 LineWidth 1.9521484375 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.701961 BluePlus 0.754077 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.701961 GreenPlus 0.754087 Opacity 1 OpacityPlus 1 Red 0.701961 RedPlus 0.754069 LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.027434 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.007121 GreenPlus 0.149131 Opacity 1 OpacityPlus 1 Red 0.986246 RedPlus 1 LineWidth 3.05224609375 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.701961 BluePlus 0.754077 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.701961 GreenPlus 0.754087 Opacity 1 OpacityPlus 1 Red 0.701961 RedPlus 0.754069 LineWidth 1.9521484375 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.998218 BluePlus 1 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.00176 GreenPlus 0.198351 Opacity 1 OpacityPlus 1 Red 0.000111 RedPlus 0.016804 LineWidth 1.205078125 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.998218 BluePlus 1 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.00176 GreenPlus 0.198351 Opacity 1 OpacityPlus 1 Red 0.000111 RedPlus 0.016804 LineWidth 1.05712890625 MiterLimit 10 StrokePosition Front WindingRule Non-Zero BinIndex 10 BinMatches DrawsFill NO DrawsLine NO LineCapStyle Square LineJoinStyle Miter LineWidth 1.9521484375 MiterLimit 10 StrokePosition Front WindingRule Non-Zero NHashBins 16 Class_Store DKDParagraph Match_Hashes BinIndex 0 BinMatches FirstLineHeadIndent 0 HeadIndent 0 LineFragmentPadding 5 LineSpacing 0 ParagraphSpacing 0 ParagraphSpacingBefore 0 TailIndent 0 TextAlignment Center FirstLineHeadIndent 0 HeadIndent 0 LineFragmentPadding 5 LineSpacing 0 ParagraphSpacing 0 ParagraphSpacingBefore 0 TailIndent 0 TextAlignment Left NHashBins 16 Class_Store DKDFont Match_Hashes BinIndex 0 BinMatches Family Avenir Name Avenir-Book Size 12 Family Avenir Name Avenir-Book Size 11 NHashBins 16 Class_Store DKDTextAttributes Match_Hashes BinIndex 0 BinMatches Font 0 ForeColor ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 0 Paragraph 0 Font 16 ForeColor ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 0 Paragraph 16 Font 16 ForeColor Blue 0.998218 BluePlus 1 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.00176 GreenPlus 0.198351 Opacity 1 OpacityPlus 1 Red 0.000111 RedPlus 0.016804 Paragraph 16 NHashBins 16 Class_Store DKDArrow Match_Hashes BinIndex 2 BinMatches ArrowAngle 160 ArrowArchiveVersion 9 ArrowBackEnd YES ArrowForEachSegment NO ArrowForm solid ArrowFrontEnd NO ArrowLineWidthFraction 0.25 ArrowOffset 0 ArrowSize 8 ReferenceArrow Relief UseCurveFillAndStroke YES NHashBins 16 Class_Store DKDDashPattern Match_Hashes BinIndex 8 BinMatches DashName Short DashOn YES DashSequence 4 4 NHashBins 16 DKDChangeTimeStamp 2020-07-14 04:42:30 +0000 DKDCreateTimeStamp 2020-07-14 02:19:50 +0000 DKDDisplayGraphicDetails AngleFormatDisplayDetails AngleDirection Right AngleForm degrees AngleRotation Counter Clockwise PrecisionAngles 1 AnglesDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 2 TextAlignDisplaySpec Left UnitsDisplaySpec Punctuation AreaForm Natural HelpTipDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 3 TextAlignDisplaySpec Left UnitsDisplaySpec Abbreviate InspectingSpecIndex 0 LengthsDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 2 TextAlignDisplaySpec Left UnitsDisplaySpec Nothing PercentDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 1 TextAlignDisplaySpec Left UnitsDisplaySpec Punctuation PercentFormatDisplayDetails PercentForm Percent PrecisionPercents 1 DKDExportDoc BMPExporBackground No (White) Background CSVStringEncoding Unicode (UTF8) DXFExportLayers All DXFExportRevision AutoCADLT 2010 EPSColorSpace RGB EPSExporBackground No (Black) Background EPSLatexPsfrag NO ExporBackground No (Black) Background ExportColorTable 256 Best ExportCompresion 1 ExportContent Just Graphics ExportExpand 1 ExportFileExtension svg ExportImageAntialias YES ExportImageInterpolation Automatic ExportPath /Users/corkep/code/bdsim/figs ExportTransparentColor Automatic GIFExporBackground White Background GIFFDither NO HideExtension NO ICOColorTable 256 Best ICOExporBackground No (White) Background JPGColorSpace sRGB_ColorSpace JPGExporBackground White Background PDFExporBackground No (Black) Background PDFPagination Single Page PDFXMirror NO PDFYMirror NO PNGAlpha YES PNGColorSpace sRGB_ColorSpace PNGExporBackground White Background PNGInterlace NO SVGCompress NO SVGDOMIds YES SVGEmbedImages YES SVGFont SVG SVGGlyphs System Font SVGOverwriteImages NO SVGProfile SVG 1.1 SVGTidyFormatting YES TIFExporBackground No (Black) Background TIFFColorSpace sRGB IEC61966-2.1 DKDGrids DynamicSnapGrid YES GuidesLayer NO MajorGrid GridAboveGraphics NO GridRGB Blue 1 BluePlus 1 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDP3ColorSpace Green 0.651064 GreenPlus 0.713725 Opacity 0.6 OpacityPlus 0.6 Red 0.432559 RedPlus 0.54902 GridSpacingX 72 GridSpacingY 72 LinkGridToRulers NO PrintLineWidth 1 PrintsGrid NO ShowsGrid NO SnapsToGrid NO MinorGrid GridAboveGraphics NO GridRGB Blue 0.848787 BluePlus 0.87451 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDP3ColorSpace Green 0.828351 GreenPlus 0.854902 Opacity 1 OpacityPlus 1 Red 0.664186 RedPlus 0.745098 GridSpacingX 18 GridSpacingY 18 LinkGridToRulers NO PrintLineWidth 0.5 PrintsGrid NO ShowsGrid YES SnapsToGrid YES SnapDrawing NO SnapEnds NO SnapGuidelines NO SnapRadiusGrid 3 SnapSound None SoftSnap NO DKDHideExtension YES DKDLayersList CloakLayerGuidelines NO CloakLayerVertices NO FullLayerScale ArchivePrecision 12 ScaleOriginX 0 ScaleOriginY 0 ScalePlusDown YES ScalePlusToRight YES ScaleScale 1 ScaleUnits Centimeters GraphicsList Bounds {{89.4337593497, 18}, {718.079221614, 663.829445993}} Class DKDGroup DKDLockInfo 14 GraphicID 4DEB8857 GroupGraphics Bounds {{113.30206836, 41.1403637535}, {115.701818767, 46.2807275069}} Class DKDRectangle DKDLockInfo 14 GraphicID 236297C5 GraphicStyle 8 Bounds {{252.144250881, 41.1403637535}, {115.701818767, 46.2807275069}} Class DKDRectangle DKDLockInfo 14 GraphicID 7055F7C5 GraphicStyle 8 Bounds {{390.986433401, 41.1403637535}, {115.701818767, 46.2807275069}} Class DKDRectangle DKDLockInfo 14 GraphicID 335D08C5 GraphicStyle 8 Bounds {{529.828615922, 41.1403637535}, {115.701818767, 46.2807275069}} Class DKDRectangle DKDLockInfo 14 GraphicID 77C918C5 GraphicStyle 8 Bounds {{668.670798443, 41.1403637535}, {115.701818767, 46.2807275069}} Class DKDRectangle DKDLockInfo 14 GraphicID 45E328C5 GraphicStyle 8 Bounds {{113.30206836, 226.263273781}, {46.2807275069, 46.2807275069}} Class DKDSquare DKDLockInfo 14 GraphicID 0F6368C5 GraphicStyle 8 Bounds {{182.72315962, 226.263273781}, {46.2807275069, 46.2807275069}} Class DKDSquare DKDLockInfo 14 GraphicID 7AB2F8C5 GraphicStyle 8 Bounds {{252.144250881, 226.263273781}, {46.2807275069, 46.2807275069}} Class DKDSquare DKDLockInfo 14 GraphicID 9DAFF8C5 GraphicStyle 8 Bounds {{321.565342141, 226.263273781}, {46.2807275069, 46.2807275069}} Class DKDSquare DKDLockInfo 14 GraphicID D18309C5 GraphicStyle 8 Bounds {{390.986433401, 226.263273781}, {46.2807275069, 46.2807275069}} Class DKDSquare DKDLockInfo 14 GraphicID E86509C5 GraphicStyle 8 Bounds {{460.407524662, 226.263273781}, {46.2807275069, 46.2807275069}} Class DKDSquare DKDLockInfo 14 GraphicID DA6099C5 GraphicStyle 8 Bounds {{529.828615922, 226.263273781}, {46.2807275069, 46.2807275069}} Class DKDSquare DKDLockInfo 14 GraphicID 7F0599C5 GraphicStyle 8 Class DKDPolygon DKDLockInfo 14 GraphicID 98A4EE07 GraphicStyle 24 SVGPath M90.1617046064,18 L807.512980964,18 L807.512980964,110.561455014 L90.1617046064,110.561455014 z Bounds {{90.1617046064, 203.122910028}, {509.088002576, 92.5614550138}} Class DKDRectangle DKDLockInfo 14 GraphicID 72698AC5 GraphicStyle 40 Bounds {{245.202582916, 133.701818767}, {46.2807275069, 45.2442427975}} Class DKDKnotchRect DKDLockInfo 14 DKDRectRadius0 0.186211073233 DKDRectRadius1 13.3261658668 DKDRectRadius2 13.5213499412 DKDRectRadius3 0.168640205449 DKDTransfer AnnotationScopeTransfer NO ArrowsScopeTransfer NO BrushScopeTransfer NO ColorAndStyleScopeTransfer NO CrossOverScopeTransfer NO DashesScopeTransfer NO DimensionScopeTransfer NO FillColorScopeTransfer NO FormatTransfer Native GradientScoptTransfer NO GraphicScopeTransfer YES HatchScopeTransfer NO InteractionTransfer Edit LineStyleScopeTransfer NO PatternScopeTransfer NO PositionTransfer Center ReferenceTransfer Screen ShadowScopeTransfer NO SizeTransfer Medium Screen UseInstructionsTransfer YES GraphicID D9E68DC5 GraphicStyle 8 NameGraphic Notch Rectangle AttributedText String start TextAttribute 0 Bounds {{245.202582916, 148.323940166}, {46.2807275069, 16}} Class DKDCenterText Containing DKDGraphic D9E68DC5 DKDLockInfo 14 GraphicID 38FFF5E6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 Bounds {{311.152840194, 133.701818767}, {46.2807275069, 45.2442427975}} Class DKDKnotchRect DKDLockInfo 14 DKDRectRadius0 0.186211073232 DKDRectRadius1 13.3261658668 DKDRectRadius2 13.5213499412 DKDRectRadius3 0.16864020545 DKDTransfer AnnotationScopeTransfer NO ArrowsScopeTransfer NO BrushScopeTransfer NO ColorAndStyleScopeTransfer NO CrossOverScopeTransfer NO DashesScopeTransfer NO DimensionScopeTransfer NO FillColorScopeTransfer NO FormatTransfer Native GradientScoptTransfer NO GraphicScopeTransfer YES HatchScopeTransfer NO InteractionTransfer Edit LineStyleScopeTransfer NO PatternScopeTransfer NO PositionTransfer Center ReferenceTransfer Screen ShadowScopeTransfer NO SizeTransfer Medium Screen UseInstructionsTransfer YES GraphicID 607042D5 GraphicStyle 8 NameGraphic Notch Rectangle AttributedText String end TextAttribute 0 Bounds {{311.152840194, 148.323940166}, {46.2807275069, 16}} Class DKDCenterText Containing DKDGraphic 607042D5 DKDLockInfo 14 GraphicID A91956E6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID E38816D5 GraphicStyle 56 SVGPath M263.712661981,239.790737462 C263.712661981,239.790737462 263.712661981,239.790737462 263.712661981,239.790737462 C265.764521123,223.715320846 255.908868853,219.603043278 253.252129302,209.139302626 C250.860312179,199.718976104 253.097745528,189.701465058 253.02055364,179.982546274 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID E29FA6D5 GraphicStyle 56 SVGPath M275.284614634,239.133177995 C275.284614634,239.133177995 275.284614634,239.133177997 275.284614634,239.133177996 C277.320884607,225.196880014 279.485040125,210.896471454 289.705462633,207.419245943 C304.847762026,202.267483412 319.929595155,215.348760908 321.297584,179.982546274 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID 207DFDD5 GraphicStyle 56 SVGPath M330.30911177,141.655744185 C330.30911177,141.655744185 330.30911177,141.655744185 330.30911177,141.655744185 C328.005614758,123.223763231 323.454824831,104.717250633 334.150543328,103.251051692 C357.513159511,100.048439065 405.339437733,151.832133609 402.435041382,88.0459759611 Bounds {{222.132172277, 342.029087345}, {169.900179324, 67.9600717296}} Class DKDRectangle DKDLockInfo 14 GraphicID 5046D0E5 GraphicStyle 8 Bounds {{426.012387466, 342.029087345}, {169.900179324, 67.9600717296}} Class DKDRectangle DKDLockInfo 14 GraphicID 6046D0E5 GraphicStyle 8 Bounds {{222.132172277, 613.869374263}, {67.9600717296, 67.9600717296}} Class DKDSquare DKDLockInfo 14 GraphicID 7046D0E5 GraphicStyle 8 Bounds {{222.132172277, 477.949230804}, {67.9600717296, 66.438065076}} Class DKDKnotchRect DKDLockInfo 14 DKDRectRadius0 0.273438179895 DKDRectRadius1 19.5685598946 DKDRectRadius2 19.8551743109 DKDRectRadius3 0.247636566585 DKDTransfer AnnotationScopeTransfer NO ArrowsScopeTransfer NO BrushScopeTransfer NO ColorAndStyleScopeTransfer NO CrossOverScopeTransfer NO DashesScopeTransfer NO DimensionScopeTransfer NO FillColorScopeTransfer NO FormatTransfer Native GradientScoptTransfer NO GraphicScopeTransfer YES HatchScopeTransfer NO InteractionTransfer Edit LineStyleScopeTransfer NO PatternScopeTransfer NO PositionTransfer Center ReferenceTransfer Screen ShadowScopeTransfer NO SizeTransfer Medium Screen UseInstructionsTransfer YES GraphicID 8046D0E5 GraphicStyle 72 NameGraphic Notch Rectangle Bounds {{324.072279872, 477.949230804}, {67.9600717296, 66.438065076}} Class DKDKnotchRect DKDLockInfo 14 DKDRectRadius0 0.273438179895 DKDRectRadius1 19.5685598946 DKDRectRadius2 19.8551743109 DKDRectRadius3 0.247636566585 DKDTransfer AnnotationScopeTransfer NO ArrowsScopeTransfer NO BrushScopeTransfer NO ColorAndStyleScopeTransfer NO CrossOverScopeTransfer NO DashesScopeTransfer NO DimensionScopeTransfer NO FillColorScopeTransfer NO FormatTransfer Native GradientScoptTransfer NO GraphicScopeTransfer YES HatchScopeTransfer NO InteractionTransfer Edit LineStyleScopeTransfer NO PatternScopeTransfer NO PositionTransfer Center ReferenceTransfer Screen ShadowScopeTransfer NO SizeTransfer Medium Screen UseInstructionsTransfer YES GraphicID 9046D0E5 GraphicStyle 88 NameGraphic Notch Rectangle Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID A046D0E5 GraphicStyle 104 SVGPath M248.833955473,631.114444733 C248.833955473,631.114444733 248.833955473,631.114444733 248.833955473,631.114444733 C251.846969788,607.508798816 237.374620902,601.470201466 233.473381223,586.104916527 C229.961161948,572.271813623 233.246679247,557.561786232 233.13332826,543.290221086 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID B046D0E5 GraphicStyle 88 SVGPath M271.474448872,629.449775811 C271.474448872,629.449775811 271.474448872,629.449775813 271.474448872,629.449775813 C274.464571561,608.985278786 277.642485396,587.986110364 292.650474341,582.880043374 C314.885901663,575.315033703 337.032538427,594.523991833 339.041336149,542.591133107 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID D046D0E5 GraphicStyle 88 SVGPath M349.738151121,496.491556657 C349.738151121,496.491556657 349.738151121,496.491556657 349.738151121,496.491556657 C346.355623864,469.425457758 339.673101214,442.249914259 355.379029694,440.09690175 C389.685427997,435.394084929 447.668555437,505.420604275 443.403648749,411.755009921 Bounds {{281.602940244, 351.310079624}, {92.7099603566, 13.6067845492}} Class DKDRectangle DKDLockInfo 14 GraphicID 785BF7E6 GraphicStyle 56 Bounds {{282.555738307, 375.779575086}, {104.361613661, 13.6438918533}} Class DKDRectangle DKDLockInfo 14 GraphicID 9CDE48E6 GraphicStyle 56 Bounds {{493.247965162, 350.173216093}, {92.7099603565, 13.6438918533}} Class DKDRectangle DKDLockInfo 14 GraphicID 5DEEB8E6 GraphicStyle 56 Bounds {{494.136610714, 374.179774944}, {67.8017683808, 13.6438918533}} Class DKDRectangle DKDLockInfo 14 GraphicID C546F8E6 GraphicStyle 56 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID BB504AE6 GraphicStyle 88 SVGPath M320.610016855,384.59058516 C320.610016855,384.59058516 320.610016856,384.590585159 320.610016856,384.590585159 C320.849269756,405.237718513 341.672156959,460.844842908 303.029969634,465.53484289 C260.795477864,470.660840649 271.809498798,575.98175828 260.28801035,614.959174427 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 DkBezDashPattern 8 GraphicID EA71EFE6 GraphicStyle 56 SVGPath M540.279468611,358.132625036 C540.279468611,358.132625036 540.279468612,358.132625036 540.279468612,358.132625036 C546.1311819,432.91931498 480.865234394,513.392146019 450.194318435,542.777882312 C425.862618313,566.090030024 342.025160302,622.903491891 294.101532073,624.258740156 AttributedText String inports TextAttribute 16 Bounds {{218.868620603, 347.290894756}, {75.0121565422, 20.780090261}} Class DKDTextArea DKDLockInfo 14 GraphicID FE34C0F6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String outports TextAttribute 16 Bounds {{218.840397698, 371.983034041}, {75.0121565422, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID ACECA1F6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String outports TextAttribute 16 Bounds {{424.135457102, 369.549734075}, {75.0121565422, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID 229EF1F6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String inports TextAttribute 16 Bounds {{423.667371526, 347.872563243}, {75.0121565422, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID 5D5F12F6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String blocklist TextAttribute 16 Bounds {{89.4337593497, 90.1437830503}, {75.0121565422, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID 20AB4EF6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String wirelist TextAttribute 16 Bounds {{89.5006235834, 274.701145749}, {75.0121565422, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID 46A0BEF6 GraphicStyle 10 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 Class DKDLine DKDLockInfo 14 GraphicID F2191407 GraphicStyle 120 SVGPath M440.746341861,80.0533679036 L477.776984438,127.713061527 AttributedText String Plug instance TextAttribute 32 Bounds {{421.048603887, 148.710252509}, {101.171637747, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID 2DC96507 GraphicStyle 136 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String Wire instance TextAttribute 32 Bounds {{476.303115817, 177.13167585}, {101.065228041, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID 103AE607 GraphicStyle 136 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String Block instance TextAttribute 32 Bounds {{478.450301112, 119.617221454}, {104.549287768, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID A923F607 GraphicStyle 136 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 Class DKDLine DKDLockInfo 14 GraphicID AE96E807 GraphicStyle 120 SVGPath M364.898881334,159.578506524 L420.858822132,157.456432575 Class DKDLine DKDLockInfo 14 GraphicID D41CE807 GraphicStyle 120 SVGPath M475.343360159,189.77412284 L420.478081054,231.067594614 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID DA7BB017 GraphicStyle 56 SVGPath M264.224812328,141.270392897 C264.224812328,141.270392897 264.224812328,141.270392897 264.224812328,141.270392897 C264.680299021,128.667852362 253.763650807,122.151372665 262.019470676,114.253720735 C266.592694184,109.878900543 263.323608314,104.779483398 263.102594609,88.2428959191 Class DKDContinuousBezier DKDLockInfo 14 DkBezArrow 2 GraphicID 9078D617 GraphicStyle 104 SVGPath M244.287500769,487.747128454 C244.287500769,487.747128454 244.287500769,487.747128454 244.287500769,487.747128454 C244.893925435,469.192550955 230.3597473,459.598411804 241.351358853,447.970788437 C247.440045032,441.529790285 243.08765816,434.021976257 242.793405521,409.67534606 AttributedText String Block instance TextAttribute 32 Bounds {{92.7034725343, 361.451732207}, {112.543745997, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID AD743A17 GraphicStyle 136 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String Wire instance TextAttribute 32 Bounds {{103.724608377, 632.781303854}, {101.065228041, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID FF236D17 GraphicStyle 136 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 AttributedText String Plug instance TextAttribute 32 Bounds {{105.035436576, 500.230321091}, {101.171637747, 20.7800902609}} Class DKDTextArea DKDLockInfo 14 GraphicID 7ADE9D17 GraphicStyle 136 TextTransform coefficientM11 1.285575764081 coefficientM12 0 coefficientM21 0 coefficientM22 1.285575764081 coefficientTX 0 coefficientTY 0 HideDimensions NO LayerColorMod DKDOnColorMod NO DKDOpacityColorMod 0.5 DKDOutlineColorMod NO DKDTintColorColorMod ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 0.5 DKDTintFractionColorMod 0.5 LayerLock NO LayerName Paper LayerState Active OutlineLayer NO DKDPagesSpec BackgroundDisplay Background CanvasBorder Blue 0.45904 BluePlus 0.533333 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.458672 GreenPlus 0.533333 Opacity 1 OpacityPlus 1 Red 0.475001 RedPlus 0.54902 CanvasColor Catalog System Catalog-Color windowBackgroundColor CanvasMargin 0 DetailsDrawerWidth 260 DisplayAttributesBar YES DisplayRulers NO FullScreen NO FullScreenCanvasMargin 141.73228 LayersDrawerWidth 266 NonFullScreenCanvasMargin 0 NumberAcrossFirst YES PagesAcross 1 PagesDown 1 PagesSpecBackgroundRGB ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 1 PagesSpecPrintBackground NO ShowPageBreaks NO SizeChecker 8 aCheckerColor Blue 0.926349 BluePlus 0.941176 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.926333 GreenPlus 0.941176 Opacity 1 OpacityPlus 1 Red 0.926361 RedPlus 0.941176 bCheckerColor Blue 0.737155 BluePlus 0.784314 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.737142 GreenPlus 0.784314 Opacity 1 OpacityPlus 1 Red 0.737164 RedPlus 0.784314 DKDPrintInfo BottomMargin 18 Copies 1 FallBackPaperSizeHeight 841.889770508 FallBackPaperSizeWidth 1028.12598425 FirstPage 1 HorizontalPagination 2 HorizontallyCentered YES LastPage -1 LeftMargin 18 MustCollate YES Orientation 1 PaperName Custom PaperSizeHeight 841.889770508 PaperSizeWidth 1028.12598425 PreviewPageNumber 1 PrintAllPages YES PrintJobDisposition NSPrintSpoolJob PrintSavePath PrintScalingFactor 1 PrinterName Brother MFC-9340CDW ReversePageOrder NO RightMargin 18 TopMargin 18 VerticalPagination 0 VerticallyCentered YES XPrintMirror NO YPrintMirror NO DKDSaveTimeStamp 2020-07-14 04:42:44 +0000 DKDTablet BrushDynamic NO BrushFit 6 PenMax 25 PenMin 0.5 PenPressureFactor 0.5 PencilDynamic NO PencilFit 7 DKDTimeFormat Field 0 Include Weekday Field 0 Type Long Field 1 Include Month Field 1 Type Short Field 2 Include Day Field 2 Type Number Field 3 Include Year Field 3 Type Long Include GMT NO Include Title YES IncludeDate YES IncludeTime YES Seperator 0 - Seperator 1 . Seperator 2 , Seperator 3 Time Seperator : TimeAfterDate YES Twelve Hour Clock YES Used Once YES DKDToolbarSelectedButtonPairs ColorTextToolbarItemIdentifier_0 HSB_0000FF ColorFillToolbarItemIdentifier_0 HSB_0000D0 ColorStrokeToolbarItemIdentifier_0 HSB_000020 GradientToolbarItemIdentifier_0 EndGradientColor Blue 0.999991 BluePlus 1 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.999974 GreenPlus 1 Opacity 1 OpacityPlus 1 Red 0.999999 RedPlus 0.999996 GradientFillClass DKDHorizontalGradientFill StartGradientColor Blue 0 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0 GreenPlus 0 Opacity 1 OpacityPlus 1 Red 0 RedPlus 0 PatternForToolbarItemIdentifier_0 Toolbar_02 TextureForToolbarItemIdentifier_0 Ancient DKDWindowState CursorMode Nothing DocCenter {496.062992126, 402.944885254} DocumentFileName /Users/corkep/code/bdsim/figs/data_structures.ezdraw DrawersOnMainView YES GDetailsLayersDrawerEdgePreference Auto GraphicDetailsOpen NO LayerActiveAbove NO LayerOpen NO LayerSelect Active Only LayersDrawerEdgePreference Auto OutlineDrawing NO WindowLocation 661 263 1054 1027 0 0 2560 1417 ZoomPercent 82.290627 GroupEdit Fixed NumberColorsToListInContextMenu 12 NumberPairColorsToListInContextMenu 6 ================================================ FILE: figs/plugs_and_wires.ezdraw ================================================ AAAA_CurrentOSXVersion macOS Catalina: 1676.105000 AAAA_DKDDocumentVersion 9.0.0 : Mobile Friendly AAAA_EazyDrawVersion 9.7.0 AAAB_Build 9087 AAAB_Distribution Free Market AAA_DKDQuicklookData JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPDwgL0xlbmd0aCA1IDAgUiAvRmls dGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAGtncnSJEuRpffxFLEEkSYIn91r2Uhv alfC3VG16RQo6E7GC8Lr93fUdDDz/DPvBVoQIFPTwgYdj6mpm/35+R/PPz/fr/35u+fP f/H99Pz0/XN6fv/p+eY/0zodr2VentO0ra9tm55/+fXzN8/9Ne/Xe12ef3/Oz3/Xz+bn L3750C/ez1/+gv4m++PP9H/500+/f9LD63i/p+e8vF/beh3Pj2jR6pGtglK/+/Tc39vr 3I8D2vSap+167uv8mqZlfc7T/lrn6Xgyy9d7nabHvF6vaT+256fnsfDH5WIO5/w6puV5 bNdrW6bnsmzq53ye8/S6zn2Gwh/e18qvfgmbJtb758dXljZN58ToJ+vVr987S5tY6kj7 XLT9PKzdA1r8NmjPzzbgV/k4XcdrZ7n0djLOcrS/PvTXz/nX536u/DUbt79qJSYxBA13 dv5nXfmf65TUFwSzz/v8uA4T+KTujXYtT5b1mo5tfS4zLGOhULbXcR0wb0ZRNoRwwfLr mmHZMsPP9749rmt/TSe/X+b59Z416zeNtu25TCfymS9R1hct+NW0MqnTGp3i/nN5X6/l LSV8T/z+Wh8Ljaf3JsL6mtHDT8/5ojF/F21/LSxmvpbXfMIBKAez3RF4cs0o/O4hJXgv l2YQrQ6UZ7IJRE8HCjYvNoEcb99Zyq6f2Zye806j/T0hg3eb97wdTABu1eLmbWEpu3py DswbLJ1Rm2TSvKIXx7wGJx8sbkXVp2Muds/r+tqZdUpEhOM9I5GQm37mjXYmhMViBdHP rnlsM01isO1EU9/GypzSNsPT5ZmzvlYarUg7lva4GIKJyUKCAc9rZUkX7JqdR9dyvGAK IglGXsv6OjczrNnZ/bgWzBFlMLs0kVyY33Z2cmsETFfydulmo1CA6Ce1pBssVSmnlOqW s06drLU9QnGTAaHbz8t5VPpfjCwrCXaHJT0uF0naWy+3335A/T1ut/lXGekbD/yW0hwb HFrnB0ZqtAUdObCpDT4iYPybVAKz3Wb+SXZz7NsOZX6d73WXvb2XFw5PtO31Po8Zvs14 Txzihb1M51u/wykc2Ot1SpNwkRKA/igVxOD3Q+LGzM9lFwWTOCb07ZIPwT4vpPWWi9bv 5td8YPKiXTuqM58Xk1lEWV/rtmxmqMuSlHmSDZyozJ40azVj6EyqOkLxJv6lHw7XeGot OSdYdixHP++DiVz4l351B7NcVzgcPJiP9+tcCQrJJywK/2S/S27OkusGg5Lnoqz7epRc GgWGu/DkharRwhJkBNkR4+5wBUoMxz8t+2JCyEnJ1M5pecBanzg6x4LoKZe365/wWTAz mbDj/S/i+hx8Ihi+r10yaMx8XFLorf0MtTGOy7O9d4XQEMq2o2u95IyAp5PkmnzpyRul CmQ/qSjdYKlOOaVUOZ81qhqKWWsr9Q0OlIo7lzpDcF6iYGUuwfHOpFwqXxgey8NaPzDH 3zw3osi1gkb+TnA9l4OY4n9AdI0yH99ES9HvY8Kkrx3L6sx8kt+8oOW8Jzz/ez5rcRPu 9liRZVn5Y0KLUEcauZGDil7LYWHWeTntGxAAjZOxupFP8PI8DkVVN/IJjq/HhcaF7CYi INbbfudGPiGp7ZTrDyMX5X0JtmFZZuSNcg1G3rVqKtd15EbeD+fKW3MK/a55hxX0qwtb SR6kkQebwugQQfIyTTMYntabMmkU0EEIDla6iT+E9daF9YeFTyeW+sY5xmDP6RRgOiyq hoVPuN5VIT8tfMLxbvuMm821YVjL+5TA08LRQcKBbDWYdKGZG74/LByAaIgJx2BwSBYu zLis8gth4aLg3RWeQ25GwUeWidvvWiu38Uf1lHqSw3XalJNKjYt5h43TU66utNd5gD57 GEs+ZbDruBm2ghCc52njIZYwul52nYmnRH9vCD0s+/1of1owkbBt20UtuLhLDpaogAUJ VoIqNwUgdhmwCnxjeyois/Dr/vy7d0XkDX8xdPqt/RUIHGNeQcF47wtIKWAAbd2ElkU7 QcaisOEB81qrEx8CZX8L4YpyAKNxGdjQtJ4HjCB4Az8fCyIhvtMTSrls0/pcwd+EBnqC AsKQ5q3Lgsnj90SbgClPopeCQ/vdRHePFcvbDvCn2AnAaPsrmEH/OKcLnwS0l7szkpCv zAfZGgWYQvzGk50z6r/Qmr2kAiq7rZmVMnnrAWcIktbvdihgJiwWysJaWTNuDlQPWsBv AtwP1H/ZwffLW8EaFhGvaMXi17d+JiNho4OXO4BHRrhmRKPRtIeyGH9gpBjbopi1vne2 UQfNtWVCEfZFyMAoDXsREAFaKEe1IqCKoaJ4T7ATH0tPO4CrjQfo5HdtDjYp9tUMp5/F vAHx7xV1q9U9FzbBIMOFrpwF4HnQnH4WXGI/cMy2jSheQsNemLlznI6YynuFdS6VR6Mg 0BAdTKlGUi5t/6oj2KzthyhtOKLJ9T7kgpacFJzmd+wSfd50Dp8nCSFXB5+BNPKUKKTz AAyxoouPJdlkvNcuFU0UM7FG1vS27c5CZqOxHGngT2kUUtEfToJ0yM4QzHyiMxquSZiu olVqQXSUqtKN5vpEV9IHzSl1zucNJTSzW12qb/LAVZy9RPApDSG5SV8YWDOX4HmZVMjF KDsqZl2KmXg8J6ZEbQfyjwOZB2kfczjwjfCg7eIiwzaPg8kY6WQ/YJQT2/FGYjtQHTdh lPmNRGE7Co73x3sCYN6WHlBcmRVQ2d2wk5nNK+0nZiUK+zp5M3klYD8IlV0Rqrc/5JXO nRRUo4Bb5JXYmeC5iGwOeVveB0e2shPGJb2VJGELTGeA24todezXiZs1mnar2pAC2C8y LyS48Dko0CU/uWCNn0mBEcUONE+0acORNVrbcdlv2YNBU5pK6Sn1d51ga9GAY6iMhmV3 quTOb7+cDNxlr8jGa19WeCaHzhYY92tzn6b5fO2sJBMF5KkajXni/i3BAwX/YwkGT/BA gfMrSpIJHkjgYtuBeYIHCm5HSRhPb5AjYmPC/qtyIJCQHK4VEst4Xw/yDvDJUjAtlwJB fxgSLhO7kpeil4KkJXigkHLBZ2TqhvQXKr3N5GAiwcM/sibB4q4VDh/f3PeEZ5tRGNpE gmd6Y5f8VX35rMBobKDUk2d4yEFhqehzt7o3bsh0KFmAG2ZPh9Aak+iQNIzCVTGSXCpb YPxppNNoM7/IBnYSMYpctfbU9PeJUVujyiZA8Y5wM4AdZdF8MG2sWn4HUpuQpy5YRZty ZDeYoa+rUiB05KvPRElwCA31/A6UxsdIuWCwyLZxO/IyUFwikboJsZmqE47a2ly43og5 ufyrn9CSGE0scVWqKYW21bRdJfvcVehtcEAW4aodTHL1x90XI91I4H9wOw0J76mUW5hW Z25yrnT/hRFafoccOv6FVeAbPJqT3sGinSSo4OkdSfPVFMSSO/wd9wXSiOSOmI/ykjk0 vVJuRwSFUFE8twNfmYpAV+V2jNeXJSVbbkfcl3NVagVjILeDFPmT0kxdsgUaMBAXnikZ REuEJx0cuR16QsfJpzkFqCEdUT5K6IFRLAMEBYhJxOh6YuEs0SjKJTXdUkITRtWsgDYH yYrMSqEQ8p/j8gyt0rkzgUkhL2HWSO4wJWKhkFFlyiABe/C6mdsxinIzJRS1Ae4BsVpm zlZnNLXyNEL1xEQsuZPDWdS13A4kmxPa1XI7zCnmjb5ZbicXV+kPSM4BAQXtPKA4lwRZ yO2w3OCllLnldorjABNL7pRUwDNK7kBw0RlByR2J0wWcjUIHqp/QlH600CefE51j3+gc y815h2Z2q0v9TRaEiieX0g6cl80OzFaS3c3AZE0hkrvJNTMd7ZAwP+R1/vy1cy7MzAy4 2y06CXF44kZhT5OKLAJ/bfbbpRqgmf1GOoK/N/PNjAVsM/MlsUPoa1mbYOUzsx9QmvlG hkTsbubbpVGgmfnSl2dtUrqZj0kdaBT31W6+fSsz374nV7lhPFfMblauvN3Mm/nG8prV a7NJ1MVELXUVpgKlZbfCnHpWhvUmv5upLqR2PJPGr8x4bVdq+Ta5JjfeyA8YQV4gUwg5 mGdtbIruTzIbgcn5pOGxsjb07CtDEJ60gdRsN5I2stRmu8CslrWB0vxgZW3oyr1lJlug NI8aaRsZajPeRrG0DbRmvNFKatE8eNdTKAr2ybnabssLdapZNes1qG7pJnpqcaVL25T6 OhNkA03BI7sFwa2gcmDQzFAiZ8PfZV2YkqfSnFB50t54OxsUmHdgvPAHw8Wcf7aQ6xH5 RN7CV9i6R+njSeS8wWJyoe3ckzYGi5+k2v3cM7EDeU8792SVDRafsJI8kMKww+JTbNMp imK14+JL2Jm9PKsyWMxOW2AY/yve8KfHpYSzjk2ln/QgWHyBSRGP3H0gJ85NOfYUOkX0 wGLaiMJBkIVcg8X0la0CqFVPAef68Rospq+YlQNDevJ5B3rsFxcYs1jgKDSYxCwbUnVG mpo5mi1uBywuiTgsDrHZ0hw7n7gxO/ek6waLT3DtAItPnXWOsPjkSFOnngmLT51n6tQz YPGDtB36YKeeCYtPji/t1BN3YBsHyggItzr2hNJgMWmGOPZEtgaLH+esg1twFFJqG5UT 6KmNZIqtEUZYXI1c/tGPTKptnvrRXJVqSqFsNe1UyVxc6W1yIGBxMinVv2Nk7B2L2w6L HyERVPlmbsitg8UpzeHYk5VZVKXqI049nTRhojBPcFTSJKpKQUZYTD49zjxpY15HejXA 4geZknbkme7rRCp+5Gm2JlgsFbUjTygtrlJnMMLix4nN+ZEnrZpTFc2OPNPxnni5ALzN PTfKAIvpq1o1WNz15OFgGM+DRjerFljoKWfeos+wPA9RHRM8jBWjHBYHM1usa7C4WB4x s4QSkTVEZ2bqkfUEuejMkxAFRaGV2psRFktv/MiTRg2qn6BJHXliJzFvrLuHxQ+KL+LE k0YttLLNuMHiU4lDHXnSpoVW8wojLH6cJCFHWHyyjR5gcSPYmWcJOBuFDng/rNYD6zBa 6FPNyQNrN28PrLE6ySBwoUyusSBUPLkUgbXnpdtKstsDK3baRKKOB5NzM70RZaV//goW lj+yLmaU2A8xndROl+wQE4qs1qpxlKuJyc7Yox9hQpPRctqlzDj4K9asoy6Hgc4ZHYjd sbBqT1BjeWPnsQ777AQzJaEDOD/BTJvVMZ2dYKZIRbETTHPQ2so2ip1gQmvAq2/VbLbr yfVsGM9ttpuVa2w382azw/Jc9zsmuH0kn5rJwrdiZRha8jssVsd9TSBhsSk2YYZmsZwS ArPJuKbBzkAOO8KEYttmMmBxgpkGq6NjO8FMg9Xxsp1gQrFNus767lhYZ32OKJ1FOuuz E8w0WJ31CZu2DWljt8767AgzRSKKHU6m4BplxMLRSgba3HbXk5tsjVfq1M3KTTZmrp4a Fu7Xl+rrTJBCW1QqPqUVJDdlTWYoyXA3WUzHZXI3tx4LpzA5vXz8MBbeCeqWa+ywMOVD NyzMeZdhYcEFsXx97vLjVgMIqeXRdkFo1QBCIVJPz13HWQMW3i3SCgvTk2NhShYHLLwD qQcsbCWedyy8I6YBCz92aoRGLNwoAxamr2zlWLjrqWE4ekIpAns70qtZRZI05x1YuBaH xrR866NY4Fi4mORYuGNkQNjgNt04zA2JoGaiUAcbYpPJRiOhWktIZkccMKDZAp4NeHNc 4FhYPREJERIobcDC1GuNWJgmOjIYsfDO7mbAwo994hh1wML7xDl5KwHEjFuKeGfnMWLh nS3QgIUbwV2tb3SqkWPh6iewcD8aw+m0IabUKVtOO7Bwv7jQ2+RAqnYwydX/UYyUtrdz FCrXnNuRIt5TIoGFO7kVFn4k9UMsvKNLqgDsU8Q7sKfHwlIQYWE5j7a53kEFXv8XXkd6 NWBhrBjTVflfui/SxI6F5dKwX7CwVHTEwrvi45AipnTpCywsmrAwPXkSYlc8VvlfuudG GbAwfqNaNUff9eRxdRjPg0Y3K4+r3cxb9BmW53G1Y4LH1WKUx7qemRFYg+WsxWNmCSUi a4quIisl7Ow/VP+XoXXnGH1IEUtvhIUb9G5YWJXvIxaWvvVY+MkvvsDCO1s9pYiRQXCJ GvgRC5syj1iYvii+Vf1fhtZdxbtdihgbEGHEwtXIA2vXjwfWYbSG0+gq5xQ65/NmfA+s /eoisBYLPLAGlzo7cF5qO9ICa7EbH2hJphJJo+Be3eSIrOpKzYr4LSysQaz1hh3esPBj U2pXBX0+k5Wl2bllGC2SumFhDphBQj0W5pTasLDss0GOFVxwx8JU9dyw8KodJsfE4mjL C6/sVYWFTc1cXivReMTCooxYuFHGvHC0kq03m+16cpuN8Wxn6NrYzcptNmYujTXEOCzP bTaYICNq9pF8cpPtWekm+0h+u8U+SyBhsSm2zmKprBqx8EZ4ERZmig0Lb0Q6r+bLCW3g mxELU0415oWBK46Ftdi2ed30vYSK+dJgtwnYp2o+KG3zuhGQGxaG5uzeqDUTFpYA2vZE lBELN8qIhbtWrgJdT64ow3i+tepm5SbbzdwVM9bXb1+LCW6yxaewgp6bzWSD4WFdE6bk MrmbW4+FU5hRyWcFE7e8MPbtiaqDkHnHwgfxcCyXoBjklhc+wDaGhdVVw8IHUhmw8CGU O2Dhww5jx7wwx6g9Fn4csHbEwgesTWzKvyovfGpLNeSFT7YZAxZ+NMqIhbtWjs66nhoW fg7jNSzczSqwcM47sHC/uMgLFwscegaTUNkGT5ORqLWnc4vbAXNLIo6FU2yFhR8UHt6w 8EEd2oCFqfBzLKzRDAtT8DNi4UMZX/LCzNDLJQ505Y6FgcYjFn4emN6IhQ/MWljYfK1z m+B5w8LE1xELN8KYF/ZGzMnlX/0EFo7RWtAzLFxTio1XTTvKJfrFORYODmB5odrBpNgK 9oyMconidmDhksgX5jbkhVOajoXHcgn7kO+GhQ9yWz0WloKMeeEDgCYs3PyQ7cClVyMW Pog2IxY+2NDe88JSUWFhcb/lhQ92uSMWPpRzbp/C0KolGEQb88IkqgwL0xNxWDmmRhmx cLQqR9/15OEgxmu61Q4Tu1l5YImZ01OLq8PyPK4GE5iUx9VilMe6YKZFcWUByAYUyyOy llA8smKSZN4kui6yUvp3w8JUt4xYWHpzywvTFfmFIS8sfRuwMBnFL7DwARQcyyUOvIyw MMsNXsor3LAw1dY3LHxwWtJj4WcjjOUS1Sh0oPrxwGqmE6OFPvmckFMLrI9u3h5Y+9UF Fi4WeGAtLkVgDV42O1CSqdjd4ugEJUTSKO3b2SY3bVkbug1hWrnE1/LC2VpV5Tcs/Fxg QY+FF5gEFi7gvmjP2D5tYVQz2gVDGbAw5UO3vPDCeXHDwrLQtn+lEv+Ghanhv2FhfV97 zwsvWLawsCy0HaKLMmLhRhmxcN+qYeGuJ9ezYbxIetas3Ga7mTebHZbnNtsxwW02+eQm 27NS5iiTdX6jZb57LYG4xdLExdZZ7EK965AX5sPaMS+8svERFjZvFOAcrRmxMMWZIxZe dSSNJ2ybg4aFV2C1sLDMs7k16tpuWHgFZt+x8EoqaswLiyIsLFE2Z9soIxaOVjK95ra7 ntxkYzxbnptsNytXuZi5evJNWre+MNlggpTeFJyvEJxPYbLBzRbJZbLF8DDZlEkj2Ecc XttSFpvC/BYWVpRnupQOc876RY3EJpSk8kNatZTXps8+VK4MpeWF+TDynhfeBKHJCwsd W154QytGLLxZpB2x8KbscVcjsQGpByz82Njo3LEwEeaGhTftcocaiUYZsDB9ZStHZ11P joWH8RoWftasokYi5x2QsV9cYOFigUPPYBIK07CwM9LUzLFwcTuwcEnEsXCIzawIEaq+ eBNgIy+M7kdH1JsPWJgQdMfC2vMNeWFt5sYaCe3c7lh4Q82HvDB7bHI7Q15Y27JbXthS I2NemNL0EQs3woiFq5FjYe3o1U+LI+gL49do+BHlhWtKqKQ8DJSYduSFa3Glt8mBwMLJ pFT/jpFpJMltx8KPkMiX5obcKi+cRtjK/e3j8K50mM98DFBZGZOF5YlfEKC6GgkpyIiF N7DTLS8svRqw8GNjtzNiYXbOX2BhqeiYF944xB+w8APv/QUWFm3EwpwQ3vLCjTJgYfqq Vi2udj15XB3G86DRzarFVdaXM29xdViex9WOCR7GilGR93FmtljXAmux3CNrJ5SIrCE6 M9MWf2mli0T6Ggl9CzLkhaU3NyyMdZOeG7Aw3/0NWBh1s/MsfRWOAbTQuuHnRyy84dOF hdUGY+BQwLxCoNOWZnps+PkxL7ypuqnLC/MzEca8cDXywBr94JQ8sA6jeWDt5uSBtZt3 JJl8dZJBBNZiQSSZkksRWHteepIp2e2B9REiUcctqesm52Z6I34rLyx/ZF3omOqOhXWY 1WPhHXA05IX5cPOGhR98zjhi4Q1/OdZIbGC1e16YLx5vWJikxK1GYtMZafvKO4HQJinp K+/EwqKMWLhRxhqJvpXbbPUUetaPFzZbswqNrZm7zfbLC5stJrjNJp+aycK3YqXMUVg4 +R0WWwIJi02xFRa2E88BC++kb4YaiZ2szz0vrAPHEQvv3Jkz1EjoCPaOhbmxx7CwzLNh 4V0gbMgL6yy3YWHh3MZu6pBvWFiUMS/cKCMWjlYy0IaFu57cZGs8ZuUm283KTTZmrp5w aJxl9OsLkw0myNYMCxefwmSLm7ImxMgBbzDcTdaOfE0md3Pr88IpzKiR+NoVSZSptVJh InBc7FKlwnn5SxSvcokMSUZdkVSlwnmNTFbBxk0zWSeb19FUNe2jLq0B0AgG1702+BHD wapQIS9JmW6WCucVSVkqHJfoVBFwXpHUlQrnFUlZKhx39lTRcdzs05cm5/U/Mae6Icin HdcIPWttednQIzmQVyQlk/LWouJkXm0U7M7bj6JUmNuPVGkLaki5cXEBNF2qkIWp1Q8o x2BwDUY8l2pRn1JT4vcCwnlFUhYL19qIyw6EkwFZdes8ogrYa4WDkVQPZ61wsjtqfFud nG6tijLglFvUChPwQrrVCAuzO7Kyn9CSbrBUpZhSaVvOunSy1paKG7XCpdvOo9B/yjKD kXWRWNYKlyV59XZdhtTJra5IolYxrfCjK5Ky4hQjlS282dplXSoCBo1IJbxc2C6NsiuS osKVaJ53S2QZbN4/kaWycUsFxt3qhU0A8FvF/Vl1m7enZGWuCuXsiqSufjevSIoqXyu4 syuSshI4b+Ho6oXzro6ulfIfGxPAFeJYURyxftn68uS4GKTmRAyxK5Jq3oRZDrGWXB1c iXtIqmY6b09JPqnA0a5I6ripMkg2+JxMOc/rapSUi9pw+wFm2Sq9NZyR+GA8a1Oro6hf reG8XlhCIPjbvU1ZLxwTf2axbF6gEiW1trxgghcMW+GprpLKeuFgJhS42rCwnKVxPMqF SyjstgSFQ3JUFItgUDhvx4ma4lKB7CcVpRtM+1pu5MCivIQ5L+zJcuFSzCqGLvWNcuFQ cXrihFk7BrmGZghVe13mEhwvk4qK4bg/BbtM2XX3pyT1W3jYLZXqoihYZBubQ2WNak4n ylhzzl2ta6zM7r8hP1zXm2XJbLKoK6xNRmb1bdpuVuim7UrvHBOn4KSdholTtqIYJg4N 6OuGU026Vk2Tuo6a7fKzGo7MtHSy5hS2W/MO2+1Xh+nZ9WbJgzSTKBsOW+rrhjUVM91g uAjwVIXbOj2heKVRdLkQ6Jk6rM5ys2y4uokq1hiMmKV04HjzUdbDpuFm3XAabldYqyWZ 94ryW5O8mJQVumm4UhjHxGG4UipLD6dERDFInHJrFIPEKd1qBf5lptxLkz2F6dZwdfNR TUrRWdEi5x16adecOeRP0w0e2KrsAr/kU5lBcVOTUaTjui4SAKQje3tqlcNhub3sOstN iUaOmNtt2Kx8eXVon5nKaJ7JyMTFmbBMXOBZTVQmsUNmPgNeZG40MUiXQQ1c/Ig0a4KZ zMSKN8LFXbrWTJ19BTRPDydyisQvqutXh3bpYTMCgbBohaJRRaWrQ6sndqN2dWiMp4gE YNHVoTmnhIUx7cSO3doSYQYHCoQmkxKqFicTzya7A/KmRAIXk9FoaX3ZrOPiTEZWP5Gw rMEqrRm4mExQSxDXrCM9WmurBHExIDKtySPUVvlhvFAwsjK2xW7P66K5pB0kkkj9ptwq P4wF+NWh2ShwceSHsQ3XkhqsVMmnlDfVkp72/HDpZK0tFTcYULrtPAr9byk1NhjoSFlJ sLssyTP2hYs7uRUuTiu0U9gvrw7NLGMXWTNBHN6CTjxFHD6F7GimiDO0ZuozvVOmR8OH ce9N5ogjtlamNWNrZmMztnY524ytmdkN71vZ3/TRXY44PXnmiMPZdx15bI3hDDm22Fpz isiSGeKMP/3qIrYGD9Bdh6DJp4x3HTczKibPM7qGXPyeUFxHpRq7Ri1FbCHYonSmiGu4 zBF3k4rsZ4bXTJDW8ipJnOE1kq305HyKHHGG1y5rmxyPFHEJxVPEITl8R6aIQ76eNgY8 e3CF4KnmUhS5IQfhgYszRRwqx+88tV2KWWsr9Y0seag4aXLPpIch2MmZ59vLXCJLnGA1 s8QRXfGy7WQGL9tF16T+OFwcSaoeF2deMqcTqcucc5ffjJWR37RcceHizBUXiyqZWoyM jGvZLmhjvBG0yxWX4CLDW7KFMuLiLldcalKtGi5WIwfYYbuVKk6dzDml7ea8Axd3mfAy gUgVp+1Gqjhsqc8Vp+kGw9MoUyaNspOC9Ax/7WgzVVzdROYyBuNnmSuuGUWuOGcdaVJM 0vfrXTI1DTdSromLMyubhlu5W5BPY3cmeFMikQQm6vqNoI0y4uJqhbwMF1dPgYtruMLF NanAxTnv0MtHt7o03eBB4eLMFZcZFDfDVhCCZ4vTdPWBkLLFYbm97DrLTYn+IC7uqhcz mmfBakb8LGoNXPCoytfEDlEdm1fqZ/1sYpCuyjZxcZTiJpjJGmLxRri4K+lNXJyFv4mc oji4cHFXQpwgLFoVLq6eAs7FeB0uzjklLIxpJ3bs1pYIMzhQuDiZlFC1OJl4NtkdkDcl krg45Va4OAtWq58oIa7BVC1o+WLSw7gobvl/RBFxzdqLiPNKfcqDMfX2QV0xgJA4XKkf BbtYuuPirqo3cXHW/qZIvDzYTJbDFspKUft25Vrh4mwUuDj7SS3xgmVt1eJ1hphS4eKs IS6drLUFLs4a4tJtAVxloOK8JGqIe1ycNcRlSVHV3Shc5NnJrcPFQf0KLo7iRR3qRL44 61XDW1RNa/iUR1f5mqE1y2PTO0UJLX37bdtdoW3EVjN4q53I2JoVuwl3oq5XmzX3rNQI ex1xxFZ6Au6Qmiof3dURpyfvWjVn33XksTWG63BxzSlia1YRZ/zpVxe4OHhQEDT5lPGu 42ZGxawjzugacklcnMKr6Fo1q9VRlBHXcFlH3E0Km7VMaIbXLKKt5VUhcYbXKMilp4aL o44YGQQz5RnGfHGWEZdQvIw4oiuVvqorHvPFUUZcKhBlxKUo3WCBi2tKEVxj1p1i1tpK faOSOlS8qq3DEOA3OtSeJyhzCY6XSUUlcUTXvgS8i64p0R+Hi6OQscfFWbua04ny1pxz VwMbK3t6nWzh4iylTdutgltsMLJ3UU+cthv1xHVTflffm4KLKmC0pD2XwFW35Im4B7ps t1HGm/K7Vs12u45c3frhXCdrTmG7WU2cyt2vLmw3eZAoNMqJw5YeHS/T4oLhabkpk0bh nm2w4D1fHKWrXTdeThyDPbsK2JxR1smm4WYpba6tKyhOw82y3DDcrNxNw436Xh0BOy7O IuA03CwUDsvlQmIVGI+4uFoFLq6eAhfXcIWLa1KhcTnv0MtHt7o03eABBs5k9OBL8inN oONm2Ar1ws7zNN0QS1huL7vOclOiP4iLuy/cM5rHpQaVCcuLDwIXPOp2hMQOeYNCwIu8 YyExSHcTQ+LiuK4hwUze6CDeCBd31z4kLs7LIRIXxwUShYu7ayYShEWrwsXVU8C5GK/D xTmnhIUx7cTF3doSYQYHChcnkxKqFicTzya7AxenRBIXp9wKF+elBtVPXHxQg5FKueNi 7QCHOgpt7YanpvqLJooBSqr2T03lpQ6Ji7U9uz01RcrD75kokfg9EzJZw8Vxz4QsPfLF cc9EKUD0k1pSg5UqYfxW4lzaFrMunWQr75g/FTcYULrtPAr95zP8xsgeF+c9E2VJKr/A YVa+uJNbh4uD+hVcnF+9d7g47zQIb8Fn8BRj9E9NcX1FXjWRnievUEjvlNcshA/jXglN e3hqygx+xMV5q0PCne7uh4ytcUNExlauXvFbJNJHd3dNpCfvWnkSRPUMfR0F02xXW3S4 uOYUsTVvmsj4068uYmvwoCBo8injXcfNjIrJ84yuIZfExSm8DhfnvQbVEYjPSoprOHSn YbluUuDJERfnRQu1vLpsIsNrXjYR4TXvdcjw2t3+EOE1r4goofhVEyG5/qqJxE5x1USp QFxZUYoi67nli2tKEVxj1oWLu6smSn3jto1Q8UfcNcHvvI4i7u2wPXM7jOW6D+d4BteU SkRXDKium6A3lTDp2pWg/jhcHB+797g47zdI640rEHLO3T0JsbK4c6JwcV63kLbbXcqQ uDhubuiQjN85Ubi4uwMiBZc3RaRs4zaJwsWNMuLirlWz3a4jx8X9cI6L48YJ1N2rCGre kVPtVxe2mzwoFErU0OeIYUvcL0GP7ZPFtLhgeFpuyqRR9O2J3xRSlpvXG2Q3eQNCDPbs bknIGeVdComL47oFVuv54riUQVmF4EBe3RCGm7c7pOHWHRCJi+OiCKTEp9C4CI5eKN0d XpBqlBEXV6vAxdVT4OIarrSpJhWmm/MOvXx0q0vTDR4ULk4+pRl03AxbQQjtmg83SaoK 49aJtNFOdh0uTmrg4vY2XDwgVe9H2XOsKoLUCybKPaBOugSYCPut56O+/kQtrw+9ePaJ QhjdFTdxgzaxXDR76Ei0N1fgi7KSkfFWvJQiCoeS/BNt2AHak0cklig05j0m3cF86YEj DomAOGzl+Ccqx3j3iUc4eFPFCHg6Pu5+rOT2OIKjNY22hd+vIIadcxOn8Cv0DgGT/5N/ WEjA++tQQGPeSiJFzXR23ouVLzOavo6HT2++nzMKJZ4XFOalT7z47IEJUqOkbNHBl+ty wIrjG5dBKqdEV3pEiouXFNmZOZCSO/31AM6k57mUi9LLU/oZnz8zPCS9nsC1P3qqhzcD 9DPU4eApED3nw4d1CwlBVsgDyvY7PjnVZRyW6OJ8k1b6DIdvy0ThmWFOGVgBFSGauFH0 Mz6K53Lyia6iEVkTUmpdP3Bv5X0pKDWavs86yBPVnPiGS7cjxrTtZSReVlBHuTYCCi+G 6VfBACi8oAOh8Yj5kLPc+aCzOElPOFOe2YUW/KZikctSNaWQiSgbEg3JaW3ZSokgFLDr ifnzFhrvPuV4pI+4wkfFZHrlySbFkviZHjOLeZN3QgXgrRLBLI6Mkj4v4/kd/cw5oKSe PSOWTFKB5Wx6H6yk5gG564soPfwEv0m9oQsrz1GESMweeT2oExsGCqGpSQi30WgU8vd+ 7KExaQn91GCpSzml1Lecdiqlr03YP1U3OQClqXdyKY2gYyXG00zFGf4oc3KhuIF1RgdT 8GV3U7RvED/80r89/ySPgrtRfoHb/PTqd/MpvL5kNDI/Rtn1GFprRQ2g2lCwr0wFH6ti nVosfoOdJBU+bLU2PWFr6Qh9yqr8CYLUP+nTJm5JsowKVRBMWo6HV6XpS6d6O33J8Zxs MZ3Cz3A8u2xppcJnA43zM90YL8s0Hyyc+7EPftgTfh8y4GnvX7nj1YNQd8drtN7xghTU irmm4xXl7ngFQ0bHK/AyOF4R5HjhWzpe0UbH2yid48Vbfuh4tUO8O16ulb853h2dGx2v tiN3x6tNy+h496ZznePVZufmeHe5xMHxijI6XqLJF47XdlKD4xVldLxOMbN3x1uNmiNg u4fNWD/hePvRwqfUnNztxLTL8XZrCwdWDIAiHxc8Qg/c8RYny10Wv8OllkxEkeMNyWlt 2Yo/jI6XQt6b49X+5OZ4wSA3x6uNzeh4d0pcbo7X9kOD4911UjE43p0DjpvjFWl0vDLC wfE6YXC81cgdb/RTjrcfLBxvTSkcb007HG+srXe8xYFwvMWlcLw9K8PxBsPL8dr2z+BY uNhOdJ3jTepXt3+D4xWAvTteow2Ot7XqHa8od8crWD06XgH00fEaZL85XtFGx9sonePl bYre8U680dHe3Ytn9/TPQA0udsGiLgCgPdnHEYR/NYg316t7uA0yXqT5BC1EUuEdRsSF tuyfT96NcRqBhasbybSB2fXmnrRNb6uJhIIsaoa+HZxCMargI8CwPbo3TsUCIIlGe+b5 Yyivr+cm8UQpBbZSXNn3MZT/MIxInHzqqDiiWwvMEdulr6LNizaKiJhHOVorvbbaKAAx +90b9GKU432gu3xZKQAO/mabRWDUY1iATD3OqMDCs/Y8/KgJq5gWwomvxHtceCJKGnj5 Fdox0xUl6/hfJmWUSQ+zcg2LHjiha8VhoH2Lo5RY63YVkh4WBPW8a5FIElI4zVegqkdg TCn4jHtQEyAquwDLeEz8kyYPjVANjesbyIZAoE/gmGW+3uwvoAA69oX0iSqIkan9jJAJ 6BaNR3lpRN4Vw4RA5HTmgd3AAHLGJFrbhVRcbsZVInQliKd3bN+mH/qdcCEbWCj6AhfN aRQbzT7BHRuxTBFaP6yW2kPegBxHo/awTTLnRMarzTKmTTILF8fPYm3qCpUFY3Uc4N7U lcv0i0kQ9A3/wEltyDxCOL+hoOtksJpMWFujlODEyqQpjuuVYFGsJ5WOC5hBsfHoCevj UV37mc9JScQ376vRyOeN7SFKMTdWJ0/fMsDQjAXk+piUPV3Mo5KNTVAY2n7nzOQ5Qkq7 23hYhzjO3Hgm1YyDf5QKQEHtdXtQSK5Rmn6FfL0Vk2o6MPTkmuLDmVGFPtWk7H5OjZcT b4oJpVteqK8zQXbgGl6MCjvouBnGUiwPE0upGAERuM3Zl85fGuLX32/Q0lFyHKC8Mztc vSltNK4SbTQ9iNso+ojCWq0yXPQf72AUPl5sFojXWTBPuf6dPd5DXoeaBn4Hy1dKpMzt ACe4mgYKHllyvMAq/BcPJsFsOBG5Hd6kbr/DDq4HX7Zq00PfMOpQm+Z29LUzaJrfYmz0 x1xp8EEK5Vt+l0/ZGEvRg4SE8ia2Ou1yjTYzbKPgCFsrdjRG4Q3WRuHijOZ3BbJYHVGK t4zbu4s8cntCYf+hJweBGnxFwkIgbO0xbjGAnAHMhLZuR/O7vH7dfqbXXbV+uUmOd9m2 imv2dq1eNNUrsypE5ijQva5o/FQ4jYdypW4k6cRzNIm0qLldnDWvlG7gPdyB4BWtMGh8 zIY2cYWArFe85h9Vs86Rm24+RJV50hkK+2oAVfudvnQkIsmpazK0QqD6DEfbWLYj4pVA L31DIY5pzwKN/aJ0CjdPksF4zD7efsdtL29WSRt9p836aNOUBRKT0jVVjXTIxlFhdKvv iFBAjIrhbJooSPtdTkp14MTUmDg94YhOia9fHg5Lr5YGE5iUnBp+p2MUvlDhLNhpy4NG KgVaMh1/84ZnIRj1JAoEl5673tYIblIQpEl5R3Iyux5MFsWGwzkhVvuVT4kO+ZVCVkyb tmiB/coW58XuOHX7nbOA1lzjYL9zNsEKvlcz23dmwi4W1O6SaSynL8H85g9cLLBZIbZE 1whNxfCzJuBq5Drg/UiYrin9aKFPNSfcrOlczTs0E+HwSVFDQaG/xYPQ8eKTXKgsoXgZ 1uL8Zk5mZbKDJhMWd7c797xfWONXXK8DPrltXWXBF5/c7RGOZ0FHjKbX7mEIF1fhoFur iHLabBkFkBuORxWfVljNx3wAPvyMCkF2eD3ZM/B4HmyWNlCAqJKIPA84jzlAo45nNsTH NSPtdwtOWZ5nVoRQ7fV6YcTue274XZ4YMEJXbG64FOsgkggV80y9Igm6w+VcwGv7EH3m KTslIkjVnKIpkAO/jcbhoJ7DNtrE1NpvKa+Fpni5o5R2RZfuqG80XsvWuCwUPy4E/8Vk fhSEZwsAtmlfjgtt/ROhBHPCj+vohBfAtyZRDkawEKO9maepOAlVbwVKNwr4Vk34mL/J E6dIBzrN4W1pvQSsG2mBhTrx4QM6BKOM9w42EoVUl152v/D5sEAUnARxlI3McVx2votz 0yOB+vyG2K7jHp5WFiBqkQTxXUhPn3C/L/ydoqDR9KG+NoWMDUX6wgUD2gq/NXMIVBfy ub/MlkAlpyKNQcB2BcCbqAUFn8ueiJwowMDwO4LUO27mtlroBFq8WdapRN3JP5qRUegC RblbRVxsFZcOQbfsAj5sMPZ8LEM0/s02Gei5/UwVmmZCGBjQ3dpw8UXzfPiZN+5Zv/NW 0m9dS1A9wT4F1WE8IlL7XUyKeDSzoermLYtFdrE6TkaQsDZymmZwQHeCsNXqmARFOGRg pYwXUrJbZnpyJUBJxCisLeRmXt1aQYOVuuWIaOD98GQaVg4hBkOJuPuhucuYEmkwfmYQ x2etpBtX6+l3vjZd6cudZSaC4AC7o42zCa3WmaTEGhsP/c5ZCYV9gJwINGc4+zMgrAgu EwicxpgKmOS4uEAUvyIU32/y7Vq5DmRHoSj9aLhsfkZXOafQOJ83E9CdgKhlv7hQ3WJB qLezieWGETgzbXHNUpBBsjytKaVyszkMnysAm9UNlviVKKLsszGNvDBnpRb4wuWANe3o nRrTcDCszBuZaEFiZBZEmTj2anPGfXHqQUIejK5XteVhsBc7K9UdqvI5u+C2KNywYVkD fA5fQ3JYAw1X1dzOOmP4RjkIGXI7fMjMaFiumnx6mNf5IobIwGUD4GcO3i5WA15gokAT doZ8+m8uHz8JQoVFyHTnsWCFBvYOuDt+KjnjbxuJqIIDsV8ya2gE9WnWnTBgCW742p2G b8QWqYbEMFa6Uwj5ci6/eWLrFO/R/d/tTsb385e/+OrjeABLIeqLjwLlEPH4vw9SgxIi eZtvED4R6LFeJmuNSN4o78FMOW5ny8QQCtckfvCJSh+hzXytIYFC53faWOm0Xfc16LQP PK20AX/gFIK7icBtOs9V3uAtz9YCws9/8T3C/b7l5t7P3319lf/zO57qNW78jP+nPAwp MSYZHGLguTy++/3z5999N/GC4He/ef7kj3/765/+9tfnn/74l79+/9Pnd//n+b++4xTm w92Z1HvsnJu9cCyoxc5BEbfzXLfOf/eHf75vlYLwejQoD1dFdLz3/afPf/vvf3jC8ltA NTaSSjRSHHCb8D/V6arnIwScZqn6/v+jS12NA2LEYgWZ9ZbIKLV/ap47FiSbB2OSDRh7 /NXzJ3//3V9+Ks35ya9/+vyv53f//g9pgvAqSAv7IWjNbC1/ZPeDWn9N6x5onU632Zx+ R6lM+7z+Z/y/lsIBFdALSC2jaVwivplu/+/Pf/z0f6dSEg32CBtiMLqkZOByW3n+jBwX LoswN8F+c6am8B8OTZkyIZBdMj5qv2CtL3gYe66xv7Y2WdTHA+BJ1hn715PaB+mYjwaY fvX+r39lDCpIsC1FHe1pP17Dr6Z/ZQhCA86R9DRRiWr64+NVzP+2/iuDsDFQooZ9CJt7 7lwJ5R6F8avl37ZulEkx47V/y5USynCe1IToMwtAuLaE7OFw9RyMGE0FGp9FI92v3ZSO 2NqfFE8/FLrtNSdCkB2/S31IjwomkJtW3pRNF7QFW/osGglXnKA+mKWS3Ej8tCMRHDXU D4WED3VMpdmzsBsQkOib/jv49umPf/jDrz/99T9/0iwJZfsfT/vjjFL8509L877BRy13 B8EcNg5y2nQFoEpyyGSyfVJCiZtXnhzq/9A6vuoHVjLBdgmDFfJzRnBzBPd1SN1yJdIK 1vKI2PeNtSgCrkgCVUMkbOpIYHE6I5QKiMm1qEDhq+JXHzr65Jo09EbFQyfCV4ky9UtI Qld8ssFB0nTxwGWBABT29R+unFYqnY9v8XlKKFFSqx9bchtUp0NeHUZ9ZgT+1fKbyqNw nZFhqI9+zDV+4HBs4UNltdnGkPh1mQOJhBhxBrIvC3CnG3Em88NRTzu6yxH9t8xWnJHt fTietEUf11JoROSnmPGiyk1LxNMDY+A6Wy8hjqb1B9iAzRQ2xy51XY0bHJSpEI5bSthJ Kj/ymR5xc4tyojroxclD+y3f/n75429zw2ZXvyL4cN7GhNuIzJeN0056tB+R49OZHd5t QPspjBQz/uP/AeAgaFIKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjEzNzA2CmVuZG9i agoyIDAgb2JqCjw8IC9UeXBlIC9QYWdlIC9QYXJlbnQgMyAwIFIgL1Jlc291cmNlcyA2 IDAgUiAvQ29udGVudHMgNCAwIFIgL01lZGlhQm94IFswIDAgMTQxNy4zMjMgMTE1NC41 NTFdCj4+CmVuZG9iago2IDAgb2JqCjw8IC9Qcm9jU2V0IFsgL1BERiAvVGV4dCBdIC9D b2xvclNwYWNlIDw8IC9DczEgNyAwIFIgL0NzMiA4IDAgUiA+PiAvRm9udCA8PAovVFQy IDEwIDAgUiAvVFQxIDkgMCBSID4+ID4+CmVuZG9iagoxMSAwIG9iago8PCAvTGVuZ3Ro IDEyIDAgUiAvTiAxIC9BbHRlcm5hdGUgL0RldmljZUdyYXkgL0ZpbHRlciAvRmxhdGVE ZWNvZGUgPj4Kc3RyZWFtCngBpVcHWJPXGj7/SMJK2FNG2MgyoOwZmQFkD0FUYhJIGCEG goC4KMUK1i0OHBUtilK0WhEoLtTioG5Q67hQSwWlFqu4sHrPCaDQ9rn3Ps/N/xz+93xn fOs9338AQF3IlUiycQBAjjhfGhLLTp6ZnMKk3QMKQBeoAkegyuXlSdjR0RFwChDnigXo PfH3sgtgSHLDAe01cey/9ih8QR4PzjoFWxE/j5cDAOYNAK2PJ5HmA6BoAeXmC/IlCIdC rJUVHxsAcSoACiqja6EYmIQIxAKpiMcMkXKLmCHcnBwu09nRmRktzU0XZf+D1WjR//PL yZYhu9HPBDaVvKy4cPh2hPaX8bmBCLtDfJjHDYobxY8LRImREPsDgJtJ8qfHQhwG8TxZ VgIbYnuI69OlwQkQ+0J8WygLRXgaAIROsTA+CWJjiMPE8yKjIPaEWMjLC0iB2AbiGqGA g/IEY0ZcFOVz4iGG+oin0txYNN8WANKbLwgMGpGT6Vm54cgGMyj/Lq8gDsnlNhcLA5Cd UBfZlckNi4bYCuIXguwQNB/uQzGQ5EejPWGfEijOjkR6/SGuEuTJ/YV9Sle+MB7lzBkA qlm+NB6thbZR49NFwRyIgyEuFEpDkRz6Sz0hyZbzDMaE+k4qi0W+Qx9pwQJxAooh4sVS rjQoBGIYK1orSMS4QABywTz4lwfEoAcwQR4QgQI5ygBckAMbE1pgD1sInCWGTQpn5IEs KM+AuPfjOOqjFWiNBI7kgnQ4MxuuG5MyAR+uH1mH9siFDfXQvn3yfXmj+hyhvgDjr4EM jgvBABwXQjQDdMslhdC+HNgPgFIZHMuAeLwWZ8gjZxAtt3XEBjSOtPSPasmFK/hyXSPr kJcjtgVAm8WgGI4h2+Sek7oki5wKmxcZQfqQLLk2KZxRBBzkcm+5bEzrJ8+Rb/0ftc6H to73fny8xmJ8GsYrH+6cDT0Uj8YnD1rzDtqdNbr6UzTlGtcYyGwkkqpVMZw5tXKLke/M UulcEe/K6sH/kLVP2RrT7jAhb1HjeSFnCv9vvIC6KNcpVykPKDcBE75/oXRS+iC6S7kH nzsf7YkexwcUe8QcEfwrgj6OMWCEWTy5BOUiGz4oL3+381PORvb5yw4YIdeLOMuW74IY lgMbyqxAntcQqJ8L85EHoy2DPEXccICMGZ+7ES3jTkB7SaseYHatPHUBMOvVms/Ltcij 3Uk2pd5QaS9JF68xkEjm1JYMCySfRlEeBMsjX0aCUnvWIdYAaw+rnvWc9eDTDNYt1m+s TtYuOPKEWE8cJY4TzUQL0QGYsNdCnCaa5aieaIXPtx/XTWT4yDmayHDEN94oo5GP+aOc Gs/9cR7K4zUWLTR/LFOZoyd1PPdQfMczBmXsf7NofEYnVoSR7MhPHcOc4cSgMWwZLgw2 A2OYwseZ4Q+ROcOMEcHQhaOhDGtGIGPSx3iMnHFkBzrviGFjdeFTFUuGo2NMQP4JIQ+k 8prFHfX3rz4yJ3iJKppo/KnC6PBkjmgaqQljOsfiKmfIhJOVADWJwAJohxTGFZ12Mawl zAlzUCVGVQgyEpslz+E/nATSmHQiObAyRQEmySZdSP9RjKqVN3xQrRqp3g6kHxz1JQNJ d1THxnsAdx+JF6po/2z9+JMhoHpSralBVGv53nLvqIHUUGowYFKdkJw6hRoGsQealS8o hHcPAAJyJUVSUYYwn8mGtxwBkyPmOdoznVlO8OuG7kxoDgDPY+R3IUyngyeTFozISPSi ACV4n9IC+vCrag6/1g7QKzfgBb+ZQfAOEAXiQTKYA/0QwkxKYWRLwDJQDirBGrARbAU7 wR5QBxrAYXAMtILT4AdwCVwFneAu/J70gidgELwEwxiG0TA6ponpYyaYJWaHOWPumC8W hEVgsVgyloZlYGJMhpVgn2GV2DpsK7YLq8O+xZqx09gF7Bp2B+vB+rE/sLc4gavgWrgR boVPwd1xNh6Ox+Oz8Qx8Pl6Ml+Gr8M14DV6PN+Kn8Ut4J96NP8GHCEAoEzqEKeFAuBMB RBSRQqQTUmIxUUFUETVEA6wB7cQNopsYIN6QVFKTZJIOMIuhZALJI+eTi8mV5FZyH9lI niVvkD3kIPmeQqcYUuwonhQOZSYlg7KAUk6potRSjlLOwQrdS3lJpVJ1YH7cYN6SqZnU hdSV1O3Ug9RT1GvUh9QhGo2mT7Oj+dCiaFxaPq2ctoVWTztJu07rpb1WUFYwUXBWCFZI URArlCpUKexXOKFwXeGRwrCimqKloqdilCJfsUhxteIexRbFK4q9isNK6krWSj5K8UqZ SsuUNis1KJ1Tuqf0XFlZ2UzZQzlGWaS8VHmz8iHl88o9ym9UNFRsVQJUUlVkKqtU9qqc Urmj8pxOp1vR/ekp9Hz6Knod/Qz9Af01Q5PhyOAw+IwljGpGI+M646mqoqqlKlt1jmqx apXqEdUrqgNqimpWagFqXLXFatVqzWq31IbUNdWd1KPUc9RXqu9Xv6Dep0HTsNII0uBr lGns1jij8VCT0DTXDNDkaX6muUfznGavFlXLWoujlalVqfWN1mWtQW0N7WnaidqF2tXa x7W7dQgdKx2OTrbOap3DOl06b3WNdNm6At0Vug2613Vf6U3S89cT6FXoHdTr1Hurz9QP 0s/SX6t/TP++AWlgaxBjsMBgh8E5g4FJWpO8JvEmVUw6POknQ9zQ1jDWcKHhbsMOwyEj Y6MQI4nRFqMzRgPGOsb+xpnGG4xPGPebaJr4mohMNpicNHnM1GaymdnMzcyzzEFTQ9NQ U5npLtPLpsNm1mYJZqVmB83umyuZu5unm28wbzMftDCxmGFRYnHA4idLRUt3S6HlJst2 y1dW1lZJVsutjln1WetZc6yLrQ9Y37Oh2/jZzLepsbk5mTrZfXLW5O2Tr9riti62Qttq 2yt2uJ2rnchuu901e4q9h73Yvsb+loOKA9uhwOGAQ4+jjmOEY6njMcenUyympExZO6V9 ynuWCysbft3uOmk4hTmVOrU4/eFs68xzrna+OZU+NXjqkqlNU59Ns5smmLZj2m0XTZcZ Lstd2lz+dHVzlbo2uPa7WbiluW1zu+Wu5R7tvtL9vAfFY7rHEo9Wjzeerp75noc9f/dy 8Mry2u/V523tLfDe4/3Qx8yH67PLp9uX6Zvm+5Vvt5+pH9evxu9nf3N/vn+t/yP2ZHYm u579dDprunT60emvAjwDFgWcCiQCQwIrAi8HaQQlBG0NehBsFpwRfCB4MMQlZGHIqVBK aHjo2tBbHCMOj1PHGQxzC1sUdjZcJTwufGv4zxG2EdKIlhn4jLAZ62fci7SMFEceiwJR nKj1UfejraPnR38fQ42JjqmO+TXWKbYktj1OM25u3P64l/HT41fH302wSZAltCWqJqYm 1iW+SgpMWpfUPXPKzEUzLyUbJIuSm1JoKYkptSlDs4JmbZzVm+qSWp7aNdt6duHsC3MM 5mTPOT5XdS537pE0SlpS2v60d9wobg13aB5n3rZ5g7wA3ibeE74/fwO/X+AjWCd4lO6T vi69L8MnY31Gv9BPWCUcEAWItoqeZYZm7sx8lRWVtTfrQ3ZS9sEchZy0nGaxhjhLfDbX OLcw95rETlIu6Z7vOX/j/EFpuLQ2D8ubndeUrwX/weyQ2cg+l/UU+BZUF7xekLjgSKF6 obiwo8i2aEXRo+Lg4q8Xkgt5C9tKTEuWlfQsYi/atRhbPG9x2xLzJWVLepeGLN23TGlZ 1rIfS1ml60pffJb0WUuZUdnSsoefh3x+oJxRLi2/tdxr+c4vyC9EX1xeMXXFlhXvK/gV FytZlVWV71byVl780unLzV9+WJW+6vJq19U71lDXiNd0rfVbu2+d+rridQ/Xz1jfuIG5 oWLDi41zN16omla1c5PSJtmm7s0Rm5u2WGxZs+XdVuHWzurp1Qe3GW5bse3Vdv726zv8 dzTsNNpZufPtV6Kvbu8K2dVYY1VTtZu6u2D3r3sS97R/7f51Xa1BbWXtn3vFe7v3xe47 W+dWV7ffcP/qA/gB2YH++tT6q98EftPU4NCw66DOwcpD4JDs0ONv077tOhx+uO2I+5GG 7yy/23ZU82hFI9ZY1Dh4THisuym56VpzWHNbi1fL0e8dv9/batpafVz7+OoTSifKTnw4 WXxy6JTk1MDpjNMP2+a23T0z88zNszFnL58LP3f+h+AfzrSz20+e9znfesHzQvNF94vH Lrleauxw6Tj6o8uPRy+7Xm684nal6arH1ZZr3tdOXPe7fvpG4I0fbnJuXuqM7LzWldB1 +1bqre7b/Nt9d7LvPPup4Kfhu0vhJb7ivtr9qgeGD2r+NflfB7tdu4/3BPZ0/Bz3892H vIdPfsn75V1v2a/0X6semTyq63Pua+0P7r/6eNbj3ieSJ8MD5b+p/7btqc3T7373/71j cOZg7zPpsw9/rHyu/3zvi2kv2oaihx68zHk5/Kritf7rfW/c37S/TXr7aHjBO9q7zX9O /rPlffj7ex9yPnz4Ny1d8BwKZW5kc3RyZWFtCmVuZG9iagoxMiAwIG9iagozMzY3CmVu ZG9iago3IDAgb2JqClsgL0lDQ0Jhc2VkIDExIDAgUiBdCmVuZG9iagoxMyAwIG9iago8 PCAvTGVuZ3RoIDE0IDAgUiAvTiAzIC9BbHRlcm5hdGUgL0RldmljZVJHQiAvRmlsdGVy IC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAGdlndUU9kWh8+9N73QEiIgJfQaegkg0jtI FQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601 896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZm BEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PF GTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjASh XJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5 OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQEl WW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+ lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y5 95nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR 6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96 An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0 oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAE ToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQD xUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7 HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREh a5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQ M4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4E tw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwk VBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0m PSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRY qpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHB QMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOl edK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyT jLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmph atlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpu msma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NO s84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/ YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgm NKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw +sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iH vQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgc cpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16I l69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegK pARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4 VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCm PRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPC qeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1 sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFM KHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752 /ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv 5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l 9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqv akfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azD z+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQ M6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3O HS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZev XvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n +l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6q P6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXw y4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6r vj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/sKZW5kc3RyZWFtCmVuZG9i agoxNCAwIG9iagoyNjEyCmVuZG9iago4IDAgb2JqClsgL0lDQ0Jhc2VkIDEzIDAgUiBd CmVuZG9iagozIDAgb2JqCjw8IC9UeXBlIC9QYWdlcyAvTWVkaWFCb3ggWzAgMCAxNDE3 LjMyMyAxMTU0LjU1MV0gL0NvdW50IDEgL0tpZHMgWyAyIDAgUiBdCj4+CmVuZG9iagox NSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgPj4KZW5kb2JqCjkg MCBvYmoKPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1RydWVUeXBlIC9CYXNlRm9udCAv S1lTUU9TK0F2ZW5pci1Cb29rIC9Gb250RGVzY3JpcHRvcgoxNiAwIFIgL0VuY29kaW5n IC9NYWNSb21hbkVuY29kaW5nIC9GaXJzdENoYXIgMzIgL0xhc3RDaGFyIDExOSAvV2lk dGhzIFsgMjc4CjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAw IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAKMCAwIDAgMCAwIDAgMCAwIDAg MCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCA1NTYgMCA2 MTEgMAoyNDAgMCAwIDI0MCAwIDU1NiA1OTMgNjExIDAgMzMyIDQyNiAzMzIgNTU2IDAg NzIyIF0gPj4KZW5kb2JqCjE2IDAgb2JqCjw8IC9UeXBlIC9Gb250RGVzY3JpcHRvciAv Rm9udE5hbWUgL0tZU1FPUytBdmVuaXItQm9vayAvRmxhZ3MgMzIgL0ZvbnRCQm94Clst MTY3IC0yODggMTAwMCA5NDBdIC9JdGFsaWNBbmdsZSAwIC9Bc2NlbnQgMTAwMCAvRGVz Y2VudCAtMzY2IC9DYXBIZWlnaHQKNzA4IC9TdGVtViA3MiAvWEhlaWdodCA0NjggL1N0 ZW1IIDY2IC9BdmdXaWR0aCA1MjcgL01heFdpZHRoIDEwMDAgL0ZvbnRGaWxlMgoxNyAw IFIgPj4KZW5kb2JqCjE3IDAgb2JqCjw8IC9MZW5ndGggMTggMCBSIC9MZW5ndGgxIDkz MjAgL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngBxVp7bJTXlT/f93k8Hr/H nvH7McN4ZjC2x4+xxzh1/BiMGaAFpyTgoaFAgQSyIUAKCZGShT9oSB11ibpZErorJdpN Q1KtFq+UwjCoAVUpj+2qRVsIIYsKyiYpSaMFadmq2QV7f+fce42D2r/X0eH+7rmP795z z+veyc4nd22iQtpLDg1t2Lp+O8lfzh9RnN3w1M6Aqjv9KAOPbH90q64/TGR959HHn3lE 1V3XiBqaNm9av1HV6TbKxGYwVN3qRtm0eevO3aqecwrl/Me3bdDtLq43bF2/W3+frqAe eGL91k0o8dc0wv9s3/bdnVKlphyUQ9uf3KT7W+NYz0c//svE3+65fmCo4Wf7Lr3xxItD ZKFXMW0kNz1EOVIjyieyyfNj/GMJx6Lksqn8LWtL+v+bqp3rPP+z4b+XeS8+/TfXp9NT 15zrzj+C7cEY9Yd5netTP+R/p9PTaYziL83+K84QtVAWfJvsFisL8ToM3kWxiIaph8JU RXkt72Jd3/hK3UUPzqrTu5hiNuNdTPgwjVJCT4Bv5JKL3C34YGDhc1uqRrK0jvLI+m6W 9mCzXL6my5O6vKrLaV02crkzS4O6XKvLA7o8ostf6/KGLku43JWldl0u1+U2XR6BYvH3 b+iykUt8p12Xa3V5QJev6fKkLn+ty2ldlnAp31PloK4v1+U2Xd7gEt+Rcfi+jEN5VfNL oRS8riFdjunyiC5v6LKdS5ELSnx3WvO3CR/H7ack9dMySqEkKpuuo0LrApXZ74FuUSGO pHVphjxj4/9sWX+VzljT38vQSP1xaJKz9tttGbJaA4GFW0YmrXWo2K1gzAsCOa2B0Ukn PPrN8VA6MBGYWLxxIjAa2Lx+42ROWEo0bJpItwcmacX4Fvz74HhwcihdOwM3pdP3YZ4c ngdD0H0ijRke0zOgFFb7HXRytS4NTDqRsfEHxif3jtRODo2ka4PBwMLJU2Pjk6dGaoPp NHrlzqwUK2YtU2t2Y82589Cep2ZZgTkwRXpigudcMR4KTp6amKidwD50PWPRvYwhzcDG MQQbX5ix9o5hLIpQsJYZoWAoiGWlR/ApT+vSFeMLsbBguo1tGZZfgbKVOuwN1EE3cRo3 aRXqC+wHhJeyvk5LrZ9QEGUx+Cn733CAGIb/+K8QFpRFGaBxzRH2//M/vDEHxJ6OYOGE Vbph2OyH8qkAqyYqAhE9Ro/Zc3PW5fyPayC3w/1zT77nnYJbhf9a9HpJGVplPJzQrzBb iKLwSosz1NqeoTZQa2mGIudB7cepgnJugwtO+MpxqkONTmCMRU2U03IC7saiakHsSS2g js7enri/3eruHbB6B5ye7khoTq47OmDFuyr8vlw3/vNXeoNeC/Tb+T12kbvCW+rLz2lr bGzL7XIvSSRGqyPh3NyTUxutX03RrgULdnnnVxfVe0sqy72eps7WeF5qeFF/oCcULPd1 Z+wtd16x/+5OF3ZiU8f0Leu2dQ3yCDv5WSyxhpyWYYeuAVprAE4ZgLhB1o4MNZUeZ2nc RtsYeltrMlQDVp7s2nMe7AQky+wmiKAMQilrB3Mtlclw7xVUOiiiZh8zYBskKt87YMA1 A04JIDiBheMZamyvPU5wtANpzfAzw0+5zGjEQhAebmexgkbye8v6oOb89ZuyJAA+dWsH L9fGIfnRv1j6ezFFo/T3cv+XyIvldHRawG9CaWRpixjsAOcwVEg4aWiScBZBkYRz2IAK 9pHcedSAwwxYpFHDWQTFkz6fwbVK02oGO4ZdsKBS2gyy16DyAsCrquJIiwx6BhwZ1EuV alAG4CxIBvUiLi4C2Twd6+E5kLRkqB7d6rniUB+ATPcpBdV0lwwgFjxvYbk6AaC3jCxG zT57DZjZ+Ztqny7oQaHsXlZQiQpvHBWWid7478zGV6mNO3TQ7Op9A1Yy2DHsoVUAj4Ke Bu0H5fD6ZzZ/HBs/pzbPH6hSSz+GTYuQTvM+ecAxA9xm570KuOh97JzFYK+x2AaLbb+7 wYl3JXori63QnJjd0z1gx7sa7HrLemcsFehvq7Wdulh/qOcbvvi8Tfd1p4fCTUMru3oe 7GuwihesqJ6XqE+EBtpqOgPzemLh5OqentXJcGTwoY4G6CHbXqldCdsrp37nbJa6YQti e8vZKGBpkbuWthytzOoGq45cUN18GFYdDKwLZQRlP8p+ZWgDautXCYB3PGjASQMCDHYY k2phC2qZZVJdzOiaxQgyI6hsTKywjxl9lMdGF8SaXGJOLUAlYk4+NLbAnKB6vyAfXQSJ th0FkDV9rIALluaj16U5Q30YD+2F+dbjazLeQQYrB+eCE62nX4Jkot8LNwtP0kJB/pCb 3kHlPdAFkCO264I22bBfeG2WQw97YQajBhw04LACLpiZG0ruVgNehZvAAOYWggvlVZUi VIrMnOwoMOeb4jEAFhnwigK8iDI0l6kBi9gP8oDn4XEEJBiwVTxqOCmjvSlECmmaL54W fVIMuDPcqxp+zoCzNFdxMtSsQIoBd+4z4CiMUna0CuBRkAjmjOFehDl9ApJ9HgM4oypu WFY/XQZ9BnLWEGTSC5EtAknfVwBYhjLdYfaKvMWDSn4OHYLshJMywnnbgB8ZsNPs/oIB mwz4xIAeBrwhLTwXHQXnNEg+3GOkNGrAWSMcDm2ygqNGOGcMYFFI0wfYk4BzBmQNOE5f M336TR8NWCIyKiug19cAHzFgR7tjNrsQpwFhfMC+3wp91YdUxnviXl8x+kSiu2vbB8NN w121VlUs2RwebK8trF/T1zXaUm4HHo7H08lI0+Cqbn97e8xnJ8t7v7N89Im2m+Hhzvqa dnRHWd85PPWDaHt914LIgx1zmwbH4/HxZDjPF6pdNtURWZnqiHXACFLwOm/ZGTjsEvrf DBW34xQ5dee03YZzyYULyUUyQ1eGvcQXt72gl0CvgyZBp0DnQddAeWvQqRQgAOoADYHG QOtA20F7QXmYtVicgovzhb0YAFG50Ic/4FWn9prRgGsGDDFg9+Th1XDUL2F/g2vSTNQv ZAYSzoE0pruKoH4DJEowaEL9EQO2CcBscJPOlY7Ocl8upN7TnUB+5YS88bcaysoamEqS SfsFf22tH3RnA24gFmIO0SHcQRzCViz2rwc4a+NN8LV4DGQjnUBiyRscUhmCC51cuC3C K6rdurBbqXBixdmEcb0Ob8Oh4pl9ETOQog6kOzrjWNshLOkWPrIAafZlxIp668UsfE+1 ihRH2D8g06qGjKskLBAnYHs4vIJdBHaxWtl5auBVOzhHDaaRIQnnJQFmRR5eADLje1d0 l1HFPap4iVkkxR6qUn7+fVQ+Bckh/AJAJn9HALQAuuXBcuAFb6u06qwIAitayLdcNuqj DHiNKw3njOEs5CyD+6w0IGoAJ0Yyqo8B93nV6NEzBkwY8BMDzopLIfQ+Y9Zx2nyM0y2Z cSbDesEMO2QA+xxrTXmD5Y8PIEWo8HtDXpUz1FuMovttyy6YM6+9cvXKZGKxKxafm3Rs 60J5uLw8UFm0dnzqkuUsW5HXlBq6aW0tv68G9olc3A5KPhC1/ymLmNigTjnAsRLq54P8 dOYd4FMEqwGsfJWMB4yPX27AHuPh1howaMA1A4jBTCYQ4qMNzTr8amZUq8AfwrdUnGd1 4zjPB+mCc7fpbZAcfArarE9DADc7aNZx4iBCw5sqvHIEcSOCSKzlIJmHFqTlbDEHYdqc RkolAW3mhFG3FKFFV/g0OJBIy3zoVkpFWf5oGVok5HIe6scEiBGcCR/F/ew0CGMc+gOA rDaXgWquRLOk0NyMxJr75TJQeXQtQmGtGv0lgDR7GNyTZjv0pUk9PQy4mXPtxZzBqEoj Ko2qchxu9BxIVvUlgJ4XQM0bRDMnpljMlwC6GYCbj1IYiw6bPeGGqRYNoJojaI6YZn31 ypUsE6MTyMtHQfYODuvzcVR8iFqmblT0CfVC7HxjkUUkIFz5ispnwjra+XX065XIdjdj ti5Xx4abm4fbqqvbuIxVp8LJb/UmvpUMo0z0ovw8OtJVX981EjUlJ8smaUYJ1YeSpaau 2V8TG4nZniwkEYONqByUTSNDMehm20yGzLdwP7vESTyWyXoHEawgFc6KAVhOVw1Ya5oC AuAR+Wov8aeO7aBulmHoW+ddrxjhHhFlKXUzlsIpvMqIKzA8wp7SQTaste5TA35qwM8N +Bx5rawuKyBLrZi8Tnnaf0HlQ5Ctk9xZBsg65kCt9Pn9edv78+Z2EEesLcyBNuj0drEC bFkzNsfNWgsWK/AVw2OHLrGGFbQRGghdZ3mPcrxRehkCN2S4/LyA5gzNUzvPyM7ZRbNO ijB6zffu6XSv+llx5ZYbbL9WxGjIG7L+S5RwpKuurmtElHDqoaRd2toRr2QFZIWsjHe0 ltrJe3XR8i6rmlORb7Qxv2JOFb9aIy4vxT8PQR8rrTzcvuX1h29nOQh3ldC9In7tOAWJ ak3jcA1OicnrVQAHZ69EchOEC1iZCmapWw4zcmYxKphRoYJwLrpWKNX61LxM/EYAcj0o IKKZjrkpk5DPN7nRH4xEcxmw/NmHyiL7DMjFDRUxAm2jZnyPGb9IARcuV2WI5OxxyxH9 tPwr3eoa7Q0td6xh210T7mys8xcVVUcSTd6klWwciq8qD9V5c4dd9W3dVVN/FJkG8Sb1 ImTa6fwnvzk3myioLzNDJnaNSezCbQd75OcnLPB19OfMpxmyr+QzUDdi9t5IItEvIE6i BM2daO7k5iFcdWXDgxRX4KoCHjxaxWkbaA/oAChnJnGLsfxjsw7ExwyfOhBCiw8HAueh PgAlwffZNcEwcb0tmOlQ0J5FLuWjmHSv4pR8EoxTIBv74JCPaAONKsCSm1ibbslNRuVQ EZYXH9oJBhw5+feeL0BiafwMi+YCegbg+6BDoLdBx0EuVsTPAWTcLwH+HSRu/2ko7H7l 6V20Cynk9/iGwNMfBTitKg59bJzDReMcjgGcAcnXzyj9cdEHUNTrIOFeVzHVRb/BFv9D tol1nBb1BzhmwCfmCnfUgPcNuERz1Dm9ZUCKAcvhkOF8pgDHsyhUGtFNbVe/Ms6H+5Qz 58cCAb8zoIc61VyXGchVNwCB8hVH3q6OAciI3QZ8YMBuI81XAd5S0nTjqc2CCCyIwMLV GYvkk5AZ+BgErGagXKIHspCbjAvvXDPH4KCC3074CI4BnDFHcM6kqhlzFnhuVXOeM+Dy 3Zii487n5gyOQtosehHOp3fly8+6WOh1I0QW69sg6beYuSzsD03zLBm78EoQxSuBFnjC yPkTI973Dbgo4sWGwgAsdHuHFcMzG17dfBWNFjJrfTvuVTdoPIu3WzG8jvMbXEWlNVTS 1eSF74jFFnQON8aTcxJrgn2+++f55jaWd4eHCpujdfWdA8HOByutTfVz8rw15cGGgtKC BV2R3lBpc1c0EPGUN/hCjZ4Sp7iuLdiUmOONxPgkqRj+Z8Aepxrr2ydQdcH95fCb+E1z XNxJjmsMYB1I5HJKmjNkwVJxheZXbnRkOZYygAMogMspYI/wGl9euOUmu1YGJD4W0hgD WAfSU3Jzhsoxpe+KKiswCX+iWt30kFry+HWSbCJ+VJmURafudzOUIvZQRSpD4Qwey4Zf 4TsiEjwgnrQck1ejrDivcvsU1s5P0bKa3UbZthjwfQPSDBAi3Mgm8xEm8uUO5+wAw0yx GV2g/i44o1KYAM+Jm5Lki3hgxY0c2aI31INnEX/cum9/fll1ibepxAl3lzfPqdiwIbnf enXqi5pgmTs/735vfl1H1Ir279sHwVIK5/Vb3NYbrcVZeMsiFS/2SHaeoVpssObu3fgI 2u+5G0+afPu8AdMmxX5JgAnLxSzB4llOX1+W5bcHyRPvXt/5l2oPFauw/DOVwLjwfunA zUp2xs6X0xp1L8b7KI6gGKKv0aJfAiGuBsllYQkUZjVIPNDTxtZ7GbDb+MoL435j+OPq 1sI/GVTJ6bjoKYx4HiTnyaOgPOxoyuDv9QPlYeEGY1ZPqNjhdxIv3qnUm9b9FiBM70ez 7raF9Z1hfbududtazVN3ov3NfrndTv013275nPBbof0ezqnE/ocTSGZc2H4OcncHmy6G cpewzWyXAA4wxoC9zDXh8M3XQeTkWRwl1DdREd1/2oBXDLhowMcCkI/hG54rWXwTLxZq +IeoyPDfK8C5qwteTj/a7AZ4QSr4UZZXtsfkPmsZ8MpuCIfNx8HZ8esGX8U5umNjEs/5 7atIZSbb2ULYUs+b05s24IABaw04acAgA/7W64ZTajgzj2SzFe9PWTsLDT9wql3zm5ac eRpgC0g0YZfiumkfwMugN0ASqz6SFjg0CNA5zwKcmeoNVGRHuxRwURpgC0jmfN40XzDg hwZ8JCBD7vPsf/i3jCwu+/gZVq3xEVRkjVcAvlDPAA7+zxPN7QYYEa5KgviJTmS0itfK whrn5FcB/fvcXHNmczlf5aYPcFiy+kviEMCJQrWkKSqqV2vFy+U/JyT/Pfy4/dzIc/bj y15e8qz97JKXrQtT+6xnhVqtvVN7odi4n8rf9E+RUv6pP/61nDPsJfR1/Kj2AH2TVmBf K/HKOA7z5j+LrU8QJ+S0NL3igeUrWoaf2vTElifbktu2/cX/AXd9NUMKZW5kc3RyZWFt CmVuZG9iagoxOCAwIG9iago0OTU0CmVuZG9iagoxMCAwIG9iago8PCAvVHlwZSAvRm9u dCAvU3VidHlwZSAvVHJ1ZVR5cGUgL0Jhc2VGb250IC9VVEZTRE0rQ291cmllciAvRm9u dERlc2NyaXB0b3IKMTkgMCBSIC9FbmNvZGluZyAvTWFjUm9tYW5FbmNvZGluZyAvRmly c3RDaGFyIDMyIC9MYXN0Q2hhciAxMTYgL1dpZHRocyBbIDYwMAowIDAgMCAwIDAgMCAw IDYwMCA2MDAgMCAwIDYwMCAwIDAgMCA2MDAgNjAwIDYwMCA2MDAgNjAwIDYwMCAwIDAg MCAwIDYwMCAwCjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAw IDAgMCAwIDAgMCAwIDAgMCAwIDAgNjAwIDAgNjAwIDAKMCAwIDAgNjAwIDYwMCAwIDYw MCAwIDAgMCAwIDAgNjAwIDYwMCAwIDYwMCA2MDAgMCAwIDAgMCA2MDAgXSA+PgplbmRv YmoKMTkgMCBvYmoKPDwgL1R5cGUgL0ZvbnREZXNjcmlwdG9yIC9Gb250TmFtZSAvVVRG U0RNK0NvdXJpZXIgL0ZsYWdzIDMyIC9Gb250QkJveCBbLTY1NSAtNDA5IDEwNjMgMTA5 MF0KL0l0YWxpY0FuZ2xlIDAgL0FzY2VudCA3NTQgL0Rlc2NlbnQgLTI0NiAvQ2FwSGVp Z2h0IDU4NyAvU3RlbVYgNzYgL1hIZWlnaHQKNDU3IC9TdGVtSCA3MCAvTWF4V2lkdGgg ODIzIC9Gb250RmlsZTIgMjAgMCBSID4+CmVuZG9iagoyMCAwIG9iago8PCAvTGVuZ3Ro IDIxIDAgUiAvTGVuZ3RoMSA4MjMyIC9GaWx0ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVh bQp4Aa2Ze3BUVZ7Hz7m30+nuhHS6051O0ulOd/qdNOQNkdeABkRQJ4pKGmXlGYKCoIDg YwoolxGjuLqWPJw447IzGC2diWJhSHC1VHzUSi1bW75qcdyqUUenVpZx0EGEZj/n3k4H svPXzHbqm/Poe8895/v7/n7nd25vuGPjCjFObBW66Fy4ZF23MD7B1ULIw8vWLFlntvXj lK8vu3NDyGzLk5TB7nUr15htrZTr169cfVfufssxISrTPSuWLDe/F2cpJ/bQYbZlK2W0 Z82GzWZb/3vKrtVrl+W+tzxFe8KaJZtzzxfq+aHblqxZQckneCX/QuvWrt9gNEVAjd+5 7o4VuetlF+1dQvK/XD4q7OJuUWC0hHAIoQk9yXql0cM1m2v0vTc7p34rK23GcP+8zSzf v/W2E2feODtk22Z7mS+suRFYKg1blousu868ceZ127b8N8b96qmDQtSLIfp1odXLIWEx K69SmScmiwZRK8q5zlb/KjO7WcwV00Q632MVMy+6RrzKQJ1cdOFt2v+5TRdzLr5tSBTy vMJ6JhOa9ZNVFR08sU7sFJvEk2I3/39NO5y9XQi9T1RZ9omwEFyanjcorJ1dL0j5cGZQ nt8+KDoCh1i9fvPfjR8UMh0KzVrVMSAX09DSdNSFqenp0OwBPTb72q5IJtQb6r1ieW9o dqhnyfIBS8wo+WJFb6YhNCDmd63i/3Vd4YEZGX++uiKTmcw4FjUOt3B5b4YRbsmNQGl0 NZzjooL0vNCAHu/suqZrYGuHf2BGR8YfDodmDbzW2TXwWoc/nMlwlTU/U2asVm/OuZA5 W+v43maOMr9rYIZ/QGR6e9WY87si4YGtvb3+XtaRaw+K18Z0SDG2Y0auAyYYAyZmDcqt nQxGEQn7VUckHAkzz0wHz7an583vmsVMw5nxwnJUdANK2QeeAGtoH6W0UO4DB8GL4Dn6 aBvXca3oAIuBur8HDOXKhZTKB0yNC1GM/V6iHRLX03fxB3cwOiy57gKuLRQ2vAZfyX2K Rip/dVnMnePyd5cIJ3WiRv7jEm7qZcKT7/HmapMR9T7pkFPlCW22LvSPLJsLJhQMFpy1 XlrYYZts+8z+maO1KFh0qngXd2iiO7vL0l3wS1ZVKC7HzA0IBthK0fYxoNqlh/i64BQt ahajpvO9/bgYpqkxjqV+mFKDC0t9Y1OZK+yKhV3hbos4u173n/08u6uw5PQ3d1hTapJS 9mXX6k2WIyIg1gxzi1V4uW2mT0ykOhvcALrBneCnYBfYDw6Ct8AHoGjRzALxOZVvgbZI ueKsrkHhb/AfEn4xbnom1+FUHU5hn55pbJIea6FV83rKfUHp9VgjtfFEXGtrdU+aLlua y/Wmy1wdlzQ2VS4oK625svnWWxZkJi+Nl5ZZ9ngfTP0ie/rh+769+9ID3vLKmXP2ys5D z8iWR25eXKnW88SF6/EyH0UD0/sDlTPG9FjcJVSuABmwCtwFHgB7wTPgEHgXfAyK8uvJ TX90PbkFmutpnThporutNZ6YINtaJ7IGX7nb69EKSyRrk090uC+b3NRcdYOnNDyP9WQy 0xfFSz3Zn3seSD0l7Tu3/vneSw+UeypnXr4n++tDz2Tfe2TpTVVqPWuy/6KPl1eKCogb FCVIwAX0BhicLqfI5nLIK5TGQxokj54qJ8mTtqqa/3S6LZbs14UVJXqhbXZpqSZ9tWXF 9oLSwnNvOt2aXlXoLi6yFxi6O3r+U5RUIoqETzoGRQUPsIMKdOVEd84GpTnrKUMVOqrQ UYWOKnRUoaMKHVXoqEJHFTqq0FGFPqIKHVWwlSiJ/AHPPANoKBvYsYEdG9ixgR0b2LGB HRvYsYEdG9ixgR0b2BmKGaF37InyK4+PKKxYCapYFE7PDOOEKjxY2DKKuZItT813EpXL wQKwEmwC94Pd4GnwMngbfAgMFX9B5TugLYLgWvTpbmnGqppeq7lKVd1Vqmnd67duXb9h 69YNhz/55PDh3/7WsiV78vT32f+Rru9Py9IfFstlsk22ymXZvuxR/n6m7EiM0s8U2Nip 5gwJl6gWOjOthmErDFuVWZl1SFhOsTYHzXHA8KIKtcYKtcZD3CdZfw13hI4xP8PqiG1S zHAarwvVKV8K18bpnSJdctO4cQU1idqQTBV5i8rKn8o0JpPnnk0mGzP7LU2aFglURO2d uh4J/vBWIBnlkwzo/67igtjHfH+HJlrER8PMy0pIzfmRAxs6RmzowIYObOjAhg5s6MCG DmzowIYObOjAhg5s6DBsGGK9aWafZnGq3kC9IVdvy1tVqBUL06oBpjLOsKqKdzGh5yJf ipph3xT2TWHfFPZNYd8U9k1h3xT2TWHfFPZNYd/UiH1T2Ddl2Neg6Udy4iSB20ZqiUeF Kg4pQk0+PdLqlOFc3epVriwdbS2vBQKT5MK7l/xofcQ6LjYhGiwpm/HKsv2fZp9bMOEe +a4lEQ7HNZserKgfP/NAdXWrnP34rdtb07ayy9LTo+Gy6Vd80PdmdviaCXfWj0/Hdad+ VU2E5bLQg+c/1U/DOcobImOqRCN4jYNKFdBun+nHayphvBLGK2G8EsYrYbwSxithvBLG K2G8EsYrxVfgezBu0RDDV4oGl/uSIfzErA0Kd4Phk27s6R6xp5vR3YzuZnQ3o7sZ3c3o bkZ3M7qb0d2M7oZOMypgQUO5yjPVTtSSt6NV2ZEdWXlnmuVFDDtaudo5Yj0n1nNiPSfW c2I9J9ZzYj0n1nNiPSfWc2I954j1nFjPaXrnyL5hRtuW5okq+PLX1jpJGdC0qWlP0y90 beO200f2fF1f43r/pk2Pr1zoT193bch79eI7b7zuhfLq2H/t6Pu3ZdpA6Jm7f/PpxtnB RPeO1Zl7XAV6wcwpDt1S3DP35s09Uf+0u17pXbUDQjXxIj5yoqCYWrvYNSyaWKzabQZF E2skFsFIEYxIVC4oHZRNlCGjfQhOFGdJWkl8IM03bcfwfhV+q0A1MCKArngkJ1A88kTC tHqGzjPsRjxQY4cZRT1LeZSdsomyWcUIl13mokEbep8mFTnsHC3NvvIaCYXEDLVZETac xJN66c3FEG0ou106G2sjiQ3Z8ZXVgQJd7i9xO61Oi6W7xNVSXuH0VGu6rdAfuC4yg6gi j2r7zy3MttSkouH+UHBWMm0r0N6rLNGkLNX85edskWC53WlLRav6a+LRKAuR4jn2Hc1y gsz+5KCoZ7leUM/sg8w+yK6jODQ06kWj3hGNetGoF4160agXjXrRqBeNetGoF4160agX jXqNmOOFJ+wC00FGNvczIi71dF6pdsWw3WQ4AsMpGB6mlAReS73yngqh4z3mLsROY8Ye N+p1o1436nWjXjfqdaNeN+p1o1436nWjXuUsLOMLKt8BY2/JRRVLXqCmgAlDqsdFBhHO BXO5/dJ1Lb/5Kpt9t/93ddVF79/406d/vnnh8+5gZapFnmlsbJ6QnaKXVPj+9NKrp2+c WZW66p+23PurhenJ8ptwIJGIGXmeJved/0av17eoPV68MwQjKMmMLoLogprN6CLgVsCt gFsBtwJuBdwKuBVwK+BWwK0gugiiC6kxe7NSYDE2U6y6lYYN3XoVq17F6hBc2o0dz8sV zuMEskm48eVgAVgJNoH7wW7wNHgZvA0+BL8HfwbjFItWKuVAux11G4mWmT4qykzGSLf2 rVmQWb2mq2v1SKkdvXr50s7OpUuziVzFyH13kvvug5HpyrNIAQzPUn5kOT4oyvIrGc38 htGEpg6neKATZZUL7VRjU0HuyWpHGTVdvdTm9AeitYHsn2rq6mrkuEBtNNB/OuQPROLV BY9Wx8MBf63KDfqyI/O4dEjNwWCqyIghSrcW5jM6l0LFaqHKqoeZuAYTai6FxtVqLird VCFRycd1QTKgbe8PRsJBWRyqqwtlTwVqI8F+tqrqQDhefWZtdTwSqK5huI7zJyz3FQSY RqO8c1g0worpDW5qOs8y/FHHH42kTuVxOprR0YyOZnQ0o6MZHc3oaEZHMzqa0dGMSgkH RSOrsR8DDUN4VUq4Db9K0aviYogI0MhaYkb0dNPbnPfSnBWK8ueJUYHlFFetuKke3XHi BjcqAkCz6bMC1QlUJ1CdQHUC1QlUJ1CdQHUC1QlUJ0Z8VuCzuAczV/Y28soCxG9F/CpN JAlppzIHdIEesBnsAHtAPxgE74CPQBF7cTE2sxp7cQXuIYz1Kw+KsP4oMTtmbGVEAk5H uooTwusRWDWB0OtHornLNLNxAtD6dg4c6H3whReeb++/5V1ZnP36rVV9zWXlLyfiEzq8 ZR3NycSeoP/BFx9+8KUDDz30krZt9tzsH985kj0xd16nv0IlfRYRIv3zGAdXKRaf/71l vP6SSMvuYY61VpFQdldnQg/Zv4fs30P27yH795D9e8j+PWT/HrJ/D9m/h+zfY7L3OZVv weiZ0KIsZBGjNqxTHXUXdERVR1TZcFDUwbhL5QqKcReMu0YYd8G4C8ZdMO6CcReMu2Dc BeMuGHfBuAvGXQbjkie4DJ6VLxUdZzwOFSRCRWbca6cyB3SBHrAZ7AB7QD8YBO+Aj8CX 4DRQcS/K/HwjuYwPZflQlg9l+VCWD2X5UJYPZflQlg9l+VCWb0RZPpSFB6uThpnDBDgU KztPwIeV8dkS2K3NsMZGQZoqCDT6Y/FkqiYe9yeqGx/I9L0x/Nhld00sC82M1SSy7/d/ nP1Uhj66cq++2BKuaZw3FIvVNF0zf/AfH38lFiuubEvU/PhXsvzYMelLoWr8vRtbL9QP 4jUR8YkZBd2G1yhFBlBkoGFMPB+1Xc4hjcREJeps17mwyNZpupsOKTqk6JCiQ4oOKTqk 6JCiQ4oOKTqk5I6LX1D5Dih3UxPwMwHlvmE1nhJBGBGER0TAWYpGF+gBm8EOsAf0A5Ii jBbGaGEYh+QgrhQ1I7VxKjf5xLnwqljYIF16bAd+tlF6AsFk3YTl76/6LHtC1n71H7K8 ocd5boX2oPOZe7YflPseefIn8epAo6+pVRZ+/Il0nxcH2+P3bXr0IcWpZDbCsocYOlV2 DhPjrKhQZRFRXrKoU586VwsW5qK0Uar3PBUsstjMdhxcVgVyO3KA6BogugaIrgGia4Do GiC6BoiuAaJrgOgaILoGYCcAOwEj31cDRI0Y056rDYp2le87CFdVIDd6O6O3M3o7o7cz ejujtzN6O6O3M3o7o7czejujtzN6u6H7puNDYkp+3OkqQntYhLHrh5T/hi5w6JzLGw5t XNGqrmg1PdzCuosw7hDOaBEeY8ZVudqgGA87E46pZ5nfYUS2tL8YA10x872LkTVxkiuR TqneJKnkQG3LHOqkHAhXHUokG65qTN3QnEq86g/KcLgmXSed8eSSsqLU0uZH5T031MdS iewf08lEIvuh3JL9ONloRkgy22h52bmWb0pqfcFgbe1Uh6YVtKW3ZJeHYpFYMOxPFEv1 nlry9lxYpmF/v5iror3PsLp6K0E/+3kxdrfkd7UyxUaZuWfZ8k5UZuhBnfar4MCvMnkz 4Jv7+khumNvqtcizS5pSCTmXFU3mKNtwLphI1dVJr2WnOesf9oajqZpolCCS0+cW4vsU OUWdzKxYVeW76m1ZyHDhdH6u7cy1gBmo95DqhDKOujqJkxdgsxJW1GjEVeWkUVanzp8q Z1F53rT8Gm1qjbYLNOFQHbwTyG/lLtVBsM53jG7lOV21qCtaTNUYMmpTHW2qw9zeOWAx iSQJmREpkqiVhrlBJ4kUSSJFEt9MEimSRIokkSJJpEii3iSRIkmkSBrbRQlTSxrLUjHI y5LLgbk9QwP18Rhk0sgWrDWXB4wjFQmXqbhRhRovFvLdmmmvb8o6WuoaGhb7g1f8w1W7 n29oTjdM/WRGtG5y5JYV3X2RqXXR2QcnplINHTu1baFkLFxe1tEx8/D+7JRwNBEO1iWC 8qmN9/R2ZxcHE3XBSCxJrBALiePr9RdxJp+83jxE6ub5/m/I1VSkcrNcN2cxO3Y1cgA7 OYCdHMBODmAnB7CTA9jJAezkAHZyADs5gJ0cQL22wxKfU/kWqJCuzqy80Ecfo+/wcknc qOVHd5X/n7TNPADm9o+/Om27OC2TKiHL7R9a9xNH3tq798ib2tPZ4199mT0uo19+KWPr 39i9+8iR3Xtelws/yJ6UpR98IJ3Zk0Z86CLHXmoZwoleUT90ceLMZfBBIz4kYFydVVOU hZTqTK9+A2jMn0fSSvrpC96s+1SHz3QO5abW4yM+4YNyFWAx3SQql4MFYCXYBO4Hu8HT 4GXwNvgQ5A6sRezG5EiYTh3tzBePKgAMirgKSbnw6jJSlQt2VpXCTJMj/kHVOJsFpL6p aXxdXfbW61femA1WJ5qmLH1y1sZfJDyuZ+virdffHkuka/XltbFIJPviL3tuSQbCTb5E dN7cyOLlNfLqcCwVPNpWn2rO/Ct6Nz7Zx9DfX/qoXyjVixKVaqtfZrwc1HxsxQExXcwQ s1DwHHG1+LG4RlxrnAVUVDRfIKtjIGHt+utmz++4qv6ytRvvWLXiDiMsm89ZTMFvzOJe oLb7PvAcOAzeA8fBf4OzDFkMqkEaTAVXgpvAanAveAj0gefO5z7cJ/J1STC+uJ0c0zZS uAuu5zkX3d84pt00pt08pt0ypq1+675wPm1j2pPHtG8c0140pr10THvZmLbxG/kF67l1 zPfwdtF8bhvTXjumbfzG/r+kZ/qFCmVuZHN0cmVhbQplbmRvYmoKMjEgMCBvYmoKNTAx NwplbmRvYmoKMSAwIG9iago8PCAvUHJvZHVjZXIgKG1hY09TIFZlcnNpb24gMTAuMTUu NSBcKEJ1aWxkIDE5RjEwMVwpIFF1YXJ0eiBQREZDb250ZXh0KSAvQ3JlYXRpb25EYXRl CihEOjIwMjAwNzEzMDcwOTUxWjAwJzAwJykgL01vZERhdGUgKEQ6MjAyMDA3MTMwNzA5 NTFaMDAnMDAnKSA+PgplbmRvYmoKeHJlZgowIDIyCjAwMDAwMDAwMDAgNjU1MzUgZiAK MDAwMDAzMTkzNSAwMDAwMCBuIAowMDAwMDEzODIzIDAwMDAwIG4gCjAwMDAwMjAzNTcg MDAwMDAgbiAKMDAwMDAwMDAyMiAwMDAwMCBuIAowMDAwMDEzODAyIDAwMDAwIG4gCjAw MDAwMTM5MzcgMDAwMDAgbiAKMDAwMDAxNzU0OSAwMDAwMCBuIAowMDAwMDIwMzIxIDAw MDAwIG4gCjAwMDAwMjA1MDAgMDAwMDAgbiAKMDAwMDAyNjE5MiAwMDAwMCBuIAowMDAw MDE0MDU3IDAwMDAwIG4gCjAwMDAwMTc1MjggMDAwMDAgbiAKMDAwMDAxNzU4NSAwMDAw MCBuIAowMDAwMDIwMzAwIDAwMDAwIG4gCjAwMDAwMjA0NTAgMDAwMDAgbiAKMDAwMDAy MDg3NSAwMDAwMCBuIAowMDAwMDIxMTI3IDAwMDAwIG4gCjAwMDAwMjYxNzEgMDAwMDAg biAKMDAwMDAyNjU3NCAwMDAwMCBuIAowMDAwMDI2ODA3IDAwMDAwIG4gCjAwMDAwMzE5 MTQgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSAyMiAvUm9vdCAxNSAwIFIgL0luZm8g MSAwIFIgL0lEIFsgPDViNGNhYTg3MjMzYzg2NDAyM2U4M2ZiYmQ2NzQwMDJlPgo8NWI0 Y2FhODcyMzNjODY0MDIzZTgzZmJiZDY3NDAwMmU+IF0gPj4Kc3RhcnR4cmVmCjMyMTAw CiUlRU9GCg== ArchiveMatchStore Archive_Stores Class_Store DKDGridRef Match_Hashes BinIndex 1 BinMatches GridRefPosition Top Left GridRefPosition Top Right BinIndex 2 BinMatches GridRefPosition Center NHashBins 16 Class_Store DKDLock Match_Hashes BinIndex 10 BinMatches CenterLock YES CenterLock YES BinIndex 14 BinMatches NHashBins 16 Class_Store DKDGraphicStyle Match_Hashes BinIndex 8 BinMatches DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0 GreenPlus 0 Opacity 1 OpacityPlus 1 Red 0 RedPlus 0 LineWidth 6.26904296875 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0 GreenPlus 0 Opacity 1 OpacityPlus 1 Red 0 RedPlus 0 LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill NO DrawsLine YES LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.8 BluePlus 0.837427 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.8 GreenPlus 0.837438 Opacity 1 OpacityPlus 1 Red 0.8 RedPlus 0.837418 LineWidth 4.791015625 MiterLimit 10 StrokePosition Front WindingRule Non-Zero BinIndex 9 BinMatches DrawsFill YES DrawsLine YES FillRGB Blue 0 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0 GreenPlus 0 Opacity 1 OpacityPlus 1 Red 0 RedPlus 0 LineCapStyle Square LineJoinStyle Miter LineRGB Blue 0.8 BluePlus 0.837427 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.8 GreenPlus 0.837438 Opacity 1 OpacityPlus 1 Red 0.8 RedPlus 0.837418 LineWidth 5.41943359375 MiterLimit 10 StrokePosition Front WindingRule Non-Zero BinIndex 10 BinMatches DrawsFill NO DrawsLine NO LineCapStyle Square LineJoinStyle Miter LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero BinIndex 11 BinMatches DrawsFill YES DrawsLine NO FillRGB Blue 0.973187 BluePlus 0.98 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.333683 GreenPlus 0.44 Opacity 1 OpacityPlus 1 Red 0.064313 RedPlus 0.06 LineCapStyle Square LineJoinStyle Miter LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill YES DrawsLine NO FillRGB Blue 0.8 BluePlus 0.837427 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.8 GreenPlus 0.837438 Opacity 1 OpacityPlus 1 Red 0.8 RedPlus 0.837418 LineCapStyle Square LineJoinStyle Miter LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill YES DrawsLine NO FillRGB Blue 0.027434 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.007121 GreenPlus 0.149131 Opacity 1 OpacityPlus 1 Red 0.986246 RedPlus 1 LineCapStyle Square LineJoinStyle Miter LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero DrawsFill YES DrawsLine NO FillRGB Blue 0.031039 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.999963 GreenPlus 0.979346 Opacity 1 OpacityPlus 1 Red 0.501957 RedPlus 0.556343 LineCapStyle Square LineJoinStyle Miter LineWidth 1 MiterLimit 10 StrokePosition Front WindingRule Non-Zero NHashBins 16 Class_Store DKDParagraph Match_Hashes BinIndex 0 BinMatches FirstLineHeadIndent 0 HeadIndent 0 LineFragmentPadding 5 LineSpacing 0 ParagraphSpacing 0 ParagraphSpacingBefore 0 TailIndent 0 TextAlignment Left NHashBins 16 Class_Store DKDFont Match_Hashes BinIndex 0 BinMatches Family Avenir Name Avenir-Book Size 18 Family Courier Name Courier Size 20 Traits 400 NHashBins 16 Class_Store DKDTextAttributes Match_Hashes BinIndex 0 BinMatches Font 0 ForeColor ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 0 Paragraph 0 Font 16 ForeColor ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 0 Paragraph 0 NHashBins 16 Class_Store DKDArrow Match_Hashes BinIndex 4 BinMatches ArrowAngle 160 ArrowArchiveVersion 9 ArrowBackEnd NO ArrowForEachSegment NO ArrowForm solid ArrowFrontEnd YES ArrowLineWidthFraction 0.25 ArrowOffset 0 ArrowSize 8 ReferenceArrow Relief UseCurveFillAndStroke YES NHashBins 16 DKDChangeTimeStamp 2020-07-13 07:06:41 +0000 DKDCreateTimeStamp 2020-07-08 01:23:36 +0000 DKDDisplayGraphicDetails AngleFormatDisplayDetails AngleDirection Right AngleForm degrees AngleRotation Counter Clockwise PrecisionAngles 1 AnglesDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 2 TextAlignDisplaySpec Left UnitsDisplaySpec Punctuation AreaForm Natural HelpTipDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 3 TextAlignDisplaySpec Left UnitsDisplaySpec Abbreviate InspectingSpecIndex 0 LengthsDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 2 TextAlignDisplaySpec Left UnitsDisplaySpec Nothing PercentDisplaySpec FormDisplaySpec Decimal PrecisionDisplaySpec 1 TextAlignDisplaySpec Left UnitsDisplaySpec Punctuation PercentFormatDisplayDetails PercentForm Percent PrecisionPercents 1 DKDGrids DynamicSnapGrid YES GuidesLayer NO MajorGrid GridAboveGraphics NO GridRGB Blue 1 BluePlus 1 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDP3ColorSpace Green 0.651064 GreenPlus 0.713725 Opacity 0.6 OpacityPlus 0.6 Red 0.432559 RedPlus 0.54902 GridSpacingX 72 GridSpacingY 72 LinkGridToRulers NO PrintLineWidth 1 PrintsGrid NO ShowsGrid NO SnapsToGrid NO MinorGrid GridAboveGraphics NO GridRGB Blue 0.848787 BluePlus 0.87451 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDP3ColorSpace Green 0.828351 GreenPlus 0.854902 Opacity 1 OpacityPlus 1 Red 0.664186 RedPlus 0.745098 GridSpacingX 18 GridSpacingY 18 LinkGridToRulers NO PrintLineWidth 0.5 PrintsGrid NO ShowsGrid YES SnapsToGrid NO SnapDrawing NO SnapEnds NO SnapGuidelines NO SnapRadiusGrid 3 SnapSound None SoftSnap YES DKDHideExtension YES DKDLayersList CloakLayerGuidelines NO CloakLayerVertices NO FullLayerScale ArchivePrecision 12 ScaleOriginX 0 ScaleOriginY 0 ScalePlusDown YES ScalePlusToRight YES ScaleScale 1 ScaleUnits Centimeters GraphicsList Class DKDContinuousBezier DKDLockInfo 14 GraphicID D8CFAFC8 GraphicStyle 8 GridRef 2 SVGPath M551.700106929,230.549685252 C551.700106929,230.549685252 551.700106929,230.549685252 551.700106929,230.549685252 C605.867687199,231.215900312 642.113382578,216.421685253 690.041050333,249.167528716 C739.139052457,282.712985175 759.530985138,335.215772518 821.986213258,331.909424844 Class DKDPath DKDLockInfo 14 GraphicID A0907DC8 GraphicStyle 24 GridRef 2 SVGPath M1181.11756755,111.980561463 L1001.11756755,111.980561463 L1001.11756755,687.980561463 L1181.11756755,687.980561463 Class DKDPath DKDLockInfo 14 GraphicID E52D4BB8 GraphicStyle 24 GridRef 2 SVGPath M197.664151499,108 L377.664151499,108 L377.664151499,684 L197.664151499,684 Bounds {{244.702076316, 137.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID 692211C8 GridRef 2 GroupGraphics Bounds {{244.702076316, 137.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID 6E309CA8 GraphicStyle 11 Bounds {{268.466025766, 160.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID 7E309CA8 GraphicStyle 9 Bounds {{346.905237263, 155.606795084}, {205.517828471, 84.7864098321}} Class DKDGroup DKDLockInfo 14 GraphicID B43413C8 GridRef 2 GroupGraphics Bounds {{346.905237263, 170.209708957}, {127.627743841, 34.753203615}} Class DKDGroup DKDLockInfo 14 GraphicID 1A6A4DA8 GridRef 2 GroupGraphics Bounds {{357.995090248, 176.836090159}, {116.537890856, 21.8368569077}} Class DKDRectangle DKDLockInfo 14 GraphicID 658BF725 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 9FD6E925 GraphicStyle 40 SVGPath M355.374102357,180.999596979 C355.450513716,180.989757649 355.531893321,180.99891282 355.603336433,180.970078988 C375.1487174,173.081729442 394.369526454,170.351376501 414.526913716,170.232475696 C433.583357689,170.120068939 453.715520994,170.117297653 472.575233617,178.020016246 Bounds {{346.905237263, 176.806503038}, {21.461192112, 21.461192112}} Class DKDCircle DKDLockInfo 26 GraphicID 6C3BAB25 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 3129FD25 GraphicStyle 40 SVGPath M355.52828239,194.17302455 C355.604693748,194.182863881 355.686073354,194.173708709 355.757516466,194.202542541 C375.302897433,202.090892087 394.523706487,204.821245028 414.681093748,204.940145833 C433.737537722,205.05255259 453.869701027,205.055323876 472.729413649,197.152605283 Class DKDPolygon DKDLockInfo 14 GraphicID 24C4E6B8 GraphicStyle 43 GridRef 1 SVGPath M471.736299132,155.781297814 L471.758629467,222.90525506 L497.212698363,240.012899695 L552.370285944,240.393204916 L552.423065735,222.90390166 L539.171278371,222.569612018 L538.970985833,155.606795084 z Bounds {{1042.69016627, 137.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID B83C2EB8 GridRef 2 GroupGraphics Bounds {{1042.69016627, 137.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID DC6F7CB8 GraphicStyle 59 Bounds {{1066.45411572, 160.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID EC6F7CB8 GraphicStyle 9 Bounds {{1042.69016627, 245.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID A3955EB8 GridRef 2 GroupGraphics Bounds {{1042.69016627, 245.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID 83955EB8 GraphicStyle 59 Bounds {{1066.45411572, 268.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID 93955EB8 GraphicStyle 9 Bounds {{1042.69016627, 461.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID F6ED5EB8 GridRef 2 GroupGraphics Bounds {{1042.69016627, 461.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID D6ED5EB8 GraphicStyle 59 Bounds {{1066.45411572, 484.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID E6ED5EB8 GraphicStyle 9 Bounds {{1042.69016627, 353.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID 0F826EB8 GridRef 2 GroupGraphics Bounds {{1042.69016627, 353.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID EE826EB8 GraphicStyle 59 Bounds {{1066.45411572, 376.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID FE826EB8 GraphicStyle 9 Bounds {{1042.69016627, 569.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID 50B56EB8 GridRef 2 GroupGraphics Bounds {{1042.69016627, 569.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID 30B56EB8 GraphicStyle 59 Bounds {{1066.45411572, 592.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID 40B56EB8 GraphicStyle 9 Bounds {{244.702076316, 245.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID D45691C8 GridRef 2 GroupGraphics Bounds {{244.702076316, 245.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID B45691C8 GraphicStyle 11 Bounds {{268.466025766, 268.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID C45691C8 GraphicStyle 9 Bounds {{244.702076316, 569.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID E20A91C8 GridRef 2 GroupGraphics Bounds {{244.702076316, 569.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID C20A91C8 GraphicStyle 11 Bounds {{268.466025766, 592.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID D20A91C8 GraphicStyle 9 Bounds {{244.702076316, 353.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID 542D91C8 GridRef 2 GroupGraphics Bounds {{244.702076316, 353.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID 342D91C8 GraphicStyle 11 Bounds {{268.466025766, 376.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID 442D91C8 GraphicStyle 9 Bounds {{244.702076316, 461.037924817}, {85.9241503666, 85.9241503666}} Class DKDGroup DKDLockInfo 14 GraphicID 7FDF91C8 GridRef 2 GroupGraphics Bounds {{244.702076316, 461.037924817}, {85.9241503666, 85.9241503666}} Class DKDCircle DKDLockInfo 10 GraphicID 5FDF91C8 GraphicStyle 11 Bounds {{268.466025766, 484.09905329}, {37.7357697744, 37.7357697744}} Class DKDCircle DKDLockInfo 10 GraphicID 6FDF91C8 GraphicStyle 9 Bounds {{349.850279578, 370.891218073}, {203.552643257, 176.843187025}} Class DKDGroup DKDLockInfo 14 GraphicID DC6BFBC8 GridRef 2 GroupGraphics Bounds {{349.850279578, 378.623398193}, {127.627743841, 34.753203615}} Class DKDGroup DKDLockInfo 14 GraphicID 98CBC8B8 GridRef 2 GroupGraphics Bounds {{360.940132563, 385.249779395}, {116.537890856, 21.8368569077}} Class DKDRectangle DKDLockInfo 14 GraphicID 48CBC8B8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 58CBC8B8 GraphicStyle 40 SVGPath M358.319144672,389.413286215 C358.39555603,389.403446884 358.476935636,389.412602056 358.548378748,389.383768224 C378.093759714,381.495418678 397.314568768,378.765065736 417.47195603,378.646164932 C436.528400004,378.533758175 456.660563309,378.530986889 475.520275931,386.433705482 Bounds {{349.850279578, 385.220192274}, {21.461192112, 21.461192112}} Class DKDCircle DKDLockInfo 10 GraphicID 68CBC8B8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 78CBC8B8 GraphicStyle 40 SVGPath M358.473324705,402.586713786 C358.549736063,402.596553116 358.631115669,402.587397945 358.702558781,402.616231777 C378.247939748,410.504581323 397.468748802,413.234934264 417.626136063,413.353835069 C436.682580037,413.466241825 456.814743342,413.469013112 475.674455964,405.566294519 Bounds {{349.850279578, 486.623398193}, {127.627743841, 34.7532036147}} Class DKDGroup DKDLockInfo 14 GraphicID 961E05C8 GridRef 2 GroupGraphics Bounds {{360.940132563, 493.249779395}, {116.537890856, 21.8368569077}} Class DKDRectangle DKDLockInfo 14 GraphicID 461E05C8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 661E05C8 GraphicStyle 40 SVGPath M358.319144672,497.413286215 C358.39555603,497.403446884 358.476935635,497.412602056 358.548378747,497.383768224 C378.093759714,489.495418678 397.314568767,486.765065736 417.471956029,486.646164932 C436.528400004,486.533758175 456.660563308,486.530986889 475.52027593,494.433705482 Bounds {{349.850279578, 493.220192274}, {21.461192112, 21.461192112}} Class DKDCircle DKDLockInfo 10 GraphicID 761E05C8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 861E05C8 GraphicStyle 40 SVGPath M358.473324705,510.586713786 C358.549736062,510.596553116 358.631115668,510.587397945 358.702558781,510.616231777 C378.247939748,518.504581323 397.468748802,521.234934264 417.626136063,521.353835069 C436.682580037,521.466241825 456.814743342,521.469013112 475.674455963,513.566294519 Class DKDPolygon DKDLockInfo 14 GraphicID A8CBC8B8 GraphicStyle 43 GridRef 1 SVGPath M473.567954086,370.891218073 L472.738486567,530.246455242 L498.192555464,547.354099877 L553.350143045,547.734405098 L553.402922835,530.245101842 L540.151135471,529.9108122 L539.77592016,371.845891736 z Bounds {{828.817727292, 483.42549498}, {207.963834842, 176.843187025}} Class DKDGroup DKDLockInfo 14 GraphicID 1E833CC8 GridRef 2 GroupGraphics Bounds {{909.153816679, 489.810309592}, {127.627745455, 34.7532036147}} Class DKDGroup DKDLockInfo 14 GraphicID 17ECF8C8 GridRef 2 GroupGraphics Bounds {{909.153816679, 496.436690794}, {116.537890856, 21.8368569077}} Class DKDRectangle DKDLockInfo 14 GraphicID C6ECF8C8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID E6ECF8C8 GraphicStyle 40 SVGPath M1028.31269543,500.600197614 C1028.23628407,500.590358283 1028.15490446,500.599513455 1028.08346135,500.570679623 C1008.53808038,492.682330077 989.317271331,489.951977135 969.159884069,489.833076331 C950.103440094,489.720669574 929.97127679,489.717898288 911.111564168,497.620616881 Bounds {{1015.32036679, 496.407102059}, {21.4611953406, 21.4611953406}} Class DKDCircle DKDLockInfo 10 GraphicID F6ECF8C8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 07ECF8C8 GraphicStyle 40 SVGPath M1028.15851539,513.773625185 C1028.08210404,513.783464515 1028.00072443,513.774309344 1027.92928132,513.803143176 C1008.38390035,521.691492722 989.163091296,524.421845663 969.005704035,524.540746468 C949.949260061,524.653153224 929.817096756,524.655924511 910.957384135,516.753205918 Bounds {{908.412296226, 595.547380381}, {127.627745455, 34.7532036147}} Class DKDGroup DKDLockInfo 14 GraphicID 67ECF8C8 GridRef 2 GroupGraphics Bounds {{908.412296226, 602.173761583}, {116.537890856, 21.8368569077}} Class DKDRectangle DKDLockInfo 14 GraphicID 27ECF8C8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 37ECF8C8 GraphicStyle 40 SVGPath M1027.57117497,606.337268403 C1027.49476361,606.327429072 1027.41338401,606.336584244 1027.3419409,606.307750412 C1007.79655993,598.419400866 988.575750878,595.689047924 968.418363617,595.57014712 C949.361919641,595.457740363 929.229756338,595.454969077 910.370043715,603.35768767 Bounds {{1014.57884634, 602.144172848}, {21.4611953406, 21.4611953406}} Class DKDCircle DKDLockInfo 10 GraphicID 47ECF8C8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID 57ECF8C8 GraphicStyle 40 SVGPath M1027.41699494,619.510695974 C1027.34058358,619.520535304 1027.25920398,619.511380133 1027.18776086,619.540213965 C1007.6423799,627.428563511 988.421570843,630.158916452 968.264183582,630.277817257 C949.207739608,630.390224013 929.075576303,630.3929953 910.215863683,622.490276707 Class DKDPolygon DKDLockInfo 14 GraphicID 77ECF8C8 GraphicStyle 43 GridRef 17 SVGPath M908.65269604,483.42549498 L909.48216356,642.780732149 L884.028094663,659.888376784 L828.870507082,660.268682005 L828.817727292,642.779378749 L842.069514656,642.445089107 L842.444729967,484.380168643 z Bounds {{824.870320644, 255.521726731}, {205.517830086, 84.786409832}} Class DKDGroup DKDLockInfo 14 GraphicID 12F55CC8 GridRef 2 GroupGraphics Bounds {{902.760405275, 270.124640604}, {127.627745455, 34.753203615}} Class DKDGroup DKDLockInfo 14 GraphicID F1F55CC8 GridRef 2 GroupGraphics Bounds {{902.760405275, 276.751021806}, {116.537890856, 21.8368569077}} Class DKDRectangle DKDLockInfo 14 GraphicID A1F55CC8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID C1F55CC8 GraphicStyle 40 SVGPath M1021.91928402,280.914528626 C1021.84287266,280.904689296 1021.76149306,280.913844467 1021.69004995,280.885010635 C1002.14466898,272.996661089 982.923859925,270.266308148 962.766472663,270.147407343 C943.71002869,270.035000586 923.577865385,270.0322293 904.718152762,277.934947893 Bounds {{1008.92695539, 276.721433071}, {21.4611953406, 21.4611953406}} Class DKDCircle DKDLockInfo 10 GraphicID D1F55CC8 GraphicStyle 27 Class DKDContinuousBezier DKDLockInfo 14 GraphicID E1F55CC8 GraphicStyle 40 SVGPath M1021.76510399,294.087956197 C1021.68869263,294.097795528 1021.60731303,294.088640356 1021.53586991,294.117474188 C1001.99048895,302.005823734 982.769679892,304.736176675 962.612292631,304.85507748 C943.555848657,304.967484237 923.423685352,304.970255523 904.56397273,297.06753693 Class DKDPolygon DKDLockInfo 14 GraphicID 02F55CC8 GraphicStyle 43 GridRef 1 SVGPath M905.557087246,255.696229461 L905.534756912,322.820186707 L880.080688016,339.927831342 L824.923100435,340.308136563 L824.870320644,322.818833307 L838.122108008,322.484543665 L838.322400546,255.521726731 z Class DKDContinuousBezier DKDLockInfo 14 GraphicID E033C1D8 GraphicStyle 8 GridRef 2 SVGPath M555.938976435,540.286121347 C555.938976435,540.286121347 555.938976435,540.286121347 555.938976435,540.286121347 C610.106556705,540.952336407 659.083587856,525.975628193 707.011255612,558.721471656 C756.109257736,592.266928115 764.30983364,654.312586665 826.76506176,651.006238991 AttributedText String output ports TextAttribute 0 Bounds {{227.739327834, 656.918327281}, {110.936, 25}} Class DKDTextArea DKDLockInfo 14 GraphicID E7B8D3D8 GraphicStyle 10 GridRef 2 AttributedText String input ports TextAttribute 0 Bounds {{1040.04915996, 660.87987904}, {110.936, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID A7F0A4D8 GraphicStyle 10 GridRef 2 AttributedText String plug TextAttribute 0 Bounds {{482.174803406, 505.277851198}, {52.3254628879, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID D28365D8 GraphicStyle 10 GridRef 2 AttributedText String plug TextAttribute 0 Bounds {{851.188498255, 621.75023468}, {52.3254628879, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID D8ECC5D8 GraphicStyle 10 GridRef 2 AttributedText String plug TextAttribute 0 Bounds {{483.962795439, 196.2599819}, {52.3254628879, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 9E7406D8 GraphicStyle 10 GridRef 2 AttributedText String plug TextAttribute 0 Bounds {{849.988698879, 298.745095945}, {52.3254628879, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 55F226D8 GraphicStyle 10 GridRef 2 AttributedText String wire TextAttribute 0 Bounds {{600.640008464, 231.690964419}, {63.8779712734, 36.331345519}} Class DKDTextArea DKDLockInfo 14 GraphicID 43C756D8 GraphicStyle 10 GridRef 2 AttributedText String wire TextAttribute 0 Bounds {{625.919125015, 511.220284543}, {52.3254628879, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 5D0AC6D8 GraphicStyle 10 GridRef 2 AttributedText String block1 TextAttribute 16 Bounds {{244.180962123, 77.1565144974}, {85.7281072785, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID EE60ABD8 GraphicStyle 10 GridRef 2 AttributedText String block2 TextAttribute 16 Bounds {{1051.3838098, 80.6960508365}, {85.8497926861, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 24253ED8 GraphicStyle 10 GridRef 2 TextTransform coefficientM11 1.001419434203 coefficientM12 0 coefficientM21 0 coefficientM22 1 coefficientTX 0 coefficientTY 0 AttributedText String block1[0] TextAttribute 16 Bounds {{120.4261771, 159.749325024}, {134.687470499, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID A8125FD8 GraphicStyle 10 GridRef 2 AttributedText String block2[1] TextAttribute 16 Bounds {{1141.27624806, 276.765016736}, {134.687470499, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 16EFFFD8 GraphicStyle 10 GridRef 2 AttributedText String block1[2:4] TextAttribute 16 Bounds {{75.6596032848, 424.411701109}, {154.202767745, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID F91460E8 GraphicStyle 10 GridRef 2 AttributedText String block2[3:5] TextAttribute 16 Bounds {{1160.45526393, 525.291062871}, {154.202767745, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 82D751E8 GraphicStyle 10 GridRef 2 Class DKDPath DKDLockInfo 14 GraphicID 953A88E8 GraphicStyle 24 GridRef 2 SVGPath M233.736076637,387.991671072 L216.086781939,387.967133955 L216,504 L234,504 Class DKDPath DKDLockInfo 14 GraphicID DD32CAE8 GraphicStyle 24 GridRef 2 SVGPath M1140.31143453,499.663648476 L1157.96072923,499.639111359 L1158.04751117,615.671977404 L1140.04751117,615.671977404 AttributedText String connect(block1[0], block2[1]) TextAttribute 16 Bounds {{602.72529445, 165.56986382}, {361.835889279, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 39F17DE8 GraphicStyle 24 GridRef 2 AttributedText String connect(block1[2:4], block2[3:5]) TextAttribute 16 Bounds {{415.455795687, 670.714431704}, {412.012175074, 25.3057174314}} Class DKDTextArea DKDLockInfo 14 GraphicID 796B7FE8 GraphicStyle 24 GridRef 2 Class DKDLine DKDLockInfo 14 DkBezArrow 4 GraphicID 98BB41F8 GraphicStyle 24 GridRef 2 SVGPath M636.784138302,220.735971802 L664.514298005,190.287697272 Class DKDLine DKDLockInfo 14 DkBezArrow 4 GraphicID B9C6B1F8 GraphicStyle 24 GridRef 2 SVGPath M723.103374232,579.105994864 L649.739835569,669.048964195 HideDimensions NO LayerColorMod DKDOnColorMod NO DKDOpacityColorMod 0.5 DKDOutlineColorMod NO DKDTintColorColorMod ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 0.5 DKDTintFractionColorMod 0.5 LayerLock NO LayerName Paper LayerState Active OutlineLayer NO DKDPagesSpec BackgroundDisplay Background CanvasBorder Blue 0.45904 BluePlus 0.533333 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.458672 GreenPlus 0.533333 Opacity 1 OpacityPlus 1 Red 0.475001 RedPlus 0.54902 CanvasColor Catalog System Catalog-Color windowBackgroundColor CanvasMargin 0 DetailsDrawerWidth 260 DisplayAttributesBar YES DisplayRulers NO FullScreen NO FullScreenCanvasMargin 141.73228 LayersDrawerWidth 266 NonFullScreenCanvasMargin 0 NumberAcrossFirst YES PagesAcross 1 PagesDown 1 PagesSpecBackgroundRGB ColorSpace NSCalibratedWhiteColorSpace Opacity 1 White 1 PagesSpecPrintBackground NO ShowPageBreaks NO SizeChecker 8 aCheckerColor Blue 0.926349 BluePlus 0.941176 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.926333 GreenPlus 0.941176 Opacity 1 OpacityPlus 1 Red 0.926361 RedPlus 0.941176 bCheckerColor Blue 0.737155 BluePlus 0.784314 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.737142 GreenPlus 0.784314 Opacity 1 OpacityPlus 1 Red 0.737164 RedPlus 0.784314 DKDPrintInfo BottomMargin 18 Copies 1 FallBackPaperSizeHeight 1190.55114746 FallBackPaperSizeWidth 1453.32283465 FirstPage 1 HorizontalPagination 2 HorizontallyCentered YES LastPage -1 LeftMargin 18 MustCollate YES Orientation 1 PaperName Custom PaperSizeHeight 1190.55114746 PaperSizeWidth 1453.32283465 PreviewPageNumber 1 PrintAllPages YES PrintJobDisposition NSPrintSpoolJob PrintSavePath PrintScalingFactor 1 PrinterName ApeosPort-V C4475 T2 (01:87:08) 2 ReversePageOrder NO RightMargin 18 TopMargin 18 VerticalPagination 0 VerticallyCentered YES XPrintMirror NO YPrintMirror NO DKDSaveTimeStamp 2020-07-13 07:09:51 +0000 DKDTablet BrushDynamic NO BrushFit 6 PenMax 25 PenMin 0.5 PenPressureFactor 0.5 PencilDynamic NO PencilFit 7 DKDTimeFormat Field 0 Include Weekday Field 0 Type Long Field 1 Include Month Field 1 Type Short Field 2 Include Day Field 2 Type Number Field 3 Include Year Field 3 Type Long Include GMT NO Include Title YES IncludeDate YES IncludeTime YES Seperator 0 - Seperator 1 . Seperator 2 , Seperator 3 Time Seperator : TimeAfterDate YES Twelve Hour Clock YES Used Once YES DKDToolbarSelectedButtonPairs ColorTextToolbarItemIdentifier_0 HSB_000020 ColorFillToolbarItemIdentifier_0 HSB_0000D0 ColorStrokeToolbarItemIdentifier_0 HSB_000020 GradientToolbarItemIdentifier_0 EndGradientColor Blue 0.999991 BluePlus 1 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0.999974 GreenPlus 1 Opacity 1 OpacityPlus 1 Red 0.999999 RedPlus 0.999996 GradientFillClass DKDHorizontalGradientFill StartGradientColor Blue 0 BluePlus 0 ColorSpace NSCalibratedRGBColorSpace ColorSpacePlus DKDsRgbColorSpace Green 0 GreenPlus 0 Opacity 1 OpacityPlus 1 Red 0 RedPlus 0 PatternForToolbarItemIdentifier_0 Toolbar_02 TextureForToolbarItemIdentifier_0 Ancient DKDArrowMenuToolbarItemIdentifier_0 810007 DashMenuToolbarIdentifier_0 810017 CombineMenuToolbarIdentifier_0 621001 DKDConvertToMenuToolbarItemIdentifier_0 612032 DKDWindowState CursorMode Nothing DocCenter {708.661417323, 477.816016622} DrawersOnMainView YES GDetailsLayersDrawerEdgePreference Auto GraphicDetailsOpen NO LayerActiveAbove NO LayerOpen NO LayerSelect Active Only LayersDrawerEdgePreference Auto OutlineDrawing NO WindowLocation 433 454 1504 914 0 0 2560 1417 ZoomPercent 97.529632 GroupEdit Fixed NumberColorsToListInContextMenu 12 NumberPairColorsToListInContextMenu 6 ================================================ FILE: pyproject.toml ================================================ [project] name = "bdsim" version = "1.1.2" authors = [{ name = "Peter Corke", email = "rvc@petercorke.com" }] dependencies = [ "numpy>=1.17.4", "scipy", "matplotlib", "spatialmath-python", "ansitable", "progress", ] license = { file = "LICENSE" } description = "Simulate dynamic systems expressed in block diagram form using Python" readme = "README.md" requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3 :: Only", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] keywords = ["python", "block diagram", "dynamic simulation"] [project.urls] homepage = "https://github.com/petercorke/bdsim" documentation = "https://petercorke.github.io/bdsim/" repository = "https://github.com/petercorke/bdsim" [project.optional-dependencies] dev = ["sympy", "pytest", "pytest-timeout", "pytest-xvfb", "coverage", "flake8"] docs = [ "sphinx", "sphinx-rtd-theme", "sphinx-autorun", "sphinxcontrib-jsmath", "sphinx-markdown-tables", "sphinx-autodoc-typehints", "sphinx-favicon", ] edit = ["PyQt5", "PIL"] [project.scripts] bdrun = "bdsim:bdrun" bdtex2icon = "bdsim.tex2icon:main" [project.gui-scripts] # ideally this would only happen if [edit] option given bdedit = "bdsim.bdedit:main" [build-system] requires = ["setuptools", "oldest-supported-numpy"] build-backend = "setuptools.build_meta" [tool.setuptools] packages = ["bdsim", "bdsim.blocks", "bdsim.bdedit"] [tool.black] line-length = 88 target-version = ['py37'] [tool.pytest.ini_options] testpaths = "tests" [tool.coverage.run] omit = [ "tex2icon.py", "io.py", "vision.py", "bdsim/bdedit/*.py", "tk_editor/*.py", "examples/RVC3/*.py", ] ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/test_bdsim.py ================================================ #!/usr/bin/env python3 import numpy as np import math import bdsim import unittest import numpy.testing as nt from pathlib import Path class BDSimTest(unittest.TestCase): def test_options(self): sim = bdsim.BDSim() self.assertFalse(sim.options.verbose) self.assertTrue(sim.options.graphics) self.assertFalse(sim.options.animation) sim.options.verbose = True self.assertTrue(sim.options.verbose) sim.options.set(verbose=False) self.assertFalse(sim.options.verbose) sim.set_options(verbose=True) self.assertTrue(sim.options.verbose) sim.options.graphics = False sim.options.animation = False self.assertFalse(sim.options.graphics) self.assertFalse(sim.options.animation) sim.options.set(animation=True) self.assertTrue(sim.options.graphics) self.assertTrue(sim.options.animation) sim.options.set(graphics=False) self.assertFalse(sim.options.graphics) self.assertFalse(sim.options.animation) with self.assertRaises(ValueError): sim.options.set(graphics=False, animation=True) def test_bdrun(self): file = Path(__file__).parent.parent / "examples" / "eg1.bd" sim = bdsim.BDSim(graphics=None, progress=False) bd = sim.blockdiagram() bd = bdsim.bdload(bd, file) self.assertEqual(len(bd.blocklist), 5) self.assertEqual(len(bd.wirelist), 6) bd.compile() sim.run(bd, T=2) def test_sim(self): # all up test sim = bdsim.BDSim(graphics=None, progress=False) bd = sim.blockdiagram() integ = bd.INTEGRATOR() step = bd.STEP(t=1) null = bd.NULL() bd.connect(step, integ) bd.connect(integ, null) bd.compile() out = sim.run(bd, 2) self.assertIsInstance(out, bdsim.components.BDStruct) self.assertTrue(hasattr(out, "t")) self.assertIsInstance(out.t, np.ndarray) self.assertEqual(out.t.ndim, 1) n = out.t.shape[0] self.assertGreater(n, 100) self.assertTrue(hasattr(out, "x")) self.assertIsInstance(out.x, np.ndarray) self.assertEqual(out.x.shape, (n, 1)) self.assertTrue(hasattr(out, "xnames")) self.assertIsInstance(out.xnames, list) self.assertEqual(len(out.xnames), 1) self.assertEqual(out.xnames[0], "integrator.0x0") self.assertTrue(hasattr(out, "ynames")) self.assertIsInstance(out.ynames, list) self.assertEqual(len(out.ynames), 0) def test_sim_implicit(self): # all up test sim = bdsim.BDSim(graphics=None, progress=False) bd = sim.blockdiagram() integ = bd.INTEGRATOR() step = bd.STEP(t=1) null = bd.NULL() integ[0] = step null[0] = integ bd.compile() out = sim.run(bd, 2) self.assertIsInstance(out, bdsim.components.BDStruct) self.assertTrue(hasattr(out, "t")) self.assertIsInstance(out.t, np.ndarray) self.assertEqual(out.t.ndim, 1) n = out.t.shape[0] self.assertGreater(n, 100) self.assertTrue(hasattr(out, "x")) self.assertIsInstance(out.x, np.ndarray) self.assertEqual(out.x.shape, (n, 1)) self.assertTrue(hasattr(out, "xnames")) self.assertIsInstance(out.xnames, list) self.assertEqual(len(out.xnames), 1) self.assertEqual(out.xnames[0], "integrator.0x0") self.assertTrue(hasattr(out, "ynames")) self.assertIsInstance(out.ynames, list) self.assertEqual(len(out.ynames), 0) # ---------------------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_blockdiagram.py ================================================ #!/usr/bin/env python3 import numpy as np import scipy.interpolate import math import bdsim import unittest import numpy.testing as nt class BlockTest(unittest.TestCase): pass class BlockDiagramTest(unittest.TestCase): pass class WiringTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.sim = bdsim.BDSim(animation=False) # create simulator def test_bd(self): bd1 = self.sim.blockdiagram() bd2 = self.sim.blockdiagram() self.assertEqual(len(bd1), 0) self.assertEqual(len(bd2), 0) bd1.CONSTANT(2) self.assertEqual(len(bd1), 1) bd1.CONSTANT(2) self.assertEqual(len(bd1), 2) self.assertEqual(len(bd2), 0) bd2.CONSTANT(2) self.assertEqual(len(bd1), 2) self.assertEqual(len(bd2), 1) def test_connect_1(self): bd = self.sim.blockdiagram() src = bd.CONSTANT(2) dst = bd.NULL(1) # 1 port bd.connect(src, dst) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs[0], 2) def test_connect_2(self): bd = self.sim.blockdiagram() src = bd.CONSTANT(2) dst1 = bd.NULL(1) # 1 port dst2 = bd.NULL(1) # 1 port bd.connect(src, dst1) bd.connect(src, dst2) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst1.inputs[0], 2) self.assertEqual(dst2.inputs[0], 2) def test_multi_connect(self): bd = self.sim.blockdiagram() src = bd.CONSTANT(2) dst1 = bd.NULL(1) # 1 port dst2 = bd.NULL(1) # 1 port bd.connect(src, dst1, dst2) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst1.inputs[0], 2) self.assertEqual(dst2.inputs[0], 2) def test_ports1(self): bd = self.sim.blockdiagram() const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst = bd.NULL(2) # 2 ports bd.connect(const1, dst[0]) bd.connect(const2, dst[1]) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 3]) def test_ports2(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3]) src = bd.DEMUX(2) bd.connect(const, src) dst1 = bd.NULL(1) # 1 port dst2 = bd.NULL(1) # 1 port bd.connect(src[0], dst1) bd.connect(src[1], dst2) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst1.inputs, [2]) self.assertEqual(dst2.inputs, [3]) def test_ports3(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports bd.connect(src[0], dst[0]) bd.connect(src[1], dst[1]) bd.connect(src[2], dst[2]) bd.connect(src[3], dst[3]) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 3, 4, 5]) def test_slice1(self): bd = self.sim.blockdiagram() src = bd.CONSTANT(2) dst = bd.NULL(2) # 1 port bd.connect(src, dst[0]) bd.connect(src, dst[1]) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 2]) def test_slice2(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports bd.connect(src[0:4], dst[0:4]) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 3, 4, 5]) def test_slice3(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports bd.connect(src[0:4], dst[3:-1:-1]) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5, 4, 3, 2]) def test_slice4(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports bd.connect(src[3:-1:-1], dst[0:4]) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5, 4, 3, 2]) def test_slice5(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports bd.connect(src[0:4:2], dst[0:4:2]) # 0, 2 bd.connect(src[1:4:2], dst[1:4:2]) # 1, 3 bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 3, 4, 5]) def test_slice5a(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports bd.connect(src[0:4:2], dst[0:2]) # 0, 2 bd.connect(src[1:4:2], dst[2:4]) # 1, 3 bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 4, 3, 5]) def test_slice6(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports bd.connect(src[3:-1:-1], dst[3:-1:-1]) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 3, 4, 5]) def test_assignment11(self): bd = self.sim.blockdiagram() src = bd.CONSTANT(2) dst = bd.NULL(1) # 1 port dst[0] = src bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs[0], 2) def test_assignment2(self): bd = self.sim.blockdiagram() const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst = bd.NULL(2) # 2 ports dst[0] = const1 dst[1] = const2 bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [2, 3]) def test_assignment3(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3, 4, 5]) src = bd.DEMUX(4) bd.connect(const, src) dst = bd.NULL(4) # 4 ports dst[3:-1:-1] = src[0:4] bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5, 4, 3, 2]) def test_chain1(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports dst[0] = bd.CONSTANT(2) >> bd.GAIN(3) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [6]) def test_chain2(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports dst[0] = bd.CONSTANT(2) >> bd.GAIN(3) >> bd.GAIN(4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [24]) def test_chain3(self): bd = self.sim.blockdiagram() const = bd.CONSTANT([2, 3]) src = bd.DEMUX(2) bd.connect(const, src) dst = bd.NULL(2) # 2 ports dst[0] = src[0] >> bd.GAIN(2) dst[1] = src[1] >> bd.GAIN(3) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [4, 9]) def test_inline1(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = bd.SUM("++", inputs=(const1, const2)) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5]) def test_inline2(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = bd.SUM("++", inputs=(const1, const2)) >> bd.GAIN(2) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [10]) def test_autosum1(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1 + const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5]) def test_autosum2a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1 + const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5]) def test_autosum2b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1[0] + const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5]) def test_autosum2c(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1[0] + const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5]) def test_autosum3a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) dst[0] = const1 + 3 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5]) def test_autosum3b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const2 = bd.CONSTANT(3) dst[0] = 2 + const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [5]) # ---------------------------------------------- def test_autosub1(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1 - const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-1]) def test_autosub2a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1 - const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-1]) def test_autosub2b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1[0] - const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-1]) def test_autosub2c(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1[0] - const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-1]) def test_autosub3a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) dst[0] = const1 - 3 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-1]) def test_autosub3b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const2 = bd.CONSTANT(3) dst[0] = 2 - const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-1]) # ---------------------------------------------- def test_autoneg1(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) dst[0] = -const1 self.assertEqual(len(bd), 3) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-2]) def test_autoneg2(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) dst[0] = -const1[0] self.assertEqual(len(bd), 3) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [-2]) # ---------------------------------------------- def test_autoprod1(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = bd.CONSTANT(3) dst[0] = const1 * const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [6]) def test_autoprod2a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = bd.CONSTANT(2) dst[0] = const1[0] / const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autoprod2b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = bd.CONSTANT(2) dst[0] = const1[0] / const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autoprod2c(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = bd.CONSTANT(2) dst[0] = const1[0] / const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autoprod3a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(2) const2 = 3 dst[0] = const1 * const2 self.assertEqual(len(bd), 3) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [6]) def test_autoprod3b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = 2 const2 = bd.CONSTANT(3) dst[0] = const1 * const2 self.assertEqual(len(bd), 3) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [6]) # ---------------------------------------------- def test_autodiv1(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = bd.CONSTANT(2) dst[0] = const1 / const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autodiv2a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = bd.CONSTANT(2) dst[0] = const1[0] / const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autodiv2b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = bd.CONSTANT(2) dst[0] = const1[0] / const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autodiv2c(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = bd.CONSTANT(2) dst[0] = const1[0] / const2[0] self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autodiv3a(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = bd.CONSTANT(3) const2 = 2 dst[0] = const1 / const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) def test_autodiv3b(self): bd = self.sim.blockdiagram() dst = bd.NULL(1) # 1 ports const1 = 3 const2 = bd.CONSTANT(2) dst[0] = const1 / const2 self.assertEqual(len(bd), 4) bd.compile(verbose=False) bd.schedule_evaluate(x=[], t=0) self.assertEqual(dst.inputs, [1.5]) class ImportTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.sim = bdsim.BDSim(animation=False) # create simulator def test_import1(self): # create a subsystem ss = self.sim.blockdiagram(name="subsystem1") f = ss.FUNCTION(lambda x: x) inp = ss.INPORT(1) outp = ss.OUTPORT(1) ss.connect(inp, f) ss.connect(f, outp) # create main system bd = self.sim.blockdiagram() const = bd.CONSTANT(1) scope = bd.SCOPE() ff = bd.SUBSYSTEM(ss, name="subsys") bd.connect(const, ff) bd.connect(ff, scope) bd.compile(verbose=False) self.assertEqual(len(bd.blocklist), 5) self.assertEqual(len(bd.wirelist), 4) # def test_import2(self): # # create a subsystem # ss = bdsim.BlockDiagram(name='subsystem1') # f = ss.FUNCTION(lambda x: x) # inp = ss.INPORT(1) # outp = ss.NULL(1) # ss.connect(inp, f) # ss.connect(f, outp) # # create main system # bd = bdsim.BlockDiagram() # const = bd.CONSTANT(1) # scope1 = bd.SCOPE() # scope2 = bd.SCOPE() # f1 = bd.SUBSYSTEM(ss, name='subsys1') # f2 = bd.SUBSYSTEM(ss, name='subsys2') # bd.connect(const, f1, f2) # bd.connect(f1, scope1) # bd.connect(f2, scope2) # bd.compile(verbose=False) # ---------------------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_components.py ================================================ import unittest import numpy.testing as nt from bdsim.components import * from bdsim.blocks import * from bdsim import BDSim, TimeQ, BlockDiagram class WireTest(unittest.TestCase): def test_init(self): b1 = Constant(2, name="block1") b2 = Null() w = Wire(b1, b2, name="wire1") w.info self.assertIsInstance(str(w), str) def test_bd(self): sim = bdsim.BDSim() # create simulator bd = sim.blockdiagram() b1 = bd.CONSTANT(2, name="block1") b2 = bd.NULL() bd.connect(b1, b2) bd.compile() w = bd.wirelist[0] self.assertIsInstance(str(w), str) self.assertEqual(str(w), "wire.0") self.assertIsInstance(w.fullname, str) self.assertEqual(w.fullname, "block1[0] --> null.0[0]") self.assertIsInstance(repr(w), str) self.assertEqual(repr(w), "wire.0: block1[0] --> null.0[0]") class PlugTest(unittest.TestCase): def test_portlist(self): block = Mux(5) p = Plug(block, type="end") p = block[3] pl = p.portlist self.assertEqual(len(pl), 1) self.assertEqual(pl[0], 3) p = block[1:4] pl = p.portlist self.assertEqual(len(pl), 3) self.assertEqual(list(pl), [1, 2, 3]) p = block[:4] pl = p.portlist self.assertEqual(len(pl), 4) self.assertEqual(list(pl), [0, 1, 2, 3]) p = block[2:] pl = p.portlist self.assertEqual(len(pl), 3) self.assertEqual(list(pl), [2, 3, 4]) class BlockTest(unittest.TestCase): def test_init(self): b1 = Constant(2) b1.info def test_predicates(self): b1 = Scope() b2 = Constant(2) b3 = ZOH(Clock(1)) self.assertTrue(b1.isgraphics) self.assertFalse(b2.isgraphics) self.assertFalse(b2.isclocked) self.assertTrue(b3.isclocked) class PriorityQTest(unittest.TestCase): def test_pushpop(self): q = TimeQ() q.push((0, "a")) q.push((3, "d")) q.push((2, "c")) q.push((1, "b")) self.assertEqual(len(q), 4) self.assertIsInstance(str(q), str) self.assertIsInstance(repr(q), str) self.assertEqual(str(q), "TimeQ: len=4, first out (0, 'a')") x = q.pop() self.assertIsInstance(x, tuple) self.assertEqual(x, (0, ["a"])) x = q.pop() self.assertEqual(x, (1, ["b"])) x = q.pop() self.assertEqual(x, (2, ["c"])) x = q.pop() self.assertEqual(x, (3, ["d"])) x = q.pop() self.assertEqual(x, (None, [])) def test_threshold(self): q = TimeQ() q.push((0, "a")) q.push((3, "d")) q.push((2, "c")) q.push((0.001, "b")) x = q.pop(dt=0.1) self.assertEqual(x, (0, ["a", "b"])) x = q.pop(dt=0.1) self.assertEqual(x, (2, ["c"])) x = q.pop(dt=0.1) self.assertEqual(x, (3, ["d"])) def test_until(self): q = TimeQ() q.push((0, "a")) q.push((3, "d")) q.push((2, "c")) q.push((0.5, "b")) x = q.pop_until(2) self.assertEqual(len(x), 3) for i in x: self.assertTrue(i[0] <= 2) self.assertEqual(len(q), 1) x = q.pop_until(2.5) self.assertEqual(len(x), 0) class ClockTest(unittest.TestCase): def test_init(self): c = Clock(2) self.assertEqual(c.T, 2) self.assertEqual(c.offset, 0) c = Clock(2, offset=1) self.assertEqual(c.T, 2) self.assertEqual(c.offset, 1) c = Clock(2, "ms") self.assertEqual(c.T, 0.002) c = Clock(2, "Hz") self.assertEqual(c.T, 0.5) def test_block(self): c = Clock(2) block = ZOH(c) self.assertEqual(len(c.blocklist), 1) self.assertEqual(c.blocklist[0], block) def test_str(self): global clocklist clocklist.clear() c = Clock(2) block = ZOH(c) self.assertIsInstance(str(c), str) self.assertEqual(str(c), "clock.0: T=2 sec, clocking 1 blocks") self.assertIsInstance(repr(c), str) self.assertEqual(repr(c), "clock.0: T=2 sec, clocking 1 blocks") c = Clock(2, offset=1, name="myclock") block = ZOH(c) self.assertIsInstance(repr(c), str) self.assertEqual(repr(c), "myclock: T=2 sec, offset=1, clocking 1 blocks") @unittest.skip def test_state(self): global clocklist clocklist.clear() c = Clock(2) bd = BlockDiagram() const = Constant(0, bd=bd) block1 = ZOH(c, x0=3, bd=bd) block2 = ZOH(c, x0=4, bd=bd) null = Null(bd=bd) bd.connect(const, block1) bd.connect(block1, block2) bd.connect(block2, null) bd.compile() self.assertEqual(len(c.blocklist), 2) nt.assert_almost_equal(c.getstate0(), np.r_[3, 4]) c._x = np.r_[5, 6] c.setstate() nt.assert_almost_equal(block1._x, np.r_[5]) nt.assert_almost_equal(block2._x, np.r_[6]) nt.assert_almost_equal(c.getstate(0.0), np.r_[13, 14]) def test_time(self): global clocklist clocklist.clear() c = Clock(2, offset=1) block1 = ZOH(c, x0=3) self.assertEqual(c.time(0), 1) self.assertEqual(c.time(1), 3) self.assertEqual(c.time(2), 5) # c.start() # t = c.next_event() class StructTest(unittest.TestCase): def test_struct(self): x = BDStruct() x.a = 1 x.b = "hello" x.c = 4.56 x.d = [1, 2, 3] self.assertEqual(x.a, 1) self.assertEqual(x.b, "hello") self.assertEqual(x.c, 4.56) self.assertEqual(x.d, [1, 2, 3]) s = str(x) print(s) self.assertEqual(len(s.split("\n")), 4) def test_struct_struct(self): x = BDStruct(f=2) x.a = BDStruct(name="baz", a=1, b=4.56) self.assertEqual( str(x), ( "a .baz::\n a = 1 (int)\n b = 4.56 (float)\nf " " = 2 (int)" ), ) def test_item(self): x = BDStruct() x["a"] = 1 x["b"] = "hello" self.assertEqual(x.a, 1) self.assertEqual(x.b, "hello") self.assertEqual(x["a"], 1) self.assertEqual(x["b"], "hello") def test_init(self): s = BDStruct(a=2, b=3) self.assertEqual(s.a, 2) self.assertEqual(s.b, 3) with self.assertRaises(AttributeError): z = s.c s.c = 4 self.assertEqual(s.c, 4) s.c = 5 self.assertEqual(s.c, 5) s.d = 6 self.assertEqual(s.d, 6) self.assertIsInstance(str(s), str) self.assertIsInstance(repr(s), str) self.assertEqual( str(s), "a = 2 (int)\nb = 3 (int)\nc = 5 (int)\nd = 6 (int)" ) def test_len(self): s = BDStruct(a=2, c=1, b=3) self.assertEqual(len(s), 3) class OptionTest(unittest.TestCase): def test_init(self): opt = OptionsBase() def test_init1(self): opt = OptionsBase(dict(foo=1, bar="hello")) self.assertEqual(opt.foo, 1) self.assertEqual(opt.bar, "hello") def test_init2(self): opt = OptionsBase({}, dict(foo=1, bar="hello")) self.assertEqual(opt.foo, 1) self.assertEqual(opt.bar, "hello") def test_init1(self): opt = OptionsBase(dict(foo=1, bar="hello"), dict(foo=2, baz=3)) self.assertEqual(opt.foo, 1) self.assertEqual(opt.bar, "hello") self.assertEqual(opt.baz, 3) def test_set(self): opt = OptionsBase(dict(foo=1, bar="hello")) opt.foo = 2 self.assertEqual(opt.foo, 1) self.assertEqual(opt.bar, "hello") def test_set2(self): opt = OptionsBase(dict(foo=1, bar="hello")) opt.set(foo=3) self.assertEqual(opt.foo, 1) self.assertEqual(opt.bar, "hello") def test_set3(self): opt = OptionsBase({}, dict(foo=1, bar="hello")) opt.set(foo=3) self.assertEqual(opt.foo, 3) self.assertEqual(opt.bar, "hello") # ---------------------------------------------------------------------------------------# if __name__ == "__main__": # opt = OptionsBase(dict(foo=1, bar='hello'), dict(foo=2)) # opt.set(foo=3) # print(opt.foo) unittest.main() ================================================ FILE: tests/test_connections.py ================================================ #!/usr/bin/env python3 import numpy as np import scipy.interpolate import math from bdsim.blocks.connections import * import unittest import numpy.testing as nt class ConnectionsTest(unittest.TestCase): def test_mux(self): block = Mux(2) nt.assert_array_equal(block._output(1, 2)[0], np.r_[1,2]) block = Mux(3) nt.assert_array_equal(block._output(1, 2, 3)[0], np.r_[1,2, 3]) block = Mux(2) nt.assert_array_equal(block._output(1, np.r_[2, 3])[0], np.r_[1, 2, 3]) def test_demux(self): block = DeMux(2) self.assertEqual(block._output(np.r_[1,2])[0], 1) self.assertEqual(block._output(np.r_[1,2])[1], 2) def test_item(self): block = Item('sig2') sig = {'sig1':1, 'sig2':2, 'sig3':3} self.assertEqual(block._output(sig)[0], 2) # subsystems are tested by test_blockdiagram # ---------------------------------------------------------------------------------------# if __name__ == '__main__': unittest.main() ================================================ FILE: tests/test_discrete.py ================================================ #!/usr/bin/env python3 import numpy as np import math import matplotlib.pyplot as plt from bdsim.blocks.discrete import * from bdsim import Clock import unittest import numpy.testing as nt class DiscreteTest(unittest.TestCase): def test_ZOH(self): clock = Clock(2, 'Hz') x = 7 block = ZOH(clock, x0=5) # state is scalar self.assertEqual(block.ndstates, 1) self.assertEqual(block.nstates, 0) nt.assert_equal(block.getstate0(), np.r_[5]) x = np.r_[1] nt.assert_equal(block.T_output(0, x=x)[0], x) u = 3 nt.assert_equal(block.T_next(u, x=x), np.r_[u]) u = np.r_[1] nt.assert_equal(block.T_next(u), u) def test_dintegrator(self): clock = Clock(2, 'Hz') block = DIntegrator(clock, x0=5) # state is scalar self.assertEqual(block.ndstates, 1) self.assertEqual(block.nstates, 0) nt.assert_equal(block.getstate0(), np.r_[5]) x = np.r_[10] u = -2 nt.assert_equal(block.T_output(u, x=x)[0], x) nt.assert_equal(block.T_next(u, x=x), x + u * clock.T) block = DIntegrator(clock, x0=5, min=-10, max=10) # state is scalar x = np.r_[10] u = 2 nt.assert_equal(block.T_next(u, x=x), x) x = np.r_[-10] u = -2 nt.assert_equal(block.T_next(u, x=x), x) def test_dintegrator_vec(self): clock = Clock(2, 'Hz') block = DIntegrator(clock, x0=[5, 6]) # state is vector self.assertEqual(block.ndstates, 2) self.assertEqual(block.nstates, 0) nt.assert_equal(block.getstate0(), np.r_[5, 6]) x = np.r_[10, 11] u = np.r_[-2, 3] nt.assert_equal(block.T_output(u, x=x)[0], x) nt.assert_equal(block.T_next(u, x=x), x + u * clock.T) # test with limits block = DIntegrator(clock, x0=[5, 6], min=[-5, -10], max=[5, 10]) # state is vector x = np.r_[-5, -10] u = np.r_[-2, -3] nt.assert_equal(block.T_next(u, x=x), x) x = np.r_[5, 10] u = np.r_[2, 3] nt.assert_equal(block.T_next(u, x=x), x) def test_pose_dintegrator(self): clock = Clock(2, 'Hz') T = SE3.Rand() block = DPoseIntegrator(clock, x0=T) nt.assert_equal(block.getstate0(), Twist3(T)) self.assertEqual(block.ndstates, 6) self.assertEqual(block.nstates, 0) x = block.getstate0() u = np.r_[1,2,3,4,5,6] nt.assert_equal(block.T_output(u, x=x)[0], T) nt.assert_almost_equal(block.T_next(u, x=x), Twist3(T * SE3.Delta(u*clock.T))) # ---------------------------------------------------------------------------------------# if __name__ == '__main__': unittest.main() ================================================ FILE: tests/test_functions.py ================================================ #!/usr/bin/env python3 import numpy as np import scipy.interpolate import math from bdsim.blocks.functions import * import unittest import numpy.testing as nt class FunctionBlockTest(unittest.TestCase): def test_gain(self): block = Gain(2) self.assertEqual(block._output(1)[0], 2) block = Gain(2) nt.assert_array_almost_equal(block._output(np.r_[1,2,3])[0], np.r_[2,4,6]) block = Gain(np.r_[1,2,3]) nt.assert_array_almost_equal(block._output(2)[0], np.r_[2,4,6]) block = Gain(np.array([[1,2],[3,4]])) nt.assert_array_almost_equal(block._output(2)[0], np.array([[2,4],[6,8]])) block = Gain(np.array([[1,2],[3,4]])) nt.assert_array_almost_equal(block._output(np.r_[1,2])[0], np.r_[7, 10]) block = Gain(np.array([[1,2],[3,4]]), premul=True) nt.assert_array_almost_equal(block._output(np.r_[1,2])[0], np.r_[5, 11]) block = Gain(np.array([[1,2],[3,4]])) nt.assert_array_almost_equal(block._output(np.array([[5,6],[7,8]]))[0], np.array([[23,34],[31,46]])) block = Gain(np.array([[1,2],[3,4]]), premul=True) nt.assert_array_almost_equal(block._output(np.array([[5,6],[7,8]]))[0], np.array([[19,22],[43,50]])) block = Gain(np.array([[1,2],[3,4]]), order='postmul') out = block._output(np.array([[5,6],[7,8]])) nt.assert_array_almost_equal(out[0], np.array([[23,34],[31,46]])) def test_sum(self): block = Sum('++') self.assertEqual(block.T_output(10, 5)[0], 15) block = Sum('+-') self.assertEqual(block.T_output(10, 5)[0], 5) block = Sum('-+') self.assertEqual(block.T_output(10, 5)[0], -5) block = Sum('-+', mode='r') self.assertEqual(block.T_output(10, 5)[0], -5) block = Sum('+-', mode='c') self.assertEqual(block.T_output(0, -5*math.pi)[0], -math.pi) self.assertEqual(block.T_output(math.pi, -math.pi)[0], 0) block = Sum('+-', mode='C') self.assertEqual(block.T_output(0, -5*math.pi)[0], math.pi) self.assertEqual(block.T_output(math.pi, -math.pi)[0], 0) block = Sum('++', mode='l') self.assertEqual(block.T_output(math.pi/2, math.pi)[0], math.pi/2) self.assertEqual(block.T_output(math.pi, -math.pi)[0], 0) block = Sum('++', mode='rc') x = np.r_[math.pi, math.pi] nt.assert_array_almost_equal(block.T_output(x, x)[0], [2*math.pi, 0]) x = np.ones((2, 4)) * math.pi y = np.vstack((np.ones((4,))*2*math.pi, np.zeros((4,)))) nt.assert_array_almost_equal(block.T_output(x, x)[0], y) def test_prod(self): block = Prod('**') self.assertEqual(block._output(10, 5)[0], 50) block = Prod('*/') self.assertEqual(block._output(10, 5)[0], 2) block = Prod('/*') self.assertEqual(block._output(10, 5)[0], 0.5) # test matrix and np cases def test_clip(self): block = Clip(min=-1, max=1) self.assertEqual(block._output(0)[0], 0) self.assertEqual(block._output(1)[0], 1) self.assertEqual(block._output(10)[0], 1) self.assertEqual(block._output(-1)[0], -1) self.assertEqual(block._output(-10)[0], -1) block = Clip(min=-2, max=3) out = block._output(1) self.assertEqual(out[0], 1) self.assertEqual(block._output(1)[0], 1) self.assertEqual(block._output(10)[0], 3) self.assertEqual(block._output(-1)[0], -1) self.assertEqual(block._output(-10)[0], -2) nt.assert_array_equal(block._output(np.r_[-10, -2, -1, 0, 1, 3, 10])[0], np.r_[-2, -2, -1, 0, 1, 3, 3]) def test_function(self): def test(x, y): return x+y block = Function(test, nin=2) self.assertEqual(block._output(1, 2)[0], 3) block = Function(lambda x, y: x+y, nin=2) self.assertEqual(block._output(1, 2)[0], 3) block = Function(lambda x, y, a, b: x+y+a+b, nin=2, fargs=(3,4)) self.assertEqual(block._output(1, 2)[0], 10) block = Function(lambda x, y, a=0, b=0: x+y+a+b, nin=2, fkwargs={'a':3, 'b':4}) self.assertEqual(block._output(1, 2)[0], 10) def test_interpolate(self): block = Interpolate(x=(0,5,10), y=(0,1,0)) self.assertEqual(block._output(0)[0], 0) self.assertEqual(block._output(2.5)[0], 0.5) self.assertEqual(block._output(5)[0], 1) self.assertEqual(block._output(7.5)[0], 0.5) self.assertEqual(block._output(10)[0], 0) block = Interpolate(x=np.r_[0,5,10], y=np.r_[0,1,0]) self.assertEqual(block._output(0)[0], 0) self.assertEqual(block._output(2.5)[0], 0.5) self.assertEqual(block._output(5)[0], 1) self.assertEqual(block._output(7.5)[0], 0.5) self.assertEqual(block._output(10)[0], 0) block = Interpolate(x=np.r_[0,5,10], y=np.r_[0,1,0].reshape((3,1))) self.assertEqual(block._output(0)[0], 0) self.assertEqual(block._output(2.5)[0], 0.5) self.assertEqual(block._output(5)[0], 1) self.assertEqual(block._output(7.5)[0], 0.5) self.assertEqual(block._output(10)[0], 0) block = Interpolate(xy=[(0,0), (5,1), (10,0)]) self.assertEqual(block._output(0)[0], 0) self.assertEqual(block._output(2.5)[0], 0.5) self.assertEqual(block._output(5)[0], 1) self.assertEqual(block._output(7.5)[0], 0.5) self.assertEqual(block._output(10)[0], 0) # block = _Interpolate(xy=np.array([(0,0), (5,1), (10,0)]).T) # self.assertEqual(block._output()[0], 0) # self.assertEqual(block._output()[0], 0.5) # self.assertEqual(block._output()[0], 1) # self.assertEqual(block._output()[0], 0.5) # self.assertEqual(block._output()[0], 0) block = Interpolate(x=(0,5,10), y=(0,1,0), time=True) self.assertEqual(block._output(t=0)[0], 0) self.assertEqual(block._output(t=2.5)[0], 0.5) self.assertEqual(block._output(t=5)[0], 1) self.assertEqual(block._output(t=7.5)[0], 0.5) self.assertEqual(block._output(t=10)[0], 0) block = Interpolate(x=np.r_[0,5,10], y=np.r_[0,1,0], time=True) self.assertEqual(block._output(t=0)[0], 0) self.assertEqual(block._output(t=2.5)[0], 0.5) self.assertEqual(block._output(t=5)[0], 1) self.assertEqual(block._output(t=7.5)[0], 0.5) self.assertEqual(block._output(t=10)[0], 0) block = Interpolate(xy=[(0,0), (5,1), (10,0)], time=True) self.assertEqual(block._output(t=0)[0], 0) self.assertEqual(block._output(t=2.5)[0], 0.5) self.assertEqual(block._output(t=5)[0], 1) self.assertEqual(block._output(t=7.5)[0], 0.5) self.assertEqual(block._output(t=10)[0], 0) # block = _Interpolate(xy=np.array([(0,0), (5,1), (10,0)]), time=True) # out = block._output(0) # self.assertIsInstance(out, list) # self.assertAlmostEqual(len(out), 1) # self.assertEqual(block._output(0)[0], 0) # self.assertEqual(block._output(2.5)[0], 0.5) # self.assertEqual(block._output(5)[0], 1) # self.assertEqual(block._output(7.5)[0], 0.) # self.assertEqual(block._output(10)[0], 0) # ---------------------------------------------------------------------------------------# if __name__ == '__main__': unittest.main() ================================================ FILE: tests/test_linalg.py ================================================ #!/usr/bin/env python3 import numpy as np import scipy.interpolate import math from bdsim.blocks.linalg import * import unittest import numpy.testing as nt class LinalgBlockTest(unittest.TestCase): def test_inverse(self): block = Inverse() x = np.array([[2, 1], [7, 4]]) xi = np.array([[4, -1], [-7, 2]]) nt.assert_array_almost_equal(block._output(x)[0], xi) nt.assert_array_almost_equal(block._output(x)[1], np.linalg.cond(x)) # singular matrix a = np.array([[1, 2], [2, 4]]) with self.assertRaises(RuntimeError): x = block._output(a)[0] # pseudo-inverse block = Inverse(pinv=True) x = block._output(a)[0] # axa = a nt.assert_array_almost_equal(a @ x @ a - a, np.zeros((2,2))) def test_transpose(self): block = Transpose() x = np.array([[1, 2], [3, 4]]) nt.assert_array_almost_equal(block._output(x)[0], x.T) def test_det(self): block = Det() x = np.array([[1, 2], [3, 4]]) nt.assert_array_almost_equal(block._output(x)[0], -2) def test_cond(self): block = Cond() x = np.array([[2, 1], [7, 4]]) nt.assert_array_almost_equal(block._output(x)[0], np.linalg.cond(x)) def test_norm(self): block = Norm() x = np.array([3, 4]) nt.assert_array_almost_equal(block._output(x)[0], 5) def test_slice1(self): x = np.arange(10) + 100 block = Slice1([2]) nt.assert_array_almost_equal(block._output(x)[0], 102) block = Slice1([4, 5, 6]) nt.assert_array_almost_equal(block._output(x)[0], [104, 105, 106]) block = Slice1((4, 7, 1)) nt.assert_array_almost_equal(block._output(x)[0], [104, 105, 106]) block = Slice1((4, 7, None)) nt.assert_array_almost_equal(block._output(x)[0], [104, 105, 106]) block = Slice1((6, 3, -1)) nt.assert_array_almost_equal(block._output(x)[0], [106, 105, 104]) with self.assertRaises(RuntimeError): x = block._output(np.array([[1,2], [3,4]]))[0] with self.assertRaises(ValueError): block = Slice1(index=(1,2)) def test_slice2(self): x = np.arange(20).reshape((4, 5)) + 100 block = Slice2(rows=[2], cols=None) nt.assert_array_almost_equal(block._output(x)[0], np.c_[np.arange(110, 115)].T) block = Slice2(rows=[2, 1], cols=None) nt.assert_array_almost_equal(block._output(x)[0], np.vstack((np.arange(110, 115), np.arange(105, 110))) ) block = Slice2(rows=(2,3,None), cols=None) nt.assert_array_almost_equal(block._output(x)[0], np.c_[np.arange(110, 115)].T) block = Slice2(rows=(2, 0, -1), cols=None) nt.assert_array_almost_equal(block._output(x)[0], np.vstack((np.arange(110, 115), np.arange(105, 110))) ) block = Slice2(cols=[2], rows=None) nt.assert_array_almost_equal(block._output(x)[0], np.c_[np.arange(102, 120, 5)]) block = Slice2(cols=[2, 1], rows=None) nt.assert_array_almost_equal(block._output(x)[0], np.column_stack((np.arange(102, 120, 5), np.arange(101, 120, 5))) ) block = Slice2(cols=(2, 3, None), rows=None) nt.assert_array_almost_equal(block._output(x)[0], np.c_[np.arange(102, 120, 5)]) block = Slice2(cols=(2, 0, -1), rows=None) nt.assert_array_almost_equal(block._output(x)[0], np.column_stack((np.arange(102, 120, 5), np.arange(101, 120, 5))) ) with self.assertRaises(RuntimeError): x = block._output(np.r_[1,2,3])[0] with self.assertRaises(ValueError): block = Slice2(rows=(1,2)) with self.assertRaises(ValueError): block = Slice2(cols=(1,2)) def test_flatten(self): x = np.arange(20).reshape((4, 5)) + 100 block = Flatten('F') nt.assert_array_almost_equal(block._output(x)[0], x.flatten('F')) block = Flatten('C') nt.assert_array_almost_equal(block._output(x)[0], x.flatten('C')) # ---------------------------------------------------------------------------------------# if __name__ == '__main__': unittest.main() ================================================ FILE: tests/test_sinks.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Define fundamental blocks available for use in block diagrams. Each class _MyClass in this module becomes a method MYCLASS() of the Simulation object. This is done in Simulation.__init__() All arguments to MYCLASS() must be named arguments and passed through to the constructor _MyClass.__init__(). These classses must subclass one of - Source, output is a constant or function of time - Sink, input only - Transfer, output is a function of state self.x (no pass through) - Function, output is a direct function of input These classes all subclass Block. Every class defined here provides several methods: - __init__, mandatory to handle block specific parameter arguments - reset, - output, to compute the output value as a function of self.inputs which is a dict indexed by input number - deriv, for Transfer subclass only, return the state derivative vector - check, to validate parameter settings Created on Thu May 21 06:39:29 2020 @author: Peter Corke """ import numpy as np import math import matplotlib.pyplot as plt from matplotlib.pyplot import Polygon import unittest import numpy.testing as nt from bdsim.blocks.sinks import * class SinkBlockTest(unittest.TestCase): def test_print(self): class State: pass # print to a string so we can check result import io f = io.StringIO() b = Print(name="print block", file=f) b.T_step(1.23, t=1.0) self.assertEqual(f.getvalue(), "PRINT(print block (t=1.000) 1.23\n") # test print of object class testObject: def strline(self): return f"testObject={self.value:d}" to = testObject() to.value = 123 # rewind the string buffer f.truncate(0) f.seek(0, 0) b.T_step(to, t=1.0) self.assertEqual(f.getvalue(), "PRINT(print block (t=1.000) testObject=123\n") ## test with format string f = io.StringIO() b = Print(name="print block", file=f, fmt="{:.1f}") b.T_step(1.23456, t=1.0) self.assertEqual(f.getvalue(), "PRINT(print block (t=1.000) 1.2\n") # rewind the string buffer f.truncate(0) f.seek(0, 0) b.T_step(np.r_[1.23456, 4.5679], t=1.0) self.assertEqual(f.getvalue(), "PRINT(print block (t=1.000) [1.2 4.6]\n") # rewind the string buffer f.truncate(0) f.seek(0, 0) b.T_step("a string", t=1.0) self.assertEqual(f.getvalue(), "PRINT(print block (t=1.000) a string\n") def test_stop(self): class State: def __init__(self): self.stop = None s = State() b = Stop(lambda x: x > 5) b.start(s) b.T_step(0) self.assertIsNone(s.stop) b.T_step(10) self.assertTrue(s.stop) self.assertIs(s.stop, b) b = Stop() s.stop = None b.start(s) b.T_step(0) self.assertIsNone(s.stop) b.T_step(1) self.assertTrue(s.stop) self.assertIs(s.stop, b) s.stop = None b.T_step(False) self.assertIsNone(s.stop) b.T_step(True) self.assertTrue(s.stop) self.assertIs(s.stop, b) with self.assertRaises(TypeError): b = Stop(func=3) def test_watch(self): from bdsim import BDSim sim = BDSim() # create simulator bd = sim.blockdiagram() b1 = bd.CONSTANT(2) b2 = bd.NULL() b3 = bd.WATCH() bd.connect(b1, b2, b3) bd.compile() # bd.start() # state is not yet setup # bd.state.watchlist # --------------------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_sources.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Define fundamental blocks available for use in block diagrams. Each class _MyClass in this module becomes a method MYCLASS() of the Simulation object. This is done in Simulation.__init__() All arguments to MYCLASS() must be named arguments and passed through to the constructor _MyClass.__init__(). These classses must subclass one of - Source, output is a constant or function of time - Sink, input only - Transfer, output is a function of state self.x (no pass through) - Function, output is a direct function of input These classes all subclass Block. Every class defined here provides several methods: - __init__, mandatory to handle block specific parameter arguments - reset, - output, to compute the output value as a function of self.inputs which is a dict indexed by input number - deriv, for Transfer subclass only, return the state derivative vector - check, to validate parameter settings Created on Thu May 21 06:39:29 2020 @author: Peter Corke """ import numpy as np import math from bdsim.blocks.sources import * import unittest import numpy.testing as nt class SourceBlockTest(unittest.TestCase): def test_constant(self): block = Constant(value=7) out = block.T_output() self.assertIsInstance(out, list) self.assertEqual(len(out), 1) self.assertEqual(out[0], 7) block = Constant(value=np.r_[1, 2, 3]) out = block.T_output() self.assertIsInstance(out, list) self.assertEqual(len(out), 1) nt.assert_array_almost_equal(out[0], np.r_[1, 2, 3]) def test_waveform_sine(self): block = WaveForm(wave="sine") self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=0.25)[0], 1) self.assertAlmostEqual(block.T_output(t=0.75)[0], -1) block = WaveForm(wave="sine", amplitude=2) self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=0.25)[0], 2) self.assertAlmostEqual(block.T_output(t=0.75)[0], -2) block = WaveForm(wave="sine", offset=1) self.assertAlmostEqual(block.T_output(t=0)[0], 1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 2) self.assertAlmostEqual(block.T_output(t=0.75)[0], 0) block = WaveForm(wave="sine", amplitude=2, offset=1) self.assertAlmostEqual(block.T_output(t=0)[0], 1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 3) self.assertAlmostEqual(block.T_output(t=0.75)[0], -1) block = WaveForm(wave="sine", min=10, max=12) self.assertAlmostEqual(block.T_output(t=0)[0], 11) self.assertAlmostEqual(block.T_output(t=0.25)[0], 12) self.assertAlmostEqual(block.T_output(t=0.75)[0], 10) block = WaveForm(wave="sine", phase=0.25) self.assertAlmostEqual(block.T_output(t=0)[0], -1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 0) self.assertAlmostEqual(block.T_output(t=0.75)[0], 0) block = WaveForm(wave="sine", unit="rad/s") self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=math.pi / 2)[0], 1) self.assertAlmostEqual(block.T_output(t=3 / 2 * math.pi)[0], -1) def test_waveform_triangle(self): block = WaveForm(wave="triangle") self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=0.25)[0], 1) self.assertAlmostEqual(block.T_output(t=0.75)[0], -1) block = WaveForm(wave="triangle", amplitude=2) self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=0.25)[0], 2) self.assertAlmostEqual(block.T_output(t=0.75)[0], -2) block = WaveForm(wave="triangle", offset=1) self.assertAlmostEqual(block.T_output(t=0)[0], 1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 2) self.assertAlmostEqual(block.T_output(t=0.75)[0], 0) block = WaveForm(wave="triangle", amplitude=2, offset=1) self.assertAlmostEqual(block.T_output(t=0)[0], 1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 3) self.assertAlmostEqual(block.T_output(t=0.75)[0], -1) block = WaveForm(wave="triangle", min=10, max=12) self.assertAlmostEqual(block.T_output(t=0)[0], 11) self.assertAlmostEqual(block.T_output(t=0.25)[0], 12) self.assertAlmostEqual(block.T_output(t=0.75)[0], 10) block = WaveForm(wave="triangle", phase=0.25) self.assertAlmostEqual(block.T_output(t=0)[0], -1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 0) self.assertAlmostEqual(block.T_output(t=0.75)[0], 0) block = WaveForm(wave="triangle", unit="rad/s") self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=math.pi / 2)[0], 1) self.assertAlmostEqual(block.T_output(t=3 / 2 * math.pi)[0], -1) def test_waveform_square(self): block = WaveForm(wave="square") self.assertAlmostEqual(block.T_output(t=0)[0], 1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 1) self.assertAlmostEqual(block.T_output(t=0.75)[0], -1) block = WaveForm(wave="square", amplitude=2) self.assertAlmostEqual(block.T_output(t=0)[0], 2) self.assertAlmostEqual(block.T_output(t=0.25)[0], 2) self.assertAlmostEqual(block.T_output(t=0.75)[0], -2) block = WaveForm(wave="square", offset=1) self.assertAlmostEqual(block.T_output(t=0)[0], 2) self.assertAlmostEqual(block.T_output(t=0.25)[0], 2) self.assertAlmostEqual(block.T_output(t=0.75)[0], 0) block = WaveForm(wave="square", amplitude=2, offset=1) self.assertAlmostEqual(block.T_output(t=0)[0], 3) self.assertAlmostEqual(block.T_output(t=0.25)[0], 3) self.assertAlmostEqual(block.T_output(t=0.75)[0], -1) block = WaveForm(wave="square", min=10, max=12) self.assertAlmostEqual(block.T_output(t=0)[0], 12) self.assertAlmostEqual(block.T_output(t=0.25)[0], 12) self.assertAlmostEqual(block.T_output(t=0.75)[0], 10) block = WaveForm(wave="square", phase=0.25) self.assertAlmostEqual(block.T_output(t=0)[0], -1) self.assertAlmostEqual(block.T_output(t=0.25)[0], 1) self.assertAlmostEqual(block.T_output(t=0.75)[0], -1) block = WaveForm(wave="square", unit="rad/s") self.assertAlmostEqual(block.T_output(t=0)[0], 1) self.assertAlmostEqual(block.T_output(t=math.pi / 2)[0], 1) self.assertAlmostEqual(block.T_output(t=3 / 2 * math.pi)[0], -1) def test_step(self): block = Step() self.assertEqual(block.T_output(t=0)[0], 0) self.assertEqual(block.T_output(t=0.9)[0], 0) self.assertEqual(block.T_output(t=1)[0], 1) block = Step(off=1, on=2) self.assertEqual(block.T_output(t=0.9)[0], 1) self.assertEqual(block.T_output(t=1)[0], 2) block = Step(T=2) self.assertEqual(block.T_output(t=1.9)[0], 0) self.assertEqual(block.T_output(t=2.1)[0], 1) def test_piecewise(self): block = Piecewise((0, 0), (1, 1), (2, 1), (2, 0), (10, 0)) out = block.T_output(t=0) self.assertIsInstance(out, list) self.assertEqual(len(out), 1) self.assertEqual(out[0], 0) self.assertEqual(block.T_output(t=0.5)[0], 0) self.assertEqual(block.T_output(t=1)[0], 1) self.assertEqual(block.T_output(t=1.1)[0], 1) self.assertEqual(block.T_output(t=1.9)[0], 1) self.assertEqual(block.T_output(t=2)[0], 0) self.assertEqual(block.T_output(t=2.1)[0], 0) self.assertEqual(block.T_output(t=9)[0], 0) def test_ramp(self): block = Ramp() self.assertAlmostEqual(block.T_output(t=0)[0], 0) self.assertAlmostEqual(block.T_output(t=1)[0], 0) self.assertAlmostEqual(block.T_output(t=2)[0], 1) block = Ramp(off=-1, T=2, slope=0.5) self.assertAlmostEqual(block.T_output(t=0)[0], -1) self.assertAlmostEqual(block.T_output(t=2)[0], -1) self.assertAlmostEqual(block.T_output(t=4)[0], 0) self.assertAlmostEqual(block.T_output(t=6)[0], 1) # ---------------------------------------------------------------------------------------# if __name__ == "__main__": unittest.main() ================================================ FILE: tests/test_spatial.py ================================================ #!/usr/bin/env python3 import numpy as np from spatialmath import * from bdsim.blocks.spatial import * import unittest import numpy.testing as nt class SpatialMathBlockTest(unittest.TestCase): def test_pose_postmul(self): A = SE3.Trans(1,2,3) * SE3.Rx(np.pi/2) block = Pose_postmul(A) B = SE3.Rz(np.pi/2) nt.assert_array_almost_equal(block._output(B)[0], B*A) def test_pose_premul(self): A = SE3.Trans(1,2,3) * SE3.Rx(np.pi/2) block = Pose_premul(A) B = SE3.Rz(np.pi/2) nt.assert_array_almost_equal(block._output(B)[0], A*B) def test_pose_inverse(self): block = Pose_inverse() A = SE3.Trans(1,2,3) * SE3.Rx(np.pi/2) nt.assert_array_almost_equal(block._output(A)[0], A.inv()) def test_vector_transform(self): A = SE3.Trans(1,2,3) * SE3.Rx(np.pi/2) block = Transform_vector() B = np.r_[1,2,3] nt.assert_array_almost_equal(block._output(A,B)[0], A*B) # ---------------------------------------------------------------------------------------# if __name__ == '__main__': unittest.main() ================================================ FILE: tests/test_transfers.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Define fundamental blocks available for use in block diagrams. Each class _MyClass in this module becomes a method MYCLASS() of the Simulation object. This is done in Simulation.__init__() All arguments to MYCLASS() must be named arguments and passed through to the constructor _MyClass.__init__(). These classses must subclass one of - Source, output is a constant or function of time - Sink, input only - Transfer, output is a function of state self.x (no pass through) - Function, output is a direct function of input These classes all subclass Block. Every class defined here provides several methods: - __init__, mandatory to handle block specific parameter arguments - reset, - output, to compute the output value as a function of self.inputs which is a dict indexed by input number - deriv, for Transfer subclass only, return the state derivative vector - check, to validate parameter settings Created on Thu May 21 06:39:29 2020 @author: Peter Corke """ import numpy as np import math import matplotlib.pyplot as plt from bdsim.blocks.transfers import * import unittest import numpy.testing as nt class TransferTest(unittest.TestCase): def test_LTI_SS(self): A=np.array([[1, 2], [3, 4]]) B=np.array([5, 6]) C=np.array([7, 8]) block = LTI_SS(A=A, B=B, C=C, x0=[30,40]) x = np.r_[10, 11] u = -2 nt.assert_equal(block.T_deriv(u, x=x), A@x + B*u) nt.assert_equal(block.T_output(u, x=x)[0], C@x) nt.assert_equal(block.getstate0(), np.r_[30, 40]) A=np.array([[1, 2], [3, 4]]) B=np.array([[5], [6]]) C=np.array([[7, 8]]) block = LTI_SS(A=A, B=B, C=C, x0=[30,40]) x = np.r_[10, 11] u = -2 nt.assert_equal(block.T_deriv(u, x=x), A@x + B@np.r_[u]) nt.assert_equal(block.T_output(u, x=x)[0], C@x) nt.assert_equal(block.getstate0(), np.r_[30, 40]) def test_LTI_SISO(self): block = LTI_SISO( [2, 1], [2, 4, 6]) nt.assert_equal(block.A, np.array([[-2, -3], [1, 0]])) nt.assert_equal(block.B, np.array([[1], [0]])) nt.assert_equal(block.C, np.array([[1, 0.5]])) def test_integrator(self): block = Integrator(x0=30) self.assertEqual(block.nstates, 1) self.assertEqual(block.ndstates, 0) x = np.r_[10] u = -2 nt.assert_equal(block.T_deriv(u, x=x), u) nt.assert_equal(block.getstate0(), np.r_[30]) block = Integrator(x0=5, min=-10, max=10) # state is scalar x = np.r_[11] u = 2 nt.assert_equal(block.T_deriv(u, x=x), 0) x = np.r_[-11] u = -2 nt.assert_equal(block.T_deriv(u, x=x), 0) def test_dintegrator_vec(self): block = Integrator(x0=[5, 6]) # state is vector self.assertEqual(block.nstates, 2) self.assertEqual(block.ndstates, 0) nt.assert_equal(block.getstate0(), np.r_[5, 6]) x = np.r_[10, 11] u = np.r_[-2, 3] nt.assert_equal(block.T_output(u, x=x)[0], x) nt.assert_equal(block.T_deriv(u, x=x), u) # test with limits block = Integrator(x0=[5, 6], min=[-5, -10], max=[5, 10]) # state is vector x = np.r_[-6, -11] u = np.r_[-2, -3] nt.assert_equal(block.T_deriv(u, x=x), [0, 0]) x = np.r_[6, 11] u = np.r_[2, 3] nt.assert_equal(block.T_deriv(u, x=x), [0, 0]) # ---------------------------------------------------------------------------------------# if __name__ == '__main__': unittest.main()