diff --git a/.ci-support/upload_label.py b/.ci-support/upload_label.py deleted file mode 100644 index d5df751d..00000000 --- a/.ci-support/upload_label.py +++ /dev/null @@ -1,25 +0,0 @@ -import os - -travis_tag = os.getenv('TRAVIS_TAG') -appveyor_tag = os.getenv('APPVEYOR_REPO_TAG_NAME') - - -def generate_label(version): - dev_names = ['dev', 'alpha', 'beta', 'rc'] - - is_dev = any(name in version for name in dev_names) - - # Output is the name of the label to which the package should be - # uploaded on anaconda.org - if is_dev: - print('pre-release') - else: - print('main') - - -if travis_tag: - generate_label(travis_tag) -elif appveyor_tag: - generate_label(appveyor_tag) -else: - print('main') diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b1fa2b92 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +# Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..4af28bc2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,116 @@ +name: Test/build + +on: + workflow_dispatch: # Allows manual triggering + push: # only build on pusheess to the main branch + branches: + - master + pull_request: + +# Stop current actions if there is a new push to the same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + + strategy: + max-parallel: 4 + fail-fast: false + matrix: + python-version: [3.11, 3.12] + platform: [ubuntu-latest, macos-latest, windows-latest] + # The include below adds jobs on older versions of python, + # but just on one platform. Windows is probably the most widely + # used for vpython, so test on that. + include: + - python-version: "3.8" + platform: windows-latest + - python-version: "3.9" + platform: windows-latest + - python-version: "3.10" + platform: windows-latest + runs-on: ${{ matrix.platform }} + + steps: + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache location + id: pip-cache + run: | + python -c "from pip._internal.locations import USER_CACHE_DIR; print('::set-output name=dir::' + USER_CACHE_DIR)" + + - uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest Cython wheel build + + - name: Build sdist and wheel + run: | + python -m build + + - name: Install vpython + run: | + pip install . + + - name: Run tests + run: | + pytest vpython + + - name: Import test + run: | + python -c "import vpython" + + build_aarch64: + + strategy: + fail-fast: false + matrix: + python-version: [cp38-cp38, cp39-cp39, cp310-cp310, cp311-cp311, cp312-cp312] + runs-on: ubuntu-latest + env: + py: /opt/python/${{ matrix.python-version }}/bin/python + img: quay.io/pypa/manylinux2014_aarch64 + + steps: + + - uses: actions/checkout@v4 + + - name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v3 + + - name: Build and Test + run: | + docker run --rm -v ${{ github.workspace }}:/ws:rw --workdir=/ws \ + ${{ env.img }} \ + bash -exc '${{ env.py }} -m venv .env && \ + source .env/bin/activate && \ + echo -e "\e[1;34m Install dependencies \e[0m" && \ + python -m pip install --upgrade pip && \ + pip install pytest Cython wheel build && \ + echo -e "\e[1;34m Build wheel \e[0m" && \ + python -m build && \ + echo -e "\e[1;34m Install vpython \e[0m" && \ + pip install . && \ + echo -e "\e[1;34m Run tests \e[0m" && \ + pytest vpython && \ + echo -e "\e[1;34m Import test \e[0m" && \ + python -c "import vpython" && \ + deactivate' diff --git a/.github/workflows/copy_conda_packages.yml b/.github/workflows/copy_conda_packages.yml new file mode 100644 index 00000000..f0be1a60 --- /dev/null +++ b/.github/workflows/copy_conda_packages.yml @@ -0,0 +1,33 @@ +on: + workflow_dispatch: # Allows manual triggering + schedule: + # Run once a day at 22:40 UTC + - cron: '55 22 * * *' + +jobs: + copy_packages: + runs-on: ubuntu-latest + steps: + + - name: checkout + uses: actions/checkout@v4 + + - name: Install conda + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + python-version: 3.11 + + - name: Install dependencies + shell: bash -l {0} + run: | + conda install anaconda-client + + - name: Perform copy + env: + BINSTAR_TOKEN: ${{ secrets.BINSTAR_TOKEN }} + shell: bash -l {0} + run: | + git clone https://github.com/glue-viz/conda-sync.git + mv conda-sync/sync.py . + python drive_copy.py diff --git a/.github/workflows/upload_pypi.yml b/.github/workflows/upload_pypi.yml new file mode 100644 index 00000000..debbea27 --- /dev/null +++ b/.github/workflows/upload_pypi.yml @@ -0,0 +1,120 @@ +name: Upload Python wheel to PyPI + +on: + release: + types: [created] + +jobs: + wheels: + + strategy: + max-parallel: 4 + fail-fast: false + matrix: + python-version: [3.8, 3.9, '3.10', 3.11, 3.12] + platform: [macos-latest, windows-latest] # Wheels on linux below + runs-on: ${{ matrix.platform }} + + steps: + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - 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 setuptools wheel twine Cython build + + - name: Build and publish wheel + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_TOKEN }} + run: | + python -m build + twine upload dist/*.whl + + linux_wheels: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Stable with rustfmt and clippy + uses: actions-rs/toolchain@v1 + with: + profile: default + toolchain: stable + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install twine + - name: Python wheels manylinux build + uses: RalfG/python-wheels-manylinux-build@v0.7.1 + with: + python-versions: 'cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312' + build-requirements: 'setuptools cython setuptools_scm' + - name: Build and publish wheel + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_TOKEN }} + run: | + twine upload dist/vpython-*-manylinux*.whl + + linux_aarch64_wheels: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v3 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install twine + - name: Python wheels manylinux build + uses: RalfG/python-wheels-manylinux-build@v0.4.2-manylinux2014_aarch64 + with: + python-versions: 'cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311 cp312-cp312' + build-requirements: 'setuptools cython setuptools_scm' + - name: Build and publish wheel + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_TOKEN }} + run: | + twine upload dist/vpython-*-manylinux*.whl + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine Cython build + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_TOKEN }} + run: | + python -m build + twine upload dist/*.tar.gz diff --git a/.gitignore b/.gitignore index 000db7c1..7dfb4c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ var/ !vpython/lib +# Notebook stuff +.ipynb_checkpoints + # 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. @@ -47,6 +50,7 @@ htmlcov/ nosetests.xml coverage.xml *,cover +.pytest_cache/ # Translations *.mo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..2d9a50eb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "labextension/jupyterlab-vpython"] + path = labextension/jupyterlab-vpython + url = https://github.com/jcoady/jupyterlab_vpython.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 00cf42b4..00000000 --- a/.travis.yml +++ /dev/null @@ -1,114 +0,0 @@ -# The language in this case has no bearing - we are going to be making use of "conda" for a -# python distribution for the scientific python stack. -os: - - linux - - osx - -env: - global: - - TARGET_ARCH="x64" - # BINSTAR_TOKEN - - secure: "GUZgrWEs3UL+lD8L59QuWgj07nK6qsTbW6Y7g9ryWiYeohJ0BmovCf+904LodRRDUWTuTx9SSpISvDU28Dj5I0rPLKDiqyrSrEFF0sm/MttNlrfcRso+lmdBStbtlqki9TrWo6qdTBKkmHYgiWGJEewDBuRZxxtcJBkU7NdtsGBr+srtk1fwRR1dSKf00XGaCQzrgjFuzkHv8NB6DMZQvt+vMolmQlw2wRtGrKFb465OKw6aVXDOWtRo5VvSTz9bLT6F1VYjmQfOfn/wa5Svb59m+55EXRQX0dp5Rarv9GfErQ6TBCW0vMEYjFC7puY9esE9zwT25xjxkghhYi4l+8PWNMl1jW33e26G5RiOyj0kiozHCtrVS7M6GnYvuVGTgHysZtacl572NOLvX06VKJxQJoihzlMC8ILb9LljoK6/buEjyaUY1B2dGZ5pCeksZNsBidMfk8kTQp460ZHbeayh0VzhO65L1MoJuxU2IqLHjuNobn86PI1BIu1jqOw+d9og5Kd/X5v3cZiUGr8O3u/RPLKSZ7gF5kLtmvYocOZ7nHihOBXlg3gvVPCs1R57+64SVsdTHVhiUHW8JbcP+G3q2vyVcCYSb5LvwQK7aj7pS4H+6dc9nRd2MWtlW5a13d5sKnnjT+EC5cse+0zx/zYuGG+9z6zCnRrGv6tS63w=" - # TWINE_PASSWORD - - secure: "nKdr6YBw/J3YRFKL6ZIpFbTMo4cMbaRAND19lP6JSYbhsVcirSqZ/LpxiTPmueFyFDmFrR1LBm3rpUGA6k/Is/HRXSsjFqXfA+5VlxglNvTIUnhRVf9IvZmLVIQdqN/IkWMgYKlkPfyZ9oct9K3CihU6lmGyW/aBf7S7DIV58MJn0HyYV+uMystTD1lEZ3UObhEN35xxmegC6sQe/YAeg656XrXZEjpPijmHXvm8NxmhEolmaZeJ0k1ZN88X8KD/V5d4NiCViiq5oowt0bSVSaGWQMRrrOhJ88m92GxuRyuVT8k1QFT8gkRsi6j3+3LV+MvCLDBzaE0x5/5/ZP6JdxkU4VLTsBceNF4/OQGVA0cCl/ZpV0ZIvrQr70DXmuqKJvW3S7Rkph2hmJBwaKpfYQ9wTI0lMZQb+FYKhV691YNAQYdvRgbCGVUnwRA+mKEWh58wNjjomEcza9eqQUSziaO93e36gwMCdl6ESSBAdWw0Z23CVz37MkJXmoMPpvKghVtvImUFaN0/yHxn21nSP/UK4dhoNbZKX78Ys25pZUk9w8UqCP8cDZlDeqoJ4xLr4GVr5i4stvOmlT8lTec8iwOlcs5S3QxQ5GiVSuC0IhLuppN4/frpD8yrr/bdDgTENjMJWSjIQ5m5JhQKMEU3ZZonXjNANr/IUz8StPyVqU0=" - - CONDA_INSTALL_LOCN="${HOME}/miniconda" - - TWINE_USERNAME="mwcraig" - matrix: - - CONDA_PY=3.5 - - CONDA_PY=3.6 - - CONDA_PY=3.7 - -install: - # Install and set up miniconda. - - if [ $TRAVIS_OS_NAME == "linux" ]; then wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; fi - - if [ $TRAVIS_OS_NAME == "osx" ]; then wget http://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh; fi - - bash miniconda.sh -b -p $CONDA_INSTALL_LOCN - - export PATH=${CONDA_INSTALL_LOCN}/bin:$PATH - - conda config --set always_yes true - - # Gets us vpnotebook - - conda config --add channels vpython - - - conda update --quiet conda - - # Install a couple of dependencies we need for sure. - # Not sure why cython is necessary since it is listed as a build dependency. - - conda install --quiet jinja2 conda-build anaconda-client cython twine pytest - - # make a wheel building environment to ensure correct python version. - - conda create --quiet -n wheel-build python=$CONDA_PY wheel cython - - # For debugging.... - - conda info -a - -script: - # Define a variable that will be YES for exactly one os/python - # combination and use it to decide whether to upload an sdist. - # This should prevent spurious failures at the deploy stage. - - export UPLOAD_SDIST="NO" - - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - if [ "$CONDA_PY" == "3.6" ]; then - export UPLOAD_SDIST="YES"; - fi - fi - - echo $UPLOAD_SDIST - # Run tests - - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - python setup.py install; - py.test vpython; - fi - # Not much of a real test yet, just try to build myself... - - conda build --quiet vpython.recipe - - export CONDA_PACKAGE=`conda build --output vpython.recipe | grep bz2` - - export OUTPUT_DIR=`dirname $CONDA_PACKAGE` - - # Uncomment the next line when bdist_wheel option in recipe starts - # paying attention to CONDA_PY. See: - # https://github.com/conda/conda-build/issues/1832 - # - export WHEEL_PACKAGE="$OUTPUT_DIR/*.whl" - - source activate wheel-build; python setup.py bdist_wheel; source deactivate - - export WHEEL_PACKAGE=dist/*.whl - - echo $CONDA_PACKAGE - - echo $WHEEL_PACKAGE - - # This will fail if the package does not actually exist - - ls $CONDA_PACKAGE $WHEEL_PACKAGE - - if [ $TRAVIS_EVENT_TYPE == "cron" ]; then - conda install -c astropy -c conda-forge extruder twine; - copy_packages vp_copy.yaml vpython; - fi - - -after_success: - - echo $TRAVIS_TAG - - git branch --contains $TRAVIS_TAG - - upload_label=$(python .ci-support/upload_label.py) - - if [[ "$TRAVIS_TAG" ]]; then upload_build="true"; else upload_build=; fi - - echo $upload_build - - echo "Uploading to $upload_label" - # If this build is because of a tag, upload the build if it succeeds. - - if [ -n "$upload_build" ]; then anaconda -t $BINSTAR_TOKEN upload -u vpython -l $upload_label $CONDA_PACKAGE; fi - # Upload wheel builds to pypi instead of relying on travis deploy because that - # does not seem to continue gracefully if a distribution already exists, - # and in most of the jobs a source distribution will already have been - # uploaded to PyPI. - # - # sdist is still uploaded with travis deploy. - # - # The twine password has been set in $TWINE_PASSWORD. - - if [ -n "$upload_build" ]; then twine upload $WHEEL_PACKAGE; fi - # In any event, remove the built wheel because having it around appears to - # confuse the deploy step, which is still trying to upload a wheel. - - rm -f $WHEEL_PACKAGE -deploy: - provider: pypi - user: mwcraig - password: - secure: t4SUebn2FYy4FK6kLNt//srRvzz7bYhHXT7fMu98svUjwT92InT3TIvpE+6AXvtZeRJL3CnUD9yO6c6xJXZ5Lj4f++P+GA9qz+ji4qBxOHO6qip9eDghwFzSx65pgc5vRMSJnL6h/iFPdfkpG76WNp3OA0vlaRLQw7GSBbV5+ZSpbhIHAPVMoTzDvcmBYe5eszf/Meig6uOdf4yh7Cik0sw/OztXlmq9gRgwVI1CwWkw7uK388yWujul5zpKFvyVBuycrfooGpibtfvZNu6xpVpWnqVT1jnNA9mj03A5wlk7RwmcRabWTa3x2FHJVtnUmDEgMaali4N95Fw3WJi8L6hdNDkkkamfkPEogka7buNpSrkBXW9K722WmxQ6KYA5It2dUO/KkWIaMoIumIuJyWEA6UHih1vxDZyWqCZakqyloE8g9wZGrUPqtpqEFwY4E6f1LesbgWHIrtbm6LOWwltYma0TfTLpAYRCCMTJMAXi3z1udjtrOCt7NEpsJaj17H5wh8nh1XwEpLS9dNcWqtRqqSF5YRHmnFo2TeefIzGH5VDp2PG94bOFTJhKKU224rEHXIo1vb4HyL0Gye7ilyZij5kdSEX6LiJbsmOpPp1e6lc83MIM1x9Cti1inIW4ZH0Y9QhwMd9SnAEr9b8vgOk5ozIi8hZmVtbA8bJhjoc= - upload_docs: no - skip_cleanup: true - on: - tags: true - distributions: "sdist" - repo: BruceSherwood/vpython-jupyter - condition: $UPLOAD_SDIST = "YES" diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 88bc6dfb..f3056671 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,28 +4,40 @@ ## Core contributors -+ Bruce Sherwood (@BruceSherwood) -+ Ruth Chabay (@ruthchabay) -+ John Coady (@jcoady) -+ Matt Craig (@mwcraig) -+ Steve Spicklemire (@sspickle) ++ Bruce Sherwood ([@BruceSherwood](https://github.com/BruceSherwood)) ++ Ruth Chabay ([@ruthchabay](https://github.com/ruthchabay)) ++ John Coady ([@jcoady](https://github.com/jcoady)) ++ Matt Craig ([@mwcraig](https://github.com/mwcraig)) ++ Steve Spicklemire ([@sspickle](https://github.com/sspickle)) ## Additional contributors The people below have contributed to the project by identifying and opening issues, offering improvements to the documentation or contributing code. -We are certain the list is incomplete; please let one of us know by opening an [issue on GitHub](https://github.com/BruceSherwood/vpython-jupyter/issues) and we will be happy to add your name! - -+ Wayne Decatur (@fomightez) -+ Antti Kaihola (@akaihola) -+ Patrick Melanson (@pmelanson) -+ Gopal Sharma (@Hippogriff) -+ Craig C. Wiegert (@wigie) -+ @jonschull -+ @KHALAK -+ @qazwsxedcrfvtg14 -+ @russkel +We are certain the list is incomplete; please let one of us know by opening an [issue on GitHub](https://github.com/vpython/vpython-jupyter/issues) and we will be happy to add your name! + ++ Wayne Decatur ([@fomightez](https://github.com/fomightez)) ++ Tomokazu Higuchi ([@higucheese](https://github.com/higucheese)) ++ Antti Kaihola ([@akaihola](https://github.com/akaihola)) ++ Patrick Melanson ([@pmelanson](https://github.com/pmelanson)) ++ Gopal Sharma ([@Hippogriff](https://github.com/Hippogriff)) ++ Craig C. Wiegert ([@wigie](https://github.com/wigie)) ++ [@jonschull](https://github.com/jonschull) ++ [@KHALAK](https://github.com/KHALAK) ++ [@odidev](https://github.com/odidev) ++ [@qazwsxedcrfvtg14](https://github.com/qazwsxedcrfvtg14) ++ [@russkel](https://github.com/russkel) ++ Kyle Dunn ([@kdunn926](https://github.com/kdunn926)) ++ Brian Su ([@brianbbsu](https://github.com/brianbbsu)) ++ [@0dminnimda](https://github.com/0dminnimda) ++ Mike Miller ([@Axe319](https://github.com/axe319)) ++ Danny Staple ([@dannystaple](https://github.com/dannystaple)) ++ Dan Miller ([@danx0r](https://github.com/danx0r)) ++ Alex Herrera ([@aherrera1721](https://github.com/aherrera1721)) ++ Michał Górny ([@mgorny](https://github.com/mgorny)) ++ Ryder Johnson ([@UZ9](https://github.com/UZ9)) ++ Elias Alstead ([@elias-a](https://github.com/elias-a)) ## Full timeline of vpython development diff --git a/Demos/ButtonsSlidersMenus2.ipynb b/Demos/ButtonsSlidersMenus2.ipynb index 51c8c9b5..c79fbfb1 100644 --- a/Demos/ButtonsSlidersMenus2.ipynb +++ b/Demos/ButtonsSlidersMenus2.ipynb @@ -3,9 +3,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { @@ -22,7 +20,79 @@ { "data": { "application/javascript": [ - "window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")}" + "if (typeof Jupyter !== \"undefined\") { window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")};}else{ element.textContent = ' ';}" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "if (typeof Jupyter !== \"undefined\") {require.undef(\"nbextensions/vpython_libraries/glow.min\");}else{element.textContent = ' ';}" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "if (typeof Jupyter !== \"undefined\") {require.undef(\"nbextensions/vpython_libraries/glowcomm\");}else{element.textContent = ' ';}" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "if (typeof Jupyter !== \"undefined\") {require.undef(\"nbextensions/vpython_libraries/jquery-ui.custom.min\");}else{element.textContent = ' ';}" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "if (typeof Jupyter !== \"undefined\") {require([\"nbextensions/vpython_libraries/glow.min\"], function(){console.log(\"GLOW LOADED\");});}else{element.textContent = ' ';}" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "if (typeof Jupyter !== \"undefined\") {require([\"nbextensions/vpython_libraries/glowcomm\"], function(){console.log(\"GLOWCOMM LOADED\");});}else{element.textContent = ' ';}" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "if (typeof Jupyter !== \"undefined\") {require([\"nbextensions/vpython_libraries/jquery-ui.custom.min\"], function(){console.log(\"JQUERY LOADED\");});}else{element.textContent = ' ';}" ], "text/plain": [ "" @@ -34,9 +104,6 @@ ], "source": [ "from vpython import *\n", - "scene = canvas() # This is needed in Jupyter notebook and lab to make programs easily rerunnable\n", - "# This version uses VPython widgets: button, radio button, checkbox, slider, menu\n", - "# See ButtonsSlidersMenus1 for a version that uses Jupyter notebook widgets: button, slider, menu\n", "scene.width = 350\n", "scene.height = 300\n", "scene.range = 1.3\n", @@ -56,7 +123,6 @@ "cone_object = cone(visible=False, radius=0.5)\n", "pyramid_object = pyramid(visible=False)\n", "cylinder_object = cylinder(visible=False, radius=0.5)\n", - "sphere(radius=0.3)\n", "\n", "col = color.cyan\n", "currentobject = box_object\n", @@ -66,32 +132,49 @@ " global col\n", " if col.equals(color.cyan): # change to red\n", " currentobject.color = col = color.red\n", + " c.text = \"Cyan\"\n", + " c.color = color.cyan\n", + " c.background = color.red\n", + " if c.name is None: # this is the top button\n", + " r1.checked = False\n", + " r2.checked = True\n", + " else: # change to cyan\n", + " currentobject.color = col = color.cyan\n", + " c.text = \"Red\"\n", + " c.color = color.red\n", + " c.background = color.cyan\n", + " if c.name is None: # this is the top button\n", + " r1.checked = True\n", + " r2.checked = False\n", + " \n", + "def cc(c):\n", + " global col\n", + " if col.equals(color.cyan): # change to red:\n", + " currentobject.color = col = color.red\n", " cbutton.text = \"Cyan\"\n", - " cbutton.textcolor = color.cyan\n", + " cbutton.color = color.cyan\n", " cbutton.background = color.red\n", - " r1.checked = False\n", - " r2.checked = True\n", " else: # change to cyan\n", " currentobject.color = col = color.cyan\n", " cbutton.text = \"Red\"\n", - " cbutton.textcolor = color.red\n", + " cbutton.color = color.red\n", " cbutton.background = color.cyan\n", - " r1.checked = True\n", - " r2.checked = False\n", " \n", - "cbutton = button(text='Red', textcolor=color.red, background=color.cyan, pos=scene.title_anchor, bind=Color)\n", + "cbutton = button(text='Red', color=color.red, background=color.cyan, \n", + " pos=scene.title_anchor, bind=Color, name=None)\n", + "\n", + "scene.caption = \"Vary the rotation speed: \\n\\n\"\n", "\n", - "scene.caption = \"Vary the rotation speed: \\n\"\n", - "speed = 150\n", "def setspeed(s):\n", - " global speed\n", - " speed = s.value\n", + " wt.text = '{:1.2f}'.format(s.value)\n", " \n", - "slider(min=20, max=500, value=250, length=350, bind=setspeed)\n", + "sl = slider(min=0.3, max=3, value=1.5, length=220, bind=setspeed, right=15)\n", "\n", - "scene.append_to_caption('\\n')\n", + "wt = wtext(text='{:1.2f}'.format(sl.value))\n", "\n", - "r1 = radio(bind=Color, checked=True, text='Cyan ')\n", + "scene.append_to_caption(' radians/s\\n')\n", + "\n", + "r1 = radio(bind=cc, checked=True, text='Cyan', name='rads')\n", "\n", "scene.append_to_caption(' ')\n", "\n", @@ -118,7 +201,7 @@ "\n", "scene.append_to_caption('\\n')\n", "\n", - "r2 = radio(bind=Color, text='Red ')\n", + "r2 = radio(bind=cc, text='Red', name='rads')\n", "\n", "scene.append_to_caption(' ')\n", "\n", @@ -128,19 +211,23 @@ " else:\n", " currentobject.opacity = 1\n", "\n", - "trans = checkbox(bind=transparency, text='Transparent')\n", + "checkbox(bind=transparency, text='Transparent')\n", "\n", + "dt = 0.01\n", "while True:\n", - " rate(100)\n", + " rate(1/dt)\n", " if running:\n", - " currentobject.rotate(angle=speed*1e-4, axis=vector(0,1,0))\n" + " currentobject.rotate(angle=sl.value*dt, axis=vector(0,1,0))\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [] @@ -163,9 +250,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.6" + "version": "3.9.7" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/Demos/CORS.ipynb b/Demos/CORS.ipynb new file mode 100644 index 00000000..4936084a --- /dev/null +++ b/Demos/CORS.ipynb @@ -0,0 +1 @@ +{"metadata":{"kernelspec":{"display_name":"Python 3 (ipykernel)","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.2","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":5,"nbformat":4,"cells":[{"id":"1ce368bf-81e1-4832-8f46-605e44d885f6","cell_type":"code","source":"from vpython import *\nscene = canvas() # This is needed in Jupyter notebook and lab to make programs easily rerunnable\nscene.range = 1\nscene.forward = vector(-1,-.5,-1)\nbox(texture=\"https://s3.amazonaws.com/glowscript/textures/flower_texture.jpg\")\n\ns = 'This illustrates the use of an image from another web site as a texture.\\n'\ns += 'This is an example of CORS, \"Cross-Origin Resource Sharing\".\\n'\nscene.caption = s\n\nscene.append_to_caption(\"\"\"\nTo rotate \"camera\", drag with right button or Ctrl-drag.\nTo zoom, drag with middle button or Alt/Option depressed, or use scroll wheel.\n On a two-button mouse, middle is left + right.\nTo pan left/right and up/down, Shift-drag.\nTouch screen: pinch/extend to zoom, swipe or two-finger rotate.\"\"\")","metadata":{"trusted":false},"outputs":[{"metadata":{},"output_type":"display_data","data":{"text/html":"
","text/plain":""}},{"metadata":{},"output_type":"display_data","data":{"application/javascript":"if (typeof Jupyter !== \"undefined\") { window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")};}else{ element.textContent = ' ';}","text/plain":""}},{"metadata":{},"output_type":"display_data","data":{"text/plain":"","text/html":"
"}},{"data":{"text/plain":"","application/javascript":"if (typeof Jupyter !== \"undefined\") { window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")};}else{ element.textContent = ' ';}"},"metadata":{},"output_type":"display_data"}],"execution_count":1},{"id":"eeec5bc6-e4c1-4ca8-81af-d8a97c6ab412","cell_type":"code","source":"","metadata":{"trusted":false},"outputs":[],"execution_count":null}]} \ No newline at end of file diff --git a/Demos/Conch.ipynb b/Demos/Conch.ipynb new file mode 100644 index 00000000..16ab5850 --- /dev/null +++ b/Demos/Conch.ipynb @@ -0,0 +1 @@ +{"metadata":{"kernelspec":{"display_name":"Python 3 (ipykernel)","language":"python","name":"python3"},"language_info":{"file_extension":".py","nbconvert_exporter":"python","codemirror_mode":{"name":"ipython","version":3},"name":"python","pygments_lexer":"ipython3","mimetype":"text/x-python","version":"3.12.1"}},"nbformat_minor":5,"nbformat":4,"cells":[{"id":"9c2f0c0c-4898-4122-9c2a-f0f77ea52094","cell_type":"code","source":"from vpython import *\nscene = canvas() # This is needed in Jupyter notebook and lab to make programs easily rerunnable\n# Kadir Haldenbilen, Feb. 2011\n\nscene.height = scene.width = 600\nscene.background = color.gray(0.7)\nscene.range = 3\nscene.ambient = 0.5*color.white\n\nscene.caption = \"\"\"To rotate \"camera\", drag with right button or Ctrl-drag.\nTo zoom, drag with middle button or Alt/Option depressed, or use scroll wheel.\n On a two-button mouse, middle is left + right.\nTo pan left/right and up/down, Shift-drag.\nTouch screen: pinch/extend to zoom, swipe or two-finger rotate.\"\"\"\n\ndef spiral(nloop=1, tightness=1.0, dir=1, scale=1):\n spr = []\n scale = []\n clrs = []\n zd = 0.01\n for t in range(1, 1024*nloop, 16):\n t *= 0.01\n x = tightness/10 * t * cos(t)*dir\n y = tightness/10 * t * sin(t)\n sc = sqrt(x*x+y*y)\n z = t/7\n spr.append(vector(x,y,z))\n clr = vector(z*cos(t), abs(sin(t)), abs(cos(t*2))).norm()\n clrs.append(clr)\n scale.append(sc)\n return spr, scale, clrs\n\npath, scale, clrs = spiral(nloop=2, tightness=0.8)\nelps = shapes.circle(radius=0.69, thickness=0.01)\n\nee = extrusion(shape=elps, path=path, color=clrs, scale=scale, texture=textures.rock)\nee.rotate(angle=pi/2)\nscene.center = ee.pos-vec(0,.5,0)\n\n","metadata":{"trusted":false},"outputs":[{"data":{"text/html":"
","text/plain":""},"metadata":{},"output_type":"display_data"},{"metadata":{},"data":{"application/javascript":"if (typeof Jupyter !== \"undefined\") { window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")};}else{ element.textContent = ' ';}","text/plain":""},"output_type":"display_data"},{"data":{"text/plain":"","text/html":"
"},"metadata":{},"output_type":"display_data"},{"metadata":{},"output_type":"display_data","data":{"text/plain":"","application/javascript":"if (typeof Jupyter !== \"undefined\") { window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")};}else{ element.textContent = ' ';}"}}],"execution_count":1},{"id":"d01c51ea-d6a6-4d7f-aac1-de28cf657c72","cell_type":"code","source":"","metadata":{"trusted":false},"outputs":[],"execution_count":null}]} \ No newline at end of file diff --git a/Demos/ElectricMotor.ipynb b/Demos/ElectricMotor.ipynb new file mode 100644 index 00000000..85dca852 --- /dev/null +++ b/Demos/ElectricMotor.ipynb @@ -0,0 +1 @@ +{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3 (ipykernel)","language":"python"},"language_info":{"name":"python","version":"3.12.2","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":5,"nbformat":4,"cells":[{"id":"b0ff27bb-79db-4eb3-aa8c-4668263e631a","cell_type":"code","source":"from vpython import *\nscene = canvas() # This is needed in Jupyter notebook and lab to make programs easily rerunnable\n# Kadir Haldenbilen, February 2011\n# Converted to Web VPython by Bruce Sherwood, May 2022\n\nscene.width = 1024\nscene.height = 768\nscene.center = vec(-2,0,0)\nscene.range = 7\n\nmotor = [] # elements that rotate\ncl = 2\nns = 24\n\ndef makearc(center=[0,0], radius=1, angle1=2*pi-pi/4, angle2=2*pi+pi/4, ccw=True):\n # Specify angle1 and angle2 as positive numbers, with angle2 > angle1\n if angle2 < angle1:\n raise Exception(\"Both angles must be positive, with angle2 > angle1\") \n n = 20\n dtheta = abs(angle2-angle1)/n\n ret = []\n for i in range(n+1):\n if ccw:\n #print(center, angle1, angle2, radius, ccw)\n ret.append([center[0]+radius*cos(angle1+i*dtheta), center[1]+radius*sin(angle1+i*dtheta)])\n else:\n #print(center, angle1, angle2, radius, ccw)\n ret.append([center[0]+radius*cos(angle2-i*dtheta), center[1]+radius*sin(angle2-i*dtheta)])\n return ret\n\ndef rotatepoints(points=[[1,0],[0,1]], angle=0, origin=vec(0,0,0)):\n s = sphere(visible=False)\n ret = []\n for p in points:\n s.pos = vec(p[0],p[1],0)\n s.rotate(angle=angle, axis=vec(0,0,1), origin=origin)\n ret.append([s.pos.x,s.pos.y])\n return ret \n\n#=====================================================================\n# Create contactor\n\np2 = makearc(center=[0,0], radius=0.5, angle1=0, angle2=pi/20, ccw=False)\np1 = makearc(center=[0,0], radius=1.2, angle1=0, angle2=pi/15, ccw=True)\np = p1+p2+[p1[0]]\nc = extrusion(path=[vec(0,0,0),vec(2,0,0)], shape=p, color=vec(1,0.5,0.3))\ncont = [c]\nfor i in range(1,24):\n con = c.clone()\n con.rotate(angle=i*2*pi/24, axis=vec(-1,0,0), origin=vec(0,0,0))\n cont.append(con)\nmotor.append(compound(cont))\n#==========================================================================\n# Create contactor soldering tips\n\np1 = makearc(center=[0,0], radius=1.4, angle1=2*pi-0.5*pi/24, angle2=2*pi+0.4*pi/24, ccw=True)\np2 = makearc(center=[0,0], radius=0.7, angle1=2*pi-0.5*pi/30, angle2=2*pi+0.4*pi/30, ccw=False)\np = p1+p2+[p1[0]]\nc = extrusion(path=[vec(-0.4,0,0),vec(0,0,0)], shape=p, color=vec(1,0.5,0.3))\ns = sphere(pos=vec(-0.2,0,1.195), radius=0.1, shininess=1)\ncont = [c]\nballs = []\nfor i in range(23):\n con = c.clone()\n con.rotate(angle=(i+1)*2*pi/24, axis=vec(1,0,0), origin=vec(0,0,0))\n cont.append(con)\n con = s.clone()\n con.rotate(angle=(i+1)*2*pi/24, axis=vec(1,0,0), origin=vec(0,0,0))\n balls.append(con)\n \nmotor.append(compound(cont))\nmotor.append(compound(balls))\n#=========================================================================\n# Add shaft insulator material\n# Define a circular ring of thickness=0.05\nsc = shapes.circle(radius=0.53, thickness=0.05)\n# Extrude the ring to get a thin hollow cylinder insulator over the shaft\nsce = extrusion(path=[vec(-7,0,0),vec(2.5,0,0)], shape=sc, color=vec(0.8,0,0),\n emissive=True) # plastic\n\n# The Rotor Shaft, defined by a simple cylinder\nshaft = cylinder(pos=vec(3.5,0,0), axis=vec(-11.25,0,0), radius=0.495, color=color.gray(.6), texture=textures.metal)\n\n# Add a piece of gear at one end of the shaft\n# Use the gear shape to define the shape, note radius, addendum, dedendum sizes\ngr = shapes.gear(n=9, radius=0.455, addendum=0.04, dedendum=0.06, fradius=0.01)\n# Extrude the gear shape appending it to the shaft end\ngre = extrusion(path=[vec(3.5,0,0),vec(5,0,0)], shape=gr, color=color.gray(.6),\n texture=textures.metal)\n\nmotor.append(gre)\n#for o in scene.objects: o.visible = False\n#==========================================================================\n# Define Rotor Core\n# Normally the core should have been built of many very thin sheets\n# For performance reasons, a single block is built\n# First define the outer circle\n\nr0 = 0.495 # radius of shaft\nr1 = 1.3 # radius of disk\nr2 = 2.7 # radius of inner portion of top of the T\nr3 = 3 # radius of outer portion of top of the T\ntheta = pi/12 # angle between Ts\nd = 0.25*theta # half angle width of upright of T\ndlt = 0.05\nthk = 5.04\n\np1 = makearc(center=[0,0], radius=r1, angle1=2*pi-pi/12+d, angle2=2*pi+pi/12-d, ccw=True)\nstart = r1*vec(cos(-pi/12+d), sin(-pi/12+d),0)\n# From middle of bottom of T base to middle of top of T upright:\nmiddledir = vec(cos(pi/12),sin(pi/12), 0)\nperp = middledir.rotate(angle=pi/2, axis=vec(0,0,1))\np1end = vec(p1[-1][0], p1[-1][1],0)\nendpt1 = p1end+(r2-r1)*middledir # top right of T upright\nnextstart = start.rotate(angle=pi/6, axis=vec(0,0,1))\nendpt2 = nextstart + (r2-r1)*middledir\na = atan(endpt1.y/endpt1.x)\np2 = makearc(center=[0,0], radius=r2, angle1=0.3*a, angle2=a, ccw=False)\np1.extend(p2)\np3 = makearc(center=[0,0], radius=r3, angle1=0.3*a, angle2=1.5*a+2*d, ccw=True)\np1.extend(p3)\nb = atan(endpt2.y/endpt2.x)\np4 = makearc(center=[0,0], radius=r2, angle1=b, angle2=1.5*a+2*d, ccw=False)\np1.extend(p4)\n\npc = p1.copy()\nfor i in range(1,12):\n p = rotatepoints(p1, angle=i*pi/6)\n pc.extend(p)\npc.append(pc[0])\n\nex = extrusion(path=[vec(-6,0,0), vec(-6+thk,0,0)], shape=[pc, shapes.circle(radius=r0)] , color=color.gray(0.7))\nmotor.append(ex)\n\n#==========================================================================\n# Do the core wire windings\nx = -3.5\nn = 20\nL = 1.3\nthk = L/50\ncon = []\nfor i in range(n):\n r = r1+i*thk*2.5\n inner = shapes.rectangle(width=5.2, height=.35, roundness=0.4)\n outer = shapes.rectangle(width=5.3+i*0.7/n, height=0.4+i*0.6/n, roundness=0.4)\n p = [vec(x,r*sin(pi/12),r*cos(pi/12)), vec(x,(r+thk)*sin(pi/12),(r+thk)*cos(pi/12))]\n ex = extrusion(path=p, shape=[outer,inner], color=vec(.7,.5,.15))\n con.append(ex)\nex = compound(con)\ncon = [ex]\n\nfor i in range(1,12):\n c = ex.clone()\n c.rotate(angle=i*pi/6, axis=vec(1,0,0), origin=vec(0,0,0))\n con.append(c)\n\nmotor.append(compound(con))\n\n#==========================================================================\n# Connect contactor surfaces to windings with cables\npos = vec(-1.3,1.1,0)\nc0 = cylinder(pos=pos, axis=vec(-0.25,1.2,0.25)-pos, radius=0.05, color=color.blue)\ncon = [c0]\nfor i in range(1,24):\n c = c0.clone()\n c.rotate(angle=i*pi/6, axis=vec(1,0,0), origin=vec(c0.pos.x,0,0))\n con.append(c)\nmotor.append(compound(con))\n\n#==========================================================================\n# Create Brushes\n# From a rectangular cross section, subtract rotor contactor circle, leaving us two\n# brushes on each sides of the contactor, with circular profile\n#br = shapes.rectangle(width=5, height=0.4)\nbr = makearc(center=[0,0], radius=1.21, angle1=2*pi-atan(0.2/1.21), angle2=2*pi+atan(0.2/1.21), ccw=True)\nd = 1+1.21*cos(atan(.2/1.21))\nbr.append([d,0.2])\nbr.append([d,-0.2])\nbr.append(br[0])\nbre = extrusion(path=[vec(0.4,0,0),vec(1.6,0,0)], color=vec(0.1,0.1,0.15),\n texture=textures.rough, shape=br)\nd = bre.clone()\nd.rotate(angle=pi, axis=vec(1,0,0), origin=vec(1,0,0))\n\n#==========================================================================\n# Create Brush Housings\n# Define a rectangular frame, with a thickness of 0.1\nbh = shapes.rectangle(width=1.3, height=0.5, thickness=0.1)\n# Extrude the rectangular frame to obtain hollow boxes for each housing\nbhe1 = extrusion(path=[vec(1,0,1.4),vec(1,0,2.9)], shape=bh, color=vec(0.9,1,0.8),\n texture=textures.rough)\nbhe2 = extrusion(path=[vec(1,0,-1.4),vec(1,0,-2.9)], shape=bh, color=vec(0.9,1,0.8),\n texture=textures.rough)\n\n#==========================================================================\n# Place a screw on each housing to fix the power cables\n# Create a screw head profile by subtracting a cross from a circle\nscrh = [shapes.circle(radius=0.15)]\nscrh.append(shapes.cross(width=0.2, thickness=0.04))\n# Extrude a little to get the screw head\nscrhw1path = [vec(1,0.2,2.7),vec(1,0.3,2.7)]\nscrhw2path = [vec(1,0.2,-2.7),vec(1,0.3,-2.7)]\nscrhw1 = extrusion(path=scrhw1path, shape=scrh, texture=textures.metal)\nscrhw2 = extrusion(path=scrhw2path, shape=scrh, texture=textures.metal)\n\n#==========================================================================\n# Create the screw bodies\n# Use a square to create the body with teeth\nscrb = shapes.rectangle(scale=0.05)\nyi = .2\nyf = -.3\ndy = (yi-yf)/20\npts = []\nfor i in range(20):\n pts.append(vec(1, yi-i*dy, 2.7))\n# Extrude the square with twist parameter to get the teeth of the screw\n# It appears that paths.line() is broken.\n#scrbe1 = extrusion(path=paths.line(start=vec(2.7,0.2,1), end=vec(2.7,-0.3,1),\nscrbe1 = extrusion(path=pts, shape=scrb, twist=0.4, color=vec(1,1,0.9),\n texture=textures.rough)\n\npts = []\nfor i in range(20):\n pts.append(vec(1, yi-i*dy, -2.7))\nscrbe2 = extrusion(path=pts, shape=scrb, twist=0.4, color=vec(1,1,0.9),\n texture=textures.rough)\n\n#==========================================================================\n\ncrdl = [ [2.8,-0.2], [2.8,-1.6], [-2.8,-1.6], [-2.8,-0.2] ] # ccw\na = acos(0.2/1.1) # starting angle for near half-circle\nc = makearc(center=[0,0], radius=1.25, angle1=1.5*pi-a, angle2=1.5*pi+a, ccw=True)\ncrdl.extend(c)\ncrdl.append(crdl[0])\ncrdl = [crdl, shapes.circle(pos=[-2.2,-0.9], radius=0.1)]\ncrdle = extrusion(path=[vec(1.8,-0.05,0),vec(0.2,-0.05,0)], shape=crdl,\n color=0.7*color.white, emissive=True)\n\n#==========================================================================\n# Connect power cables to the brushes\n# Use simple curves to define cables\ncol = vec(1,0.5,0.3)\ncbl1i = curve(pos=[scrhw1path[0], scrhw1path[0]- vec(0,0,-2)],\n radius=0.03, color=col)\ncbl1o = curve(pos=[scrhw1path[0], scrhw1path[0]- vec(0,0,-1.5)],\n radius=0.05, color=vec(0,0,1))\ncb12ipos = [scrhw2path[0], scrhw2path[0], scrhw2path[0]+vec(0,0,-0.5), \n scrhw2path[0]+vec(0,0,-0.5)+vec(0,-2,0),\n scrhw2path[0]+vec(0,0,-0.5)+vec(0,-2,0)+vec(0,0,7)]\ncbl2i = curve(pos=cb12ipos, radius=0.03, color=col)\n\n#==========================================================================\n# Add ball-bearings at both ends of the shaft\nbra = shapes.circle(radius=0.6, thickness=0.2)\nbrb = shapes.circle(radius=1.1, thickness=0.2)\nbr1 = extrusion(path=[vec(2.5,0,0), vec(3.25,0,0)], shape=bra, color=color.gray(.7))\nbr1 = extrusion(path=[vec(2.5,0,0), vec(3.25,0,0)], shape=brb, color=color.gray(.7))\nbr2 = extrusion(path=[vec(-7,0,0), vec(-7.75,0,0)], shape=bra, color=color.gray(.7),\n shininess=1, texture=textures.metal)\nbr2 = extrusion(path=[vec(-7,0,0), vec(-7.75,0,0)], shape=brb, color=color.gray(.7),\n shininess=1, texture=textures.metal)\n\n#==========================================================================\n# Do not forget to add the balls\nbbrs1 = []\nbbrs2 = []\nfor i in range(7):\n ex1 = sphere(pos=vec(3, 0.75*cos(i*2*pi/7.0), 0.75*sin(i*2*pi/7.0)),\n radius=0.25)\n bbrs1.append(ex1)\n \n ex2 = sphere(pos=vec(-7.375, 0.75*cos(i*2*pi/7.0), 0.75*sin(i*2*pi/7.0)),\n radius=0.25)\n bbrs2.append(ex2)\nmotor.append(compound(bbrs1, texture=textures.rough))\nmotor.append(compound(bbrs2, texture=textures.rough))\n\n#==========================================================================\n# Here is a complex path definition for stator winding, which is not planar.\n# The path is made up of an arc in YZ + line in ZX + arc in ZY + line in XZ\npp = paths.arc(angle1=-pi/3.5, angle2=pi/3.5, radius=3.4)\nfor p in pp:\n p.y = -p.x\n p.x = -1\ntt = []\nfor p in pp:\n tt.append(vec(-6,p.y,p.z))\ntt.reverse()\npp.extend(tt)\npp.append(pp[0])\nextrusion(path=pp, shape=shapes.circle(radius=0.3), color=color.red)\n \n#==========================================================================\n# Make stator base:\n# We did not include all stator parts here for better visualisation\n# Use a rounded rectangle for the stator base.\n# Subtract a large circle in the middle to allow for the rotor\n# Subtract circular holes to place the stator windings\n# Subtract some more holes for fixing the stator core on the motor body\n\n#def makearc(center=[0,0], radius=1, angle1=2*pi-pi/4, angle2=2*pi+pi/4, ccw=True):\n # Specify angle1 and angle2 as positive numbers, with angle2 > angle1\np1 = makearc(center=[1.5,0], radius=1.5, angle1=3*pi/2, angle2=2*pi, ccw=True)\np2 = makearc(center=[2.7,0.22], radius=0.3, angle1=pi+pi/4, angle2=2*pi-pi/4, ccw=False)\np3 = makearc(center=[0,2.1], radius=3.1, angle1=pi+0.95*pi/4, angle2=2*pi-0.95*pi/4, ccw=False)\np4 = makearc(center=[-2.7,0.22], radius=0.3, angle1=pi+pi/4, angle2=2*pi-pi/4, ccw=False)\np5 = makearc(center=[-1.5,0], radius=1.5, angle1=pi, angle2=1.5*pi, ccw=True)\n\np1.extend(p2)\np1.extend(p3)\np1.extend(p4)\np1.extend(p5)\np1.append(p1[0])\n\nh1 = shapes.circle(pos=[2,-1.07], radius=0.15)\nh2 = shapes.circle(pos=[-2,-1.07], radius=0.15)\n\nextrusion(path=[vec(-6,-2.3,0), vec(-1,-2.3,0)], shape=[p1,h1,h2])\n\n#==========================================================================\n# Create the motor cover as a rotational extrusion along the mootor\n# Add two rounded rectangles which will cover all the rotor and stator.\n# Leave the tips of shaft outside the cover\nL = 11\nr = 2\nR = 5\nt = 0.25\nx = 3*t\ns = [ [-L/2, -r/2-t], [-L/2,-r/2], [-L/2+x,-r/2], [-L/2+x,R/2], [L/2-x,R/2], [L/2-x,-r/2], [L/2,-r/2],\n [L/2,-r/2-t], [L/2-x-t,-r/2-t], [L/2-x-t,R/2-t], [-L/2+x+t,R/2-t], \n [-L/2+x+t,-r/2-t], [-L/2+t,-r/2-t], [-L/2,-r/2-t] ]\n\np = paths.arc(radius=R/2, angle1=0, angle2=pi+pi/4)\nex = extrusion(path=p, shape=s, color=0.6*color.green, up=vec(1,0,0), texture=textures.rough)\nex.rotate(angle=pi/2, axis=vec(0,0,1))\nex.pos = vec(-2.2,0,-0.65)\n\nrun = True\ndef running(b):\n global run\n if b.text == 'Run': b.text = 'Pause'\n else: b.text = 'Run'\n run = not run\n \nbutton(text='Pause', bind=running)\n\nscene.append_to_caption(\"\"\"\\nTo rotate \"camera\", drag with right button or Ctrl-drag.\nTo zoom, drag with middle button or Alt/Option depressed, or use scroll wheel.\n On a two-button mouse, middle is left + right.\nTo pan left/right and up/down, Shift-drag.\nTouch screen: pinch/extend to zoom, swipe or two-finger rotate.\"\"\")\n\n# Connect power cables\nangl = pi/400\n# Turn on the motor\nwhile True:\n rate(100)\n if run:\n for o in motor:\n o.rotate(angle=angl, axis=vec(-1,0,0))\n \n\n ","metadata":{"trusted":false},"outputs":[{"data":{"text/plain":"","text/html":"
"},"metadata":{},"output_type":"display_data"},{"metadata":{},"data":{"text/plain":"","application/javascript":"if (typeof Jupyter !== \"undefined\") { window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")};}else{ element.textContent = ' ';}"},"output_type":"display_data"},{"output_type":"display_data","data":{"text/html":"
","text/plain":""},"metadata":{}},{"metadata":{},"output_type":"display_data","data":{"application/javascript":"if (typeof Jupyter !== \"undefined\") { window.__context = { glowscript_container: $(\"#glowscript\").removeAttr(\"id\")};}else{ element.textContent = ' ';}","text/plain":""}}],"execution_count":null},{"id":"e329e81d-2c60-4e49-ae5b-f0fa19add348","cell_type":"code","source":"","metadata":{"trusted":false},"outputs":[],"execution_count":null}]} \ No newline at end of file diff --git a/Demos/RotatingBoxes.ipynb b/Demos/RotatingBoxes.ipynb index 78fb6cd6..659e55e9 100644 --- a/Demos/RotatingBoxes.ipynb +++ b/Demos/RotatingBoxes.ipynb @@ -107,7 +107,6 @@ "source": [ "from vpython import *\n", "scene = canvas() # This is needed in Jupyter notebook and lab to make programs easily rerunnable\n", - "from time import clock\n", "\n", "N = 10\n", "\n", diff --git a/Demos_no_notebook/ButtonsSlidersMenus.py b/Demos_no_notebook/ButtonsSlidersMenus.py index 8e61aee4..960521b5 100644 --- a/Demos_no_notebook/ButtonsSlidersMenus.py +++ b/Demos_no_notebook/ButtonsSlidersMenus.py @@ -1,6 +1,4 @@ from vpython import * -# This version uses VPython widgets: button, radio button, checkbox, slider, menu -# See ButtonsSlidersMenus1 for a version that uses Jupyter notebook widgets: button, slider, menu scene.width = 350 scene.height = 300 scene.range = 1.3 @@ -20,7 +18,6 @@ def Run(b): cone_object = cone(visible=False, radius=0.5) pyramid_object = pyramid(visible=False) cylinder_object = cylinder(visible=False, radius=0.5) -sphere(radius=0.3) col = color.cyan currentobject = box_object @@ -29,36 +26,50 @@ def Run(b): def Color(c): global col if col.equals(color.cyan): # change to red + currentobject.color = col = color.red + c.text = "Cyan" + c.color = color.cyan + c.background = color.red + if c.name is None: # this is the top button + r1.checked = False + r2.checked = True + else: # change to cyan + currentobject.color = col = color.cyan + c.text = "Red" + c.color = color.red + c.background = color.cyan + if c.name is None: # this is the top button + r1.checked = True + r2.checked = False + +def cc(c): + global col + if col.equals(color.cyan): # change to red: currentobject.color = col = color.red cbutton.text = "Cyan" - cbutton.textcolor = color.cyan + cbutton.color = color.cyan cbutton.background = color.red - r1.checked = False - r2.checked = True else: # change to cyan currentobject.color = col = color.cyan cbutton.text = "Red" - cbutton.textcolor = color.red + cbutton.color = color.red cbutton.background = color.cyan - r1.checked = True - r2.checked = False -cbutton = button(text='Red', textcolor=color.red, background=color.cyan, pos=scene.title_anchor, bind=Color) +cbutton = button(text='Red', color=color.red, background=color.cyan, + pos=scene.title_anchor, bind=Color, name=None) + +scene.caption = "Vary the rotation speed: \n\n" -scene.caption = "Vary the rotation speed:\n" -speed = 150 def setspeed(s): - global speed - speed = s.value - wt.text = '{:1.0f}'.format(s.value) + wt.text = '{:1.2f}'.format(s.value) -sl = slider(min=20, max=500, value=250, length=280, bind=setspeed, right=15) +sl = slider(min=0.3, max=3, value=1.5, length=220, bind=setspeed, right=15) -wt = wtext(text='{:1.0f}'.format(sl.value)) +wt = wtext(text='{:1.2f}'.format(sl.value)) -scene.append_to_caption('\n') +scene.append_to_caption(' radians/s\n') -r1 = radio(bind=Color, checked=True, text='Cyan') +r1 = radio(bind=cc, checked=True, text='Cyan', name='rads') scene.append_to_caption(' ') @@ -85,7 +96,7 @@ def M(m): scene.append_to_caption('\n') -r2 = radio(bind=Color, text='Red') +r2 = radio(bind=cc, text='Red', name='rads') scene.append_to_caption(' ') @@ -97,8 +108,8 @@ def transparency(b): checkbox(bind=transparency, text='Transparent') +dt = 0.01 while True: - rate(100) + rate(1/dt) if running: - currentobject.rotate(angle=speed*1e-4, axis=vector(0,1,0)) - + currentobject.rotate(angle=sl.value*dt, axis=vector(0,1,0)) diff --git a/Demos_no_notebook/CORS.py b/Demos_no_notebook/CORS.py new file mode 100644 index 00000000..00770e80 --- /dev/null +++ b/Demos_no_notebook/CORS.py @@ -0,0 +1,17 @@ +from vpython import * +scene.range = 1 +scene.forward = vector(-1,-.5,-1) +box(texture="https://s3.amazonaws.com/glowscript/textures/flower_texture.jpg") + +s = 'This illustrates the use of an image from another web site as a texture.\n' +s += 'This is an example of CORS, "Cross-Origin Resource Sharing".\n' +scene.caption = s + +scene.append_to_caption(""" +To rotate "camera", drag with right button or Ctrl-drag. +To zoom, drag with middle button or Alt/Option depressed, or use scroll wheel. + On a two-button mouse, middle is left + right. +To pan left/right and up/down, Shift-drag. +Touch screen: pinch/extend to zoom, swipe or two-finger rotate.""") + +scene.pause() diff --git a/Demos_no_notebook/Color-RGB-HSV-from-terminal.py b/Demos_no_notebook/Color-RGB-HSV-from-terminal.py new file mode 100644 index 00000000..a3626e65 --- /dev/null +++ b/Demos_no_notebook/Color-RGB-HSV-from-terminal.py @@ -0,0 +1,47 @@ +from vpython import * +# This version uses VPython slider +# See ButtonsSlidersMenus1 for a version that uses Jupyter slider + +scene.userzoom = False +scene.userspin = False +scene.width = 400 +scene.height = 200 +scene.range = 1 +scene.background = color.red +box(pos=vector(10,0,0)) # Force creation of canvas; box is not seen because it is outside the canvas +cancopy = 'You can Ctrl-C or Command-C copy these RGB and HSV values:\n' +scene.title = cancopy +scene.append_to_title("RGB = <1.000, 0.000, 0.000>, HSV = <0.000, 0.000, 0.000>") + +C = ['Red', 'Green', 'Blue', 'Hue', 'Saturation', 'Value'] +sliders = [] + +def set_background(sl): + if sl.id < 3: + rgb = vector(sliders[0].value, sliders[1].value, sliders[2].value) + hsv = color.rgb_to_hsv(rgb) + sliders[3].value = int(1000*hsv.x)/1000# reset HSV slider positions; display 3 figures + sliders[4].value = int(1000*hsv.y)/1000 + sliders[5].value = int(1000*hsv.z)/1000 + else: + hsv = vector(sliders[3].value, sliders[4].value, sliders[5].value) + rgb = color.hsv_to_rgb(hsv) + sliders[0].value = int(1000*rgb.x)/1000 # reset RGB slider positions; display 3 figures + sliders[1].value = int(1000*rgb.y)/1000 + sliders[2].value = int(1000*rgb.z)/1000 + scene.background = rgb + # For readability, limit precision of display of quantities to 3 figures + f = "RGB = <{:1.3f}, {:1.3f}, {:1.3f}>, HSV = <{:1.3f}, {:1.3f}, {:1.3f}>" + scene.title = cancopy + f.format(rgb.x, rgb.y, rgb.z, hsv.x, hsv.y, hsv.z) + +scene.caption = '\n' +for i in range(6): # Create the 3 RGB and 3 HSV sliders + sliders.append(slider(length=300, left=10, min=0, max=1, bind=set_background, id=i)) + scene.append_to_caption(' '+C[i]+'\n\n') # Display slider name + if i == 2: scene.append_to_caption("\n\n") # Separate the RGB and HSV sliders +sliders[0].value = 1 # make the background red +sliders[4].value = sliders[5].value = 1 + + +while True: # Needed when running from a terminal + rate(30) diff --git a/Demos_no_notebook/Conch.py b/Demos_no_notebook/Conch.py new file mode 100644 index 00000000..c7d72dc4 --- /dev/null +++ b/Demos_no_notebook/Conch.py @@ -0,0 +1,40 @@ +from vpython import * +# Kadir Haldenbilen, Feb. 2011 + +scene.height = scene.width = 600 +scene.background = color.gray(0.7) +scene.range = 3 +scene.ambient = 0.5*color.white + +scene.caption = """To rotate "camera", drag with right button or Ctrl-drag. +To zoom, drag with middle button or Alt/Option depressed, or use scroll wheel. + On a two-button mouse, middle is left + right. +To pan left/right and up/down, Shift-drag. +Touch screen: pinch/extend to zoom, swipe or two-finger rotate.""" + +def spiral(nloop=1, tightness=1.0, dir=1, scale=1): + spr = [] + scale = [] + clrs = [] + zd = 0.01 + for t in range(1, 1024*nloop, 16): + t *= 0.01 + x = tightness/10 * t * cos(t)*dir + y = tightness/10 * t * sin(t) + sc = sqrt(x*x+y*y) + z = t/7 + spr.append(vector(x,y,z)) + clr = vector(z*cos(t), abs(sin(t)), abs(cos(t*2))).norm() + clrs.append(clr) + scale.append(sc) + return spr, scale, clrs + +path, scale, clrs = spiral(nloop=2, tightness=0.8) +elps = shapes.circle(radius=0.69, thickness=0.01) + +ee = extrusion(shape=elps, path=path, color=clrs, scale=scale, texture=textures.rock) +ee.rotate(angle=pi/2) +scene.center = ee.pos-vec(0,.5,0) + +scene.pause() + diff --git a/Demos_no_notebook/DipoleElectricField-from-terminal.py b/Demos_no_notebook/DipoleElectricField-from-terminal.py new file mode 100644 index 00000000..29d6d53a --- /dev/null +++ b/Demos_no_notebook/DipoleElectricField-from-terminal.py @@ -0,0 +1,65 @@ +from vpython import * + +scale = 4e-14/1e17 +ec = 1.6e-19 # electron charge +kel = 9e9 # Coulomb constant +scene.range = 2e-13 + +charges = [ sphere( pos=vector(-1e-13,0,0), Q=ec, color=color.red, size=1.2e-14*vector(1,1,1) ), + sphere( pos=vector( 1e-13,0,0), Q=-ec, color=color.blue, size=1.2e-14*vector(1,1,1) )] + +s = "Click or drag to plot an electric field vector produced by the two charges.\n" +s += "On a touch screen, tap, or press and hold, then drag.\n" +s += "Arrows representing the field are bluer if low magnitude, redder if high." +scene.caption = s + +def getfield(p): + f = vec(0,0,0) + for c in charges: + f = f + (p-c.pos) * kel * c.Q / mag(p-c.pos)**3 + return f + +def mouse_to_field(a): + p = scene.mouse.pos + f = getfield(p) + m = mag(f) + red = max( 1-1e17/m, 0 ) + blue = min( 1e17/m, 1 ) + if red >= blue: + blue = blue/red + red = 1.0 + else: + red = red/blue + blue = 1.0 + a.pos = p + a.axis = scale*f + a.color = vector(red,0,blue) + a.visible = True + +drag = False +a = None + +def down(ev): + global a, drag + a = arrow(shaftwidth=6e-15, visible=False) + mouse_to_field(a) + drag = True + +def move(ev): + global a, drag + if not drag: return + mouse_to_field(a) + +def up(ev): + global a, drag + mouse_to_field(a) + drag = False + +scene.bind("mousedown", down) + +scene.bind("mousemove", move) + +scene.bind("mouseup", up) + +while True: # Needed when running from a terminal + rate(30) diff --git a/Demos_no_notebook/ElectricMotor.py b/Demos_no_notebook/ElectricMotor.py new file mode 100644 index 00000000..2c3f98af --- /dev/null +++ b/Demos_no_notebook/ElectricMotor.py @@ -0,0 +1,360 @@ +from vpython import * +# Kadir Haldenbilen, February 2011 +# Converted to Web VPython by Bruce Sherwood, May 2022 + +scene.width = 1024 +scene.height = 768 +scene.center = vec(-2,0,0) +scene.range = 7 + +motor = [] # elements that rotate +cl = 2 +ns = 24 + +def makearc(center=[0,0], radius=1, angle1=2*pi-pi/4, angle2=2*pi+pi/4, ccw=True): + # Specify angle1 and angle2 as positive numbers, with angle2 > angle1 + if angle2 < angle1: + raise Exception("Both angles must be positive, with angle2 > angle1") + n = 20 + dtheta = abs(angle2-angle1)/n + ret = [] + for i in range(n+1): + if ccw: + #print(center, angle1, angle2, radius, ccw) + ret.append([center[0]+radius*cos(angle1+i*dtheta), center[1]+radius*sin(angle1+i*dtheta)]) + else: + #print(center, angle1, angle2, radius, ccw) + ret.append([center[0]+radius*cos(angle2-i*dtheta), center[1]+radius*sin(angle2-i*dtheta)]) + return ret + +def rotatepoints(points=[[1,0],[0,1]], angle=0, origin=vec(0,0,0)): + s = sphere(visible=False) + ret = [] + for p in points: + s.pos = vec(p[0],p[1],0) + s.rotate(angle=angle, axis=vec(0,0,1), origin=origin) + ret.append([s.pos.x,s.pos.y]) + return ret + +#===================================================================== +# Create contactor + +p2 = makearc(center=[0,0], radius=0.5, angle1=0, angle2=pi/20, ccw=False) +p1 = makearc(center=[0,0], radius=1.2, angle1=0, angle2=pi/15, ccw=True) +p = p1+p2+[p1[0]] +c = extrusion(path=[vec(0,0,0),vec(2,0,0)], shape=p, color=vec(1,0.5,0.3)) +cont = [c] +for i in range(1,24): + con = c.clone() + con.rotate(angle=i*2*pi/24, axis=vec(-1,0,0), origin=vec(0,0,0)) + cont.append(con) +motor.append(compound(cont)) +#========================================================================== +# Create contactor soldering tips + +p1 = makearc(center=[0,0], radius=1.4, angle1=2*pi-0.5*pi/24, angle2=2*pi+0.4*pi/24, ccw=True) +p2 = makearc(center=[0,0], radius=0.7, angle1=2*pi-0.5*pi/30, angle2=2*pi+0.4*pi/30, ccw=False) +p = p1+p2+[p1[0]] +c = extrusion(path=[vec(-0.4,0,0),vec(0,0,0)], shape=p, color=vec(1,0.5,0.3)) +s = sphere(pos=vec(-0.2,0,1.195), radius=0.1, shininess=1) +cont = [c] +balls = [] +for i in range(23): + con = c.clone() + con.rotate(angle=(i+1)*2*pi/24, axis=vec(1,0,0), origin=vec(0,0,0)) + cont.append(con) + con = s.clone() + con.rotate(angle=(i+1)*2*pi/24, axis=vec(1,0,0), origin=vec(0,0,0)) + balls.append(con) + +motor.append(compound(cont)) +motor.append(compound(balls)) +#========================================================================= +# Add shaft insulator material +# Define a circular ring of thickness=0.05 +sc = shapes.circle(radius=0.53, thickness=0.05) +# Extrude the ring to get a thin hollow cylinder insulator over the shaft +sce = extrusion(path=[vec(-7,0,0),vec(2.5,0,0)], shape=sc, color=vec(0.8,0,0), + emissive=True) # plastic + +# The Rotor Shaft, defined by a simple cylinder +shaft = cylinder(pos=vec(3.5,0,0), axis=vec(-11.25,0,0), radius=0.495, color=color.gray(.6), texture=textures.metal) + +# Add a piece of gear at one end of the shaft +# Use the gear shape to define the shape, note radius, addendum, dedendum sizes +gr = shapes.gear(n=9, radius=0.455, addendum=0.04, dedendum=0.06, fradius=0.01) +# Extrude the gear shape appending it to the shaft end +gre = extrusion(path=[vec(3.5,0,0),vec(5,0,0)], shape=gr, color=color.gray(.6), + texture=textures.metal) + +motor.append(gre) +#for o in scene.objects: o.visible = False +#========================================================================== +# Define Rotor Core +# Normally the core should have been built of many very thin sheets +# For performance reasons, a single block is built +# First define the outer circle + +r0 = 0.495 # radius of shaft +r1 = 1.3 # radius of disk +r2 = 2.7 # radius of inner portion of top of the T +r3 = 3 # radius of outer portion of top of the T +theta = pi/12 # angle between Ts +d = 0.25*theta # half angle width of upright of T +dlt = 0.05 +thk = 5.04 + +p1 = makearc(center=[0,0], radius=r1, angle1=2*pi-pi/12+d, angle2=2*pi+pi/12-d, ccw=True) +start = r1*vec(cos(-pi/12+d), sin(-pi/12+d),0) +# From middle of bottom of T base to middle of top of T upright: +middledir = vec(cos(pi/12),sin(pi/12), 0) +perp = middledir.rotate(angle=pi/2, axis=vec(0,0,1)) +p1end = vec(p1[-1][0], p1[-1][1],0) +endpt1 = p1end+(r2-r1)*middledir # top right of T upright +nextstart = start.rotate(angle=pi/6, axis=vec(0,0,1)) +endpt2 = nextstart + (r2-r1)*middledir +a = atan(endpt1.y/endpt1.x) +p2 = makearc(center=[0,0], radius=r2, angle1=0.3*a, angle2=a, ccw=False) +p1.extend(p2) +p3 = makearc(center=[0,0], radius=r3, angle1=0.3*a, angle2=1.5*a+2*d, ccw=True) +p1.extend(p3) +b = atan(endpt2.y/endpt2.x) +p4 = makearc(center=[0,0], radius=r2, angle1=b, angle2=1.5*a+2*d, ccw=False) +p1.extend(p4) + +pc = p1.copy() +for i in range(1,12): + p = rotatepoints(p1, angle=i*pi/6) + pc.extend(p) +pc.append(pc[0]) + +ex = extrusion(path=[vec(-6,0,0), vec(-6+thk,0,0)], shape=[pc, shapes.circle(radius=r0)] , color=color.gray(0.7)) +motor.append(ex) + +#========================================================================== +# Do the core wire windings +x = -3.5 +n = 20 +L = 1.3 +thk = L/50 +con = [] +for i in range(n): + r = r1+i*thk*2.5 + inner = shapes.rectangle(width=5.2, height=.35, roundness=0.4) + outer = shapes.rectangle(width=5.3+i*0.7/n, height=0.4+i*0.6/n, roundness=0.4) + p = [vec(x,r*sin(pi/12),r*cos(pi/12)), vec(x,(r+thk)*sin(pi/12),(r+thk)*cos(pi/12))] + ex = extrusion(path=p, shape=[outer,inner], color=vec(.7,.5,.15)) + con.append(ex) +ex = compound(con) +con = [ex] + +for i in range(1,12): + c = ex.clone() + c.rotate(angle=i*pi/6, axis=vec(1,0,0), origin=vec(0,0,0)) + con.append(c) + +motor.append(compound(con)) + +#========================================================================== +# Connect contactor surfaces to windings with cables +pos = vec(-1.3,1.1,0) +c0 = cylinder(pos=pos, axis=vec(-0.25,1.2,0.25)-pos, radius=0.05, color=color.blue) +con = [c0] +for i in range(1,24): + c = c0.clone() + c.rotate(angle=i*pi/6, axis=vec(1,0,0), origin=vec(c0.pos.x,0,0)) + con.append(c) +motor.append(compound(con)) + +#========================================================================== +# Create Brushes +# From a rectangular cross section, subtract rotor contactor circle, leaving us two +# brushes on each sides of the contactor, with circular profile +#br = shapes.rectangle(width=5, height=0.4) +br = makearc(center=[0,0], radius=1.21, angle1=2*pi-atan(0.2/1.21), angle2=2*pi+atan(0.2/1.21), ccw=True) +d = 1+1.21*cos(atan(.2/1.21)) +br.append([d,0.2]) +br.append([d,-0.2]) +br.append(br[0]) +bre = extrusion(path=[vec(0.4,0,0),vec(1.6,0,0)], color=vec(0.1,0.1,0.15), + texture=textures.rough, shape=br) +d = bre.clone() +d.rotate(angle=pi, axis=vec(1,0,0), origin=vec(1,0,0)) + +#========================================================================== +# Create Brush Housings +# Define a rectangular frame, with a thickness of 0.1 +bh = shapes.rectangle(width=1.3, height=0.5, thickness=0.1) +# Extrude the rectangular frame to obtain hollow boxes for each housing +bhe1 = extrusion(path=[vec(1,0,1.4),vec(1,0,2.9)], shape=bh, color=vec(0.9,1,0.8), + texture=textures.rough) +bhe2 = extrusion(path=[vec(1,0,-1.4),vec(1,0,-2.9)], shape=bh, color=vec(0.9,1,0.8), + texture=textures.rough) + +#========================================================================== +# Place a screw on each housing to fix the power cables +# Create a screw head profile by subtracting a cross from a circle +scrh = [shapes.circle(radius=0.15)] +scrh.append(shapes.cross(width=0.2, thickness=0.04)) +# Extrude a little to get the screw head +scrhw1path = [vec(1,0.2,2.7),vec(1,0.3,2.7)] +scrhw2path = [vec(1,0.2,-2.7),vec(1,0.3,-2.7)] +scrhw1 = extrusion(path=scrhw1path, shape=scrh, texture=textures.metal) +scrhw2 = extrusion(path=scrhw2path, shape=scrh, texture=textures.metal) + +#========================================================================== +# Create the screw bodies +# Use a square to create the body with teeth +scrb = shapes.rectangle(scale=0.05) +yi = .2 +yf = -.3 +dy = (yi-yf)/20 +pts = [] +for i in range(20): + pts.append(vec(1, yi-i*dy, 2.7)) +# Extrude the square with twist parameter to get the teeth of the screw +# It appears that paths.line() is broken. +#scrbe1 = extrusion(path=paths.line(start=vec(2.7,0.2,1), end=vec(2.7,-0.3,1), +scrbe1 = extrusion(path=pts, shape=scrb, twist=0.4, color=vec(1,1,0.9), + texture=textures.rough) + +pts = [] +for i in range(20): + pts.append(vec(1, yi-i*dy, -2.7)) +scrbe2 = extrusion(path=pts, shape=scrb, twist=0.4, color=vec(1,1,0.9), + texture=textures.rough) + +#========================================================================== + +crdl = [ [2.8,-0.2], [2.8,-1.6], [-2.8,-1.6], [-2.8,-0.2] ] # ccw +a = acos(0.2/1.1) # starting angle for near half-circle +c = makearc(center=[0,0], radius=1.25, angle1=1.5*pi-a, angle2=1.5*pi+a, ccw=True) +crdl.extend(c) +crdl.append(crdl[0]) +crdl = [crdl, shapes.circle(pos=[-2.2,-0.9], radius=0.1)] +crdle = extrusion(path=[vec(1.8,-0.05,0),vec(0.2,-0.05,0)], shape=crdl, + color=0.7*color.white, emissive=True) + +#========================================================================== +# Connect power cables to the brushes +# Use simple curves to define cables +col = vec(1,0.5,0.3) +cbl1i = curve(pos=[scrhw1path[0], scrhw1path[0]- vec(0,0,-2)], + radius=0.03, color=col) +cbl1o = curve(pos=[scrhw1path[0], scrhw1path[0]- vec(0,0,-1.5)], + radius=0.05, color=vec(0,0,1)) +cb12ipos = [scrhw2path[0], scrhw2path[0], scrhw2path[0]+vec(0,0,-0.5), + scrhw2path[0]+vec(0,0,-0.5)+vec(0,-2,0), + scrhw2path[0]+vec(0,0,-0.5)+vec(0,-2,0)+vec(0,0,7)] +cbl2i = curve(pos=cb12ipos, radius=0.03, color=col) + +#========================================================================== +# Add ball-bearings at both ends of the shaft +bra = shapes.circle(radius=0.6, thickness=0.2) +brb = shapes.circle(radius=1.1, thickness=0.2) +br1 = extrusion(path=[vec(2.5,0,0), vec(3.25,0,0)], shape=bra, color=color.gray(.7)) +br1 = extrusion(path=[vec(2.5,0,0), vec(3.25,0,0)], shape=brb, color=color.gray(.7)) +br2 = extrusion(path=[vec(-7,0,0), vec(-7.75,0,0)], shape=bra, color=color.gray(.7), + shininess=1, texture=textures.metal) +br2 = extrusion(path=[vec(-7,0,0), vec(-7.75,0,0)], shape=brb, color=color.gray(.7), + shininess=1, texture=textures.metal) + +#========================================================================== +# Do not forget to add the balls +bbrs1 = [] +bbrs2 = [] +for i in range(7): + ex1 = sphere(pos=vec(3, 0.75*cos(i*2*pi/7.0), 0.75*sin(i*2*pi/7.0)), + radius=0.25) + bbrs1.append(ex1) + + ex2 = sphere(pos=vec(-7.375, 0.75*cos(i*2*pi/7.0), 0.75*sin(i*2*pi/7.0)), + radius=0.25) + bbrs2.append(ex2) +motor.append(compound(bbrs1, texture=textures.rough)) +motor.append(compound(bbrs2, texture=textures.rough)) + +#========================================================================== +# Here is a complex path definition for stator winding, which is not planar. +# The path is made up of an arc in YZ + line in ZX + arc in ZY + line in XZ +pp = paths.arc(angle1=-pi/3.5, angle2=pi/3.5, radius=3.4) +for p in pp: + p.y = -p.x + p.x = -1 +tt = [] +for p in pp: + tt.append(vec(-6,p.y,p.z)) +tt.reverse() +pp.extend(tt) +pp.append(pp[0]) +extrusion(path=pp, shape=shapes.circle(radius=0.3), color=color.red) + +#========================================================================== +# Make stator base: +# We did not include all stator parts here for better visualisation +# Use a rounded rectangle for the stator base. +# Subtract a large circle in the middle to allow for the rotor +# Subtract circular holes to place the stator windings +# Subtract some more holes for fixing the stator core on the motor body + +#def makearc(center=[0,0], radius=1, angle1=2*pi-pi/4, angle2=2*pi+pi/4, ccw=True): + # Specify angle1 and angle2 as positive numbers, with angle2 > angle1 +p1 = makearc(center=[1.5,0], radius=1.5, angle1=3*pi/2, angle2=2*pi, ccw=True) +p2 = makearc(center=[2.7,0.22], radius=0.3, angle1=pi+pi/4, angle2=2*pi-pi/4, ccw=False) +p3 = makearc(center=[0,2.1], radius=3.1, angle1=pi+0.95*pi/4, angle2=2*pi-0.95*pi/4, ccw=False) +p4 = makearc(center=[-2.7,0.22], radius=0.3, angle1=pi+pi/4, angle2=2*pi-pi/4, ccw=False) +p5 = makearc(center=[-1.5,0], radius=1.5, angle1=pi, angle2=1.5*pi, ccw=True) + +p1.extend(p2) +p1.extend(p3) +p1.extend(p4) +p1.extend(p5) +p1.append(p1[0]) + +h1 = shapes.circle(pos=[2,-1.07], radius=0.15) +h2 = shapes.circle(pos=[-2,-1.07], radius=0.15) + +extrusion(path=[vec(-6,-2.3,0), vec(-1,-2.3,0)], shape=[p1,h1,h2]) + +#========================================================================== +# Create the motor cover as a rotational extrusion along the mootor +# Add two rounded rectangles which will cover all the rotor and stator. +# Leave the tips of shaft outside the cover +L = 11 +r = 2 +R = 5 +t = 0.25 +x = 3*t +s = [ [-L/2, -r/2-t], [-L/2,-r/2], [-L/2+x,-r/2], [-L/2+x,R/2], [L/2-x,R/2], [L/2-x,-r/2], [L/2,-r/2], + [L/2,-r/2-t], [L/2-x-t,-r/2-t], [L/2-x-t,R/2-t], [-L/2+x+t,R/2-t], + [-L/2+x+t,-r/2-t], [-L/2+t,-r/2-t], [-L/2,-r/2-t] ] + +p = paths.arc(radius=R/2, angle1=0, angle2=pi+pi/4) +ex = extrusion(path=p, shape=s, color=0.6*color.green, up=vec(1,0,0), texture=textures.rough) +ex.rotate(angle=pi/2, axis=vec(0,0,1)) +ex.pos = vec(-2.2,0,-0.65) + +run = True +def running(b): + global run + if b.text == 'Run': b.text = 'Pause' + else: b.text = 'Run' + run = not run + +button(text='Pause', bind=running) + +scene.append_to_caption("""\nTo rotate "camera", drag with right button or Ctrl-drag. +To zoom, drag with middle button or Alt/Option depressed, or use scroll wheel. + On a two-button mouse, middle is left + right. +To pan left/right and up/down, Shift-drag. +Touch screen: pinch/extend to zoom, swipe or two-finger rotate.""") + +# Connect power cables +angl = pi/400 +# Turn on the motor +while True: + rate(100) + if run: + for o in motor: + o.rotate(angle=angl, axis=vec(-1,0,0)) + diff --git a/Demos_no_notebook/HardSphereGas.py b/Demos_no_notebook/HardSphereGas.py index 04d302e7..563322c7 100644 --- a/Demos_no_notebook/HardSphereGas.py +++ b/Demos_no_notebook/HardSphereGas.py @@ -1,5 +1,4 @@ from vpython import * -from time import clock # Hard-sphere gas. @@ -48,7 +47,7 @@ p = [] apos = [] pavg = sqrt(2*mass*1.5*k*T) # average kinetic energy p**2/(2mass) = (3/2)kT - + for i in range(Natoms): x = L*random()-L/2 y = L*random()-L/2 @@ -152,24 +151,24 @@ def checkCollisions(): # Update all positions for i in range(Natoms): Atoms[i].pos = apos[i] = apos[i] + (p[i]/mass)*dt - + # Check for collisions hitlist = checkCollisions() - + for i in range(Natoms): loc = apos[i] if abs(loc.x) > L/2: if loc.x < 0: p[i].x = abs(p[i].x) else: p[i].x = -abs(p[i].x) - + if abs(loc.y) > L/2: if loc.y < 0: p[i].y = abs(p[i].y) else: p[i].y = -abs(p[i].y) - + if abs(loc.z) > L/2: if loc.z < 0: p[i].z = abs(p[i].z) else: p[i].z = -abs(p[i].z) - + timer = clock()-timer print(timer) diff --git a/Demos_no_notebook/MousePicking-from-terminal.py b/Demos_no_notebook/MousePicking-from-terminal.py new file mode 100644 index 00000000..08887e4b --- /dev/null +++ b/Demos_no_notebook/MousePicking-from-terminal.py @@ -0,0 +1,56 @@ +from vpython import * +scene.width = scene.height = 500 +scene.background = color.gray(0.8) +scene.range = 2.2 +scene.caption = "Click to pick an object and make it red." +scene.append_to_caption("\nNote picking of individual curve segments.") +box(pos=vector(-1,0,0), color=color.cyan, opacity=1) +box(pos=vector(1,-1,0), color=color.green) +arrow(pos=vector(-1,-1.3,0), color=color.orange) +cone(pos=vector(2,0,0), axis=vector(0,1,-.3), color=color.blue, size=vector(2,1,1)) +sphere(pos=vector(-1.5,1.5,0), color=color.white, size=.4*vector(3,2,1)) +square = curve(color=color.yellow, radius=.05) +square.append(vector(0,0,0)) +square.append(pos=vector(0,1,0), color=color.cyan, radius=.1) +square.append(vector(1,1,0)) +square.append(pos=vector(1,0,0), radius=.1) +square.append(vector(0.3,-.3,0)) +v0 = vertex(pos=vector(-.5,1.2,0), color=color.green) +v1 = vertex(pos=vector(1,1.2,0), color=color.red) +v2 = vertex(pos=vector(1,2,0), color=color.blue) +v3 = vertex(pos=vector(-.5,2,0), color=color.yellow) +quad(vs=[v0, v1, v2, v3]) +extrusion(path=[vector(-1.8,-1.3,0), vector(-1.4,-1.3,0)], + shape=shapes.circle(radius=.5, thickness=0.4), color=color.yellow) +ring(pos=vector(-0.6,-1.3,0), size=vector(0.2,1,1), color=color.green) +lasthit = None +lastpick = None +lastcolor = None + +def getevent(evt): + global lasthit, lastpick, lastcolor + if lasthit != None: + if lastpick != None: lasthit.modify(lastpick, color=lastcolor) + else: lasthit.color = vector(lastcolor) + lasthit = lastpick = None + + hit = scene.mouse.pick + if hit != None: + lasthit = hit + lastpick = None + if isinstance(hit, curve): # pick individual point of curve + lastpick = hit.segment + lastcolor = hit.point(lastpick)['color'] + hit.modify(lastpick, color=color.red) + elif isinstance(hit, quad): + lasthit = hit.v0 + lastcolor = vector(lasthit.color) # make a copy + lasthit.color = color.red + else: + lastcolor = vector(hit.color) # make a copy + hit.color = color.red + +scene.bind("mousedown", getevent) + +while True: # Needed when running from a terminal + rate(30) diff --git a/Demos_no_notebook/convert_stl/Part1_bin.stl b/Demos_no_notebook/convert_stl/Part1_bin.stl new file mode 100644 index 00000000..99edb62b Binary files /dev/null and b/Demos_no_notebook/convert_stl/Part1_bin.stl differ diff --git a/Demos_no_notebook/convert_stl/convert_stl.py b/Demos_no_notebook/convert_stl/convert_stl.py index 39e4016f..b44594fb 100644 --- a/Demos_no_notebook/convert_stl/convert_stl.py +++ b/Demos_no_notebook/convert_stl/convert_stl.py @@ -1,85 +1,102 @@ -from vpython import * - -# Convert 3D .stl file ("stereo lithography") to VPython 7 object. - -# Limitations: -# Code for binary files needs to be updated to VPython 7. -# Does not deal with color. -# Does not assign texpos values to vertex objects, -# so cannot add a meaningful texture to the final compound object. - -# Original converter and STLbot by Derek Lura 10/06/09 -# Be sure to look at the bottom of the STLbot figure! -# Part1.stl found at 3Dcontentcentral.com; also see 3dvia.com - -# Factory function and handling of binary files by Bruce Sherwood 1/26/10 -# Conversion to VPython 7 by Bruce Sherwood 2018 May 8 - -# Give this factory function an .stl file and it returns a compound object, -# to permit moving and rotating. - -# Specify the file as a file name. - -# See http://en.wikipedia.org/wiki/STL_(file_format) -# Text .stl file starts with a header that begins with the word "solid". -# Binary .stl file starts with a header that should NOT begin with the word "solid", -# but this rule seems not always to be obeyed. -# Currently the 16-bit unsigned integer found after each triangle in a binary -# file is ignored; some versions of .stl files put color information in this value. - -def stl_to_triangles(fileinfo): # specify file - # Accept a file name or a file descriptor; make sure mode is 'rb' (read binary) - fd = open(fileinfo, mode='rb') - text = fd.read() - tris = [] # list of triangles to compound - if False: # prevent executing code for binary file - pass - # The following code for binary files must be updated: -# if chr(0) in text: # if binary file -# text = text[84:] -# L = len(text) -# N = 2*(L//25) # 25/2 floats per point: 4*3 float32's + 1 uint16 -# triPos = [] -# triNor = [] -# n = i = 0 -# while n < L: -# triNor[i] = fromstring(text[n:n+12], float32) -# triPos[i] = fromstring(text[n+12:n+24], float32) -# triPos[i+1] = fromstring(text[n+24:n+36], float32) -# triPos[i+2] = fromstring(text[n+36:n+48], float32) -# colors = fromstring(text[n+48:n+50], uint16) -# if colors != 0: -# print '%x' % colors -# if triNor[i].any(): -# triNor[i] = triNor[i+1] = triNor[i+2] = norm(vector(triNor[i])) -# else: -# triNor[i] = triNor[i+1] = triNor[i+2] = \ -# norm(cross(triPos[i+1]-triPos[i],triPos[i+2]-triPos[i])) -# n += 50 -# i += 3 - else: - fd.seek(0) - fList = fd.readlines() - - # Decompose list into vertex positions and normals - vs = [] - for line in fList: - FileLine = line.split( ) - if FileLine[0] == b'facet': - N = vec(float(FileLine[2]), float(FileLine[3]), float(FileLine[4])) - elif FileLine[0] == b'vertex': - vs.append( vertex(pos=vec(float(FileLine[1]), float(FileLine[2]), float(FileLine[3])), normal=N, color=color.white) ) - if len(vs) == 3: - tris.append(triangle(vs=vs)) - vs = [] - - return compound(tris) - -if __name__ == '__main__': - man = stl_to_triangles('STLbot.stl') - man.pos = vec(-200,0,0) - man.color = color.cyan - part = stl_to_triangles('Part1.stl') - part.size *= 200 - part.pos = vec(250,0,0) - part.color = color.orange \ No newline at end of file +from vpython import * + +# Convert 3D .stl file ("stereo lithography") to VPython 7 object. + +# Limitations: +# Code for binary files needs to be updated to VPython 7. +# Does not deal with color. +# Does not assign texpos values to vertex objects, +# so cannot add a meaningful texture to the final compound object. + +# Original converter and STLbot by Derek Lura 10/06/09 +# Be sure to look at the bottom of the STLbot figure! +# Part1.stl found at 3Dcontentcentral.com; also see 3dvia.com + +# Factory function and handling of binary files by Bruce Sherwood 1/26/10 +# Conversion to VPython 7 by Bruce Sherwood 2018 May 8 + +# Give this factory function an .stl file and it returns a compound object, +# to permit moving and rotating. + +# Specify the file as a file name. + +# See http://en.wikipedia.org/wiki/STL_(file_format) +# Text .stl file starts with a header that begins with the word "solid". +# Binary .stl file starts with a header that should NOT begin with the word "solid", +# but this rule seems not always to be obeyed. +# Currently the 16-bit unsigned integer found after each triangle in a binary +# file is ignored; some versions of .stl files put color information in this value. + +def stl_to_triangles(fileinfo): # specify file + # Accept a file name or a file descriptor; make sure mode is 'rb' (read binary) + fd = open(fileinfo, mode='rb') + text = fd.read() + tris = [] # list of triangles to compound + keywords = [b'outer', b'endloop', b'endfacet', b'solid', b'endsolid'] + if False: # prevent executing code for binary file + pass + # The following code for binary files must be updated: +# if chr(0) in text: # if binary file +# text = text[84:] +# L = len(text) +# N = 2*(L//25) # 25/2 floats per point: 4*3 float32's + 1 uint16 +# triPos = [] +# triNor = [] +# n = i = 0 +# while n < L: +# triNor[i] = fromstring(text[n:n+12], float32) +# triPos[i] = fromstring(text[n+12:n+24], float32) +# triPos[i+1] = fromstring(text[n+24:n+36], float32) +# triPos[i+2] = fromstring(text[n+36:n+48], float32) +# colors = fromstring(text[n+48:n+50], uint16) +# if colors != 0: +# print '%x' % colors +# if triNor[i].any(): +# triNor[i] = triNor[i+1] = triNor[i+2] = norm(vector(triNor[i])) +# else: +# triNor[i] = triNor[i+1] = triNor[i+2] = \ +# norm(cross(triPos[i+1]-triPos[i],triPos[i+2]-triPos[i])) +# n += 50 +# i += 3 + else: + fd.seek(0) + fList = fd.readlines() + print('Number of lines =', len(fList)) + + # Decompose list into vertex positions and normals + ret = [] # will return a list of compounds if necessary + vs = [] + vertices = 0 + for line in fList: + FileLine = line.split( ) + first = FileLine[0] + if first == b'facet': + N = vec(float(FileLine[2]), float(FileLine[3]), float(FileLine[4])) + elif first == b'vertex': + vertices += 1 + vs.append( vertex(pos=vec(float(FileLine[1]), float(FileLine[2]), float(FileLine[3])), normal=N, color=color.white) ) + if len(vs) == 3: + tris.append(triangle(vs=vs)) + vs = [] + if vertices > 64000: + print(vertices) + ret.append(compound(tris)) + tris = [] + vertices = 0 + elif first in keywords: + pass + else: + print(line) # for debugging + if len(tris) > 0: ret.append(compound(tris)) + if len(ret) == 1: return ret[0] + else: return ret + +if __name__ == '__main__': + man = stl_to_triangles('STLbot.stl') + man.pos = vec(-200,0,0) + man.color = color.cyan + part = stl_to_triangles('Part1.stl') + part.size *= 200 + part.pos = vec(250,0,0) + part.color = color.orange + print('Done') diff --git a/Demos_no_notebook/convert_stl/convert_stl_bin.py b/Demos_no_notebook/convert_stl/convert_stl_bin.py new file mode 100644 index 00000000..f5d60ea8 --- /dev/null +++ b/Demos_no_notebook/convert_stl/convert_stl_bin.py @@ -0,0 +1,86 @@ +# UINT8[80] – Header - 80 bytes +# UINT32 – Number of triangles - 4 bytes +# foreach triangle - 50 bytes: + # REAL32[3] – Normal vector - 12 bytes + # REAL32[3] – Vertex 1 - 12 bytes + # REAL32[3] – Vertex 2 - 12 bytes + # REAL32[3] – Vertex 3 - 12 bytes + # UINT16 – Attribute byte count - 2 bytes +# end + +from vpython import * +import struct +from collections import namedtuple +import numpy as np + +Ctypes = namedtuple('Ctype', ['fmt', 'size']) + +Ctype_names = ['char', 'signed char', 'unsigned char', '_Bool', 'short', \ + 'unsigned short', 'int', 'unsigned int', 'long', 'unsigned long',\ + 'long long', 'unsigned long long', 'float', 'double', 'char[]'] +Ctype_sizes = [1, 1, 1, 1, 2,\ + 2, 4, 4, 4, 4,\ + 8, 8, 4, 8, -1] +Ctype_formats = ['c', 'b', 'B', '?', 'h',\ + 'H', 'i', 'I', 'l', 'L',\ + 'q', 'Q', 'f', 'd', 's'] + +Ctype_dict = {} +for i, name in enumerate(Ctype_names): + Ctype_dict[name] = Ctypes(Ctype_formats[i], Ctype_sizes[i]) + +def binary_reader(fid, Ctype, **opts): + assert Ctype in Ctype_dict, "Ctype not found in Ctype_dict" + if Ctype == 'char[]': + string = [] + for ch in range(opts['size']): + char = struct.unpack(Ctype_dict['char'].fmt, fid.read(Ctype_dict['char'].size))[0] + if chr(0).encode('utf-8') not in char: + string.append(char.decode('utf-8')) + else: + string.append(' ') + return ''.join(string) + + if Ctype == 'char': + return ord(struct.unpack(Ctype_dict[Ctype].fmt, fid.read(Ctype_dict[Ctype].size))[0].decode('utf-8')) + return struct.unpack(Ctype_dict[Ctype].fmt, fid.read(Ctype_dict[Ctype].size))[0] + +def stl_to_triangles(filename): + with open(filename, "rb") as f: + header = binary_reader(f,'char[]', size = 80) + numOfTri = binary_reader(f,'unsigned int') + + print(header) + print(numOfTri) + + tris = [] + + for i in range(numOfTri): + vs = [] + x1, x2, x3 = binary_reader(f,'float'), binary_reader(f,'float'), binary_reader(f,'float') + N = vec(x1, x2, x3) + + x1, x2, x3 = binary_reader(f,'float'), binary_reader(f,'float'), binary_reader(f,'float') + p1 = vertex(pos = vec(x1, x2, x3), normal=N, color=color.white) + vs.append(p1) + + x1, x2, x3 = binary_reader(f,'float'), binary_reader(f,'float'), binary_reader(f,'float') + p2 = vertex(pos = vec(x1, x2, x3), normal=N, color=color.white) + vs.append(p2) + + x1, x2, x3 = binary_reader(f,'float'), binary_reader(f,'float'), binary_reader(f,'float') + p3 = vertex(pos = vec(x1, x2, x3), normal=N, color=color.white) + vs.append(p3) + + attr = binary_reader(f,'unsigned short') + tris.append(triangle(vs=vs)) + + return compound(tris) + +if __name__ == '__main__': + filename = r"\convert_stl\Part1_bin.stl" # update this file + man = stl_to_triangles(filename) + man.color = color.orange + + + \ No newline at end of file diff --git a/Demos_no_notebook/qt.py b/Demos_no_notebook/qt.py new file mode 100644 index 00000000..6b9f76a2 --- /dev/null +++ b/Demos_no_notebook/qt.py @@ -0,0 +1,4 @@ +from vpython import * +set_browser(type='pyqt5') +b = box() +scene.caption = "A box should appear in the window above" diff --git a/JupyterPythonDemos.zip b/JupyterPythonDemos.zip index 946c4e2c..08177048 100644 Binary files a/JupyterPythonDemos.zip and b/JupyterPythonDemos.zip differ diff --git a/MANIFEST.in b/MANIFEST.in index 547d72bf..07607cc9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,4 @@ include CHANGES.txt include LICENSE.txt include README.md -include ez_setup.py -include versioneer.py -include vpython/_version.py include vpython/cyvector.pyx diff --git a/README.md b/README.md index dc6102a0..f5f615da 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,56 @@ controls available to zoom and rotate the camera: Shift-drag to pan left/right and up/down. Touch screen: pinch/extend to zoom, swipe or two-finger rotate. -Currently, to re-run a VPython program in a Jupyter notebook you need to click the circular arrow icon to "restart the kernel" and then click the red-highlighted button, then click in the first cell, then click the run icon. Alternatively, if you insert "scene = canvas()" at the start of your program, you can rerun the program without restarting the kernel. +Currently, to re-run a VPython program in a Jupyter notebook you need to click the circular arrow icon to "restart the kernel" and then click the red-highlighted button, then click in the first cell, then click the run icon. Alternatively, if you insert `scene = canvas()` at the start of your program, you can rerun the program without restarting the kernel. -Run example VPython programs: [![Binder](http://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/BruceSherwood/vpython-jupyter/7.4.7?filepath=index.ipynb) +Run example VPython programs: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/vpython/vpython-jupyter/7.6.1?filepath=index.ipynb) + +## Installation for developers from package source + +You should install Cython (`conda install cython` or `pip install cython`) so +that the fast version of the vector class can be generated and compiled. You +may also need to install a compiler (command line tools on Mac, community +edition on Visual Studio on Windows). + +If you don't have a compilier vpython should still work, but code that +generates a lot of vectors may run a little slower. + +To install vpython from source run this command from the source directory +after you have downloaded it: + +``` +pip install -e . +``` + +The `-e` option installs the code with symbolic links so that change you make +show up without needing to reinstall. + +If you also need the JupyterLab extension, please see the instructions +in the `labextension` folder. ## vpython build status (for the vpython developers) -[![Build Status](https://travis-ci.org/BruceSherwood/vpython-jupyter.svg?branch=master)](https://travis-ci.org/BruceSherwood/vpython-jupyter) [![Build status](https://ci.appveyor.com/api/projects/status/wsdjmh8aehd1o0qg?svg=true)](https://ci.appveyor.com/project/mwcraig/vpython-jupyter) +![Testing workfloww](https://github.com/vpython/vpython-jupyter/actions/workflows/build.yml/badge.svg) + + +## Working with the source code + +Here is an overview of the software architecture: + +https://vpython.org/contents/VPythonArchitecture.pdf + +The vpython module uses the GlowScript library (vpython/vpython_libraries/glow.min.js). The GlowScript repository is here: + +https://github.com/vpython/glowscript + +In the GlowScript repository's docs folder, GlowScriptOverview.txt provides more details on the GlowScript architecture. + +Here is information on how to run GlowScript VPython locally, which makes possible testing changes to the GlowScript library: + +https://www.glowscript.org/docs/GlowScriptDocs/local.html + +If you execute build_original_no_overload.py, and change the statement "if True:" to "if False", you will generate into the ForInstalledPython folder an un-minified glow.min.js which can be copied to site-packages/vpython/vpython_libraries and tested by running your test in (say) idle or spyder. (Running in Jupyter notebook or Jupyterlab requires additional fiddling.) + +Note that in site-packages/vpython/vpython_libraries it is glowcomm.html that is used by launchers such as idle or spyder; glowcomm.js is used with Jupyter notebook (and a modified version is used in Jupyterlab). +Placing console.log(....) statements in the GlowScript code or in the JavaScript section of glowcomm.html can be useful in debugging. You may also need to put debugging statements into site-packages/vpython/vpython.py. diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..29cf641c --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,41 @@ +# How to do a vpython release + +There are two things that need to be released: + ++ The Python package `vpython-jupyter` ++ The JupyterLab extension called `vpython` + +## Releasing the Python package + +Versions for the Python package are generated using +[`versioneer`](https://github.com/warner/python-versioneer). That means that +to make a new release the first step is to generate a git tag with the release +numbers. + +Release numbers should be in the format `X.Y.Z`, e.g. `7.4.2` or `7.5.0`. + +Currently, all of the build/upload of the releases is handled on Travis-CI and +Appveyor, but in principle one could build the packages on local machines and +upload them. + +Tagging the commit in GitHub or doing the tag locally and pushing it to GitHub +will trigger the builds without any further action. + +If you want to build locally: + ++ Build and upload the source distribution (once per release) + * `python setup.py sdist` -- generate the source distribution + * `twine upload dist/*.tar.gz` -- upload the generated distribution ++ Build and upload binary wheels (once per release *per platform*) + * `python setup.py wheel` -- generate binary distribution on the platform + on which you are running. + * `twine upload dist/*.whl` -- upload the generated distribution ++ Build the conda package (once per release *per platform*) + * `conda build vpython.recipe` + * `anaconda upload `*recipe_location* -- replace *recipe_location* with + the directory output by conda build. + +## Releasing the JupyterLab Extension + +Please see the instructions in the `labextension` folder for building and +releasing the JupyterLab extension. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 02ac9b2a..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,129 +0,0 @@ -# Configure appveyor for builds. - -environment: - # Install appropriate conda here based on TARGET_ARCH - CONDA_INSTALL_LOCN: "C:\\conda" - - CONDA_VERSION_PIN: "4.5" - TQDM_PIN: "" - - # These installs are needed on windows but not other platforms. - INSTALL_ON_WINDOWS: "patch psutil" - - BINSTAR_TOKEN: - secure: eSsu75dpqknmh2NnrfPASDSvzojBRSyoyW9ZdF5VauukhWxmYHbW8E5UE2ZmYnxT - - PYPI_PASSWORD: - secure: Xtu8c7MEUwNUJN08oehTSQ== - - PYPI_USER: - secure: hSkBSpb86gg/J+PxsYWbeg== - matrix: - # Unfortunately, compiler/SDK configuration for 64 bit builds depends on - # python version. Right now conda build does not configure the SDK, and - # the appveyor setup only sets up the SDK once, so separate by python - # versions. - - TARGET_ARCH: "x64" - CONDA_PY: "3.7" - CONDA_INSTALL_LOCN: "C:\\Miniconda37-x64" - - TARGET_ARCH: "x64" - CONDA_PY: "3.6" - CONDA_INSTALL_LOCN: "C:\\Miniconda36-x64" - - TARGET_ARCH: "x64" - CONDA_PY: "3.5" - CONDA_INSTALL_LOCN: "C:\\Miniconda35-x64" - # Need to pin this on python 35 for reasons unfathomable to me (well, - # probably related to the fact that tqdm 4.29.0 is noarch, and - # conda 3.5.11 doesn't handle that well, maybe) - TQDM_PIN: "=4.26" - - # 32-bit builds - - TARGET_ARCH: "x86" - CONDA_PY: "3.7" - CONDA_INSTALL_LOCN: "C:\\Miniconda37" - - TARGET_ARCH: "x86" - CONDA_PY: "3.6" - CONDA_INSTALL_LOCN: "C:\\Miniconda36" - - TARGET_ARCH: "x86" - CONDA_PY: "3.5" - CONDA_INSTALL_LOCN: "C:\\Miniconda35" - # Need to pin this on python 35 for reasons unfathomable to me (well, - # probably related to the fact that tqdm 4.29.0 is noarch, and - # conda 3.5.11 doesn't handle that well, maybe) - TQDM_PIN: "=4.26" - -# We always use a 64-bit machine, but can build x86 distributions -# with the TARGET_ARCH variable. -platform: - - x64 - -# Only run the x86 jobs when there is a release -for: -- - matrix: - only: - - TARGET_ARCH: "x86" - - skip_non_tags: true - -# Uncomment this to allow RDP into appveyor worker -# init: -# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) - -install: - - cmd: call %CONDA_INSTALL_LOCN%\Scripts\activate.bat - # Gets us vpnotebook - - cmd: conda config --add channels vpython - - - cmd: conda config --set always_yes true - # - conda install conda=4.5 - # - cmd: conda update --quiet conda python - - - echo %CONDA_PY% - # Need to do this to pick up newer versions of conda - - if "%CONDA_PY%"=="3.5" conda update --quiet conda - - - cmd: conda install --quiet conda=%CONDA_VERSION_PIN% jinja2 conda-build anaconda-client cython wheel %INSTALL_ON_WINDOWS% tqdm%TQDM_PIN% - - #- conda create --quiet -n wheel-build python=%CONDA_PY% wheel cython - - conda info -a - -# Skip .NET project specific build phase. -build: off - -test_script: - # Not much of a real test yet, just try to build myself... - - conda build --quiet vpython.recipe - # ...and build a wheel, in the test environment so we have the right - # python version. - # Write the output file location to a file... - - conda build --output vpython.recipe > to_upload.txt - - set /P BUILT_PACKAGE= label.txt - - set /P UPLOAD_LABEL=="+version) - return - except pkg_resources.VersionConflict: - e = sys.exc_info()[1] - if was_imported: - sys.stderr.write( - "The required version of distribute (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U distribute'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) - except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) - finally: - if not no_fake: - _create_fake_setuptools_pkg_info(to_dir) - -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15): - """Download distribute from a specified location and return its filename - - `version` should be a valid distribute version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download - attempt. - """ - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - try: - from urllib.request import urlopen - except ImportError: - from urllib2 import urlopen - tgz_name = "distribute-%s.tar.gz" % version - url = download_base + tgz_name - saveto = os.path.join(to_dir, tgz_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - log.warn("Downloading %s", url) - src = urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = src.read() - dst = open(saveto, "wb") - dst.write(data) - finally: - if src: - src.close() - if dst: - dst.close() - return os.path.realpath(saveto) - -def _no_sandbox(function): - def __no_sandbox(*args, **kw): - try: - from setuptools.sandbox import DirectorySandbox - if not hasattr(DirectorySandbox, '_old'): - def violation(*args): - pass - DirectorySandbox._old = DirectorySandbox._violation - DirectorySandbox._violation = violation - patched = True - else: - patched = False - except ImportError: - patched = False - - try: - return function(*args, **kw) - finally: - if patched: - DirectorySandbox._violation = DirectorySandbox._old - del DirectorySandbox._old - - return __no_sandbox - -def _patch_file(path, content): - """Will backup the file then patch it""" - existing_content = open(path).read() - if existing_content == content: - # already patched - log.warn('Already patched.') - return False - log.warn('Patching...') - _rename_path(path) - f = open(path, 'w') - try: - f.write(content) - finally: - f.close() - return True - -_patch_file = _no_sandbox(_patch_file) - -def _same_content(path, content): - return open(path).read() == content - -def _rename_path(path): - new_name = path + '.OLD.%s' % time.time() - log.warn('Renaming %s into %s', path, new_name) - os.rename(path, new_name) - return new_name - -def _remove_flat_installation(placeholder): - if not os.path.isdir(placeholder): - log.warn('Unkown installation at %s', placeholder) - return False - found = False - for file in os.listdir(placeholder): - if fnmatch.fnmatch(file, 'setuptools*.egg-info'): - found = True - break - if not found: - log.warn('Could not locate setuptools*.egg-info') - return - - log.warn('Removing elements out of the way...') - pkg_info = os.path.join(placeholder, file) - if os.path.isdir(pkg_info): - patched = _patch_egg_dir(pkg_info) - else: - patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) - - if not patched: - log.warn('%s already patched.', pkg_info) - return False - # now let's move the files out of the way - for element in ('setuptools', 'pkg_resources.py', 'site.py'): - element = os.path.join(placeholder, element) - if os.path.exists(element): - _rename_path(element) - else: - log.warn('Could not find the %s element of the ' - 'Setuptools distribution', element) - return True - -_remove_flat_installation = _no_sandbox(_remove_flat_installation) - -def _after_install(dist): - log.warn('After install bootstrap.') - placeholder = dist.get_command_obj('install').install_purelib - _create_fake_setuptools_pkg_info(placeholder) - -def _create_fake_setuptools_pkg_info(placeholder): - if not placeholder or not os.path.exists(placeholder): - log.warn('Could not find the install location') - return - pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) - setuptools_file = 'setuptools-%s-py%s.egg-info' % \ - (SETUPTOOLS_FAKED_VERSION, pyver) - pkg_info = os.path.join(placeholder, setuptools_file) - if os.path.exists(pkg_info): - log.warn('%s already exists', pkg_info) - return - - log.warn('Creating %s', pkg_info) - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - - pth_file = os.path.join(placeholder, 'setuptools.pth') - log.warn('Creating %s', pth_file) - f = open(pth_file, 'w') - try: - f.write(os.path.join(os.curdir, setuptools_file)) - finally: - f.close() - -_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) - -def _patch_egg_dir(path): - # let's check if it's already patched - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - if os.path.exists(pkg_info): - if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): - log.warn('%s already patched.', pkg_info) - return False - _rename_path(path) - os.mkdir(path) - os.mkdir(os.path.join(path, 'EGG-INFO')) - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - return True - -_patch_egg_dir = _no_sandbox(_patch_egg_dir) - -def _before_install(): - log.warn('Before install bootstrap.') - _fake_setuptools() - - -def _under_prefix(location): - if 'install' not in sys.argv: - return True - args = sys.argv[sys.argv.index('install')+1:] - for index, arg in enumerate(args): - for option in ('--root', '--prefix'): - if arg.startswith('%s=' % option): - top_dir = arg.split('root=')[-1] - return location.startswith(top_dir) - elif arg == option: - if len(args) > index: - top_dir = args[index+1] - return location.startswith(top_dir) - if arg == '--user' and USER_SITE is not None: - return location.startswith(USER_SITE) - return True - - -def _fake_setuptools(): - log.warn('Scanning installed packages') - try: - import pkg_resources - except ImportError: - # we're cool - log.warn('Setuptools or Distribute does not seem to be installed.') - return - ws = pkg_resources.working_set - try: - setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', - replacement=False)) - except TypeError: - # old distribute API - setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) - - if setuptools_dist is None: - log.warn('No setuptools distribution found') - return - # detecting if it was already faked - setuptools_location = setuptools_dist.location - log.warn('Setuptools installation detected at %s', setuptools_location) - - # if --root or --preix was provided, and if - # setuptools is not located in them, we don't patch it - if not _under_prefix(setuptools_location): - log.warn('Not patching, --root or --prefix is installing Distribute' - ' in another location') - return - - # let's see if its an egg - if not setuptools_location.endswith('.egg'): - log.warn('Non-egg installation') - res = _remove_flat_installation(setuptools_location) - if not res: - return - else: - log.warn('Egg installation') - pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') - if (os.path.exists(pkg_info) and - _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): - log.warn('Already patched.') - return - log.warn('Patching...') - # let's create a fake egg replacing setuptools one - res = _patch_egg_dir(setuptools_location) - if not res: - return - log.warn('Patched done.') - _relaunch() - - -def _relaunch(): - log.warn('Relaunching...') - # we have to relaunch the process - # pip marker to avoid a relaunch bug - if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']: - sys.argv[0] = 'setup.py' - args = [sys.executable] + sys.argv - sys.exit(subprocess.call(args)) - - -def _extractall(self, path=".", members=None): - """Extract all members from the archive to the current working - directory and set owner, modification time and permissions on - directories afterwards. `path' specifies a different directory - to extract to. `members' is optional and must be a subset of the - list returned by getmembers(). - """ - import copy - import operator - from tarfile import ExtractError - directories = [] - - if members is None: - members = self - - for tarinfo in members: - if tarinfo.isdir(): - # Extract directories with a safe mode. - directories.append(tarinfo) - tarinfo = copy.copy(tarinfo) - tarinfo.mode = 448 # decimal for oct 0700 - self.extract(tarinfo, path) - - # Reverse sort directories. - if sys.version_info < (2, 4): - def sorter(dir1, dir2): - return cmp(dir1.name, dir2.name) - directories.sort(sorter) - directories.reverse() - else: - directories.sort(key=operator.attrgetter('name'), reverse=True) - - # Set correct owner, mtime and filemode on directories. - for tarinfo in directories: - dirpath = os.path.join(path, tarinfo.name) - try: - self.chown(tarinfo, dirpath) - self.utime(tarinfo, dirpath) - self.chmod(tarinfo, dirpath) - except ExtractError: - e = sys.exc_info()[1] - if self.errorlevel > 1: - raise - else: - self._dbg(1, "tarfile: %s" % e) - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - tarball = download_setuptools() - _install(tarball) - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/index.ipynb b/index.ipynb index 94c11c39..55ad4a2f 100644 --- a/index.ipynb +++ b/index.ipynb @@ -22,11 +22,11 @@ "\n", "[Binary star](Demos/BinaryStar.ipynb): two stars orbit each other\n", "\n", - "[Bounce](Demos/Bounce.ipynb): a ball bounces in a 3D box\n", + "[Bounce](Demos/Bounce-VPython.ipynb): a ball bounces in a 3D box\n", "\n", "[Local moving lights](Demos/BoxLightTest.ipynb): local lights moving around the scene\n", "\n", - "[Buttons, sliders, and menus](Demos/ButtonsSlidersMenus1.ipynb): rotating box, Jupyter widgets\n", + "[Buttons, sliders, and menus](Demos/ButtonsSlidersMenus1-async.ipynb): rotating box, Jupyter widgets\n", "\n", "[Buttons, sliders, and menus](Demos/ButtonsSlidersMenus2.ipynb): rotating box, GlowScript widgets\n", "\n", @@ -68,7 +68,7 @@ "\n", "[Surreal Stonehenge](Demos/Stonehenge.ipynb): fly through a surreal scene\n", "\n", - "[Textures](Demos/Textures1.ipynb): textures included with Jupyter VPython, Jupyter widgets\n", + "[Textures](Demos/Textures1-async.ipynb): textures included with Jupyter VPython, Jupyter widgets\n", "\n", "[Textures](Demos/Textures2.ipynb): textures included with Jupyter VPython, GlowScript widgets\n", "\n", diff --git a/labextension/jupyterlab-vpython b/labextension/jupyterlab-vpython new file mode 160000 index 00000000..6a217306 --- /dev/null +++ b/labextension/jupyterlab-vpython @@ -0,0 +1 @@ +Subproject commit 6a217306bb3fea48cb04ae0e6980b871bae5c626 diff --git a/labextension/vpython/BuildingJlabExtension.txt b/labextension/vpython/BuildingJlabExtension.txt deleted file mode 100644 index a8b09a3f..00000000 --- a/labextension/vpython/BuildingJlabExtension.txt +++ /dev/null @@ -1,72 +0,0 @@ - From John Coady: Here are instructions on how to create your own vpython jupyterlab extension on your computer. You will need to come up with a unique name for your vpython labextension so you can make is something like "vpythontest" or "myvpython" or something else that you would like to call it. Just don't call it "vpython" otherwise you might have installation problems with the official vpython labextension. So here are the steps. - -Step 1: In a console window, activate the conda environment where you have jupyterlab installed. - - jupyter labextension list - -JupyterLab v0.35.3 -Known labextensions: - app dir: C:\Users\Bruce\Anaconda3\share\jupyter\lab - vpython v0.2.9 enabled ok - - -Step 2: Uninstall the official vpython labextension from this jupyterlab so that there are no conflicts with your custom vpython labextension. - - jupyter labextension uninstall vpython - - Check that the vpython labextension is uninstalled. - - jupyter labextension list - - -Step 3: In the console window, navigate to your home directory. The following command will ask you for the name you want to give your custom labextension (it will also ask you for a github address, which you can ignore). Remember, don't call your extension vpython. Here, "my-cookie-cutter-name" will be the name you give your custom extension, and the cd will put you in a folder created by the coookiecutter machinery. - - pip install cookiecutter - cookiecutter https://github.com/jupyterlab/extension-cookiecutter-ts - cd my-cookie-cutter-name - - -Step 4: Now that you are in your directory for your custom labextension, enter the following commands. - - npm install - npm install --save @jupyterlab/notebook @jupyterlab/application @jupyterlab/apputils @jupyterlab/docregistry @phosphor/disposable script-loader - - -Step 5: If you look at the contents of this directory where your labextension is located it should contain some files and a directory called "src" and another directory called "style". Copy all of the files and the directories from - - https://github.com/BruceSherwood/vpython-jupyter/tree/master/labextension/vpython - -to your custom labextension directory and overwrite the existing files. - - -Step 6: Build your custom extension. - - npm run build - - -Step 7: You should now have a directory called "lib" in your custom labextension folder. Copy vpython_data and vpython_libraries folders and their contents from - - https://github.com/BruceSherwood/vpython-jupyter/tree/master/vpython - - to the lib directory. - - -Step 8: Now build and install your custom labextension. - - npm run build - jupyter labextension install . - - -Step 9: Check that your labextension is installed. - - jupyter labextension list - - -Step 10: In a separate console window activate the conda environment that contains jupyterlab that you are working with and launch jupyterlab. - - jupyter lab - - You should have your custom vpython working in jupyterlab. - - -If you make a change to the code then you just need to repeat step 8 to rebuild and install your changes in jupyterlab. You should probably close jupyterlab and restart it for the changes to be picked up. \ No newline at end of file diff --git a/labextension/vpython/README.md b/labextension/vpython/README.md index 0b6ca56e..00e81f0f 100644 --- a/labextension/vpython/README.md +++ b/labextension/vpython/README.md @@ -1,34 +1,108 @@ -# vpython +# jupyterlab_vpython -VPython labextension +A VPython extension for JupyterLab +## Requirements -## Prerequisites +- JupyterLab >= 3.0.0 +- VPython >= 7.6.4 -* JupyterLab ^0.35.0 -* VPython Package >=7.4.8 (pip install vpython) or (conda install -c vpython vpython) +## Install -## Installation +To install the extension, execute: ```bash -jupyter labextension install vpython +pip install jupyterlab_vpython +``` + +## Uninstall + +To remove the extension, execute: + +```bash +pip uninstall jupyterlab_vpython ``` ## Development -For a development install (requires npm version 4 or later), do the following in the repository directory: +For a development install use the instructions for creating a custom jupyter labextension as a guide: + +https://jupyterlab.readthedocs.io/en/latest/extension/extension_tutorial.html + +Follow the steps in the tutorial + +Step 1: + +```bash +conda create -n jupyterlab-ext --override-channels --strict-channel-priority -c conda-forge -c nodefaults jupyterlab=4 nodejs=18 git copier=7 jinja2-time +``` + +Step 2: + +```bash +conda activate jupyterlab-ext +``` + +Step 3: Get the jupyterlab_vpython repository + +```bash +git clone https://github.com/jcoady/jupyterlab_vpython.git +``` + +Step 4: ```bash -npm install -npm install --save @jupyterlab/notebook @jupyterlab/application @jupyterlab/apputils @jupyterlab/docregistry @phosphor/disposable script-loader -npm run build -jupyter labextension install . +cd jupyterlab_vpython ``` -To rebuild the package and the JupyterLab app: +Step 5: +```bash +jlpm install +jlpm add @jupyterlab/application +jlpm add @jupyterlab/apputils +jlpm add @jupyterlab/coreutils +jlpm add @jupyterlab/docregistry +jlpm add @jupyterlab/notebook +jlpm add @lumino/disposable +jlpm add file-loader +jlpm add plotly.js-dist-min +jlpm add script-loader + +cp -r ../../vpython/vpython_{libraries,data} . +jlpm run build +``` + +Step 6: install the extension into the JupyterLab environment. + +```bash +pip install -ve . +``` + +Step 7: After the install completes, open a second terminal. +Run these commands to activate the jupyterlab-ext environment +and start JupyterLab in your default web browser. + +```bash +conda activate jupyterlab-ext +jupyter lab +``` + +Step 8: Packaging the extension. + +Before generating a package, we first need to install build. + +```bash +pip install build +``` + +To create a Python source package (.tar.gz) in the dist/ directory, do: ```bash -npm run build -jupyter lab build +python -m build -s ``` +To create a Python wheel package (.whl) in the dist/ directory, do: + +```bash +python -m build +``` diff --git a/labextension/vpython/package-lock.json b/labextension/vpython/package-lock.json deleted file mode 100644 index f195fd13..00000000 --- a/labextension/vpython/package-lock.json +++ /dev/null @@ -1,981 +0,0 @@ -{ - "name": "jupyterlab_vp", - "version": "0.2.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@jupyterlab/application": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/application/-/application-0.19.1.tgz", - "integrity": "sha512-SyfJyXmAPBtlJbvyuvMqOM6nIjsXDElXbDs+YKJRGET7pm3PZ85Zz3YZ1qyoJtH/CUcUQU1ihqqAHcm2H5MQ7w==", - "requires": { - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/docregistry": "^0.19.1", - "@jupyterlab/rendermime": "^0.19.1", - "@jupyterlab/rendermime-interfaces": "^1.2.1", - "@jupyterlab/services": "^3.2.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/application": "^1.6.0", - "@phosphor/commands": "^1.6.1", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/messaging": "^1.2.2", - "@phosphor/properties": "^1.1.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/widgets": "^1.6.0" - } - }, - "@jupyterlab/apputils": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/apputils/-/apputils-0.19.1.tgz", - "integrity": "sha512-//vajDyVyKwXU7qycRBJC37dljjWeya+YpJV3z5sSXgVBefEBZ0kcrJ92ugDSht4I4SRv6x+kw6K9mBXKaYu9Q==", - "requires": { - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/services": "^3.2.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/commands": "^1.6.1", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/domutils": "^1.1.2", - "@phosphor/messaging": "^1.2.2", - "@phosphor/properties": "^1.1.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/virtualdom": "^1.1.2", - "@phosphor/widgets": "^1.6.0", - "@types/react": "~16.4.13", - "react": "~16.4.2", - "react-dom": "~16.4.2", - "sanitize-html": "~1.18.2" - } - }, - "@jupyterlab/attachments": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/attachments/-/attachments-0.19.1.tgz", - "integrity": "sha512-JujH1ExOao6ytb0J305iqOtj+GxISq/9zgt9kugwUCYZpvGdHau7WAYQs5YDPIQSZjSdYzjBREssFoirOOSVmA==", - "requires": { - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@jupyterlab/rendermime": "^0.19.1", - "@jupyterlab/rendermime-interfaces": "^1.2.1", - "@phosphor/disposable": "^1.1.2", - "@phosphor/signaling": "^1.2.2" - } - }, - "@jupyterlab/cells": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/cells/-/cells-0.19.1.tgz", - "integrity": "sha512-7vVriCTVC6sXPDvh1/L2WzdAga2Xkg0cFgEcXuIJLwt25ppmqs9DYgMhhUBr0G734mZwmQDXCJ/BqOUw1qgHKg==", - "requires": { - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/attachments": "^0.19.1", - "@jupyterlab/codeeditor": "^0.19.1", - "@jupyterlab/codemirror": "^0.19.1", - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@jupyterlab/outputarea": "^0.19.1", - "@jupyterlab/rendermime": "^0.19.1", - "@jupyterlab/services": "^3.2.1", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/messaging": "^1.2.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/widgets": "^1.6.0", - "react": "~16.4.2" - } - }, - "@jupyterlab/codeeditor": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/codeeditor/-/codeeditor-0.19.1.tgz", - "integrity": "sha512-mCL4YiCCX5JOAIs21mleSmlkejAw6FVLwmvKGxBCwoNj+cSYUKzGBYuA2xvxAZi/5HaiQU8R+ITbAwk2QoMc6w==", - "requires": { - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/messaging": "^1.2.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/widgets": "^1.6.0", - "react": "~16.4.2", - "react-dom": "~16.4.2" - } - }, - "@jupyterlab/codemirror": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/codemirror/-/codemirror-0.19.1.tgz", - "integrity": "sha512-JbZRm9vW1lN4Y4VghBQEys7vWoaZM54E0OdTlWjJq1kF8WhKWzp8Do/tnQ5fKVUGw40BMB0xBnfAaHHCSs5vHw==", - "requires": { - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/codeeditor": "^0.19.1", - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/signaling": "^1.2.2", - "codemirror": "~5.39.0" - } - }, - "@jupyterlab/coreutils": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-2.2.1.tgz", - "integrity": "sha512-XkGMBXqEAnENC4fK/L3uEqqxyNUtf4TI/1XNDln7d5VOPHQJSBTbYlBAZ0AQotn2qbs4WqmV6icxje2ITVedqQ==", - "requires": { - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/signaling": "^1.2.2", - "ajv": "~5.1.6", - "comment-json": "^1.1.3", - "minimist": "~1.2.0", - "moment": "~2.21.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@jupyterlab/docregistry": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/docregistry/-/docregistry-0.19.1.tgz", - "integrity": "sha512-Brw+z+KwmcgQ7DnYuY3RniYf1Ecckwh3UG277XUdpqs+edITBZbhXgsRTIJc+ifLr8laTz04dqLUCJjwDBt8XA==", - "requires": { - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/codeeditor": "^0.19.1", - "@jupyterlab/codemirror": "^0.19.1", - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@jupyterlab/rendermime": "^0.19.1", - "@jupyterlab/rendermime-interfaces": "^1.2.1", - "@jupyterlab/services": "^3.2.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/messaging": "^1.2.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/widgets": "^1.6.0" - } - }, - "@jupyterlab/notebook": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/notebook/-/notebook-0.19.1.tgz", - "integrity": "sha512-HZ0p5LymOq4gUDgqAdnrnRp4m+apehzOX2Mm/7KVuugS9xuV3YX/MHxNb6UP6E7Ftp/dryt0xj+xs19NXnriBA==", - "requires": { - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/cells": "^0.19.1", - "@jupyterlab/codeeditor": "^0.19.1", - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/docregistry": "^0.19.1", - "@jupyterlab/observables": "^2.1.1", - "@jupyterlab/rendermime": "^0.19.1", - "@jupyterlab/services": "^3.2.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/domutils": "^1.1.2", - "@phosphor/dragdrop": "^1.3.0", - "@phosphor/messaging": "^1.2.2", - "@phosphor/properties": "^1.1.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/virtualdom": "^1.1.2", - "@phosphor/widgets": "^1.6.0", - "react": "~16.4.2" - } - }, - "@jupyterlab/observables": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/observables/-/observables-2.1.1.tgz", - "integrity": "sha512-PzmJ/jF5fWzHCvjPAWBi3YjtHRAF0bwyjpd8W8aJt64TzhEZh0se8xCNGOURzD+8TxOsTF9JpQ9wIDBr4V226Q==", - "requires": { - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/messaging": "^1.2.2", - "@phosphor/signaling": "^1.2.2" - } - }, - "@jupyterlab/outputarea": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/outputarea/-/outputarea-0.19.1.tgz", - "integrity": "sha512-LTlILc1AK3yb1z60r1KRJMyG/wkBBMCEkEYkriBQiMiHXsC/g831qudJCcNv9nKCtUAMeIO8EYvwFyMlwfGzIA==", - "requires": { - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@jupyterlab/rendermime": "^0.19.1", - "@jupyterlab/rendermime-interfaces": "^1.2.1", - "@jupyterlab/services": "^3.2.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/messaging": "^1.2.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/widgets": "^1.6.0" - } - }, - "@jupyterlab/rendermime": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/rendermime/-/rendermime-0.19.1.tgz", - "integrity": "sha512-MD+/lMwFa/b9QtJyFghTXFqkuRha74+vWTaPcfzREdHRiKYHYPHWOD/gb8cLIr4c7J3VaEVZWEpI8udYgyANvA==", - "requires": { - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/codemirror": "^0.19.1", - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@jupyterlab/rendermime-interfaces": "^1.2.1", - "@jupyterlab/services": "^3.2.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/messaging": "^1.2.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/widgets": "^1.6.0", - "ansi_up": "^3.0.0", - "marked": "~0.4.0" - } - }, - "@jupyterlab/rendermime-interfaces": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/rendermime-interfaces/-/rendermime-interfaces-1.2.1.tgz", - "integrity": "sha512-0kKRNbqsZSQwVEUuh/YhRZA8NNCJjr9R+Fte9FJeYx6j2LLr+FvYSX7PK5ysVb22w8sxmCW58km52MlDBIy7Gg==", - "requires": { - "@phosphor/coreutils": "^1.3.0", - "@phosphor/widgets": "^1.6.0" - } - }, - "@jupyterlab/services": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/services/-/services-3.2.1.tgz", - "integrity": "sha512-zCMruGGYxTe427ESK4YUO1V/liFOFYpebYPRsJ+j9CFdV+Hm760+nx4pFX6N6Z9TbS+5cs8BgZ+ebm8unRZrJg==", - "requires": { - "@jupyterlab/coreutils": "^2.2.1", - "@jupyterlab/observables": "^2.1.1", - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/signaling": "^1.2.2" - } - }, - "@phosphor/algorithm": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.1.2.tgz", - "integrity": "sha1-/R3pEEyafzTpKGRYbd8ufy53eeg=" - }, - "@phosphor/application": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@phosphor/application/-/application-1.6.0.tgz", - "integrity": "sha512-SeW2YFjtpmCYbZPpB4aTehyIsI/iGoSrV60Y4/OAp1CwDRT8eu1Rv276F67aermXQFjLK6c58JGucbhNq/BepQ==", - "requires": { - "@phosphor/commands": "^1.5.0", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/widgets": "^1.6.0" - } - }, - "@phosphor/collections": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@phosphor/collections/-/collections-1.1.2.tgz", - "integrity": "sha1-xMC4uREpkF+zap8kPy273kYtq40=", - "requires": { - "@phosphor/algorithm": "^1.1.2" - } - }, - "@phosphor/commands": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@phosphor/commands/-/commands-1.6.1.tgz", - "integrity": "sha512-iRgn7QX64e0VwZ91KFo964a/LVpw9XtiYIYtpymEyKY757NXvx6ZZMt1CqKfntoDcSZJeVte4eV8jJWhZoVlDA==", - "requires": { - "@phosphor/algorithm": "^1.1.2", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/domutils": "^1.1.2", - "@phosphor/keyboard": "^1.1.2", - "@phosphor/signaling": "^1.2.2" - } - }, - "@phosphor/coreutils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@phosphor/coreutils/-/coreutils-1.3.0.tgz", - "integrity": "sha1-YyktOBwBLFqw0Blug87YKbfgSkI=" - }, - "@phosphor/disposable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.1.2.tgz", - "integrity": "sha1-oZLdai5sadXQnTns8zTauTd4Bg4=", - "requires": { - "@phosphor/algorithm": "^1.1.2" - } - }, - "@phosphor/domutils": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@phosphor/domutils/-/domutils-1.1.2.tgz", - "integrity": "sha1-4u/rBS85jEK5O4npurJq8VzABRQ=" - }, - "@phosphor/dragdrop": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@phosphor/dragdrop/-/dragdrop-1.3.0.tgz", - "integrity": "sha1-fOatOdbKIW1ipW94EE0Cp3rmcwc=", - "requires": { - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2" - } - }, - "@phosphor/keyboard": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@phosphor/keyboard/-/keyboard-1.1.2.tgz", - "integrity": "sha1-PjIjRFF2QkCpjhSANNWoeXQi3R8=" - }, - "@phosphor/messaging": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@phosphor/messaging/-/messaging-1.2.2.tgz", - "integrity": "sha1-fYlt3TeXuUo0dwje0T2leD23XBQ=", - "requires": { - "@phosphor/algorithm": "^1.1.2", - "@phosphor/collections": "^1.1.2" - } - }, - "@phosphor/properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@phosphor/properties/-/properties-1.1.2.tgz", - "integrity": "sha1-eMx37/RSg52gIlXeSOgUlGzAmig=" - }, - "@phosphor/signaling": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.2.2.tgz", - "integrity": "sha1-P8+Xyojji/s1f+j+a/dRM0elFKk=", - "requires": { - "@phosphor/algorithm": "^1.1.2" - } - }, - "@phosphor/virtualdom": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@phosphor/virtualdom/-/virtualdom-1.1.2.tgz", - "integrity": "sha1-zlXIbu8x5dDiax3JbqMr1oRFj0E=", - "requires": { - "@phosphor/algorithm": "^1.1.2" - } - }, - "@phosphor/widgets": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@phosphor/widgets/-/widgets-1.6.0.tgz", - "integrity": "sha512-HqVckVF8rJ15ss0Zf/q0AJ69ZKNFOO26qtNKAdGZ9SmmkSMf73X6pB0R3Fj5+Y4Sjl8ezIIKG6mXj+DxOofnwA==", - "requires": { - "@phosphor/algorithm": "^1.1.2", - "@phosphor/commands": "^1.5.0", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.2", - "@phosphor/domutils": "^1.1.2", - "@phosphor/dragdrop": "^1.3.0", - "@phosphor/keyboard": "^1.1.2", - "@phosphor/messaging": "^1.2.2", - "@phosphor/properties": "^1.1.2", - "@phosphor/signaling": "^1.2.2", - "@phosphor/virtualdom": "^1.1.2" - } - }, - "@types/prop-types": { - "version": "15.5.6", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.6.tgz", - "integrity": "sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ==" - }, - "@types/react": { - "version": "16.4.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.4.16.tgz", - "integrity": "sha512-lxyoipLWweAnLnSsV4Ho2NAZTKKmxeYgkTQ6PaDiPDU9JJBUY2zJVVGiK1smzYv8+ZgbqEmcm5xM74GCpunSEA==", - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "ajv": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.1.6.tgz", - "integrity": "sha1-Sy8aGd7Ok9V6whYDfj6XkcfdFWQ=", - "requires": { - "co": "^4.6.0", - "json-schema-traverse": "^0.3.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "ansi_up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-3.0.0.tgz", - "integrity": "sha1-J/Rdj0V9nO/1nk6gPI5vE8GjA+g=" - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "codemirror": { - "version": "5.39.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.39.2.tgz", - "integrity": "sha512-mchBy0kQ1Wggi+e58SmoLgKO4nG7s/BqNg6/6TRbhsnXI/KRG+fKAvRQ1LLhZZ6ZtUoDQ0dl5aMhE+IkSRh60Q==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "comment-json": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-1.1.3.tgz", - "integrity": "sha1-aYbDMw/uDEyeAMI5jNYa+l2PI54=", - "requires": { - "json-parser": "^1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "csstype": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz", - "integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw==" - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" - } - } - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" - } - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" - }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", - "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "json-parser": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/json-parser/-/json-parser-1.1.5.tgz", - "integrity": "sha1-5i7FJh0aal/CDoEqMgdAxtkAVnc=", - "requires": { - "esprima": "^2.7.0" - } - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "~0.0.0" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, - "lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "marked": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.4.0.tgz", - "integrity": "sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "moment": { - "version": "2.21.0", - "resolved": "http://registry.npmjs.org/moment/-/moment-2.21.0.tgz", - "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-posix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", - "integrity": "sha1-BrJhE/Vr6rBCVFojv6iAA8ysJg8=" - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - }, - "querystringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", - "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==" - }, - "raw-loader": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", - "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=" - }, - "react": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz", - "integrity": "sha512-dMv7YrbxO4y2aqnvA7f/ik9ibeLSHQJTI6TrYAenPSaQ6OXfb+Oti+oJiy8WBxgRzlKatYqtCjphTgDSCEiWFg==", - "requires": { - "fbjs": "^0.8.16", - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.0" - } - }, - "react-dom": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.2.tgz", - "integrity": "sha512-Usl73nQqzvmJN+89r97zmeUpQDKDlh58eX6Hbs/ERdDHzeBzWy+ENk7fsGQ+5KxArV1iOFPT46/VneklK9zoWw==", - "requires": { - "fbjs": "^0.8.16", - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.0" - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sanitize-html": { - "version": "1.18.5", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.18.5.tgz", - "integrity": "sha512-z0MV+AqOnDZVSQQHr/vwimRykKVyPuGZnjWDzIiV1mdgQEG9HMx9qrEapcOQeUmSsPvHZ04BXTuXQkB/vvbU9A==", - "requires": { - "chalk": "^2.3.0", - "htmlparser2": "^3.9.0", - "lodash.clonedeep": "^4.5.0", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.mergewith": "^4.6.0", - "postcss": "^6.0.14", - "srcset": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "script-loader": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/script-loader/-/script-loader-0.7.2.tgz", - "integrity": "sha512-UMNLEvgOAQuzK8ji8qIscM3GIrRCWN6MmMXGD4SD5l6cSycgGsCo0tX5xRnfQcoghqct0tjHjcykgI1PyBE2aA==", - "requires": { - "raw-loader": "~0.5.1" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "srcset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", - "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", - "requires": { - "array-uniq": "^1.0.2", - "number-is-nan": "^1.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", - "dev": true - }, - "ua-parser-js": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", - "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" - }, - "url-parse": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", - "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", - "requires": { - "querystringify": "^2.0.0", - "requires-port": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "whatwg-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - } - } -} diff --git a/labextension/vpython/package.json b/labextension/vpython/package.json index 3c4a7045..d1aaef83 100644 --- a/labextension/vpython/package.json +++ b/labextension/vpython/package.json @@ -1,48 +1,195 @@ { - "name": "vpython", - "version": "0.3.0", - "description": "VPython extension for Jupyterlab", - "keywords": [ - "jupyter", - "jupyterlab", - "jupyterlab-extension" - ], - "homepage": "", - "bugs": { - "url": "" - }, - "license": "BSD-3-Clause", - "author": "John Coady", - "files": [ - "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", - "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" - ], - "main": "lib/index.js", - "types": "lib/index.d.ts", - "repository": { - "type": "", - "url": "" - }, - "scripts": { - "build": "tsc", - "clean": "rimraf lib", - "watch": "tsc -w", - "prepare": "npm run build" - }, - "dependencies": { - "@jupyterlab/application": "^0.19.1", - "@jupyterlab/apputils": "^0.19.1", - "@jupyterlab/docregistry": "^0.19.1", - "@jupyterlab/notebook": "^0.19.1", - "@phosphor/disposable": "^1.1.2", - "script-loader": "^0.7.2" - }, - "devDependencies": { - "rimraf": "^2.6.1", - "script-loader": "^0.7.2", - "typescript": "~2.9.2" - }, - "jupyterlab": { - "extension": true - } + "name": "jupyterlab_vpython", + "version": "3.1.6", + "description": "A VPython extension for JupyterLab", + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension" + ], + "homepage": "https://github.com/jcoady/jupyterlab_vpython", + "bugs": { + "url": "https://github.com/jcoady/jupyterlab_vpython/issues" + }, + "license": "BSD-3-Clause", + "author": { + "name": "John Coady", + "email": "johncoady@shaw.ca" + }, + "files": [ + "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf,otf,ico}", + "style/**/*.{css,.js,eot,gif,html,jpg,json,png,svg,woff2,ttf,otf,ico}", + "vpython_data/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf,otf,ico}", + "vpython_libraries/*" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "style": "style/index.css", + "repository": { + "type": "git", + "url": "https://github.com/jcoady/jupyterlab_vpython.git" + }, + "scripts": { + "build": "jlpm build:lib && jlpm build:labextension:dev", + "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension", + "build:labextension": "jupyter labextension build .", + "build:labextension:dev": "jupyter labextension build --development True .", + "build:lib": "tsc --sourceMap", + "build:lib:prod": "tsc", + "clean": "jlpm clean:lib", + "clean:lib": "rimraf lib tsconfig.tsbuildinfo", + "clean:lintcache": "rimraf .eslintcache .stylelintcache", + "clean:labextension": "rimraf jupyterlab_vpython/labextension jupyterlab_vpython/_version.py", + "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", + "eslint": "jlpm eslint:check --fix", + "eslint:check": "eslint . --cache --ext .ts,.tsx", + "install:extension": "jlpm build", + "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", + "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", + "prettier": "jlpm prettier:base --write --list-different", + "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", + "prettier:check": "jlpm prettier:base --check", + "stylelint": "jlpm stylelint:check --fix", + "stylelint:check": "stylelint --cache \"style/**/*.css\"", + "watch": "run-p watch:src watch:labextension", + "watch:src": "tsc -w --sourceMap", + "watch:labextension": "jupyter labextension watch ." + }, + "dependencies": { + "@jupyterlab/application": "^4.0.7", + "@jupyterlab/apputils": "^4.1.7", + "@jupyterlab/coreutils": "^6.0.7", + "@jupyterlab/docregistry": "^4.0.7", + "@jupyterlab/notebook": "^4.0.7", + "@lumino/disposable": "^2.1.2", + "file-loader": "^6.2.0", + "plotly.js-dist-min": "^2.26.2", + "script-loader": "^0.7.2" + }, + "devDependencies": { + "@jupyterlab/builder": "^4.0.0", + "@types/json-schema": "^7.0.11", + "@types/react": "^18.0.26", + "@types/react-addons-linked-state-mixin": "^0.14.22", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", + "css-loader": "^6.7.1", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.0", + "rimraf": "^5.0.1", + "source-map-loader": "^1.0.2", + "style-loader": "^3.3.1", + "stylelint": "^15.10.1", + "stylelint-config-recommended": "^13.0.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-csstree-validator": "^3.0.0", + "stylelint-prettier": "^4.0.0", + "typescript": "~5.0.2", + "yjs": "^13.5.0" + }, + "sideEffects": [ + "style/*.css", + "style/index.js" + ], + "styleModule": "style/index.js", + "publishConfig": { + "access": "public" + }, + "jupyterlab": { + "extension": true, + "outputDir": "jupyterlab_vpython/labextension" + }, + "eslintIgnore": [ + "node_modules", + "dist", + "coverage", + "**/*.d.ts" + ], + "eslintConfig": { + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": [ + "PascalCase" + ], + "custom": { + "regex": "^I[A-Z]", + "match": true + } + } + ], + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "none" + } + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/quotes": [ + "error", + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": false + } + ], + "curly": [ + "error", + "all" + ], + "eqeqeq": "error", + "prefer-arrow-callback": "error" + } + }, + "prettier": { + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "avoid", + "endOfLine": "auto", + "overrides": [ + { + "files": "package.json", + "options": { + "tabWidth": 4 + } + } + ] + }, + "stylelint": { + "extends": [ + "stylelint-config-recommended", + "stylelint-config-standard", + "stylelint-prettier/recommended" + ], + "plugins": [ + "stylelint-csstree-validator" + ], + "rules": { + "csstree/validator": true, + "property-no-vendor-prefix": null, + "selector-no-vendor-prefix": null, + "value-no-vendor-prefix": null + } + } } diff --git a/labextension/vpython/src/glowcommlab.d.ts b/labextension/vpython/src/glowcommlab.d.ts index 1e731f20..36cbca86 100644 --- a/labextension/vpython/src/glowcommlab.d.ts +++ b/labextension/vpython/src/glowcommlab.d.ts @@ -1,3 +1,3 @@ export function onmessage (msg: any): void; -export function setupWebsocket (msg: any): void; +export function setupWebsocket (msg: any, serviceUrl: any): void; export var comm: any; diff --git a/labextension/vpython/src/glowcommlab.js b/labextension/vpython/src/glowcommlab.js index 7062fc7b..4950ddb7 100644 --- a/labextension/vpython/src/glowcommlab.js +++ b/labextension/vpython/src/glowcommlab.js @@ -1,31 +1,55 @@ -//define(["nbextensions/vpython_libraries/plotly.min", -// "nbextensions/vpython_libraries/glow.min", -// "nbextensions/vpython_libraries/jquery-ui.custom.min"], function(Plotly) { - -import 'script-loader!./vpython_libraries/jquery.min.js'; -import 'script-loader!./vpython_libraries/jquery-ui.custom.min.js'; -import 'script-loader!./vpython_libraries/glow.min.js'; -import 'script-loader!./vpython_libraries/plotly.min.js'; +import 'script-loader!../vpython_libraries/jquery.min.js'; +import 'script-loader!../vpython_libraries/jquery-ui.custom.min.js'; +import 'script-loader!../vpython_libraries/glow.min.js'; +//import 'script-loader!../vpython_libraries/plotly.min.js'; +import Plotly from 'plotly.js-dist-min' import '../style/jquery-ui.custom.css' import '../style/ide.css' +import img from '../vpython_data/flower_texture.jpg' // use this to get the path to vpython_data + +// Ensure downstream JupyterLab webpack places the fonts and images in predictable locations +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/earth_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/favicon.ico' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/flower_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/granite_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/gravel_bumpmap.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/gravel_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/metal_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/NimbusRomNo9L-Med.otf' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/Roboto-Medium.ttf' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/rock_bumpmap.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/rock_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/rough_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/rug_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/stones_bumpmap.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/stones_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/stucco_bumpmap.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/stucco_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/wood_old_bumpmap.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/wood_old_texture.jpg' +import '!!file-loader?name=/vpython_data/[name].[ext]!../vpython_data/wood_texture.jpg' export var comm var ws = null var isopen = false +var isjupyterlab_vpython = true console.log('START OF GLOWCOMM') -export function createWebsocket(msg) { +export function createWebsocket(msg, serviceUrl) { if (msg.content.data.wsport !== undefined) { // create websocket instance - var port = msg.content.data.wsport - var uri = msg.content.data.wsuri - ws = new WebSocket("ws://localhost:" + port + uri); - ws.binaryType = "arraybuffer"; + var port = msg.content.data.wsport + var uri = msg.content.data.wsuri + var url; + + url = serviceUrl + port + uri; + ws = new WebSocket(url); + ws.binaryType = "arraybuffer"; // Handle incoming websocket message callback ws.onmessage = function(evt) { - console.log("WebSocket Message Received: " + evt.data) + console.log("WebSocket Message Received: " + evt.data) }; // Close Websocket callback @@ -43,25 +67,26 @@ export function createWebsocket(msg) { } } -var wsmsg - -function wscheckfontsloaded() { +function wscheckfontsloaded(msg,serviceUrl) { "use strict"; if (window.__font_sans === undefined || window.__font_serif === undefined) { - setTimeout(wscheckfontsloaded,10) + setTimeout(wscheckfontsloaded,10,msg,serviceUrl) } else { - createWebsocket(wsmsg) + createWebsocket(msg,serviceUrl) } } -export function setupWebsocket(msg) { +export function setupWebsocket(msg,serviceUrl) { "use strict"; - wsmsg = msg - wscheckfontsloaded() + wscheckfontsloaded(msg,serviceUrl) } -var datadir = window.location.href + '/static/vpython_data/' -window.Jupyter_VPython = "/lab/static/vpython_data/" // prefix used by glow.min.js for textures +// New datadir value for a prebuilt extension (new in JupyterLab 3.0) +var url = new URL(img); +var datadir = url.pathname.substring(0, url.pathname.lastIndexOf('/')) + "/vpython_data/"; // path to vpython_data dir +window.Jupyter_VPython = datadir // prefix used by glow.min.js for textures +url = null +window.Plotly = Plotly function fontloading() { "use strict"; @@ -167,9 +192,11 @@ function send() { // periodically send events and update_canvas and request obje // *************************************************************************************************** // -// THE REST OF THIS FILE IS IDENTICAL IN glowcomm.html AND glowcomm.js (except for the last few lines) // +// THE REST OF THIS FILE IS NEARLY IDENTICAL IN glowcomm.html AND glowcomm.js AND glowcommlab.js // + +// Should eventually have glowcomm.html, glowcom.js, and glowcommlab.js all import this common component. -// Should eventually have glowcomm.html and glowcom.js both import this common component. +window.__GSlang = "vpython" function msclock() { "use strict"; @@ -191,7 +218,7 @@ var lastrange = 1 var lastautoscale = true var lastsliders = {} var lastkeysdown = [] -var interval = 33 // milliseconds +var interval = 17 // milliseconds function update_canvas() { // mouse location and other stuff "use strict"; @@ -275,6 +302,7 @@ function update_canvas() { // mouse location and other stuff /* var request = new XMLHttpRequest() + function send_to_server(data, callback) { // send to HTTP server var data= JSON.stringify(data) request.open('get', data, true) @@ -287,6 +315,7 @@ function send_to_server(data, callback) { // send to HTTP server } request.send() } + function ok(req) { ; } */ @@ -294,6 +323,8 @@ function send_pick(cvs, p, seg) { "use strict"; var evt = {event: 'pick', 'canvas': cvs, 'pick': p, 'segment':seg} events.push(evt) + if (timer !== null) clearTimeout(timer) + send() // send the info NOW } function send_compound(cvs, pos, size, up) { @@ -301,6 +332,8 @@ function send_compound(cvs, pos, size, up) { var evt = {event: '_compound', 'canvas': cvs, 'pos': [pos.x, pos.y, pos.z], 'size': [size.x, size.y, size.z], 'up': [up.x, up.y, up.z]} events.push(evt) + if (timer !== null) clearTimeout(timer) + send() // send the info NOW } var waitfor_canvas = null @@ -372,7 +405,7 @@ function process_waitfor(event) { } function process_binding(event) { // event associated with a previous bind command - "use strict"; + "use strict"; event.bind = true process(event) } @@ -413,7 +446,7 @@ function control_handler(obj) { // button, menu, slider, radio, checkbox, winpu } // attrs are X in {'a': '23X....'} avaiable: none -var attrs = {'a':'pos', 'b':'up', 'c':'color', 'd':'trail_color', // don't use single and double quotes; available: comma, but maybe that would cause trouble +let attrs = {'a':'pos', 'b':'up', 'c':'color', 'd':'trail_color', // don't use single and double quotes; available: comma, but maybe that would cause trouble 'e':'ambient', 'f':'axis', 'g':'size', 'h':'origin', 'i':'textcolor', 'j':'direction', 'k':'linecolor', 'l':'bumpaxis', 'm':'dot_color', 'n':'foreground', 'o':'background', 'p':'ray', 'E':'center', '#':'forward', '+':'resizable', @@ -440,34 +473,35 @@ var attrs = {'a':'pos', 'b':'up', 'c':'color', 'd':'trail_color', // don't use s '?':'font', '/':'texture'} // attrsb are X in {'b': '23X....'}; ran out of easily typable one-character codes -var attrsb = {'a':'userzoom', 'b':'userspin', 'c':'range', 'd':'autoscale', 'e':'fov', +let attrsb = {'a':'userzoom', 'b':'userspin', 'c':'range', 'd':'autoscale', 'e':'fov', 'f':'normal', 'g':'data', 'h':'checked', 'i':'disabled', 'j':'selected', 'k':'vertical', 'l':'min', 'm':'max', 'n':'step', 'o':'value', 'p':'left', 'q':'right', 'r':'top', 's':'bottom', 't':'_cloneid', 'u':'logx', 'v':'logy', 'w':'dot', 'x':'dot_radius', 'y':'markers', 'z':'legend', 'A':'label','B':'delta', 'C':'marker_color', - 'D':'size_units', 'E':'userpan'} + 'D':'size_units', 'E':'userpan', 'F':'scroll', 'G':'choices', 'H':'depth', + 'I':'round', 'J':'name', 'K':'offset', 'L':'attach_idx'} -// methods are X in {'m': '23X....'} -var methods = {'a':'select', 'b':'pos', 'c':'start', 'd':'stop', 'f':'clear', // unused eghijklmnopvxyzCDFAB +// methods are X in {'m': '23X....'} available: u +let methods = {'a':'select', 'b':'pos', 'c':'start', 'd':'stop', 'f':'clear', // unused eghijklmnopvxyzCDFAB 'q':'plot', 's':'add_to_trail', - 't':'follow', 'u':'_attach_arrow', 'w':'clear_trail', + 't':'follow', 'w':'clear_trail', 'G':'bind', 'H':'unbind', 'I':'waitfor', 'J':'pause', 'K':'pick', 'L':'GSprint', 'M':'delete', 'N':'capture'} -var vecattrs = ['pos', 'up', 'color', 'trail_color', 'axis', 'size', 'origin', '_attach_arrow', +let vecattrs = ['pos', 'up', 'color', 'trail_color', 'axis', 'size', 'origin', 'direction', 'linecolor', 'bumpaxis', 'dot_color', 'ambient', 'add_to_trail', 'textcolor', 'foreground', 'background', 'ray', 'ambient', 'center', 'forward', 'normal', - 'marker_color'] + 'marker_color', 'offset'] -var textattrs = ['text', 'align', 'caption', 'title', 'title_align', 'xtitle', 'ytitle', 'selected', 'capture', - 'label', 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint'] +let textattrs = ['text', 'align', 'caption', 'title', 'title_align', 'xtitle', 'ytitle', 'selected', 'capture', 'name', + 'label', 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint', 'choices'] // patt gets idx and attr code; vpatt gets x,y,z of a vector -var patt = /(\d+)(.)(.*)/ -var vpatt = /([^,]*),([^,]*),(.*)/ -var quadpatt = /([^,]*),([^,]*),(.*)/ -var plotpatt = /([^,]*),([^,]*)/ +const patt = /(\d+)(.)(.*)/ +const vpatt = /([^,]*),([^,]*),(.*)/ +const quadpatt = /([^,]*),([^,]*),(.*)/ +const plotpatt = /([^,]*),([^,]*)/ function decode(data) { "use strict"; @@ -479,12 +513,12 @@ function decode(data) { var ms = [] if ('attrs' in data) { - var c = data['attrs'] + let c = data['attrs'] for (i=0; i -1) { - // '\n' doesn't survive JSON transmission, so in vpython.py we replace '\n' with '
' - val = m[3].replace(/
/g, "\n") + if (attr == 'choices') { // menu choices are wrapped in a list + val = m[3].slice(2, -2).split("', '") // choices separated by ', ' + } else { + // '\n' doesn't survive JSON transmission, so in vpython.py we replace '\n' with '
' + val = m[3].replace(/
/g, "\n") + } } else if (attr == 'rotate') { // angle,x,y,z,x,y,z var temp = m[3] val = [] @@ -529,6 +567,9 @@ function decode(data) { } } else if (attr == 'waitfor' || attr == 'pause' || attr == 'delete') { val = m[3] + } else if (attr == 'follow') { + if (m[3] == 'None') val = null + else val = Number(m[3]) } else val = Number(m[3]) out = {'idx':idx, 'attr':attr, 'val':val} if (datatype == 'attr') as.push(out) @@ -578,7 +619,9 @@ function handler(data) { for (var d in data) { for (var i in data[d]) console.log(i, JSON.stringify(data[d][i])) } - */ + */ + + if (data.cmds !== undefined && data.cmds.length > 0) handle_cmds(data.cmds) if (data.methods !== undefined && data.methods.length > 0) handle_methods(data.methods) @@ -605,7 +648,8 @@ function handle_cmds(dcmds) { //assembling cfg var vlst = ['pos', 'color', 'size', 'axis', 'up', 'direction', 'center', 'forward', 'foreground', 'background', 'ambient', 'linecolor', 'dot_color', 'trail_color', 'textcolor', 'attrval', - 'origin', 'normal', 'bumpaxis','texpos', 'start_face_color', 'end_face_color', 'marker_color'] + 'origin', 'normal', 'bumpaxis','texpos', 'start_face_color', 'end_face_color', 'marker_color', + 'start_normal', 'end_normal', 'offset'] if ((obj != 'gcurve') && ( obj != 'gdots' ) ) vlst.push( 'size' ) var cfg = {} var objects = [] @@ -619,14 +663,16 @@ function handle_cmds(dcmds) { cfg[attr] = o2vec3(val) } } else if ( (attr == 'pos' && (obj == 'curve' || obj == 'points')) || - (attr == 'path' && obj == 'extrusion') ) { // only occurs in constructor - var ptlist = [] - for (var kk = 0; kk < val.length; kk++) { - ptlist.push( o2vec3(val[kk]) ) + (obj == 'extrusion' && (attr == 'path' || attr == 'color') ) ) { // only occurs in constructor + let ptlist = [] + if (val[0].length === undefined) { // a single triple [x,y,z] to convert to a vector + ptlist = o2vec3(val) + } else { + for (var kk = 0; kk < val.length; kk++) { + ptlist.push( o2vec3(val[kk]) ) + } } cfg[attr] = ptlist - } else if (attr === "axis" && obj == 'arrow') { - cfg['axis_and_length'] = o2vec3(val) } else if (vlst.indexOf(attr) !== -1) { cfg[attr] = o2vec3(val) } else if (triangle_quad.indexOf(attr) !== -1) { @@ -682,6 +728,8 @@ function handle_cmds(dcmds) { } // creating the objects cfg.idx = idx // reinsert idx, having looped thru all other attributes + // triangle and quad objects should not have a canvas attribute; canvas is provided in the vertex objectsE + if ((obj == 'triangle' || obj == 'quad') && cfg.canvas !== undefined) delete cfg.canvas switch (obj) { case 'box': {glowObjs[idx] = box(cfg); break} case 'sphere': {glowObjs[idx] = sphere(cfg); break} @@ -755,42 +803,40 @@ function handle_cmds(dcmds) { } break } - case 'local_light': {glowObjs[idx] = local_light(cfg); break} + case 'local_light': { + let g = glowObjs[idx] = local_light(cfg) + if (cfg.offset !== undefined) { // mocking up attach_light + g.__obj = glowObjs[cfg.attach_idx] + g.canvas.attached_lights.push(g) + } + break + } case 'distant_light': {glowObjs[idx] = distant_light(cfg); break} case 'canvas': { - var container = document.getElementById("glowscript"); - if (container !== null) { - window.__context = { glowscript_container: $("#glowscript").removeAttr("id")} - } - glowObjs[idx] = canvas(cfg) - glowObjs[idx]['idx'] = idx - try{ - glowObjs[idx].wrapper[0].addEventListener("contextmenu", function(event){ - event.preventDefault(); - event.stopPropagation(); - }); - } - catch(err) { - console.log("glowcomm canvas contextmenu event : ",err.message); + if ((typeof isjupyterlab_vpython !== 'undefined') && (isjupyterlab_vpython === true)) { + var container = document.getElementById("glowscript"); + if (container !== null) { + window.__context = { glowscript_container: $("#glowscript").removeAttr("id")} + } + glowObjs[idx] = canvas(cfg) + glowObjs[idx]['idx'] = idx + try{ + glowObjs[idx].wrapper[0].addEventListener("contextmenu", function(event){ + event.preventDefault(); + event.stopPropagation(); + }); + } + catch(err) { + console.log("glowcomm canvas contextmenu event : ",err.message); + } + } else { + glowObjs[idx] = canvas(cfg) + glowObjs[idx]['idx'] = idx } - break // Display frames per second and render time: //$("
").appendTo(glowObjs[idx].title) } - case 'attach_arrow': { - var attrs = ['pos', 'size', 'axis', 'up', 'color'] - var o = glowObjs[cfg['obj']] - delete cfg['obj'] - var attr = cfg['attr'] - delete cfg['attr'] - var val = cfg['attrval'] - delete cfg['attrval'] - if (attrs.indexOf(attr) < 0) attr = '_attach_arrow' - o.attr = val - glowObjs[idx] = attach_arrow( o, attr, cfg ) - break - } case 'attach_trail': { if ( typeof cfg['_obj'] === 'string' ) { var o = cfg['_obj'] // the string '_func' @@ -822,10 +868,13 @@ function handle_cmds(dcmds) { break } case 'radio': { + cfg.canvas = canvas.get_selected() + cfg = fix_location(cfg) + delete cfg.canvas cfg.objName = obj cfg.bind = control_handler - cfg = fix_location(cfg) glowObjs[idx] = radio(cfg) + // glowObjs[idx].canvas = canvas.get_selected() break } case 'button': { @@ -855,60 +904,18 @@ function handle_cmds(dcmds) { default: console.log("Unable to create object") } - - /* - if (obj === 'redisplay') { - var c = document.getElementById(cmd.sceneId) - if (c !== null) { - var scn = "#" + cmd.sceneId - glowObjs[idx].sceneclone = $(scn).clone(true,true) - //document.getElementById('glowscript2').appendChild(c) - //document.getElementById('glowscript2').replaceWith(c) - $('#glowscript2').replaceWith(c) - c = document.getElementById(cmd.sceneId) - var cont = scn + " .glowscript" - window.__context = { glowscript_container: $(cont) } - } else { - window.__context = { glowscript_container: $("#glowscript").removeAttr("id") } - var newcnvs = canvas() - for (var obj in glowObjs[idx].objects) { - var o = glowObjs[idx].objects[obj] - if ((o.constructor.name !== 'curve') && (o.constructor.name !== 'points')) { - glowObjs[o.gidx] = o.clone({canvas: newcnvs}) - var olen = newcnvs.objects.length - if (olen > 0) { - newcnvs.objects[olen - 1].gidx = o.gidx - } - } - } - glowObjs[idx] = newcnvs - $("#glowscript2").attr("id",cmd.sceneId) - } - } else if (obj === 'delete') { - b = glowObjs[idx] - //console.log("delete : ",idx) - if ((b !== null) || (b.visible !== undefined)) { - b.visible = false - } - glowObjs[idx] = null - } else if (obj === 'heartbeat') { - //console.log("heartbeat") - } else if (obj === 'debug') { - console.log("debug : ", cmd) - } - */ } // end of cmds (constructors and special data) } -function handle_methods(dmeth) { +async function handle_methods(dmeth) { "use strict"; //console.log('METHODS') - for (var idmeth=0; idmeth 0) { - obj.pause(val, process_pause) + await obj.pause(val) } else { - obj.pause(process_pause) + await obj.pause() } + process_pause() } else if (method === 'pick') { - var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas + var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas var seg = null if (p !== null) { if (p instanceof curve) seg = p.segment @@ -970,32 +979,20 @@ function handle_methods(dmeth) { function handle_attrs(dattrs) { "use strict"; //console.log('ATTRS') - for (var idattrs=0; idattrs = { +const plugin: JupyterFrontEndPlugin = { activate, id: 'vpython', autoStart: true @@ -35,27 +35,36 @@ class VPythonExtension implements DocumentRegistry.IWidgetExtension): IDisposable { - Promise.all([panel.revealed, panel.session.ready, context.ready]).then(function() { - const session = context.session; - const kernelInstance = session.kernel; + Promise.all([panel.revealed, panel.sessionContext.ready, context.ready]).then(function() { + const session = context.sessionContext.session; + const kernelInstance = session?.kernel; (window).JLab_VPython = true; - try { - kernelInstance.registerCommTarget('glow', (vp_comm, commMsg) => { + try { + kernelInstance?.registerCommTarget('glow', (vp_comm: any, commMsg: any) => { // Use Dynamic import() Expression to import glowcomm when comm is opened import("./glowcommlab").then(glowcommlab => { glowcommlab.comm = vp_comm vp_comm.onMsg = glowcommlab.onmessage - glowcommlab.setupWebsocket(commMsg) + // Get Websocket URL of current notebook server + let ws_url = PageConfig.getWsUrl() + + // Construct URL of our proxied service + let serviceUrl = ws_url + 'proxy/'; + glowcommlab.setupWebsocket(commMsg, serviceUrl) }); - vp_comm.onClose = (msg) => {console.log("comm onClose");}; + vp_comm.onClose = (msg: any) => {console.log("comm onClose");}; }); } catch(err) { - console.log("register glow error : ",err.message); + if (err instanceof Error) { + console.log("register glow error : ",err.message); + } else { + console.log('Unexpected error', err); + } } }); @@ -69,7 +78,7 @@ class VPythonExtension implements DocumentRegistry.IWidgetExtension= 2.1.2 < 3" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - -"js-tokens@^3.0.0 || ^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - -json-parser@^1.0.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/json-parser/-/json-parser-1.1.5.tgz#e62ec5261d1a6a5fc20e812a320740c6d9005677" - dependencies: - esprima "^2.7.0" - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - dependencies: - jsonify "~0.0.0" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -lodash.escaperegexp@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -marked@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.4.0.tgz#9ad2c2a7a1791f10a852e0112f77b571dce10c66" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - -minimist@~1.2.0: - version "1.2.0" - resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -moment@~2.21.0: - version "2.21.0" - resolved "http://registry.npmjs.org/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" - -node-fetch@^1.0.1, node-fetch@~1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - -options@>=0.0.5: - version "0.0.6" - resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - -path-posix@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f" - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - dependencies: - asap "~2.0.3" - -prop-types@^15.6.0: - version "15.6.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" - dependencies: - loose-envify "^1.3.1" - object-assign "^4.1.1" - -querystringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" - -raw-loader@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" - -react-dom@~16.4.0: - version "16.4.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -react@~16.4.0: - version "16.4.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.4.2.tgz#2cd90154e3a9d9dd8da2991149fdca3c260e129f" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -readable-stream@^2.0.2: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - -rimraf@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - dependencies: - glob "^7.0.5" - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - -sanitize-html@~1.14.3: - version "1.14.3" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.14.3.tgz#62afd7c2d44ffd604599121d49e25b934e7a5514" - dependencies: - htmlparser2 "^3.9.0" - lodash.escaperegexp "^4.1.2" - xtend "^4.0.0" - -script-loader@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/script-loader/-/script-loader-0.7.2.tgz#2016db6f86f25f5cf56da38915d83378bb166ba7" - dependencies: - raw-loader "~0.5.1" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - dependencies: - safe-buffer "~5.1.0" - -typescript@~2.9.2: - version "2.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" - -ua-parser-js@^0.7.18: - version "0.7.18" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" - -ultron@1.0.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" - -url-parse@~1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.3.tgz#bfaee455c889023219d757e045fa6a684ec36c15" - dependencies: - querystringify "^2.0.0" - requires-port "^1.0.0" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -whatwg-fetch@>=0.10.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -ws@~1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51" - dependencies: - options ">=0.0.5" - ultron "1.0.x" - -xtend@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..11433052 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = ["setuptools", + "setuptools_scm", + "cython"] +build-backend = 'setuptools.build_meta' + +[tool.setuptools_scm] + diff --git a/requirements.txt b/requirements.txt index 36e57b4d..8957809e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ Cython -vpnotebook vpython diff --git a/setup.cfg b/setup.cfg index 05b6d90f..e69de29b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +0,0 @@ -[versioneer] -VCS = git -style = pep440 -versionfile_source = vpython/_version.py -versionfile_build = vpython/_version.py -tag_prefix = -parentdir_prefix = diff --git a/setup.py b/setup.py index d6e5af39..91c1b4a0 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,4 @@ -import sys - -try: - from setuptools import setup # try first in case it's already there. -except ImportError: - from ez_setup import use_setuptools - use_setuptools() - from setuptools import setup +from setuptools import setup from distutils.extension import Extension @@ -16,21 +9,16 @@ except ImportError: extensions = [Extension('vpython.cyvector', ['vpython/cyvector.c'])] -import versioneer - -install_requires = ['jupyter', 'vpnotebook', 'numpy', 'ipykernel', - 'autobahn>=18.8.2'] -if sys.version_info.major == 3 and sys.version_info.minor >= 5: - install_requires.append('autobahn') +install_requires = ['jupyter', 'jupyter-server-proxy', 'jupyterlab-vpython>=3.1.8', 'notebook>=7.0.0', 'numpy', 'ipykernel', + 'autobahn>=22.6.1, <27'] setup_args = dict( name='vpython', packages=['vpython'], - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), description='VPython for Jupyter Notebook', long_description=open('README.md').read(), + long_description_content_type="text/markdown", author='John Coady / Ruth Chabay / Bruce Sherwood / Steve Spicklemire', author_email='bruce.sherwood@gmail.com', url='http://pypi.python.org/pypi/vpython/', @@ -49,6 +37,7 @@ ], ext_modules=extensions, install_requires=install_requires, + python_requires=">=3.7", package_data={'vpython': ['vpython_data/*', 'vpython_libraries/*', 'vpython_libraries/images/*']}, diff --git a/versioneer.py b/versioneer.py deleted file mode 100644 index 7ed2a21d..00000000 --- a/versioneer.py +++ /dev/null @@ -1,1774 +0,0 @@ - -# Version: 0.16 - -"""The Versioneer - like a rocketeer, but for versions. - -The Versioneer -============== - -* like a rocketeer, but for versions! -* https://github.com/warner/python-versioneer -* Brian Warner -* License: Public Domain -* Compatible With: python2.6, 2.7, 3.3, 3.4, 3.5, and pypy -* [![Latest Version] -(https://pypip.in/version/versioneer/badge.svg?style=flat) -](https://pypi.python.org/pypi/versioneer/) -* [![Build Status] -(https://travis-ci.org/warner/python-versioneer.png?branch=master) -](https://travis-ci.org/warner/python-versioneer) - -This is a tool for managing a recorded version number in distutils-based -python projects. The goal is to remove the tedious and error-prone "update -the embedded version string" step from your release process. Making a new -release should be as easy as recording a new tag in your version-control -system, and maybe making new tarballs. - - -## Quick Install - -* `pip install versioneer` to somewhere to your $PATH -* add a `[versioneer]` section to your setup.cfg (see below) -* run `versioneer install` in your source tree, commit the results - -## Version Identifiers - -Source trees come from a variety of places: - -* a version-control system checkout (mostly used by developers) -* a nightly tarball, produced by build automation -* a snapshot tarball, produced by a web-based VCS browser, like github's - "tarball from tag" feature -* a release tarball, produced by "setup.py sdist", distributed through PyPI - -Within each source tree, the version identifier (either a string or a number, -this tool is format-agnostic) can come from a variety of places: - -* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows - about recent "tags" and an absolute revision-id -* the name of the directory into which the tarball was unpacked -* an expanded VCS keyword ($Id$, etc) -* a `_version.py` created by some earlier build step - -For released software, the version identifier is closely related to a VCS -tag. Some projects use tag names that include more than just the version -string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool -needs to strip the tag prefix to extract the version identifier. For -unreleased software (between tags), the version identifier should provide -enough information to help developers recreate the same tree, while also -giving them an idea of roughly how old the tree is (after version 1.2, before -version 1.3). Many VCS systems can report a description that captures this, -for example `git describe --tags --dirty --always` reports things like -"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the -0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has -uncommitted changes. - -The version identifier is used for multiple purposes: - -* to allow the module to self-identify its version: `myproject.__version__` -* to choose a name and prefix for a 'setup.py sdist' tarball - -## Theory of Operation - -Versioneer works by adding a special `_version.py` file into your source -tree, where your `__init__.py` can import it. This `_version.py` knows how to -dynamically ask the VCS tool for version information at import time. - -`_version.py` also contains `$Revision$` markers, and the installation -process marks `_version.py` to have this marker rewritten with a tag name -during the `git archive` command. As a result, generated tarballs will -contain enough information to get the proper version. - -To allow `setup.py` to compute a version too, a `versioneer.py` is added to -the top level of your source tree, next to `setup.py` and the `setup.cfg` -that configures it. This overrides several distutils/setuptools commands to -compute the version when invoked, and changes `setup.py build` and `setup.py -sdist` to replace `_version.py` with a small static file that contains just -the generated version data. - -## Installation - -First, decide on values for the following configuration variables: - -* `VCS`: the version control system you use. Currently accepts "git". - -* `style`: the style of version string to be produced. See "Styles" below for - details. Defaults to "pep440", which looks like - `TAG[+DISTANCE.gSHORTHASH[.dirty]]`. - -* `versionfile_source`: - - A project-relative pathname into which the generated version strings should - be written. This is usually a `_version.py` next to your project's main - `__init__.py` file, so it can be imported at runtime. If your project uses - `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. - This file should be checked in to your VCS as usual: the copy created below - by `setup.py setup_versioneer` will include code that parses expanded VCS - keywords in generated tarballs. The 'build' and 'sdist' commands will - replace it with a copy that has just the calculated version string. - - This must be set even if your project does not have any modules (and will - therefore never import `_version.py`), since "setup.py sdist" -based trees - still need somewhere to record the pre-calculated version strings. Anywhere - in the source tree should do. If there is a `__init__.py` next to your - `_version.py`, the `setup.py setup_versioneer` command (described below) - will append some `__version__`-setting assignments, if they aren't already - present. - -* `versionfile_build`: - - Like `versionfile_source`, but relative to the build directory instead of - the source directory. These will differ when your setup.py uses - 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, - then you will probably have `versionfile_build='myproject/_version.py'` and - `versionfile_source='src/myproject/_version.py'`. - - If this is set to None, then `setup.py build` will not attempt to rewrite - any `_version.py` in the built tree. If your project does not have any - libraries (e.g. if it only builds a script), then you should use - `versionfile_build = None`. To actually use the computed version string, - your `setup.py` will need to override `distutils.command.build_scripts` - with a subclass that explicitly inserts a copy of - `versioneer.get_version()` into your script file. See - `test/demoapp-script-only/setup.py` for an example. - -* `tag_prefix`: - - a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. - If your tags look like 'myproject-1.2.0', then you should use - tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this - should be an empty string, using either `tag_prefix=` or `tag_prefix=''`. - -* `parentdir_prefix`: - - a optional string, frequently the same as tag_prefix, which appears at the - start of all unpacked tarball filenames. If your tarball unpacks into - 'myproject-1.2.0', this should be 'myproject-'. To disable this feature, - just omit the field from your `setup.cfg`. - -This tool provides one script, named `versioneer`. That script has one mode, -"install", which writes a copy of `versioneer.py` into the current directory -and runs `versioneer.py setup` to finish the installation. - -To versioneer-enable your project: - -* 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and - populating it with the configuration values you decided earlier (note that - the option names are not case-sensitive): - - ```` - [versioneer] - VCS = git - style = pep440 - versionfile_source = src/myproject/_version.py - versionfile_build = myproject/_version.py - tag_prefix = - parentdir_prefix = myproject- - ```` - -* 2: Run `versioneer install`. This will do the following: - - * copy `versioneer.py` into the top of your source tree - * create `_version.py` in the right place (`versionfile_source`) - * modify your `__init__.py` (if one exists next to `_version.py`) to define - `__version__` (by calling a function from `_version.py`) - * modify your `MANIFEST.in` to include both `versioneer.py` and the - generated `_version.py` in sdist tarballs - - `versioneer install` will complain about any problems it finds with your - `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all - the problems. - -* 3: add a `import versioneer` to your setup.py, and add the following - arguments to the setup() call: - - version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), - -* 4: commit these changes to your VCS. To make sure you won't forget, - `versioneer install` will mark everything it touched for addition using - `git add`. Don't forget to add `setup.py` and `setup.cfg` too. - -## Post-Installation Usage - -Once established, all uses of your tree from a VCS checkout should get the -current version string. All generated tarballs should include an embedded -version string (so users who unpack them will not need a VCS tool installed). - -If you distribute your project through PyPI, then the release process should -boil down to two steps: - -* 1: git tag 1.0 -* 2: python setup.py register sdist upload - -If you distribute it through github (i.e. users use github to generate -tarballs with `git archive`), the process is: - -* 1: git tag 1.0 -* 2: git push; git push --tags - -Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at -least one tag in its history. - -## Version-String Flavors - -Code which uses Versioneer can learn about its version string at runtime by -importing `_version` from your main `__init__.py` file and running the -`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can -import the top-level `versioneer.py` and run `get_versions()`. - -Both functions return a dictionary with different flavors of version -information: - -* `['version']`: A condensed version string, rendered using the selected - style. This is the most commonly used value for the project's version - string. The default "pep440" style yields strings like `0.11`, - `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section - below for alternative styles. - -* `['full-revisionid']`: detailed revision identifier. For Git, this is the - full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". - -* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that - this is only accurate if run in a VCS checkout, otherwise it is likely to - be False or None - -* `['error']`: if the version string could not be computed, this will be set - to a string describing the problem, otherwise it will be None. It may be - useful to throw an exception in setup.py if this is set, to avoid e.g. - creating tarballs with a version string of "unknown". - -Some variants are more useful than others. Including `full-revisionid` in a -bug report should allow developers to reconstruct the exact code being tested -(or indicate the presence of local changes that should be shared with the -developers). `version` is suitable for display in an "about" box or a CLI -`--version` output: it can be easily compared against release notes and lists -of bugs fixed in various releases. - -The installer adds the following text to your `__init__.py` to place a basic -version in `YOURPROJECT.__version__`: - - from ._version import get_versions - __version__ = get_versions()['version'] - del get_versions - -## Styles - -The setup.cfg `style=` configuration controls how the VCS information is -rendered into a version string. - -The default style, "pep440", produces a PEP440-compliant string, equal to the -un-prefixed tag name for actual releases, and containing an additional "local -version" section with more detail for in-between builds. For Git, this is -TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags ---dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the -tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and -that this commit is two revisions ("+2") beyond the "0.11" tag. For released -software (exactly equal to a known tag), the identifier will only contain the -stripped tag, e.g. "0.11". - -Other styles are available. See details.md in the Versioneer source tree for -descriptions. - -## Debugging - -Versioneer tries to avoid fatal errors: if something goes wrong, it will tend -to return a version of "0+unknown". To investigate the problem, run `setup.py -version`, which will run the version-lookup code in a verbose mode, and will -display the full contents of `get_versions()` (including the `error` string, -which may help identify what went wrong). - -## Updating Versioneer - -To upgrade your project to a new release of Versioneer, do the following: - -* install the new Versioneer (`pip install -U versioneer` or equivalent) -* edit `setup.cfg`, if necessary, to include any new configuration settings - indicated by the release notes -* re-run `versioneer install` in your source tree, to replace - `SRC/_version.py` -* commit any changed files - -### Upgrading to 0.16 - -Nothing special. - -### Upgrading to 0.15 - -Starting with this version, Versioneer is configured with a `[versioneer]` -section in your `setup.cfg` file. Earlier versions required the `setup.py` to -set attributes on the `versioneer` module immediately after import. The new -version will refuse to run (raising an exception during import) until you -have provided the necessary `setup.cfg` section. - -In addition, the Versioneer package provides an executable named -`versioneer`, and the installation process is driven by running `versioneer -install`. In 0.14 and earlier, the executable was named -`versioneer-installer` and was run without an argument. - -### Upgrading to 0.14 - -0.14 changes the format of the version string. 0.13 and earlier used -hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a -plus-separated "local version" section strings, with dot-separated -components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old -format, but should be ok with the new one. - -### Upgrading from 0.11 to 0.12 - -Nothing special. - -### Upgrading from 0.10 to 0.11 - -You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running -`setup.py setup_versioneer`. This will enable the use of additional -version-control systems (SVN, etc) in the future. - -## Future Directions - -This tool is designed to make it easily extended to other version-control -systems: all VCS-specific components are in separate directories like -src/git/ . The top-level `versioneer.py` script is assembled from these -components by running make-versioneer.py . In the future, make-versioneer.py -will take a VCS name as an argument, and will construct a version of -`versioneer.py` that is specific to the given VCS. It might also take the -configuration arguments that are currently provided manually during -installation by editing setup.py . Alternatively, it might go the other -direction and include code from all supported VCS systems, reducing the -number of intermediate scripts. - - -## License - -To make Versioneer easier to embed, all its code is dedicated to the public -domain. The `_version.py` that it creates is also in the public domain. -Specifically, both are released under the Creative Commons "Public Domain -Dedication" license (CC0-1.0), as described in -https://creativecommons.org/publicdomain/zero/1.0/ . - -""" - -from __future__ import print_function -try: - import configparser -except ImportError: - import ConfigParser as configparser -import errno -import json -import os -import re -import subprocess -import sys - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_root(): - """Get the project root directory. - - We require that all commands are run from the project root, i.e. the - directory that contains setup.py, setup.cfg, and versioneer.py . - """ - root = os.path.realpath(os.path.abspath(os.getcwd())) - setup_py = os.path.join(root, "setup.py") - versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - # allow 'python path/to/setup.py COMMAND' - root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) - setup_py = os.path.join(root, "setup.py") - versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - err = ("Versioneer was unable to run the project root directory. " - "Versioneer requires setup.py to be executed from " - "its immediate directory (like 'python setup.py COMMAND'), " - "or in a way that lets it use sys.argv[0] to find the root " - "(like 'python path/to/setup.py COMMAND').") - raise VersioneerBadRootError(err) - try: - # Certain runtime workflows (setup.py install/develop in a setuptools - # tree) execute all dependencies in a single python process, so - # "versioneer" may be imported multiple times, and python's shared - # module-import table will cache the first one. So we can't use - # os.path.dirname(__file__), as that will find whichever - # versioneer.py was first imported, even in later projects. - me = os.path.realpath(os.path.abspath(__file__)) - if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]: - print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(me), versioneer_py)) - except NameError: - pass - return root - - -def get_config_from_root(root): - """Read the project setup.cfg file to determine Versioneer config.""" - # This might raise EnvironmentError (if setup.cfg is missing), or - # configparser.NoSectionError (if it lacks a [versioneer] section), or - # configparser.NoOptionError (if it lacks "VCS="). See the docstring at - # the top of versioneer.py for instructions on writing your setup.cfg . - setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.SafeConfigParser() - with open(setup_cfg, "r") as f: - parser.readfp(f) - VCS = parser.get("versioneer", "VCS") # mandatory - - def get(parser, name): - if parser.has_option("versioneer", name): - return parser.get("versioneer", name) - return None - cfg = VersioneerConfig() - cfg.VCS = VCS - cfg.style = get(parser, "style") or "" - cfg.versionfile_source = get(parser, "versionfile_source") - cfg.versionfile_build = get(parser, "versionfile_build") - cfg.tag_prefix = get(parser, "tag_prefix") - if cfg.tag_prefix in ("''", '""'): - cfg.tag_prefix = "" - cfg.parentdir_prefix = get(parser, "parentdir_prefix") - cfg.verbose = get(parser, "verbose") - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - -# these dictionaries contain VCS-specific tools -LONG_VERSION_PY = {} -HANDLERS = {} - - -def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - """Call the given command(s).""" - assert isinstance(commands, list) - p = None - for c in commands: - try: - dispcmd = str([c] + args) - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % dispcmd) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %s (error)" % dispcmd) - return None - return stdout -LONG_VERSION_PY['git'] = ''' -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (built by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. Generated by -# versioneer-0.16 (https://github.com/warner/python-versioneer) - -"""Git implementation of _version.py.""" - -import errno -import os -import re -import subprocess -import sys - - -def get_keywords(): - """Get the keywords needed to look up the version information.""" - # these strings will be replaced by git during git-archive. - # setup.py/versioneer.py will grep for the variable names, so they must - # each be defined on a line of their own. _version.py will just call - # get_keywords(). - git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" - git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" - keywords = {"refnames": git_refnames, "full": git_full} - return keywords - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_config(): - """Create, populate and return the VersioneerConfig() object.""" - # these strings are filled in when 'setup.py versioneer' creates - # _version.py - cfg = VersioneerConfig() - cfg.VCS = "git" - cfg.style = "%(STYLE)s" - cfg.tag_prefix = "%(TAG_PREFIX)s" - cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" - cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" - cfg.verbose = False - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -LONG_VERSION_PY = {} -HANDLERS = {} - - -def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - """Call the given command(s).""" - assert isinstance(commands, list) - p = None - for c in commands: - try: - dispcmd = str([c] + args) - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %%s" %% dispcmd) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %%s" %% (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %%s (error)" %% dispcmd) - return None - return stdout - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes - both the project name and a version string. - """ - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%%s', but '%%s' doesn't start with " - "prefix '%%s'" %% (root, dirname, parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None} - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %%d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%%s', no digits" %% ",".join(refs-tags)) - if verbose: - print("likely tags: %%s" %% ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %%s" %% r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None - } - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags"} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %%s" %% root) - raise NotThisMethod("no .git directory") - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%%s*" %% tag_prefix], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%%s'" - %% describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%%s' doesn't start with prefix '%%s'" - print(fmt %% (full_tag, tag_prefix)) - pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" - %% (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - return pieces - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post.devDISTANCE - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += ".post.dev%%d" %% pieces["distance"] - else: - # exception #1 - rendered = "0.post.dev%%d" %% pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%%s" %% pieces["short"] - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%%s" %% pieces["short"] - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Eexceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%%d" %% pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"]} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%%s'" %% style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None} - - -def get_versions(): - """Get version information or return default if unable to do so.""" - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - cfg = get_config() - verbose = cfg.verbose - - try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) - except NotThisMethod: - pass - - try: - root = os.path.realpath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): - root = os.path.dirname(root) - except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree"} - - try: - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) - return render(pieces, cfg.style) - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - except NotThisMethod: - pass - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version"} -''' - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None - } - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags"} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - raise NotThisMethod("no .git directory") - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%s' doesn't start with prefix '%s'" - print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - return pieces - - -def do_vcs_install(manifest_in, versionfile_source, ipy): - """Git-specific installation logic for Versioneer. - - For Git, this means creating/changing .gitattributes to mark _version.py - for export-time keyword substitution. - """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - files = [manifest_in, versionfile_source] - if ipy: - files.append(ipy) - try: - me = __file__ - if me.endswith(".pyc") or me.endswith(".pyo"): - me = os.path.splitext(me)[0] + ".py" - versioneer_file = os.path.relpath(me) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) - present = False - try: - f = open(".gitattributes", "r") - for line in f.readlines(): - if line.strip().startswith(versionfile_source): - if "export-subst" in line.strip().split()[1:]: - present = True - f.close() - except EnvironmentError: - pass - if not present: - f = open(".gitattributes", "a+") - f.write("%s export-subst\n" % versionfile_source) - f.close() - files.append(".gitattributes") - run_command(GITS, ["add", "--"] + files) - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes - both the project name and a version string. - """ - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with " - "prefix '%s'" % (root, dirname, parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None} - -SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.16) from -# revision-control system data, or from the parent directory name of an -# unpacked source archive. Distribution tarballs contain a pre-generated copy -# of this file. - -import json -import sys - -version_json = ''' -%s -''' # END VERSION_JSON - - -def get_versions(): - return json.loads(version_json) -""" - - -def versions_from_file(filename): - """Try to determine the version from _version.py if present.""" - try: - with open(filename) as f: - contents = f.read() - except EnvironmentError: - raise NotThisMethod("unable to read _version.py") - mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", - contents, re.M | re.S) - if not mo: - raise NotThisMethod("no version_json in _version.py") - return json.loads(mo.group(1)) - - -def write_to_version_file(filename, versions): - """Write the given version number to the given _version.py file.""" - os.unlink(filename) - contents = json.dumps(versions, sort_keys=True, - indent=1, separators=(",", ": ")) - with open(filename, "w") as f: - f.write(SHORT_VERSION_PY % contents) - - print("set %s to '%s'" % (filename, versions["version"])) - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post.devDISTANCE - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] - else: - # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Eexceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"]} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%s'" % style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None} - - -class VersioneerBadRootError(Exception): - """The project root directory is unknown or missing key files.""" - - -def get_versions(verbose=False): - """Get the project version from whatever source is available. - - Returns dict with two keys: 'version' and 'full'. - """ - if "versioneer" in sys.modules: - # see the discussion in cmdclass.py:get_cmdclass() - del sys.modules["versioneer"] - - root = get_root() - cfg = get_config_from_root(root) - - assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" - handlers = HANDLERS.get(cfg.VCS) - assert handlers, "unrecognized VCS '%s'" % cfg.VCS - verbose = verbose or cfg.verbose - assert cfg.versionfile_source is not None, \ - "please set versioneer.versionfile_source" - assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" - - versionfile_abs = os.path.join(root, cfg.versionfile_source) - - # extract version from first of: _version.py, VCS command (e.g. 'git - # describe'), parentdir. This is meant to work for developers using a - # source checkout, for users of a tarball created by 'setup.py sdist', - # and for users of a tarball/zipball created by 'git archive' or github's - # download-from-tag feature or the equivalent in other VCSes. - - get_keywords_f = handlers.get("get_keywords") - from_keywords_f = handlers.get("keywords") - if get_keywords_f and from_keywords_f: - try: - keywords = get_keywords_f(versionfile_abs) - ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) - if verbose: - print("got version from expanded keyword %s" % ver) - return ver - except NotThisMethod: - pass - - try: - ver = versions_from_file(versionfile_abs) - if verbose: - print("got version from file %s %s" % (versionfile_abs, ver)) - return ver - except NotThisMethod: - pass - - from_vcs_f = handlers.get("pieces_from_vcs") - if from_vcs_f: - try: - pieces = from_vcs_f(cfg.tag_prefix, root, verbose) - ver = render(pieces, cfg.style) - if verbose: - print("got version from VCS %s" % ver) - return ver - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - if verbose: - print("got version from parentdir %s" % ver) - return ver - except NotThisMethod: - pass - - if verbose: - print("unable to compute version") - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, "error": "unable to compute version"} - - -def get_version(): - """Get the short version string for this project.""" - return get_versions()["version"] - - -def get_cmdclass(): - """Get the custom setuptools/distutils subclasses used by Versioneer.""" - if "versioneer" in sys.modules: - del sys.modules["versioneer"] - # this fixes the "python setup.py develop" case (also 'install' and - # 'easy_install .'), in which subdependencies of the main project are - # built (using setup.py bdist_egg) in the same python process. Assume - # a main project A and a dependency B, which use different versions - # of Versioneer. A's setup.py imports A's Versioneer, leaving it in - # sys.modules by the time B's setup.py is executed, causing B to run - # with the wrong versioneer. Setuptools wraps the sub-dep builds in a - # sandbox that restores sys.modules to it's pre-build state, so the - # parent is protected against the child's "import versioneer". By - # removing ourselves from sys.modules here, before the child build - # happens, we protect the child from the parent's versioneer too. - # Also see https://github.com/warner/python-versioneer/issues/52 - - cmds = {} - - # we add "version" to both distutils and setuptools - from distutils.core import Command - - class cmd_version(Command): - description = "report generated version string" - user_options = [] - boolean_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - vers = get_versions(verbose=True) - print("Version: %s" % vers["version"]) - print(" full-revisionid: %s" % vers.get("full-revisionid")) - print(" dirty: %s" % vers.get("dirty")) - if vers["error"]: - print(" error: %s" % vers["error"]) - cmds["version"] = cmd_version - - # we override "build_py" in both distutils and setuptools - # - # most invocation pathways end up running build_py: - # distutils/build -> build_py - # distutils/install -> distutils/build ->.. - # setuptools/bdist_wheel -> distutils/install ->.. - # setuptools/bdist_egg -> distutils/install_lib -> build_py - # setuptools/install -> bdist_egg ->.. - # setuptools/develop -> ? - - # we override different "build_py" commands for both environments - if "setuptools" in sys.modules: - from setuptools.command.build_py import build_py as _build_py - else: - from distutils.command.build_py import build_py as _build_py - - class cmd_build_py(_build_py): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - _build_py.run(self) - # now locate _version.py in the new build/ directory and replace - # it with an updated value - if cfg.versionfile_build: - target_versionfile = os.path.join(self.build_lib, - cfg.versionfile_build) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - cmds["build_py"] = cmd_build_py - - if "cx_Freeze" in sys.modules: # cx_freeze enabled? - from cx_Freeze.dist import build_exe as _build_exe - - class cmd_build_exe(_build_exe): - def run(self): - root = get_root() - cfg = get_config_from_root(root) - versions = get_versions() - target_versionfile = cfg.versionfile_source - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, versions) - - _build_exe.run(self) - os.unlink(target_versionfile) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % - {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - cmds["build_exe"] = cmd_build_exe - del cmds["build_py"] - - # we override different "sdist" commands for both environments - if "setuptools" in sys.modules: - from setuptools.command.sdist import sdist as _sdist - else: - from distutils.command.sdist import sdist as _sdist - - class cmd_sdist(_sdist): - def run(self): - versions = get_versions() - self._versioneer_generated_versions = versions - # unless we update this, the command will keep using the old - # version - self.distribution.metadata.version = versions["version"] - return _sdist.run(self) - - def make_release_tree(self, base_dir, files): - root = get_root() - cfg = get_config_from_root(root) - _sdist.make_release_tree(self, base_dir, files) - # now locate _version.py in the new base_dir directory - # (remembering that it may be a hardlink) and replace it with an - # updated value - target_versionfile = os.path.join(base_dir, cfg.versionfile_source) - print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, - self._versioneer_generated_versions) - cmds["sdist"] = cmd_sdist - - return cmds - - -CONFIG_ERROR = """ -setup.cfg is missing the necessary Versioneer configuration. You need -a section like: - - [versioneer] - VCS = git - style = pep440 - versionfile_source = src/myproject/_version.py - versionfile_build = myproject/_version.py - tag_prefix = - parentdir_prefix = myproject- - -You will also need to edit your setup.py to use the results: - - import versioneer - setup(version=versioneer.get_version(), - cmdclass=versioneer.get_cmdclass(), ...) - -Please read the docstring in ./versioneer.py for configuration instructions, -edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. -""" - -SAMPLE_CONFIG = """ -# See the docstring in versioneer.py for instructions. Note that you must -# re-run 'versioneer.py setup' after changing this section, and commit the -# resulting files. - -[versioneer] -#VCS = git -#style = pep440 -#versionfile_source = -#versionfile_build = -#tag_prefix = -#parentdir_prefix = - -""" - -INIT_PY_SNIPPET = """ -from ._version import get_versions -__version__ = get_versions()['version'] -del get_versions -""" - - -def do_setup(): - """Main VCS-independent setup function for installing Versioneer.""" - root = get_root() - try: - cfg = get_config_from_root(root) - except (EnvironmentError, configparser.NoSectionError, - configparser.NoOptionError) as e: - if isinstance(e, (EnvironmentError, configparser.NoSectionError)): - print("Adding sample versioneer config to setup.cfg", - file=sys.stderr) - with open(os.path.join(root, "setup.cfg"), "a") as f: - f.write(SAMPLE_CONFIG) - print(CONFIG_ERROR, file=sys.stderr) - return 1 - - print(" creating %s" % cfg.versionfile_source) - with open(cfg.versionfile_source, "w") as f: - LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - - ipy = os.path.join(os.path.dirname(cfg.versionfile_source), - "__init__.py") - if os.path.exists(ipy): - try: - with open(ipy, "r") as f: - old = f.read() - except EnvironmentError: - old = "" - if INIT_PY_SNIPPET not in old: - print(" appending to %s" % ipy) - with open(ipy, "a") as f: - f.write(INIT_PY_SNIPPET) - else: - print(" %s unmodified" % ipy) - else: - print(" %s doesn't exist, ok" % ipy) - ipy = None - - # Make sure both the top-level "versioneer.py" and versionfile_source - # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so - # they'll be copied into source distributions. Pip won't be able to - # install the package without this. - manifest_in = os.path.join(root, "MANIFEST.in") - simple_includes = set() - try: - with open(manifest_in, "r") as f: - for line in f: - if line.startswith("include "): - for include in line.split()[1:]: - simple_includes.add(include) - except EnvironmentError: - pass - # That doesn't cover everything MANIFEST.in can do - # (http://docs.python.org/2/distutils/sourcedist.html#commands), so - # it might give some false negatives. Appending redundant 'include' - # lines is safe, though. - if "versioneer.py" not in simple_includes: - print(" appending 'versioneer.py' to MANIFEST.in") - with open(manifest_in, "a") as f: - f.write("include versioneer.py\n") - else: - print(" 'versioneer.py' already in MANIFEST.in") - if cfg.versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - cfg.versionfile_source) - with open(manifest_in, "a") as f: - f.write("include %s\n" % cfg.versionfile_source) - else: - print(" versionfile_source already in MANIFEST.in") - - # Make VCS-specific changes. For git, this means creating/changing - # .gitattributes to mark _version.py for export-time keyword - # substitution. - do_vcs_install(manifest_in, cfg.versionfile_source, ipy) - return 0 - - -def scan_setup_py(): - """Validate the contents of setup.py against Versioneer's expectations.""" - found = set() - setters = False - errors = 0 - with open("setup.py", "r") as f: - for line in f.readlines(): - if "import versioneer" in line: - found.add("import") - if "versioneer.get_cmdclass()" in line: - found.add("cmdclass") - if "versioneer.get_version()" in line: - found.add("get_version") - if "versioneer.VCS" in line: - setters = True - if "versioneer.versionfile_source" in line: - setters = True - if len(found) != 3: - print("") - print("Your setup.py appears to be missing some important items") - print("(but I might be wrong). Please make sure it has something") - print("roughly like the following:") - print("") - print(" import versioneer") - print(" setup( version=versioneer.get_version(),") - print(" cmdclass=versioneer.get_cmdclass(), ...)") - print("") - errors += 1 - if setters: - print("You should remove lines like 'versioneer.VCS = ' and") - print("'versioneer.versionfile_source = ' . This configuration") - print("now lives in setup.cfg, and should be removed from setup.py") - print("") - errors += 1 - return errors - -if __name__ == "__main__": - cmd = sys.argv[1] - if cmd == "setup": - errors = do_setup() - errors += scan_setup_py() - if errors: - sys.exit(1) diff --git a/vp_copy.yaml b/vp_copy.yaml index 0fee938e..4eb694ba 100644 --- a/vp_copy.yaml +++ b/vp_copy.yaml @@ -2,4 +2,8 @@ # version is fine) that should be copied from conda-forge to the vpython # channel. This should include all dependencies of vpython that are # not in the defaults channel. -{autobahn: null, six: null, txaio: null} +- name: autobahn +- name: txaio +- name: vpython +- name: python_abi +- name: jupyterlab-vpython diff --git a/vpython.recipe/bld.bat b/vpython.recipe/bld.bat deleted file mode 100644 index 7274c092..00000000 --- a/vpython.recipe/bld.bat +++ /dev/null @@ -1,8 +0,0 @@ -"%PYTHON%" setup.py install --single-version-externally-managed --record record.txt -if errorlevel 1 exit 1 - -:: Add more build steps here, if they are necessary. - -:: See -:: http://docs.continuum.io/conda/build.html -:: for a list of environment variables that are set during the build process. diff --git a/vpython.recipe/meta.yaml b/vpython.recipe/meta.yaml deleted file mode 100644 index 6d4c940c..00000000 --- a/vpython.recipe/meta.yaml +++ /dev/null @@ -1,56 +0,0 @@ -{% set data = load_setup_py_data() %} - -package: - name: vpython - version: {{data.get('version')}} - -source: - path: ../ -# patches: - # List any patch files here - # - fix.patch - -build: - # noarch_python: True - # preserve_egg_dir: True - # entry_points: - # Put any entry points (scripts to be generated automatically) here. The - # syntax is module:function. For example - # - # - vpython = vpython:main - # - # Would create an entry point called vpython that calls vpython.main() - - script: python setup.py install --single-version-externally-managed --record record.txt # [ not win ] - # If this is a new build for the same version, increment the build - # number. If you do not include this key, it defaults to 0. - number: 0 - -requirements: - build: - - python - - setuptools - - jupyter - - vpnotebook - - ujson - - cython - - wheel - - run: - - python - - jupyter - - vpnotebook - - ujson - - autobahn >=18.8.2 - - numpy - - ipykernel - -outputs: - # Someday, maybe: - type: wheel - - type: conda - name: vpython - -about: - home: http://vpython.org - license: MIT - summary: 'VPython for Jupyter Notebook' diff --git a/vpython.recipe/run_test.py b/vpython.recipe/run_test.py deleted file mode 100644 index 4e3e4956..00000000 --- a/vpython.recipe/run_test.py +++ /dev/null @@ -1,26 +0,0 @@ -import sys - -from jupyter_client import kernelspec - - -# If we get this far, vpnotebook ought to have been installed and -# we should have a vpython kernel. - -assert 'vpython' in kernelspec.find_kernel_specs().keys() - -# Python 2.7 and 3.4: -major, minor = sys.version_info[0:2] -if major == 2 or (major == 3 and minor == 4): - # Make sure vpython is installed. The import is expected to fail because it - # tries to connect to a jupyter comm, and we are not running a notebook. - # The test is really that the error message expected if that is the - # failure is the actual error message. - try: - import vpython - except Exception as e: - assert "The non-notebook version of vpython requires" in str(e) - except OSError as e: - if sys.platform.startswith('win'): - assert "The system cannot find the path specified" in str(e) - else: - assert "No such file or directory" in str(e) diff --git a/vpython/__init__.py b/vpython/__init__.py index 3fc6ce52..1c17f053 100644 --- a/vpython/__init__.py +++ b/vpython/__init__.py @@ -1,45 +1,37 @@ -from ._version import get_versions +from pkg_resources import get_distribution, DistributionNotFound + from .gs_version import glowscript_version -__version__ = get_versions()['version'] + +try: + __version__ = get_distribution(__name__).version +except DistributionNotFound: + # package is not installed + pass __gs_version__ = glowscript_version() -del get_versions + del glowscript_version +del get_distribution +del DistributionNotFound # Keep the remaining imports later to ensure that __version__ and # __gs_version__ exist before importing vpython, which itself imports # both of those. -from ._notebook_helpers import _isnotebook, __is_spyder -import platform -__p = platform.python_version() - -# Delete platform now that we are done with it -del platform - -__ispython3 = (__p[0] == '3') -__require_notebook = (not __ispython3) or (__p[2] < '5') # Python 2.7 or 3.4 require Jupyter notebook - -if __require_notebook and (not _isnotebook): - s = "The non-notebook version of vpython requires Python 3.5 or later." - s += "\nvpython does work on Python 2.7 and 3.4 in the Jupyter notebook environment." - raise Exception(s) - +from ._notebook_helpers import __is_spyder from .vpython import canvas # Need to initialize canvas before user does anything and before -# importing GSprint scene = canvas() from .vpython import * from .shapespaths import * from ._vector_import_helper import * from .rate_control import rate -from .gsprint import GSprint -# gsprint and vpython are showing up in the +# vpython is showing up in the # namespace, so delete them -del gsprint, vpython +del vpython # cyvector may be in the namespace. Get rid of it try: diff --git a/vpython/_notebook_helpers.py b/vpython/_notebook_helpers.py index 61c9dffe..9e168006 100644 --- a/vpython/_notebook_helpers.py +++ b/vpython/_notebook_helpers.py @@ -4,15 +4,40 @@ def __is_spyder(): return any('SPYDER' in name for name in os.environ) + +def __is_idle(): + return 'idlelib' in sys.modules + +def __is_PyCharm(): + return "PYCHARM_HOSTED" in os.environ +def __is_vscode(): + return 'TERM_PROGRAM' in os.environ.keys() and os.environ['TERM_PROGRAM'] == 'vscode' + +def __is_spyder_or_similar_IDE(): + return __is_idle() or __is_spyder() or __is_PyCharm() def _spyder_run_setting_is_correct(): - from spyder.config.main import CONF - return CONF['run']['default/interpreter/dedicated'] + try: + # This is the spyder 3 location + from spyder.config.main import CONF + except ImportError: + # CONF moved in spyder 4 + from spyder.config.manager import CONF + + # Use this instead of accessing like a dictionary so that a + # default value can be supplied if the setting is missing. + return CONF.get('run', 'default/interpreter/dedicated', False) def _warn_if_spyder_settings_wrong(): - if not _spyder_run_setting_is_correct(): + from spyder import __version__ + from packaging import version + pre_4 = version.parse(__version__) < version.parse('4.0.0') + # It looks like spyder 4 works fine without this setting, perhaps + # related to the change that they run files in an empty namespace + # instead of the namespace of the current console. + if not _spyder_run_setting_is_correct() and pre_4: print('\x1b[1;31m**** Please set spyder preference Run to ' '"Execute in a dedicated console" for the best ' 'vpython experience. ****\x1b[0m') @@ -45,3 +70,4 @@ def __checkisnotebook(): # IMPORTANT NOTE: this is evaluated ONCE the first time this is imported. _isnotebook = __checkisnotebook() _in_spyder = __is_spyder() +_in_spyder_or_similar_IDE = __is_spyder_or_similar_IDE() diff --git a/vpython/_version.py b/vpython/_version.py deleted file mode 100644 index c2e14d6e..00000000 --- a/vpython/_version.py +++ /dev/null @@ -1,484 +0,0 @@ - -# This file helps to compute a version number in source trees obtained from -# git-archive tarball (such as those provided by githubs download-from-tag -# feature). Distribution tarballs (built by setup.py sdist) and build -# directories (produced by setup.py build) will contain a much shorter file -# that just contains the computed version number. - -# This file is released into the public domain. Generated by -# versioneer-0.16 (https://github.com/warner/python-versioneer) - -"""Git implementation of _version.py.""" - -import errno -import os -import re -import subprocess -import sys - - -def get_keywords(): - """Get the keywords needed to look up the version information.""" - # these strings will be replaced by git during git-archive. - # setup.py/versioneer.py will grep for the variable names, so they must - # each be defined on a line of their own. _version.py will just call - # get_keywords(). - git_refnames = "$Format:%d$" - git_full = "$Format:%H$" - keywords = {"refnames": git_refnames, "full": git_full} - return keywords - - -class VersioneerConfig: - """Container for Versioneer configuration parameters.""" - - -def get_config(): - """Create, populate and return the VersioneerConfig() object.""" - # these strings are filled in when 'setup.py versioneer' creates - # _version.py - cfg = VersioneerConfig() - cfg.VCS = "git" - cfg.style = "pep440" - cfg.tag_prefix = "" - cfg.parentdir_prefix = "" - cfg.versionfile_source = "vpython/_version.py" - cfg.verbose = False - return cfg - - -class NotThisMethod(Exception): - """Exception raised if a method is not valid for the current scenario.""" - - -LONG_VERSION_PY = {} -HANDLERS = {} - - -def register_vcs_handler(vcs, method): # decorator - """Decorator to mark a method as the handler for a particular VCS.""" - def decorate(f): - """Store f in HANDLERS[vcs][method].""" - if vcs not in HANDLERS: - HANDLERS[vcs] = {} - HANDLERS[vcs][method] = f - return f - return decorate - - -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): - """Call the given command(s).""" - assert isinstance(commands, list) - p = None - for c in commands: - try: - dispcmd = str([c] + args) - # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) - break - except EnvironmentError: - e = sys.exc_info()[1] - if e.errno == errno.ENOENT: - continue - if verbose: - print("unable to run %s" % dispcmd) - print(e) - return None - else: - if verbose: - print("unable to find command, tried %s" % (commands,)) - return None - stdout = p.communicate()[0].strip() - if sys.version_info[0] >= 3: - stdout = stdout.decode() - if p.returncode != 0: - if verbose: - print("unable to run %s (error)" % dispcmd) - return None - return stdout - - -def versions_from_parentdir(parentdir_prefix, root, verbose): - """Try to determine the version from the parent directory name. - - Source tarballs conventionally unpack into a directory that includes - both the project name and a version string. - """ - dirname = os.path.basename(root) - if not dirname.startswith(parentdir_prefix): - if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with " - "prefix '%s'" % (root, dirname, parentdir_prefix)) - raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None} - - -@register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): - """Extract version information from the given file.""" - # the code embedded in _version.py can just fetch the value of these - # keywords. When used from setup.py, we don't want to import _version.py, - # so we do it with a regexp instead. This function is not used from - # _version.py. - keywords = {} - try: - f = open(versionfile_abs, "r") - for line in f.readlines(): - if line.strip().startswith("git_refnames ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): - mo = re.search(r'=\s*"(.*)"', line) - if mo: - keywords["full"] = mo.group(1) - f.close() - except EnvironmentError: - pass - return keywords - - -@register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): - """Get version information from git keywords.""" - if not keywords: - raise NotThisMethod("no keywords at all, weird") - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): - if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = set([r.strip() for r in refnames.strip("()").split(",")]) - # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of - # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) - if not tags: - # Either we're using git < 1.8.3, or there really are no tags. We use - # a heuristic: assume all version tags have a digit. The old git %d - # expansion behaves like git log --decorate=short and strips out the - # refs/heads/ and refs/tags/ prefixes that would let us distinguish - # between branches and tags. By ignoring refnames without digits, we - # filter out many common branch names like "release" and - # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) - if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) - if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) - for ref in sorted(tags): - # sorting will prefer e.g. "2.0" over "2.0rc1" - if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] - if verbose: - print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None - } - # no suitable tags, so version is "0+unknown", but full hex is still there - if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags"} - - -@register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): - """Get version from 'git describe' in the root of the source tree. - - This only gets called if the git-archive 'subst' keywords were *not* - expanded, and _version.py hasn't already been rewritten with a short - version string, meaning we're inside a checked out source tree. - """ - if not os.path.exists(os.path.join(root, ".git")): - if verbose: - print("no .git in %s" % root) - raise NotThisMethod("no .git directory") - - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] - # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] - # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) - # --long was added in git-1.5.5 - if describe_out is None: - raise NotThisMethod("'git describe' failed") - describe_out = describe_out.strip() - full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) - if full_out is None: - raise NotThisMethod("'git rev-parse' failed") - full_out = full_out.strip() - - pieces = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None - - # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] - # TAG might have hyphens. - git_describe = describe_out - - # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty - if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] - - # now we have TAG-NUM-gHEX or HEX - - if "-" in git_describe: - # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) - if not mo: - # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) - return pieces - - # tag - full_tag = mo.group(1) - if not full_tag.startswith(tag_prefix): - if verbose: - fmt = "tag '%s' doesn't start with prefix '%s'" - print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) - return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] - - # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) - - # commit: short hex revision ID - pieces["short"] = mo.group(3) - - else: - # HEX: no tags - pieces["closest-tag"] = None - count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) - pieces["distance"] = int(count_out) # total number of commits - - return pieces - - -def plus_or_dot(pieces): - """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" - - -def render_pep440(pieces): - """Build up version string, with post-release "local version identifier". - - Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you - get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty - - Exceptions: - 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - else: - # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" - return rendered - - -def render_pep440_pre(pieces): - """TAG[.post.devDISTANCE] -- No -dirty. - - Exceptions: - 1: no tags. 0.post.devDISTANCE - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += ".post.dev%d" % pieces["distance"] - else: - # exception #1 - rendered = "0.post.dev%d" % pieces["distance"] - return rendered - - -def render_pep440_post(pieces): - """TAG[.postDISTANCE[.dev0]+gHEX] . - - The ".dev0" means dirty. Note that .dev0 sorts backwards - (a dirty tree will appear "older" than the corresponding clean one), - but you shouldn't be releasing software with -dirty anyways. - - Exceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - return rendered - - -def render_pep440_old(pieces): - """TAG[.postDISTANCE[.dev0]] . - - The ".dev0" means dirty. - - Eexceptions: - 1: no tags. 0.postDISTANCE[.dev0] - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - else: - # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - return rendered - - -def render_git_describe(pieces): - """TAG[-DISTANCE-gHEX][-dirty]. - - Like 'git describe --tags --dirty --always'. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render_git_describe_long(pieces): - """TAG-DISTANCE-gHEX[-dirty]. - - Like 'git describe --tags --dirty --always -long'. - The distance/hash is unconditional. - - Exceptions: - 1: no tags. HEX[-dirty] (note: no 'g' prefix) - """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) - else: - # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" - return rendered - - -def render(pieces, style): - """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"]} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": - rendered = render_pep440(pieces) - elif style == "pep440-pre": - rendered = render_pep440_pre(pieces) - elif style == "pep440-post": - rendered = render_pep440_post(pieces) - elif style == "pep440-old": - rendered = render_pep440_old(pieces) - elif style == "git-describe": - rendered = render_git_describe(pieces) - elif style == "git-describe-long": - rendered = render_git_describe_long(pieces) - else: - raise ValueError("unknown style '%s'" % style) - - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None} - - -def get_versions(): - """Get version information or return default if unable to do so.""" - # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have - # __file__, we can work backwards from there to the root. Some - # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which - # case we can only use expanded keywords. - - cfg = get_config() - verbose = cfg.verbose - - try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) - except NotThisMethod: - pass - - try: - root = os.path.realpath(__file__) - # versionfile_source is the relative path from the top of the source - # tree (where the .git directory might live) to this file. Invert - # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): - root = os.path.dirname(root) - except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree"} - - try: - pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) - return render(pieces, cfg.style) - except NotThisMethod: - pass - - try: - if cfg.parentdir_prefix: - return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) - except NotThisMethod: - pass - - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version"} diff --git a/vpython/cyvector.pyx b/vpython/cyvector.pyx index 0856dcc4..183848e7 100644 --- a/vpython/cyvector.pyx +++ b/vpython/cyvector.pyx @@ -1,3 +1,5 @@ +# cython: language_level=3 + from random import random # List of names imported from this module with import * @@ -56,7 +58,7 @@ cdef class vector(object): return self def __repr__(self): - return '<{:.6g}, {:.6g}, {:.6g}>'.format(self._x, self._y, self._z) + return 'vector({:.6g}, {:.6g}, {:.6g})'.format(self._x, self._y, self._z) def __str__(self): return '<{:.6g}, {:.6g}, {:.6g}>'.format(self._x, self._y, self._z) @@ -83,6 +85,12 @@ cdef class vector(object): return vector(self * other._x, self * other._y, self * other._z) return NotImplemented + def __rmul__(self, other): # required to prevent y * x error + if isinstance(other, (float, int)): + return vector(self._x * other, self._y * other, self._z * other) + + return NotImplemented + def __eq__(self,other): if type(self) is vector and type(other) is vector: return self.equals(other) @@ -313,7 +321,9 @@ cpdef vector adjust_up(vector oldaxis, vector newaxis, vector up, vector save_ol return save_oldaxis if save_oldaxis is not None: # Restore saved oldaxis now that newaxis is nonzero - oldaxis = save_oldaxis + oldaxis._x = save_oldaxis._x # avoid creating a new vector + oldaxis._y = save_oldaxis._y + oldaxis._z = save_oldaxis._z save_oldaxis = None if newaxis.dot(up) != 0: # axis and up not orthogonal angle = oldaxis.diff_angle(newaxis) diff --git a/vpython/gsprint.py b/vpython/gsprint.py deleted file mode 100644 index 97f7756a..00000000 --- a/vpython/gsprint.py +++ /dev/null @@ -1,27 +0,0 @@ -from .vpython import baseObj - -# This must come after creating a canvas - - -class MISC(baseObj): - def __init__(self): - # _canvas_constructing is set below to - # avoid initiating the connection to javascript - # until the first object is drawn. - baseObj._canvas_constructing = True - super(MISC, self).__init__() - baseObj._canvas_constructing = False - - def prnt(self, s): - self.addmethod('GSprint', s) - - -__misc = MISC() - - -def GSprint(*args): - s = '' - for a in args: - s += str(a) + ' ' - s = s[:-1] - __misc.prnt(s) diff --git a/vpython/no_notebook.py b/vpython/no_notebook.py old mode 100644 new mode 100755 index 0d0a53c8..db7ac079 --- a/vpython/no_notebook.py +++ b/vpython/no_notebook.py @@ -1,5 +1,5 @@ -from .vpython import GlowWidget, baseObj, vector, canvas -from ._notebook_helpers import _in_spyder, _undo_vpython_import_in_spyder +from .vpython import GlowWidget, baseObj, vector, canvas, _browsertype +from ._notebook_helpers import _in_spyder, _undo_vpython_import_in_spyder, _in_spyder_or_similar_IDE from http.server import BaseHTTPRequestHandler, HTTPServer import os @@ -13,17 +13,51 @@ import txaio import copy import socket +import multiprocessing +import time import signal from urllib.parse import unquote from .rate_control import rate +makeDaemonic = (platform.system() == "Windows") + +# Redefine `Thread.run` to not show a traceback for Spyder when stopping +# the server by raising a KeyboardInterrupt or SystemExit. +if _in_spyder_or_similar_IDE: + def install_thread_stopped_message(): + """ + Workaround to prevent showing a traceback when VPython server stops. + + See: + https://bugs.python.org/issue1230540 + """ + run_old = threading.Thread.run + + def run(*args, **kwargs): + try: + run_old(*args, **kwargs) + except (KeyboardInterrupt, SystemExit): + pass + # ("VPython server stopped.") + except: + raise + threading.Thread.run = run + + install_thread_stopped_message() + + # Check for Ctrl+C. SIGINT will also be sent by our code if WServer is closed. def signal_handler(signal, frame): - stop_server() - + #print("in signal handler, calling stop server") + try: + stop_server() + except (KeyboardInterrupt): + pass + except: + raise signal.signal(signal.SIGINT, signal_handler) @@ -31,13 +65,22 @@ def signal_handler(signal, frame): # get glowcomm.html, library .js files, images, or font files -def find_free_port(): +def find_free_port(port=0): s = socket.socket() - s.bind(('', 0)) # find an available port + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + #if hasattr(socket, 'SO_REUSEPORT'): # This may be required on systems that support it. Needs testing. + # s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + try : + s.bind(('', port)) # bind to a port + except: + raise return s.getsockname()[1] +if "VPYTHON_HTTP_PORT" in os.environ: + __HTTP_PORT = int(os.environ["VPYTHON_HTTP_PORT"]) +else: + __HTTP_PORT = find_free_port() -__HTTP_PORT = find_free_port() __SOCKET_PORT = find_free_port() try: @@ -148,7 +191,7 @@ def onOpen(self): # in favor of "async def onMessage...", and "yield from" with "await". # Attempting to use the older Python 3.4 syntax was not successful, so this # no-notebook version of VPython requires Python 3.5.3 or later. - #@asyncio.coroutine + # @asyncio.coroutine # def onMessage(self, data, isBinary): # data includes canvas update, events, pick, compound # data includes canvas update, events, pick, compound async def onMessage(self, data, isBinary): @@ -184,8 +227,18 @@ async def onMessage(self, data, isBinary): # message format used by notebook msg = {'content': {'data': [m]}} loop = asyncio.get_event_loop() - await loop.run_in_executor(None, GW.handle_msg, msg) - + try: + await loop.run_in_executor(None, GW.handle_msg, msg) + except: + # + # this will throw a runtime exception after the main Thread + # has stopped, but we don't really case since the main thread + # is no longer there to do anything anyway. + if threading.main_thread().is_alive(): + raise + else: + pass + def onClose(self, wasClean, code, reason): """Called when browser tab is closed.""" global websocketserving @@ -202,14 +255,14 @@ def onClose(self, wasClean, code, reason): # need it here because in spyder the script may have stopped on its # own ( because it has no infinite loop in it ) so the only signal # that the tab has been closed comes via the websocket. - if _in_spyder: + if _in_spyder_or_similar_IDE: _undo_vpython_import_in_spyder() # We want to exit, but the main thread is running. # Only the main thread can properly call sys.exit, so have a signal # handler call it on the main thread's behalf. if platform.system() == 'Windows': - if threading.main_thread().is_alive(): + if threading.main_thread().is_alive() and not _in_spyder_or_similar_IDE: # On windows, if we get here then this signal won't be caught # by our signal handler. Just call it ourselves. os.kill(os.getpid(), signal.CTRL_C_EVENT) @@ -220,22 +273,58 @@ def onClose(self, wasClean, code, reason): try: + no_launch = os.environ.get("VPYTHON_NO_LAUNCH_BROWSER", False) + if no_launch=="0": + no_launch=False if platform.python_implementation() == 'PyPy': server_address = ('', 0) # let HTTPServer choose a free port __server = HTTPServer(server_address, serveHTTP) port = __server.server_port # get the chosen port # Change the global variable to store the actual port used __HTTP_PORT = port - _webbrowser.open('http://localhost:{}'.format(port) + if not no_launch: + _webbrowser.open('http://localhost:{}'.format(port) ) # or webbrowser.open_new_tab() else: __server = HTTPServer(('', __HTTP_PORT), serveHTTP) # or webbrowser.open_new_tab() - _webbrowser.open('http://localhost:{}'.format(__HTTP_PORT)) + if not no_launch: + if _browsertype == 'default': # uses default browser + _webbrowser.open('http://localhost:{}'.format(__HTTP_PORT)) + except: pass -__w = threading.Thread(target=__server.serve_forever) + +if _browsertype == 'pyqt': + if platform.python_implementation() == 'PyPy': + raise RuntimeError('The pyqt browser cannot be used PyPy. Please use ' + 'the default browser instead by removing ' + 'set_browser("pyqt") from your code.') + elif sys.platform.startswith('win'): + raise RuntimeError('The pyqt browser cannot be used on Windows. ' + 'Please use the default browser instead by ' + 'removing set_browser("pyqt") from your code.') + elif sys.version_info.major == 3 and sys.version_info.minor >= 8: + raise RuntimeError('The pyqt browser cannot be used on Python 3.8. ' + 'Please use the default browser instead by ' + 'removing set_browser("pyqt") from your code.') + + +def start_Qapp(port): + # creates a python browser with PyQt5 + # runs qtbrowser.py in a separate process + filepath = os.path.dirname(__file__) + filename = filepath + '/qtbrowser.py' + os.system('python ' + filename + ' http://localhost:{}'.format(port)) + + +# create a browser in its own process +if _browsertype == 'pyqt': + __m = multiprocessing.Process(target=start_Qapp, args=(__HTTP_PORT,)) + __m.start() + +__w = threading.Thread(target=__server.serve_forever, daemon=makeDaemonic) __w.start() @@ -268,14 +357,16 @@ def start_websocket_server(): # Put the websocket server in a separate thread running its own event loop. # That works even if some other program (e.g. spyder) already running an # async event loop. -__t = threading.Thread(target=start_websocket_server) +__t = threading.Thread(target=start_websocket_server, daemon=makeDaemonic) __t.start() def stop_server(): """Shuts down all threads and exits cleanly.""" + #print("in stop server") global __server __server.shutdown() + event_loop = txaio.config.loop event_loop.stop() # We've told the event loop to stop, but it won't shut down until we poke @@ -285,28 +376,45 @@ def stop_server(): # If we are in spyder, undo our import. This gets done in the websocket # server onClose above if the browser tab is closed but is not done # if the user stops the kernel instead. - if _in_spyder: + if _in_spyder_or_similar_IDE: _undo_vpython_import_in_spyder() # We don't want Ctrl-C to try to sys.exit inside spyder, i.e. # in an ipython console with a separate python kernel running. - if _in_spyder: + if _in_spyder_or_similar_IDE: raise KeyboardInterrupt if threading.main_thread().is_alive(): + #print("main is alive...") sys.exit(0) else: - pass + # + # check to see if the event loop is still going, if so join it. + # + #print("main is dead..") + if __t.is_alive(): + #print("__t is alive still") + if threading.get_ident() != __t.ident: + #print("but it's not my thread, so I'll join...") + __t.join() + else: + #print("__t is alive, but that's my thread! So skip it.") + pass + else: + if makeDaemonic: + sys.exit(0) + # If the main thread has already stopped, the python interpreter # is likely just running .join on the two remaining threads (in # python/threading.py:_shutdown). Since we just stopped those threads, # we'll now exit. - - + GW = GlowWidget() while not (httpserving and websocketserving): # try to make sure setup is complete - rate(60) + time.sleep(0.1) + # Dummy variable to import _ = None + diff --git a/vpython/qtbrowser.py b/vpython/qtbrowser.py new file mode 100644 index 00000000..5a293b5e --- /dev/null +++ b/vpython/qtbrowser.py @@ -0,0 +1,25 @@ +import sys +import PyQt5.QtCore +import PyQt5.QtWebEngineWidgets +from PyQt5.QtWidgets import QApplication + + +if len(sys.argv) > 1: + + if sys.argv[1]: + + app = QApplication(sys.argv) + + web = PyQt5.QtWebEngineWidgets.QWebEngineView() + web.load(PyQt5.QtCore.QUrl(sys.argv[1])) + web.show() + + sys.exit(app.exec_()) + + else: + print("Please give a URL as the first command-line argument " + "when running the program.") + +else: + print("Please give a URL as the first command-line argument " + "when running the program.") diff --git a/vpython/rate_control.py b/vpython/rate_control.py index 058ada4d..a44c9ae1 100644 --- a/vpython/rate_control.py +++ b/vpython/rate_control.py @@ -1,5 +1,11 @@ import time -import platform + +_clock = time.perf_counter + + +_tick = 1/60 + +#import platform import queue import json @@ -13,24 +19,24 @@ # Unresolved bug: rate(X) yields only about 0.8X iterations per second. MIN_RENDERS = 10 -MAX_RENDERS = 30 +MAX_RENDERS = 60 INTERACT_PERIOD = 1.0/MAX_RENDERS USER_FRACTION = 0.5 -_plat = platform.system() - -if _plat == 'Windows': - # On Windows, the best timer is supposedly time.clock() - _clock = time.clock - _tick = 1/60 -elif _plat == 'Macintosh': - # On platforms other than Windows, the best timer is supposedly time.time() - _clock = time.time - _tick = 0.01 -else: # 'Unix' - # On platforms other than Windows, the best timer is supposedly time.time() - _clock = time.time - _tick = 0.01 # though sleep seems to be accurate at the 1 millisecond level +##_plat = platform.system() + +##if _plat == 'Windows': +## # On Windows, the best timer is supposedly time.clock() +## _clock = time.clock +## _tick = 1/INTERACT_PERIOD +##elif _plat == 'Macintosh': +## # On platforms other than Windows, the best timer is supposedly time.time() +## _clock = time.time +## _tick = 0.01 +##else: # 'Unix' +## # On platforms other than Windows, the best timer is supposedly time.time() +## _clock = time.time +## _tick = 0.01 # though sleep seems to be accurate at the 1 millisecond level ##Possible way to get one-millisecond accuracy in sleep on Windows: ##http://msdn.microsoft.com/en-us/library/windows/desktop/ms686298(v=vs.85).aspx @@ -233,7 +239,7 @@ def message_sender(msg): class _RateKeeper2(RateKeeper): def __init__(self, interactPeriod=INTERACT_PERIOD, interactFunc=simulateDelay): - self.rval = 30 + self.rval = MAX_RENDERS self.tocall = None self._sender = None super(_RateKeeper2, self).__init__(interactPeriod=interactPeriod, interactFunc=self.sendtofrontend) diff --git a/vpython/shapespaths.py b/vpython/shapespaths.py index 7e849fb3..0eb8d0bf 100644 --- a/vpython/shapespaths.py +++ b/vpython/shapespaths.py @@ -70,7 +70,7 @@ def roundc(cps, roundness=0.1, invert=False, nseg=16): v = p1 - center dtheta = -dtheta - for j in range(nseg): + for j in range(1,nseg): # don't repeat the starting point of this arc v1 = center + v.rotate(j*dtheta) ncp.append([v1.x, v1.y]) diff --git a/vpython/test/test_namespace.py b/vpython/test/test_namespace.py index 5490f6ad..8287cd92 100644 --- a/vpython/test/test_namespace.py +++ b/vpython/test/test_namespace.py @@ -3,7 +3,6 @@ API_NAMES = [ 'Camera', - 'GSprint', 'GSversion', 'GlowWidget', 'Mouse', @@ -22,6 +21,7 @@ 'atan2', 'atanh', 'attach_arrow', + 'attach_light', 'attach_trail', 'baseObj', 'box', @@ -82,6 +82,7 @@ 'isfinite', 'isinf', 'isnan', + 'keysdown', 'label', 'ldexp', 'lgamma', @@ -117,6 +118,7 @@ 'roundc', 'scalecp', 'scene', + 'set_browser', 'shape_object', 'shapes', 'shapespaths', @@ -140,7 +142,11 @@ 'version', 'vertex', 'winput', - 'wtext' + 'wtext', + 'wtext', + 'winput', + 'keysdown', + 'sign' ] @@ -156,6 +162,26 @@ def test_names_in_base_namspace(): if python_version.major == 3 and python_version.minor >= 7: api_name_set.add('remainder') + # Python 3.8 added even more math functions. + if python_version.major == 3 and python_version.minor >= 8: + for name in ['dist', 'comb', 'prod', 'perm', 'isqrt']: + api_name_set.add(name) + + # Python 3.9 adds three more new math functions. + if python_version.major == 3 and python_version.minor >= 9: + for name in ['lcm', 'ulp', 'nextafter']: + api_name_set.add(name) + + # Python 3.11 adds two more new math functions. + if python_version.major == 3 and python_version.minor >= 11: + for name in ['cbrt', 'exp2']: + api_name_set.add(name) + + # Python 3.12 adds one more new math function. + if python_version.major == 3 and python_version.minor >= 12: + for name in ['sumprod']: + api_name_set.add(name) + print(sorted(api_name_set - current_names)) # We may have added new names, so start with this weaker test diff --git a/vpython/vector.py b/vpython/vector.py index 087c3323..7f6c4836 100644 --- a/vpython/vector.py +++ b/vpython/vector.py @@ -51,7 +51,7 @@ def __str__(self): return '<{:.6g}, {:.6g}, {:.6g}>'.format(self._x, self._y, self._z) def __repr__(self): - return '<{:.6g}, {:.6g}, {:.6g}>'.format(self._x, self._y, self._z) + return 'vector({:.6g}, {:.6g}, {:.6g})'.format(self._x, self._y, self._z) def __add__(self, other): if type(other) is vector: @@ -302,7 +302,9 @@ def adjust_up(oldaxis, newaxis, up, save_oldaxis): # adjust up when axis is chan return save_oldaxis if save_oldaxis is not None: # Restore saved oldaxis now that newaxis is nonzero - oldaxis = save_oldaxis + oldaxis._x = save_oldaxis._x # avoid creating a new vector + oldaxis._y = save_oldaxis._y + oldaxis._z = save_oldaxis._z save_oldaxis = None if newaxis.dot(up) != 0: # axis and up not orthogonal angle = oldaxis.diff_angle(newaxis) @@ -318,7 +320,6 @@ def adjust_up(oldaxis, newaxis, up, save_oldaxis): # adjust up when axis is chan oldaxis._x = newaxis._x # avoid creating a new vector oldaxis._y = newaxis._y oldaxis._z = newaxis._z - return save_oldaxis def adjust_axis(oldup, newup, axis, save_oldup): # adjust axis when up is changed if abs(newup._x) + abs(newup._y) + abs(newup._z) == 0: diff --git a/vpython/vpython.py b/vpython/vpython.py index 45f3a82f..28c1056c 100644 --- a/vpython/vpython.py +++ b/vpython/vpython.py @@ -6,35 +6,46 @@ from math import sqrt, tan, pi import time -try: - clock = time.perf_counter # time.clock is deprecated in Python 3.3, gone in 3.8 -except: - clock = time.clock + +# vpython provides clock in its namespace +clock = time.perf_counter + +def sign(x): # for compatibility with Web VPython + if x > 0: return 1 + if x < 0: return -1 + return 0 + import sys from . import __version__, __gs_version__ from ._notebook_helpers import _isnotebook -from ._vector_import_helper import (vector, mag, norm, dot, adjust_up, +from ._vector_import_helper import (vector, mag, norm, cross, dot, adjust_up, adjust_axis, object_rotate) + + +def Exit(): + # no infinite loop here so build processs can finish. + # print("in atexit") + pass + +import atexit + +if platform.system() == 'Windows': + atexit.register(Exit) # List of names that will be imported from this file with import * __all__ = ['Camera', 'GlowWidget', 'version', 'GSversion', 'Mouse', 'arrow', 'attach_arrow', - 'attach_trail', 'baseObj', 'box', 'bumpmaps', 'button', + 'attach_light', 'attach_trail', 'baseObj', 'box', 'bumpmaps', 'button', 'canvas', 'checkbox', 'clock', 'color', 'combin', 'compound', 'cone', 'controls', 'curve', 'curveMethods', 'cylinder', 'distant_light', 'ellipsoid', 'event_return', 'extrusion', 'faces', 'frame', 'gcurve', 'gdots', 'ghbars', 'gobj', 'graph', 'gvbars', 'helix', 'label', 'local_light', 'menu', 'meta_canvas', 'points', 'pyramid', - 'quad', 'radio', 'ring', 'simple_sphere', 'sleep', 'slider', 'sphere', + 'quad', 'radio', 'ring', 'set_browser', 'simple_sphere', 'sleep', 'slider', 'sphere', 'standardAttributes', 'text', 'textures', 'triangle', 'vertex', - 'wtext', 'winput', 'keysdown'] + 'wtext', 'winput', 'keysdown', 'sign'] -__p = platform.python_version() -_ispython3 = (__p[0] == '3') -if _ispython3: - from inspect import signature # Python 3; needed to allow zero arguments in a bound function -else: - from inspect import getargspec # Python 2; needed to allow zero arguments in a bound function +from inspect import signature # Python 3; needed to allow zero arguments in a bound function # __version__ is the version number of the Jupyter VPython installer, generated in building the installer. version = [__version__, 'jupyter'] @@ -97,30 +108,31 @@ 'right':'q', 'top':'r', 'bottom':'s', '_cloneid':'t', 'logx':'u', 'logy':'v', 'dot':'w', 'dot_radius':'x', 'markers':'y', 'legend':'z', 'label':'A', 'delta':'B', 'marker_color':'C', - 'size_units':'D', 'userpan':'E'} + 'size_units':'D', 'userpan':'E', 'scroll':'F', 'choices':'G', 'depth':'H', + 'round':'I', 'name':'J', 'offset':'K', 'attach_idx':'L', 'ccw':'M'} # methods are X in {'m': '23X....'} # pos is normally updated as an attribute, but for interval-based trails, it is updated (multiply) as a method -__methods = {'select':'a', 'pos':'b', 'start':'c', 'stop':'d', 'clear':'f', # unused eghijklmnopvxyzCDFAB +__methods = {'select':'a', 'pos':'b', 'start':'c', 'stop':'d', 'clear':'f', # unused eghijklmnopvxyzCDFABu 'plot':'q', 'add_to_trail':'s', - 'follow':'t', '_attach_arrow':'u', 'clear_trail':'w', - 'bind':'G', 'unbind':'H', 'waitfor':'I', 'pause':'J', 'pick':'K', 'GSprint':'L', + 'follow':'t', 'clear_trail':'w', + 'bind':'G', 'unbind':'H', 'waitfor':'I', 'pause':'J', 'pick':'K', 'delete':'M', 'capture':'N'} -__vecattrs = ['pos', 'up', 'color', 'trail_color', 'axis', 'size', 'origin', '_attach_arrow', - 'direction', 'linecolor', 'bumpaxis', 'dot_color', 'ambient', 'add_to_trail', +__vecattrs = ['pos', 'up', 'color', 'trail_color', 'axis', 'size', 'origin', + 'direction', 'linecolor', 'bumpaxis', 'ambient', 'add_to_trail', 'foreground', 'background', 'ray', 'ambient', 'center', 'forward', 'normal', - 'marker_color'] + 'marker_color', 'offset', 'dot_color'] -__textattrs = ['text', 'align', 'caption', 'title', 'xtitle', 'ytitle', 'selected', 'label', 'capture', - 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint'] +__textattrs = ['text', 'align', 'caption', 'title', 'xtitle', 'ytitle', 'selected', 'label', 'capture', 'name', + 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'choices'] def _encode_attr2(sendval, val, ismethods): s = '' if sendval in __vecattrs: # it would be good to do some kind of compression of doubles s += "{:.16G},{:.16G},{:.16G}".format(val[0], val[1], val[2]) elif sendval in __textattrs: - # '\n' doesn't survive JSON transmission, so we replace '\n' with '
' (and convert back in glowcomm) + # '\n' doesn't survive JSON transmission, so we replace '\n' with '
' (and convert back in glowcomm) if not isinstance(val, str): val = print_to_string(val) val = val.replace('\n', '
') s += val @@ -129,8 +141,12 @@ def _encode_attr2(sendval, val, ismethods): s += "{:.16G},".format(p) s = s[:-1] elif sendval == 'plot' or sendval == 'data': - for p in val: - s += "{:.16G},{:.16G},".format(p[0], p[1]) +# for p in val: +# s += "{:.16G},{:.16G},".format(p[0], p[1]) + if sendval == 'data' and len(val) == 0: s += "None, None," + else: + for p in val: + s += "{:.16G},{:.16G},".format(p[0], p[1]) s = s[:-1] elif sendval in ['v0', 'v1', 'v2', 'v3']: # val is the vertex object referenced by triangle or quad s += str(val.idx) @@ -188,7 +204,8 @@ class baseObj(object): updates = {'cmds':[], 'methods':[], 'attrs':{}} object_registry = {} ## idx -> instance attach_arrows = [] - attach_trails = [] ## needed only for functions + attach_trails = [] # needed only for functions + follow_objects = [] # entries are [invisible object to follow, function to call for pos, prevous pos] attrs = set() # each element is (idx, attr name) @classmethod @@ -203,16 +220,21 @@ def empty(cls): @classmethod def handle_attach(cls): # called when about to send data to the browser + ## update every attach_arrow if relevant vector has changed for aa in cls.attach_arrows: - obj = baseObj.object_registry[aa._obj] + if not aa._run: continue + obj = baseObj.object_registry[aa._object] + if not hasattr(obj, aa.attr): # no longer an attribute of the object + continue vval = getattr(obj, aa.attr) # could be 'velocity', for example if not isinstance(vval, vector): - raise AttributeError("attach_arrow value must be a vector.") - if (isinstance(aa._last_val, vector) and aa._last_val.equals(vval)): - continue - aa.addmethod('_attach_arrow', vval.value) - aa._last_val = vector(vval) # keep copy of last vector + raise AttributeError('attach_arrow attribute "'+aa.attr+'" value must be a vector.') + if aa._last_pos.equals(obj._pos) and aa._last_val.equals(vval): continue + aa._last_val = vector(vval) # keep copies of last vectors + aa._last_pos = vector(obj._pos) + aa.pos = obj._pos + aa.axis = aa._scale*vval ## update every attach_trail that depends on a function for aa in cls.attach_trails: @@ -227,6 +249,15 @@ def handle_attach(cls): # called when about to send data to the browser continue aa._last_val = fval + ## update every scene.camera.follow(function) + for aa in cls.follow_objects: + obj = aa[0] + val = aa[1]() + lastpos = aa[2] + if val != lastpos: + aa[2] = val + obj.pos = val + def __init__(self, **kwargs): if not (baseObj._view_constructed or baseObj._canvas_constructing): @@ -374,19 +405,18 @@ def handle_msg(self, msg): elif evt['widget'] == 'checkbox': obj._checked = evt['value'] elif evt['widget'] == 'radio': - obj._checked = evt['value'] + obj.checked = evt['value'] elif evt['widget'] == 'winput': obj._text = evt['text'] obj._number = evt['value'] + # inspect the bound function and see what it's expecting - if _ispython3: # Python 3 - a = signature(obj._bind) - if str(a) != '()': obj._bind( obj ) - else: obj._bind() - else: # Python 2 - a = getargspec(obj._bind) - if len(a.args) > 0: obj._bind( obj ) - else: obj._bind() + a = signature(obj._bind) + if str(a) != '()': + obj._bind( obj ) + else: + obj._bind() + else: ## a canvas event if 'trigger' not in evt: cvs = baseObj.object_registry[evt['canvas']] @@ -470,6 +500,11 @@ class standardAttributes(baseObj): 'make_trail', 'trail_type', 'interval', 'retain', 'trail_color', 'trail_radius', 'texture', 'pickable'], ['red', 'green', 'blue','length', 'width', 'height']], + 'group':[['pos', 'color', 'trail_color'], + ['axis', 'size', 'up'], + ['visible', 'make_trail', 'trail_type', 'interval', + 'retain', 'trail_color', 'trail_radius'], + ['red', 'green', 'blue','length', 'width', 'height']], 'sphere':[['pos', 'color', 'trail_color'], ['axis', 'size', 'up'], ['visible', 'opacity','shininess', 'emissive', @@ -488,7 +523,7 @@ class standardAttributes(baseObj): 'shininess', 'emissive', 'texture', 'frame', 'material', 'make_trail', 'trail_type', 'interval', 'retain', 'trail_color', 'trail_radius', 'texture', - 'shaftwidth', 'headwidth', 'headlength', 'pickable'], + 'round', 'shaftwidth', 'headwidth', 'headlength', 'pickable'], ['red', 'green', 'blue','length', 'width', 'height']], 'ring':[['pos', 'color', 'trail_color', 'size'], ['axis', 'up'], @@ -499,25 +534,25 @@ class standardAttributes(baseObj): 'helix':[['pos', 'color', 'trail_color'], ['axis', 'size', 'up'], ['visible', 'opacity','shininess', 'emissive', - 'make_trail', 'trail_type', 'interval', + 'make_trail', 'trail_type', 'interval', 'ccw', 'retain', 'trail_color', 'trail_radius', 'coils', 'thickness', 'pickable'], - ['red', 'green', 'blue','length', 'width', 'height']], + ['red', 'green', 'blue','length', 'width', 'height', 'radius']], 'curve':[['origin', 'color'], ['axis', 'size', 'up'], ['visible', 'shininess', 'emissive', 'radius', 'retain', 'pickable'], ['red', 'green', 'blue','length', 'width', 'height']], 'points':[['color'], [], - ['visible', 'shininess', 'emissive', 'radius', 'retain', 'pickable', 'size_units'], + ['visible', 'opacity', 'shininess', 'emissive', 'radius', 'retain', 'pickable', 'size_units'], ['red', 'green', 'blue']], 'label':[['pos', 'color', 'background', 'linecolor'], [], ['visible', 'xoffset', 'yoffset', 'font', 'height', 'opacity', 'border', 'line', 'box', 'space', 'align', 'linewidth', 'pixel_pos'], ['text']], - 'local_light':[['pos', 'color'], + 'local_light':[['pos', 'color', 'offset'], [], - ['visible'], + ['visible', 'attach_idx'], []], 'distant_light':[['direction', 'color'], [], @@ -541,10 +576,6 @@ class standardAttributes(baseObj): [], ['texture', 'bumpmap', 'visible', 'pickable'], ['v0', 'v1', 'v2', 'v3'] ], - 'attach_arrow': [ [ 'color', 'attrval'], - [], - ['shaftwidth', 'scale', 'obj', 'attr'], - [] ], 'attach_trail': [ ['color'], [], ['radius', 'pps', 'retain', 'type', '_obj'], @@ -553,10 +584,12 @@ class standardAttributes(baseObj): [], ['location', 'text'], []], - 'extrusion':[ ['pos', 'color', 'start_face_color', 'end_face_color'], + 'extrusion':[ ['pos', 'color', 'start_face_color', 'end_face_color', + 'start_normal', 'end_normal'], [ 'axis', 'size', 'up' ], ['path', 'shape', 'visible', 'opacity','shininess', 'emissive', - 'show_start_face', 'show_end_face', + 'show_start_face', 'show_end_face', 'smooth', 'smooth_joints', 'sharp_joints', + 'scale', 'xscale', 'yscale', 'twist', 'make_trail', 'trail_type', 'interval', 'show_start_face', 'show_end_face', 'retain', 'trail_color', 'trail_radius', 'texture', 'pickable' ], ['red', 'green', 'blue','length', 'width', 'height'] ], @@ -584,13 +617,14 @@ def setup(self, args): del args['_objName'] # default values + self._sizing = True # axis/size connection is the default; False for sphere, ring, text, compound self._pos = vector(0,0,0) self._axis = vector(1,0,0) self._up = vector(0,1,0) self._color = vector(1,1,1) defaultSize = args['_default_size'] if defaultSize is not None: # is not points or vertex or triangle or quad - self._size = defaultSize ## because VP differs from JS + self._size = defaultSize del args['_default_size'] self._texture = None self._opacity = 1.0 @@ -610,6 +644,8 @@ def setup(self, args): self._size_units = 'pixels' self._texture = None self._pickable = True + self._offset = vector(0,0,0) + self._attach_idx = None self._save_oldaxis = None # used in linking axis and up self._save_oldup = None # used in linking axis and up _special_clone = None @@ -625,40 +661,39 @@ def setup(self, args): if a in args: argsToSend.append(a) val = args[a] - if isinstance(val, vector): setattr(self, '_'+a, vector(val)) ## '_' bypasses setters; copy of val - else: raise AttributeError(a+' must be a vector') + if objName == 'extrusion' and a == 'color': + setattr(self, '_'+a, val) # '_' bypasses setters + else: + if isinstance(val, vector): setattr(self, '_'+a, vector(val)) # '_' bypasses setters; copy of val + else: raise AttributeError(a+' must be a vector') del args[a] - vectorInteractions = [ ('size','axis'), ('axis','size'), ('axis','up'), ('up','axis')] - - # override defaults for vector attributes with side effects + # Track side effects of modifying size, axis, or up # For consistency with GlowScript, axis is listed before up in the attrLists, # so that setting axis may affect up, but then setting up can affect axis afterwards. - attrs = standardAttributes.attrLists[objName][1] + attrs = standardAttributes.attrLists[objName][1] # vector attributes with interactions for a in attrs: if a in args: val = args[a] if isinstance(val, vector): - setattr(self, a, vector(val)) ## use setter to take care of side effects; copy of val + setattr(self, a, vector(val)) if a not in argsToSend: argsToSend.append(a) - for vi in vectorInteractions: - if vi[0] == a: - if vi[1] not in argsToSend: - argsToSend.append(vi[1]) - elif objName == 'points' and a == 'size': ## in this case size is a scalar - argsToSend.append(a) + if a == 'size': + self._axis = self._axis.norm()*val.x + elif a == 'axis': + self._size.x = mag(val) + self.axis = val # this will have the side effect of modifying up + elif a == 'up': + self.up = val # this will have the side effect of modifying axis else: raise AttributeError(a+' must be a vector') del args[a] if defaultSize is not None: - ## If not 3D text, always send size because Python size differs from JS size - if 'size' not in argsToSend and objName != 'text': - argsToSend.append('size') self._trail_radius = 0.1 * self._size.y ## default depends on size elif objName == 'points': self._trail_radius = self._radius # points object - + # override defaults for scalar attributes without side effects attrs = standardAttributes.attrLists[objName][2] for a in attrs: @@ -678,6 +713,9 @@ def setup(self, args): setattr(self, a, args[a]) ## use setter to take care of side effects if scalarInteractions[a] not in argsToSend: argsToSend.append(scalarInteractions[a]) # e.g. if a is radius, send size + # The following makes sure that size.x is nonzero. + # It will be reset when axis is no longer zero. + if defaultSize is not None and self._size._x == 0: self._size._x = 1 del args[a] # set values of user-defined attributes @@ -695,10 +733,8 @@ def setup(self, args): elif isinstance(aval, vertex): aval = aval.idx if objName in nosize and a == 'size': continue # do not send superfluous size - #cmd["attrs"].append({"attr":a, "value": aval}) cmd[a] = aval - # set canvas if self.canvas is None: ## not specified in constructor self.canvas = canvas.get_selected() @@ -713,19 +749,16 @@ def setup(self, args): if _special_clone is not None: cmd["_cloneid"] = _special_clone self.appendcmd(cmd) - # if ('frame' in args and args['frame'] is not None): - # frame.objects.append(self) - # frame.update_obj_list() - # attribute vectors have these methods which call self.addattr() # The vector class calls a change function when there's a change in x, y, or z. - noSize = ['points', 'label', 'vertex', 'triangle', 'quad', 'attach_arrow', 'attach_trail'] - self._color.on_change = self._on_color_change + noSize = ['points', 'label', 'vertex', 'triangle', 'quad', 'attach_trail'] + if not (objName == 'extrusion'): # + self._color.on_change = self._on_color_change if objName not in noSize: self._axis.on_change = self._on_axis_change self._size.on_change = self._on_size_change self._up.on_change = self._on_up_change - noPos = ['curve', 'points', 'triangle', 'quad', 'attach_arrow'] + noPos = ['curve', 'points', 'triangle', 'quad'] if objName not in noPos: self._pos.on_change = self._on_pos_change elif objName == 'curve': @@ -757,50 +790,61 @@ def up(self): def up(self,value): self._save_oldup = adjust_axis(self._up, value, self._axis, self._save_oldup) # this sets self._axis and self._up if not self._constructing: - # must update both axis and up when either is changed - self.addattr('axis') self.addattr('up') @property def axis(self): return self._axis @axis.setter - def axis(self,value): - self._save_oldaxis = adjust_up(self._axis, value, self._up, self._save_oldaxis) # this sets self._axis and self._up + def axis(self,value): # sphere or ring or text or compound have no axis/size link + if value.mag2 == 0: + if self._save_oldaxis is None: self._save_oldaxis = self._axis + self._axis = value + else: + self._save_oldaxis = adjust_up(self._axis, value, self._up, self._save_oldaxis) # this sets self._axis and self._up if not self._constructing: - # must update both axis and up when either is changed self.addattr('axis') - self.addattr('up') - m = value.mag - if abs(self._size._x - m) > 0.0001*self._size._x: # need not update size if very small change - self._size._x = m - if not self._constructing: - self.addattr('size') + if self._sizing: + self._size._x = value.mag # changing axis length changes size.x @property def size(self): return self._size @size.setter - def size(self,value): - self._size.value = value + def size(self,value): # sphere or ring or text or compound have no axis/size link + currentaxis = self._axis + self._size = value + if value.x == 0: + if self._save_oldaxis is not None: + currentaxis = self._save_oldaxis + self._save_oldaxis = None + else: + currentaxis = vector(1,0,0) if not self._constructing: - self.addattr('size') - a = self._axis.norm() * value.x - if mag(self._axis) == 0: - a = vector(value.x,0,0) - v = self._axis - if not v.equals(a): - self._axis.value = a - if not self._constructing: - self.addattr('axis') + self.addattr('size') + if self._sizing: + self._axis = currentaxis.norm()*value.x @property def length(self): return self._size.x @length.setter def length(self,value): - self._axis = self._axis.norm() * value - self._size._x = value + if value == 0: + if self._save_oldaxis is None: self._save_oldaxis = vector(self._axis.x, self._axis.y, self._axis.z) + self._axis = vector(0,0,0) + self._size._x = 0 + else: + if self._save_oldaxis is not None: + self._axis = self._save_oldaxis + self._save_oldaxis = None + if self._size._x == 0: + self.axis = vector(value, 0, 0) + else: + if self._sizing: + self.axis = value*self._axis.norm() # this will set length if self._sizing + else: + self._size._x = value # for objects whose axis and size are not linked if not self._constructing: self.addattr('axis') self.addattr('size') @@ -828,6 +872,9 @@ def color(self): return self._color @color.setter def color(self,value): + if isinstance(self._color, list): # may be a list of extrusion colors of the form [ [1,0,0], [0,1,0] ] + self._color = vector(0,0,0) # change list to a vector + self._color.value = value # modify the vector self._color.value = value if not self._constructing: self.addattr('color') @@ -1030,6 +1077,28 @@ def rotate(self, angle=None, axis=None, origin=None): self._pos.value = newpos self.addattr('pos') + def bounding_box(self): + centered = False + if self._objName in ['box', 'ellipsoid', 'sphere', 'simple_sphere', 'ring']: + centered = True + elif self._objName[:8] == 'compound': # compound ObjName has trailing index + centered = True + x = norm(self._axis) + y = norm(self._up) + z = norm(cross(x,y)) + L = self._size.x + H = self._size.y + W = self._size.z + p = vector(self._pos) # make a copy of pos, so changes to p won't affect the object + if not centered: + p += 0.5*L*x # move to center + pts = [] + for dx in [-L/2, L/2]: + for dy in [-H/2, H/2]: + for dz in [-W/2, W/2]: + pts.append(p + dx*x + dy*y + dz*z) + return pts + def _on_size_change(self): # the vector class calls this when there's a change in x, y, or z self._axis.value = self._axis.norm() * self._size.x # update axis length when box.size.x is changed self.addattr('size') @@ -1071,13 +1140,13 @@ def clone(self, **args): '_pickable':self._pickable} elif objName == 'curve': oldargs = {'origin':self._origin, 'pos':self.pos, - 'color':self._color, r'adius':self._radius, + 'color':self._color, 'radius':self._radius, 'size':self._size, 'axis':self._axis, 'up':self._up, 'shininess':self._shininess, 'emissive':self._emissive, 'visible':True, 'pickable':self._pickable} elif objName == 'helix': oldargs = {'pos':self.pos, 'color':self._color, - 'thickness':self._thickness, 'coils':self._coils, + 'thickness':self._thickness, 'coils':self._coils, 'ccw':self._ccw, 'size':self._size, 'axis':self._axis, 'up':self._up, 'shininess':self._shininess, 'emissive':self._emissive, 'visible':True, 'pickable':self._pickable} @@ -1118,6 +1187,7 @@ def __init__(self, **args): args['_default_size'] = vector(2,2,2) args['_objName'] = "sphere" super(sphere, self).setup(args) + self._sizing = False # no axis/size connection @property def radius(self): @@ -1208,6 +1278,7 @@ def __init__(self, **args): args['_default_size'] = vector(0.2,2.2,2.2) args['_objName'] = "ring" super(ring, self).setup(args) + self._sizing = False # no axis/size connection @property def thickness(self): @@ -1257,42 +1328,19 @@ class arrow(standardAttributes): def __init__(self, **args): args['_default_size'] = vector(1,0.2,0.2) args['_objName'] = "arrow" + self._round = False self._shaftwidth = 0 self._headwidth = 0 self._headlength = 0 super(arrow, self).setup(args) - + @property - def size(self): - return self._size - @size.setter - def size(self,value): # no need to send to browser both arrow.size and arrow.axis - self._size.value = value - if not self._constructing: - self.addattr('size') - a = self._axis.norm() * value.x - if mag(self._axis) == 0: - a = vector(value.x,0,0) - v = self._axis - if not v.equals(a): - self._axis.value = a - - @property - def axis(self): - return self._axis - @axis.setter - def axis(self,value): # no need to send to browser both arrow.size and arrow.axis - oldaxis = norm(self._axis) - self._axis.value = value - m = value.mag - if abs(self._size._x - m) > 0.0001*self._size._x: # need not update size if very small change - self._size._x = m - self._save_oldaxis = adjust_up(norm(oldaxis), self._axis, self._up, self._save_oldaxis) - if not self._constructing: - # must update both axis and up when either is changed - self.addattr('axis') - self.addattr('up') + def round(self): + return self._round + @round.setter + def round(self,value): + raise AttributeError('Cannot change the "round" attribute of an arrow.') @property def shaftwidth(self): @@ -1321,20 +1369,12 @@ def headlength(self,value): if not self._constructing: self.addattr('headlength') -class attach_arrow(standardAttributes): - def __init__(self, obj, attr, **args): - attrs = ['pos', 'size', 'axis', 'up', 'color'] - args['_default_size'] = None - self.obj = args['obj'] = obj.idx - self.attr = args['attr'] = attr # could be for example "velocity" - self.attrval = args['attrval'] = getattr(baseObj.object_registry[self.obj], attr) - args['_objName'] = "attach_arrow" - self._last_val = None - self._scale = 1 - self._shaftwidth = 0 - super(attach_arrow, self).setup(args) - # Only if the attribute is a user attribute do we need to add to attach_arrows: - if attr not in attrs: baseObj.attach_arrows.append(self) + @property + def round(self): + return self._round + @round.setter + def round(self,value): + raise AttributeError('Cannot change the "round" attribute of an arrow.') @property def scale(self): @@ -1355,10 +1395,51 @@ def shaftwidth(self, value): self.addattr("shaftwidth") def stop(self): - self.addmethod('stop', 'None') + self._run = self.visible = False def start(self): - self.addmethod('start', 'None') + self._run = self.visible = True + +def attach_arrow(o, attr, **args): # factory function returns arrow with special attributes + ''' + The object "o" with a vector attribute "p" will have an arrow attached with options such as "color". + The length of the arrow will be args.scale*o.p", updated with every render of the scene. + If one creates a new attachment with "arr = attach_arrow(obj, attr, options)" you + can later change (for example) its color with "arr.color = ..." + ''' + if not hasattr(o, attr): raise AttributeError('Cannot attach an arrow to an object that has no "'+attr+'" attribute.') + if not isinstance(getattr(o, attr), vector): raise AttributeError('The attach_arrow attribute "'+attr+ '" is not a vector.') + if not isinstance(o.pos, vector): raise AttributeError("The object's pos attribute is not a vector.") + + scale = 1 + if 'scale' in args: scale = args['scale'] + shaftwidth = 0.5*o._size.y + if 'shaftwidth' in args: shaftwidth = args['shaftwidth'] + c = o.color + if 'color' in args: c = args['color'] + # Set _last_val to strange values so that the first update to WebGL won't match: + a = arrow(canvas=o.canvas, pickable=False, _object=o.idx, attr=attr, color=c, + scale=scale, shaftwidth=shaftwidth, _run=True, + _last_val=vector(134.472, 789.472, 465.472), _last_pos=vector(134.472, 789.472, 465.472)) + baseObj.attach_arrows.append(a) + return a + +def attach_light(o, **args): # factory function returns local_light with special attributes + ''' + The object "o" will have a local_light attached with options "offset" and "color". + The local_light will constantly be positioned at o.pos plus the offset. + ''' + if not isinstance(o.pos, vector): raise AttributeError("Cannot attach a light to an object that has no pos attribute.") + if 'color' in args: + if not isinstance(args['color'], vector): raise AttributeError("The color attribute must be a vector.") + else: + args['color'] = o.color # default color + if 'offset' in args: + if not isinstance(args['offset'], vector): raise AttributeError('The attach_light attribute "offset" must be a vector.') + else: + args['offset'] = vector(0,0,0) # default offset + a = local_light(pos=o.pos+args['offset'], attach_idx=o.idx, color=args['color'], offset=args['offset']) + return a class attach_trail(standardAttributes): def __init__(self, obj, **args): @@ -1431,6 +1512,7 @@ def __init__(self,**args): args['_objName'] = 'helix' args['_default_size'] = vector(1,2,2) self._coils = 5 + self._ccw = True self._thickness = 1/20 ## radius/20 super(helix, self).setup(args) @@ -1453,13 +1535,24 @@ def coils(self,value): if not self._constructing: self.addattr('coils') + @property + def ccw(self): + return self._ccw + @ccw.setter + def ccw(self,value): + self._ccw =value + if not self._constructing: + self.addattr('ccw') + @property def radius(self): return self._size.y/2 @radius.setter def radius(self,value): d = 2*value - self.size = vector(self._size.x,d,d) # size will call addattr if appropriate + self._size = vector(self._size.x,d,d) + if not self._constructing: + self.addattr('size') class compound(standardAttributes): compound_idx = 0 # same numbering scheme as in GlowScript @@ -1494,6 +1587,7 @@ def __init__(self, objList, **args): self.compound_idx += 1 args['_objName'] = 'compound'+str(self.compound_idx) super(compound, self).setup(args) + self._sizing = False # no axis/size connection except that changing axis.mag changes compound length for obj in objList: # GlowScript will make the objects invisible, so need not set obj.visible @@ -1565,7 +1659,7 @@ def __init__(self, **args): cv = args['canvas'] else: cv = canvas.get_selected() - if cv.vertexCount > canvas.maxVertices-1: + if cv.vertexCount >= canvas.maxVertices: raise ValueError('too many vertex objects in use for this canvas') args['_default_size'] = None args['_objName'] = "vertex" @@ -1755,12 +1849,15 @@ def process_args(self, *args1, **args): c = None r = None vis = None + op = None if 'color' in args: c = args['color'] if 'radius' in args: r = args['radius'] if 'visible' in args: vis = args['visible'] + if 'opacity' in args: + op = args['opacity'] if len(args1) > 0: if len(args1) == 1: tpos = self.parse_pos(args1[0]) @@ -1778,9 +1875,12 @@ def process_args(self, *args1, **args): col = c rad = r vi = vis + opaq = None cp = {'pos':pt['pos'].value} if 'color' in pt: col = pt['color'] + if 'opacity' in pt: + opaq = pt['opacity'] if 'radius' in pt: rad = pt['radius'] if 'visible' in pt: @@ -1794,6 +1894,9 @@ def process_args(self, *args1, **args): if vi is not None: pt['visible'] = vi cp['visible'] = vi + if opaq is not None: + pt['opacity'] = opaq + cp['opacity'] = opaq pts.append(pt) cps.append(cp) return [pts, cps] @@ -1857,8 +1960,8 @@ def pop(self, *args): return val def point(self,N): - if N >= len(self._pts) or (N < 0 and -N >= len(self.pts)): - raise ValueError('N = {} is outside the bounds 0-{} of the curve points'.format(N, len(self._pos))) + if N >= len(self._pts) or (N < 0 and -N > len(self._pts)): + raise ValueError('N = {} is outside the bounds 0-{} of the curve points'.format(N, len(self._pts)-1)) info = self._pts[N] if 'color' not in info: info['color'] = self.color if 'radius' not in info: info['radius'] = self.radius @@ -1986,7 +2089,7 @@ def __init__(self,*args1, **args): if 'pos' in args: tpos = args['pos'] del args['pos'] - + super(curveMethods, self).setup(args) if tpos is not None: @@ -2017,16 +2120,20 @@ def setup(self, args): super(gobj, self).__init__() ## default values of shared attributes self._color = vector(0,0,0) - self._dot_color = vector(0,0,0) self._marker_color = vector(0,0,0) self._dot = False + self._dot_color = vector(0,0,0) self._delta = 1 self._width = 2 self._radius = 3 self._label = '' self._legend = False self._interval = -1 + self._iterations = 0 + self._firstplot = True self._graph = None + self._data = [] + self._visible = True objName = args['_objName'] del args['_objName'] self._constructing = True ## calls are from constructor @@ -2045,7 +2152,7 @@ def setup(self, args): del args['pos'] ## override default vector attributes - vectorAttributes = ['color', 'dot_color', 'marker_color'] + vectorAttributes = ['color', 'marker_color'] for a in vectorAttributes: if a in args: argsToSend.append(a) @@ -2066,6 +2173,7 @@ def setup(self, args): cmd = {"cmd": objName, "idx": self.idx} for a in argsToSend: + if a == 'interval': continue # do not send to browser; handle here instead aval = getattr(self,a) if isinstance(aval, vector): aval = aval.value @@ -2073,7 +2181,18 @@ def setup(self, args): self._constructing = False self.appendcmd(cmd) - + + @property + def visible(self): return self._visible + @visible.setter + def visible(self,val): + if self._visible and not val: + self.delete() + elif val and not self._visible: + self.plot(self._data) + self._visible = val + #self.addattr('visible') # currently GlowScript ignores the visible attribute + @property def radius(self): return self._radius @radius.setter @@ -2126,12 +2245,12 @@ def interval(self): return self._interval @interval.setter def interval(self,val): self._interval = val - self.addattr('interval') + self._iterations = 0 def __del__(self): cmd = {"cmd": "delete", "idx": self.idx} self.appendcmd(cmd) - super(gcurve, self).__del__() + super(gobj, self).__del__() def resolveargs(self, *vars): ret = [] @@ -2162,18 +2281,32 @@ def preresolve2(self, args): raise AttributeError("Cannot currently change color in a plot statement.") if 'pos' in args: return self.resolveargs(args['pos']) + elif 'data' in args: + return self.resolveargs(args['data']) else: raise AttributeError("Must be plot(x,y) or plot(pos=[x,y]) or plot([x,y]) or plot([x,y], ...) or plot([ [x,y], ... ])") def plot(self, *args1, **args2): + if self._interval == 0: + return + if self._interval > 0: + self._iterations += 1 + if self._firstplot: + self._firstplot = False + elif self._iterations < self._interval: + return + self._iterations = 0 if len(args1) > 0: p = self.preresolve1(args1) else: p = self.preresolve2(args2) + self._data = self._data + p self.addmethod('plot', p) - + def delete(self): self.addmethod('delete', 'None') + self._firstplot = True + self._iterations = 0 @property def label(self): return self._label @@ -2227,14 +2360,6 @@ def dot(self,val): self._dot = val self.addattr('dot') - @property - def dot_color(self): return self._dot_color - @dot_color.setter - def dot_color(self,val): - if not isinstance(val, vector): raise TypeError('dot_color must be a vector') - self._dot_color = vector(val) - self.addattr('dot_color') - @property def dot_radius(self): return self._dot_radius @dot_radius.setter @@ -2242,6 +2367,13 @@ def dot_radius(self,val): self._dot_radius = val self.addattr('dot_radius') + @property + def dot_color(self): return self._dot_color + @dot_color.setter + def dot_color(self,val): + self._dot_color = val + self.addattr('dot_color') + class gdots(gobj): def __init__(self, **args): args['_objName'] = "gdots" @@ -2282,6 +2414,7 @@ def __init__(self, **args): self._title = "" self._xtitle = "" self._ytitle = "" + self._scroll = False argsToSend = [] ## override default vector attributes @@ -2296,7 +2429,7 @@ def __init__(self, **args): ## override default scalar attributes scalarAttributes = ['width', 'height', 'title', 'xtitle', 'ytitle','align', - 'xmin', 'xmax', 'ymin', 'ymax', 'logx', 'logy', 'fast'] + 'xmin', 'xmax', 'ymin', 'ymax', 'logx', 'logy', 'fast', 'scroll'] for a in scalarAttributes: if a in args: argsToSend.append(a) @@ -2309,6 +2442,12 @@ def __init__(self, **args): cmd = {"cmd": objName, "idx": self.idx} + if self._scroll: + if not ('xmin' in argsToSend and 'xmax' in argsToSend): + raise AttributeError("For a scrolling graph, both xmin and xmax must be specified.") + if self._xmax <= self._xmin: + raise AttributeError("For a scrolling graph, xmax must be greater than xmin.") + ## send only args specified in constructor for a in argsToSend: aval = getattr(self,a) @@ -2322,11 +2461,16 @@ def __init__(self, **args): def fast(self): return self._fast @fast.setter def fast(self,val): - # if _isnotebook and not val: - # raise AttributeError('"fast = False" is currently not available in a Jupyter notebook.') self._fast = val self.addattr('fast') + @property + def scroll(self): return self._scroll + @scroll.setter + def scroll(self,val): + self._scroll = val + self.addattr('scroll') + @property def width(self): return self._width @width.setter @@ -2378,7 +2522,7 @@ def foreground(self): return self._foreground @foreground.setter def foreground(self,val): if not isinstance(val, vector): raise TypeError('foreground must be a vector') - self._foreground = vector(value) + self._foreground = vector(val) self.addattr('foreground') @property @@ -2386,7 +2530,7 @@ def background(self): return self._background @background.setter def background(self,val): if not isinstance(val,vector): raise TypeError('background must be a vector') - self._background = vector(value) + self._background = vector(val) self.addattr('background') @property @@ -2694,7 +2838,6 @@ def project(self, **args): class Camera(object): def __init__(self, canvas): self._canvas = canvas - self._followthis = None self._pos = None @property @@ -2707,21 +2850,23 @@ def canvas(self, value): @property def pos(self): c = self._canvas - return c.center-(norm(c.forward)*(c.range / tan(c.fov/2))) + return c.center-(norm(c.axis)*(c.range / tan(c.fov/2))) @pos.setter def pos(self, value): c = self._canvas + c.autoscale = False c.center = value+self.axis @property def axis(self): c = self._canvas - return norm(c.forward)*( c.range / tan(c.fov/2) ) + return norm(c._axis)*( c.range / tan(c.fov/2) ) @axis.setter def axis(self, value): c = self._canvas - c.center = self.pos+value # use current self.pos before it is changed by change in c.forward - c.forward = norm(value) + c.autoscale = False + c.center = self.pos+value # use current self.pos before it is changed by change in c.axis + c.axis = norm(value) c.range = mag(value)*tan(c.fov/2) @property @@ -2732,14 +2877,17 @@ def up(self, value): self._canvas.up = value def rotate(self, angle=0, axis=None, origin=None): + if angle == 0: return c = self._canvas if axis is None: axis = c.up - newaxis = self.axis.rotate(angle=angle, axis=axis) - newpos = self.pos - if origin is not None: - newpos = origin + (self.pos-origin).rotate(angle=angle, axis=axis) - c.center = newpos + newaxis - c.forward = norm(newaxis) + if origin is not None and origin != self.pos: + origin = self.pos + (self.pos-origin).rotate(angle=angle, axis=axis) + else: + origin = self.pos + if c._axis.diff_angle(axis) > 1e-6: + c.axis = c._axis.rotate(angle=angle, axis=axis) + c.up = c._up.rotate(angle=angle, axis=axis) + c.center = origin + self.axis class meta_canvas(object): @property @@ -2752,7 +2900,7 @@ def selected(self, value): class canvas(baseObj): selected = None hasmouse = None - maxVertices = 65535 ## 2^16 - 1 due to GS weirdness + maxVertices = 4.2e9 ## 2^32 def __init__(self, **args): baseObj._canvas_constructing = True @@ -2782,7 +2930,8 @@ def __init__(self, **args): # The following determine the view: self._range = 1 # user can alter with zoom - self._forward = vector(0,0,-1) # user can alter with spin + self._axis = vector(0,0,-1) # user can alter with spin + self._forward = vector(0,0,-1) # self.axis is primal internally; self._forward is now a synonym self._up = vector(0,1,0) # user with touch screen can rotate around z self._autoscale = True # set False if user zooms self._center = vector(0,0,0) # cannot be altered by user @@ -2851,8 +3000,15 @@ def __init__(self, **args): distant_light(direction=vector(-0.88, -0.22, -0.44), color=color.gray(0.3)) baseObj._canvas_constructing = False - def follow(self, obj): ## should allow a function also - self.addmethod('follow', obj.idx) + def follow(self, obj): + if obj is None: + self.addmethod('follow', 'None') + elif callable(obj): + b = box(visible=False) + baseObj.follow_objects.append([b, obj, vector(1.2e15,3.4e14,-5.6e13)]) + self.addmethod('follow', b.idx) + else: + self.addmethod('follow', obj.idx) def select(self): canvas.selected = self @@ -2985,11 +3141,20 @@ def center(self,value): raise TypeError('center must be a vector') @property - def forward(self): - return self._forward + def axis(self): + return self._axis + @axis.setter + def axis(self,value): + self._axis = self._set_forward = vector(value) + if not self._constructing: + self.appendcmd({"forward":value.value}) + + @property + def forward(self): # scene.forward is an external synonym for scene.axis + return self._axis @forward.setter def forward(self,value): - self._forward = self._set_forward = vector(value) + self._axis = self._set_forward = vector(value) if not self._constructing: self.appendcmd({"forward":value.value}) @@ -3070,15 +3235,27 @@ def lights(self, value): @property def pixel_to_world(self): - return self._pixel_to_world + # Convert number of pixels into distance in real-world coordinates + w = self._width + h = self._height + d = 2*self._range + if w >= h: + return d/h + else: + return d/w @pixel_to_world.setter def pixel_to_world(self, value): raise AttributeError('pixel_to_world is read-only') - def capture(self, filename): - if not isinstance(filename, str): raise AttributeError('A capture file name must be a string.') - if '.png' not in filename: filename += '.png' - self.addmethod('capture', filename) + def capture(self, filename, capture_labels=True): + if not isinstance(filename, str): + raise TypeError("'filename' for Capture must be a string.") + + if filename.endswith(".png"): + filename += ".png" + + include_labels = "T" if capture_labels else "F" + self.addmethod("capture", include_labels + filename) @property def objects(self): @@ -3109,7 +3286,6 @@ def handle_event(self, evt): ## events and scene info updates self.mouse.setpick( evt ) self._waitfor = True # what pick is looking for elif ev == '_compound': # compound, text, extrusion - print('compound event return') obj = self._compound p = evt['pos'] if obj._objName == 'text': @@ -3117,9 +3293,12 @@ def handle_event(self, evt): ## events and scene info updates obj._descender = p[1] obj._up.value = list_to_vec(evt['up']) else: + if obj._objName == 'extrusion': + obj._color = obj._firstcolor # use the first segment color to represent multicolor extrusions # Set attribute_vector.value, which avoids nullifying the # on_change functions that detect changes in e.g. obj.pos.y obj._pos.value = list_to_vec(p) + obj._origin = obj._pos s = evt['size'] obj._size.value = obj._size0 = list_to_vec(s) obj._axis.value = obj._size._x*norm(obj._axis) @@ -3133,14 +3312,12 @@ def handle_event(self, evt): ## events and scene info updates del evt['height'] for fct in self._binds['resize']: # inspect the bound function and see what it's expecting - if _ispython3: # Python 3 - a = signature(fct) - if str(a) != '()': fct( evt ) - else: fct() - else: # Python 2 - a = getargspec(fct) - if len(a.args) > 0: fct( evt ) - else: fct() + a = signature(fct) + if str(a) != '()': + fct(evt) + else: + fct() + else: # pause/waitfor, update_canvas if 'pos' in evt: pos = evt['pos'] @@ -3160,19 +3337,17 @@ def handle_event(self, evt): ## events and scene info updates evt1 = event_return(evt) ## turn it into an object for fct in self._binds[ev]: # inspect the bound function and see what it's expecting - if _ispython3: # Python 3 - a = signature(fct) - if str(a) != '()': fct( evt1 ) - else: fct() - else: # Python 2 - a = getargspec(fct) - if len(a.args) > 0: fct( evt1 ) - else: fct() + a = signature(fct) + if str(a) != '()': + fct( evt1 ) + else: + fct() + self._waitfor = evt1 # what pause and waitfor are looking for else: ## user can change forward (spin), range/autoscale (zoom), up (touch), center (pan) if 'forward' in evt and self.userspin and not self._set_forward: fwd = evt['forward'] - self._forward = list_to_vec(fwd) + self._axis = list_to_vec(fwd) # the fundamental meaning of scene.forward is scene.axis self._set_forward = False if 'up' in evt and self.userspin and not self._set_up: cup = evt['up'] @@ -3266,6 +3441,24 @@ def __init__(self, **args): if (canvas.get_selected() is not None): canvas.get_selected()._lights.append(self) + @property + def offset(self): + return self._offset + @offset.setter + def offset(self, value): + self._offset = vector(value) + if not self._constructing: + self.addattr('offset') + + @property + def attach_idx(self): + return self._attach_idx + @attach_idx.setter + def attach_idx(self, value): + self._attach_idx = vector(value) + if not self._constructing: + self.addattr('attach_idx') + class distant_light(standardAttributes): def __init__(self, **args): args['_default_size'] = vector(1,1,1) @@ -3321,7 +3514,7 @@ def text(self, value): class controls(baseObj): attrlists = { 'button': ['text', 'color', 'textcolor', 'background', 'disabled'], 'checkbox':['checked', 'text', 'disabled'], - 'radio':['checked', 'text', 'disabled'], + 'radio':['checked', 'text', 'disabled', 'name'], 'menu':['selected', 'choices', 'index', 'disabled'], 'slider':['vertical', 'min', 'max', 'step', 'value', 'length', 'width', 'left', 'right', 'top', 'bottom', 'align', 'disabled'], @@ -3332,13 +3525,13 @@ def setup(self, args): ## default values of common attributes self._constructing = True argsToSend = [] - objName = args['_objName'] + self.objName = args['_objName'] del args['_objName'] if 'pos' in args: self.location = args['pos'] if self.location == print_anchor: #self.location = [-1, print_anchor] - raise AttributeError(objName+': Cannot specify "print_anchor" in VPython 7.') + raise AttributeError(self.objName+': Cannot specify "print_anchor" in VPython 7.') argsToSend.append('location') del args['pos'] if 'canvas' in args: ## specified in constructor @@ -3366,12 +3559,12 @@ def setup(self, args): ## override default scalar attributes for a,val in args.items(): - if a in controls.attrlists[objName]: + if a in controls.attrlists[self.objName]: argsToSend.append(a) setattr(self, '_'+a, val) else: setattr(self, a, val) - cmd = {"cmd": objName, "idx": self.idx} + cmd = {"cmd": self.objName, "idx": self.idx} cmd["canvas"] = self.canvas.idx ## send only args specified in constructor @@ -3393,10 +3586,10 @@ def bind(self, value): @property def pos(self): - raise AttributeError(objName+' pos attribute is not available.') + raise AttributeError(self.objName+' pos attribute is not available.') @pos.setter def pos(self, value): - raise AttributeError(objName+' pos attribute cannot be changed.') + raise AttributeError(self.objName+' pos attribute cannot be changed.') @property def disabled(self): @@ -3463,6 +3656,7 @@ def __init__(self, **args): args['_objName'] = 'checkbox' self._checked = False self._text = '' + self._name = '' super(checkbox, self).setup(args) @property @@ -3483,12 +3677,22 @@ def checked(self, value): if not self._constructing: self.addattr('checked') +_radio_groups = {} # radio buttons grouped by name + class radio(controls): def __init__(self, **args): args['_objName'] = 'radio' self._checked = False self._text = '' + self._name = '' super(radio, self).setup(args) + if type(self._name) != str: + raise AttributeError("A radio group name must be a string.") + if self._name != '': + if self._name in _radio_groups: + _radio_groups[self._name].append(self) + else: + _radio_groups[self._name] = [self] @property def text(self): @@ -3504,10 +3708,22 @@ def checked(self): return self._checked @checked.setter def checked(self, value): + if self._checked == value: return + if len(self._name) > 0: + for r in _radio_groups[self.name]: + r._checked = False self._checked = value if not self._constructing: self.addattr('checked') + @property + def name(self): + return self._name + @name.setter + def name(self, value): + if not self._constructing: + raise AttributeError('Cannot change the name attribute of a radio widget.') + class winput(controls): def __init__(self, **args): args['_objName'] = 'winput' @@ -3564,7 +3780,8 @@ def choices(self): return self._choices @choices.setter def choices(self, value): - raise AttributeError('choices cannot be modified after a menu is created') + self._choices = value + self.addattr('choices') @property def index(self): @@ -3611,6 +3828,7 @@ def __init__(self, **args): self._top = 0 self._bottom = 0 self._align = 'left' + self._disabled = False super(slider, self).setup(args) @property @@ -3713,16 +3931,17 @@ def __init__(self, **args): savesize = args['size'] del args['size'] self._shape = [ ] - pozz = args['path'] - npozz = [] - for pp in pozz: - npozz.append(pp.value) ## convert vectors to lists - args['path'] = npozz[:] - self._show_start_face = True - self._show_end_face = True + self._pathlength = len(args['path']) + args['path'] = self.vecs_to_list(args, 'path') if 'color' in args: - self._start_face_color = args['color'] - self._end_face_color = args['color'] + if isinstance(args['color'], vector): args['_firstcolor'] = args['color'] + else: args['_firstcolor'] = args['color'][0] + args['color'] = self.vecs_to_list(args, 'color') + else: + args['_firstcolor'] = vector(1,1,1) + for attr in ['scale', 'xscale', 'yscale', 'twist']: + if attr in args and isinstance(args[attr],list) and len(args[attr]) != self._pathlength: + raise AttributeError("The "+attr+" list must be the same length as the list of points on the path ("+str(self._pathlength)+").") super(extrusion, self).setup(args) @@ -3731,6 +3950,16 @@ def __init__(self, **args): if savesize is not None: self._size = savesize + def vecs_to_list(self, a, attr): + pozz = a[attr] + if isinstance(pozz, vector): return pozz.value + if len(pozz) != self._pathlength: + raise AttributeError("The "+attr+" list must be the same length as the list of points on the path ("+str(self._pathlength)+").") + npozz = [] + for pp in pozz: + npozz.append(pp.value) ## convert vectors to lists + return npozz[:] + @property def axis(self): return self._axis @@ -3804,6 +4033,44 @@ def shape(self): @shape.setter def shape(self, value): raise AttributeError('shape cannot be changed after extrusion is created') + + @property + def smooth(self): + if self._constructing: + return self._smooth + else: + return None + @smooth.setter + def smooth(self, value): + raise AttributeError('smooth cannot be changed after extrusion is created') + + def checkjointlist(self, js, name): + if not isinstance(js, list): raise AttributeError(name+' must be a list of joint indices.') + for k in js: + if k < 0 or k > self._pathlength: + raise AttributeError(str(k)+' is not in the range of joints in the path (0-'+str(self._pathlength-1)+').') + + @property + def smooth_joints(self): + if self._constructing: + self.checkjointlist(self._smooth_joints, 'smooth_joints') + return self._smooth_joints + else: + return None + @smooth_joints.setter + def smooth_joints(self, value): + raise AttributeError('smooth_joints cannot be changed after extrusion is created') + + @property + def sharp_joints(self): + if self._constructing: + self.checkjointlist(self._sharp_joints, 'sharp_joints') + return self._sharp_joints + else: + return None + @sharp_joints.setter + def sharp_joints(self, value): + raise AttributeError('sharp_joints cannot be changed after extrusion is created') @property def show_start_face(self): @@ -3845,9 +4112,80 @@ def end_face_color(self): def end_face_color(self,value): raise AttributeError('end_face_color cannot be changed after extrusion is created') + @property + def start_normal(self): + if self._constructing: + return self._start_normal + else: + return None + @start_normal.setter + def start_normal(self,value): + raise AttributeError('start_normal cannot be changed after extrusion is created') + + @property + def end_normal(self): + if self._constructing: + return self._end_normal + else: + return None + @end_normal.setter + def end_normal(self,value): + raise AttributeError('end_normal cannot be changed after extrusion is created') + + @property + def twist(self): + if self._constructing: + return self._twist + else: + return None + @twist.setter + def twist(self,value): + raise AttributeError('twist cannot be changed after extrusion is created') + + @property + def scale(self): + if self._constructing: + return self._scale + else: + return None + @scale.setter + def scale(self,value): + raise AttributeError('scale cannot be changed after extrusion is created') + + @property + def xscale(self): + if self._constructing: + return self._xscale + else: + return None + @xscale.setter + def xscale(self,value): + raise AttributeError('xscale cannot be changed after extrusion is created') + + @property + def yscale(self): + if self._constructing: + return self._yscale + else: + return None + @yscale.setter + def yscale(self,value): + raise AttributeError('xscale cannot be changed after extrusion is created') + + @property + def yscale(self): + if self._constructing: + return self._yscale + else: + return None + @yscale.setter + def yscale(self,value): + raise AttributeError('xscale cannot be changed after extrusion is created') + class text(standardAttributes): def __init__(self, **args): + self._sizing = False # no axis/size connection args['_default_size'] = vector(1,1,1) # to keep standardAttributes happy args['_objName'] = "text" self._height = 1 ## not derived from size @@ -3889,10 +4227,10 @@ def __init__(self, **args): self.canvas._compound = self # used by event handler to update pos and size _wait(self.canvas) # sets _length, _descender, up - @property def size(self): raise AttributeError("The text object has no size attribute.") + raise AttributeError("The text object has no size attribute.") @size.setter def size(self, val): raise AttributeError("The text object has no size attribute.") @@ -3902,10 +4240,10 @@ def axis(self): return self._axis @axis.setter def axis(self,value): # changing axis does not affect size - oldaxis = vector(self.axis) + old = vector(self.axis) u = self.up self._axis.value = value - self._save_oldaxis = adjust_up(norm(oldaxis), self._axis, self._up, self._save_oldaxis) + self._save_oldaxis = adjust_up(norm(old), self._axis, self._up, self._save_oldaxis) self.addattr('axis') self.addattr('up') @@ -3943,7 +4281,7 @@ def depth(self, val): # sign issue ?? if abs(val) < 0.01*self._height: if val < 0: val = -0.01*self._height else: val = 0.01*self._height - self._depth = value + self._depth = val self.addattr('depth') @property @@ -4106,3 +4444,12 @@ def keysdown(): for k in keysdownlist: # return a copy of keysdownlist keys.append(k) return keys + +# global variable for type of web browser to display vpython +_browsertype = 'default' +def set_browser(type='default'): + global _browsertype + if type=='pyqt': + _browsertype='pyqt' + else: + _browsertype='default' diff --git a/vpython/vpython_libraries/glow.min.js b/vpython/vpython_libraries/glow.min.js old mode 100644 new mode 100755 index 766c0af4..c89b9c98 --- a/vpython/vpython_libraries/glow.min.js +++ b/vpython/vpython_libraries/glow.min.js @@ -1,3 +1,3 @@ -(function(){})();(function(factory){if(typeof define==="function"&&define.amd){define(["jquery"],factory)}else if(typeof exports==="object"){module.exports=factory}else{factory(jQuery)}})(function($){var toFix=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],toBind="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],slice=Array.prototype.slice,nullLowestDeltaTimeout,lowestDelta;if($.event.fixHooks){for(var i=toFix.length;i;){$.event.fixHooks[toFix[--i]]=$.event.mouseHooks}}var special=$.event.special.mousewheel={version:"3.1.11",setup:function(){if(this.addEventListener){for(var i=toBind.length;i;){this.addEventListener(toBind[--i],handler,false)}}else{this.onmousewheel=handler}$.data(this,"mousewheel-line-height",special.getLineHeight(this));$.data(this,"mousewheel-page-height",special.getPageHeight(this))},teardown:function(){if(this.removeEventListener){for(var i=toBind.length;i;){this.removeEventListener(toBind[--i],handler,false)}}else{this.onmousewheel=null}$.removeData(this,"mousewheel-line-height");$.removeData(this,"mousewheel-page-height")},getLineHeight:function(elem){var $parent=$(elem)["offsetParent"in $.fn?"offsetParent":"parent"]();if(!$parent.length){$parent=$("body")}return parseInt($parent.css("fontSize"),10)},getPageHeight:function(elem){return $(elem).height()},settings:{adjustOldDeltas:true,normalizeOffset:true}};$.fn.extend({mousewheel:function(fn){return fn?this.bind("mousewheel",fn):this.trigger("mousewheel")},unmousewheel:function(fn){return this.unbind("mousewheel",fn)}});function handler(event){var orgEvent=event||window.event,args=slice.call(arguments,1),delta=0,deltaX=0,deltaY=0,absDelta=0,offsetX=0,offsetY=0;event=$.event.fix(orgEvent);event.type="mousewheel";if("detail"in orgEvent){deltaY=orgEvent.detail*-1}if("wheelDelta"in orgEvent){deltaY=orgEvent.wheelDelta}if("wheelDeltaY"in orgEvent){deltaY=orgEvent.wheelDeltaY}if("wheelDeltaX"in orgEvent){deltaX=orgEvent.wheelDeltaX*-1}if("axis"in orgEvent&&orgEvent.axis===orgEvent.HORIZONTAL_AXIS){deltaX=deltaY*-1;deltaY=0}delta=deltaY===0?deltaX:deltaY;if("deltaY"in orgEvent){deltaY=orgEvent.deltaY*-1;delta=deltaY}if("deltaX"in orgEvent){deltaX=orgEvent.deltaX;if(deltaY===0){delta=deltaX*-1}}if(deltaY===0&&deltaX===0){return}if(orgEvent.deltaMode===1){var lineHeight=$.data(this,"mousewheel-line-height");delta*=lineHeight;deltaY*=lineHeight;deltaX*=lineHeight}else if(orgEvent.deltaMode===2){var pageHeight=$.data(this,"mousewheel-page-height");delta*=pageHeight;deltaY*=pageHeight;deltaX*=pageHeight}absDelta=Math.max(Math.abs(deltaY),Math.abs(deltaX));if(!lowestDelta||absDelta=1?"floor":"ceil"](delta/lowestDelta);deltaX=Math[deltaX>=1?"floor":"ceil"](deltaX/lowestDelta);deltaY=Math[deltaY>=1?"floor":"ceil"](deltaY/lowestDelta);if(special.settings.normalizeOffset&&this.getBoundingClientRect){var boundingRect=this.getBoundingClientRect();offsetX=event.clientX-boundingRect.left;offsetY=event.clientY-boundingRect.top}event.deltaX=deltaX;event.deltaY=deltaY;event.deltaFactor=lowestDelta;event.offsetX=offsetX;event.offsetY=offsetY;event.deltaMode=0;args.unshift(event,delta,deltaX,deltaY);if(nullLowestDeltaTimeout){clearTimeout(nullLowestDeltaTimeout)}nullLowestDeltaTimeout=setTimeout(nullLowestDelta,200);return($.event.dispatch||$.event.handle).apply(this,args)}function nullLowestDelta(){lowestDelta=null}function shouldAdjustOldDeltas(orgEvent,absDelta){return special.settings.adjustOldDeltas&&orgEvent.type==="mousewheel"&&absDelta%120===0}});(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){function Plot(placeholder,data_,options_,plugins){var series=[],options={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:.85},xaxis:{show:null,position:"bottom",mode:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null,twelveHourClock:false},yaxis:{autoscaleMargin:.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false},shadowSize:3},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},hooks:{}},canvas=null,overlay=null,eventHolder=null,ctx=null,octx=null,xaxes=[],yaxes=[],plotOffset={left:0,right:0,top:0,bottom:0},canvasWidth=0,canvasHeight=0,plotWidth=0,plotHeight=0,hooks={processOptions:[],processRawData:[],processDatapoints:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},plot=this;plot.setData=setData;plot.setupGrid=setupGrid;plot.draw=draw;plot.getPlaceholder=function(){return placeholder};plot.getCanvas=function(){return canvas};plot.getPlotOffset=function(){return plotOffset};plot.width=function(){return plotWidth};plot.height=function(){return plotHeight};plot.offset=function(){var o=eventHolder.offset();o.left+=plotOffset.left;o.top+=plotOffset.top;return o};plot.getData=function(){return series};plot.getAxes=function(){var res={},i;$.each(xaxes.concat(yaxes),function(_,axis){if(axis)res[axis.direction+(axis.n!=1?axis.n:"")+"axis"]=axis});return res};plot.getXAxes=function(){return xaxes};plot.getYAxes=function(){return yaxes};plot.c2p=canvasToAxisCoords;plot.p2c=axisToCanvasCoords;plot.getOptions=function(){return options};plot.highlight=highlight;plot.unhighlight=unhighlight;plot.triggerRedrawOverlay=triggerRedrawOverlay;plot.pointOffset=function(point){return{left:parseInt(xaxes[axisNumber(point,"x")-1].p2c(+point.x)+plotOffset.left),top:parseInt(yaxes[axisNumber(point,"y")-1].p2c(+point.y)+plotOffset.top)}};plot.shutdown=shutdown;plot.resize=function(){getCanvasDimensions();resizeCanvas(canvas);resizeCanvas(overlay)};plot.hooks=hooks;initPlugins(plot);parseOptions(options_);setupCanvases();setData(data_);setupGrid();draw();bindEvents();function executeHooks(hook,args){args=[plot].concat(args);for(var i=0;i=options.colors.length){i=0;++variation}}var colori=0,s;for(i=0;iaxis.datamax&&max!=fakeInfinity)axis.datamax=max}$.each(allAxes(),function(_,axis){axis.datamin=topSentry;axis.datamax=bottomSentry;axis.used=false});for(i=0;i0&&points[k-ps]!=null&&points[k-ps]!=points[k]&&points[k-ps+1]!=points[k+1]){for(m=0;mxmax)xmax=val}if(f.y){if(valymax)ymax=val}}}if(s.bars.show){var delta=s.bars.align=="left"?0:-s.bars.barWidth/2;if(s.bars.horizontal){ymin+=delta;ymax+=delta+s.bars.barWidth}else{xmin+=delta;xmax+=delta+s.bars.barWidth}}updateAxis(s.xaxis,xmin,xmax);updateAxis(s.yaxis,ymin,ymax)}$.each(allAxes(),function(_,axis){if(axis.datamin==topSentry)axis.datamin=null;if(axis.datamax==bottomSentry)axis.datamax=null})}function makeCanvas(skipPositioning,cls){var c=document.createElement("canvas");c.className=cls;c.width=canvasWidth;c.height=canvasHeight;if(!skipPositioning)$(c).css({position:"absolute",left:0,top:0});$(c).appendTo(placeholder);if(!c.getContext)c=window.G_vmlCanvasManager.initElement(c);c.getContext("2d").save();return c}function getCanvasDimensions(){canvasWidth=placeholder.width();canvasHeight=placeholder.height();if(canvasWidth<=0||canvasHeight<=0)throw"Invalid dimensions for plot, width = "+canvasWidth+", height = "+canvasHeight}function resizeCanvas(c){if(c.width!=canvasWidth)c.width=canvasWidth;if(c.height!=canvasHeight)c.height=canvasHeight;var cctx=c.getContext("2d");cctx.restore();cctx.save()}function setupCanvases(){var reused,existingCanvas=placeholder.children("canvas.base"),existingOverlay=placeholder.children("canvas.overlay");if(existingCanvas.length==0||existingOverlay==0){placeholder.html("");placeholder.css({padding:0});if(placeholder.css("position")=="static")placeholder.css("position","relative");getCanvasDimensions();canvas=makeCanvas(true,"base");overlay=makeCanvas(false,"overlay");reused=false}else{canvas=existingCanvas.get(0);overlay=existingOverlay.get(0);reused=true}ctx=canvas.getContext("2d");octx=overlay.getContext("2d");eventHolder=$([overlay,canvas]);if(reused){placeholder.data("plot").shutdown();plot.resize();octx.clearRect(0,0,canvasWidth,canvasHeight);eventHolder.unbind();placeholder.children().not([canvas,overlay]).remove()}placeholder.data("plot",plot)}function bindEvents(){if(options.grid.hoverable){eventHolder.mousemove(onMouseMove);eventHolder.mouseleave(onMouseLeave)}if(options.grid.clickable)eventHolder.click(onClick);executeHooks(hooks.bindEvents,[eventHolder])}function shutdown(){if(redrawTimeout)clearTimeout(redrawTimeout);eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mouseleave",onMouseLeave);eventHolder.unbind("click",onClick);executeHooks(hooks.shutdown,[eventHolder])}function setTransformationHelpers(axis){function identity(x){return x}var s,m,t=axis.options.transform||identity,it=axis.options.inverseTransform;if(axis.direction=="x"){s=axis.scale=plotWidth/Math.abs(t(axis.max)-t(axis.min));m=Math.min(t(axis.max),t(axis.min))}else{s=axis.scale=plotHeight/Math.abs(t(axis.max)-t(axis.min));s=-s;m=Math.max(t(axis.max),t(axis.min))}if(t==identity)axis.p2c=function(p){return(p-m)*s};else axis.p2c=function(p){return(t(p)-m)*s};if(!it)axis.c2p=function(c){return m+c/s};else axis.c2p=function(c){return it(m+c/s)}}function measureTickLabels(axis){var opts=axis.options,i,ticks=axis.ticks||[],labels=[],l,w=opts.labelWidth,h=opts.labelHeight,dummyDiv;function makeDummyDiv(labels,width){return $('
'+'
'+labels.join("")+"
").appendTo(placeholder)}if(axis.direction=="x"){if(w==null)w=Math.floor(canvasWidth/(ticks.length>0?ticks.length:1));if(h==null){labels=[];for(i=0;i'+l+"
")}if(labels.length>0){labels.push('
');dummyDiv=makeDummyDiv(labels,"width:10000px;");h=dummyDiv.height();dummyDiv.remove()}}}else if(w==null||h==null){for(i=0;i'+l+"")}if(labels.length>0){dummyDiv=makeDummyDiv(labels,"");if(w==null)w=dummyDiv.children().width();if(h==null)h=dummyDiv.find("div.tickLabel").height();dummyDiv.remove()}}if(w==null)w=0;if(h==null)h=0;axis.labelWidth=w;axis.labelHeight=h}function allocateAxisBoxFirstPhase(axis){var lw=axis.labelWidth,lh=axis.labelHeight,pos=axis.options.position,tickLength=axis.options.tickLength,axismargin=options.grid.axisMargin,padding=options.grid.labelMargin,all=axis.direction=="x"?xaxes:yaxes,index;var samePosition=$.grep(all,function(a){return a&&a.options.position==pos&&a.reserveSpace});if($.inArray(axis,samePosition)==samePosition.length-1)axismargin=0;if(tickLength==null)tickLength="full";var sameDirection=$.grep(all,function(a){return a&&a.reserveSpace});var innermost=$.inArray(axis,sameDirection)==0;if(!innermost&&tickLength=="full")tickLength=5;if(!isNaN(+tickLength))padding+=+tickLength;if(axis.direction=="x"){lh+=padding;if(pos=="bottom"){plotOffset.bottom+=lh+axismargin;axis.box={top:canvasHeight-plotOffset.bottom,height:lh}}else{axis.box={top:plotOffset.top+axismargin,height:lh};plotOffset.top+=lh+axismargin}}else{lw+=padding;if(pos=="left"){axis.box={left:plotOffset.left+axismargin,width:lw};plotOffset.left+=lw+axismargin}else{plotOffset.right+=lw+axismargin;axis.box={left:canvasWidth-plotOffset.right,width:lw}}}axis.position=pos;axis.tickLength=tickLength;axis.box.padding=padding;axis.innermost=innermost}function allocateAxisBoxSecondPhase(axis){if(axis.direction=="x"){axis.box.left=plotOffset.left;axis.box.width=plotWidth}else{axis.box.top=plotOffset.top;axis.box.height=plotHeight}}function setupGrid(){var i,axes=allAxes();$.each(axes,function(_,axis){axis.show=axis.options.show;if(axis.show==null)axis.show=axis.used;axis.reserveSpace=axis.show||axis.options.reserveSpace;setRange(axis)});allocatedAxes=$.grep(axes,function(axis){return axis.reserveSpace});plotOffset.left=plotOffset.right=plotOffset.top=plotOffset.bottom=0;if(options.grid.offsets!==undefined){var o=options.grid.offsets;plotOffset.left=o.left;plotOffset.right=o.right;plotOffset.top=o.top;plotOffset.bottom=o.bottom}if(options.grid.show){$.each(allocatedAxes,function(_,axis){setupTickGeneration(axis);setTicks(axis);snapRangeToTicks(axis,axis.ticks);measureTickLabels(axis)});for(i=allocatedAxes.length-1;i>=0;--i)allocateAxisBoxFirstPhase(allocatedAxes[i]);var minMargin=options.grid.minBorderMargin;if(minMargin==null){minMargin=0;for(i=0;i=0)min=0}if(opts.max==null){max+=delta*margin;if(max>0&&axis.datamax!=null&&axis.datamax<=0)max=0}}}axis.min=min;axis.max=max}function setupTickGeneration(axis){var opts=axis.options;var noTicks;if(typeof opts.ticks=="number"&&opts.ticks>0)noTicks=opts.ticks;else noTicks=.3*Math.sqrt(axis.direction=="x"?canvasWidth:canvasHeight);var delta=(axis.max-axis.min)/noTicks,size,generator,unit,formatter,i,magn,norm;if(opts.mode=="time"){var timeUnitSize={second:1e3,minute:60*1e3,hour:60*60*1e3,day:24*60*60*1e3,month:30*24*60*60*1e3,year:365.2425*24*60*60*1e3};var spec=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var minSize=0;if(opts.minTickSize!=null){if(typeof opts.tickSize=="number")minSize=opts.tickSize;else minSize=opts.minTickSize[0]*timeUnitSize[opts.minTickSize[1]]}for(var i=0;i=minSize)break;size=spec[i][0];unit=spec[i][1];if(unit=="year"){magn=Math.pow(10,Math.floor(Math.log(delta/timeUnitSize.year)/Math.LN10));norm=delta/timeUnitSize.year/magn;if(norm<1.5)size=1;else if(norm<3)size=2;else if(norm<7.5)size=5;else size=10;size*=magn}axis.tickSize=opts.tickSize||[size,unit];generator=function(axis){var ticks=[],tickSize=axis.tickSize[0],unit=axis.tickSize[1],d=new Date(axis.min);var step=tickSize*timeUnitSize[unit];if(unit=="second")d.setUTCSeconds(floorInBase(d.getUTCSeconds(),tickSize));if(unit=="minute")d.setUTCMinutes(floorInBase(d.getUTCMinutes(),tickSize));if(unit=="hour")d.setUTCHours(floorInBase(d.getUTCHours(),tickSize));if(unit=="month")d.setUTCMonth(floorInBase(d.getUTCMonth(),tickSize));if(unit=="year")d.setUTCFullYear(floorInBase(d.getUTCFullYear(),tickSize));d.setUTCMilliseconds(0);if(step>=timeUnitSize.minute)d.setUTCSeconds(0);if(step>=timeUnitSize.hour)d.setUTCMinutes(0);if(step>=timeUnitSize.day)d.setUTCHours(0);if(step>=timeUnitSize.day*4)d.setUTCDate(1);if(step>=timeUnitSize.year)d.setUTCMonth(0);var carry=0,v=Number.NaN,prev;do{prev=v;v=d.getTime();ticks.push(v);if(unit=="month"){if(tickSize<1){d.setUTCDate(1);var start=d.getTime();d.setUTCMonth(d.getUTCMonth()+1);var end=d.getTime();d.setTime(v+carry*timeUnitSize.hour+(end-start)*tickSize);carry=d.getUTCHours();d.setUTCHours(0)}else d.setUTCMonth(d.getUTCMonth()+tickSize)}else if(unit=="year"){d.setUTCFullYear(d.getUTCFullYear()+tickSize)}else d.setTime(v+step)}while(vmaxDec)dec=maxDec;magn=Math.pow(10,-dec);norm=delta/magn;if(norm<1.5)size=1;else if(norm<3){size=2;if(norm>2.25&&(maxDec==null||dec+1<=maxDec)){size=2.5;++dec}}else if(norm<7.5)size=5;else size=10;size*=magn;if(opts.minTickSize!=null&&size0){if(opts.min==null)axis.min=Math.min(axis.min,niceTicks[0]);if(opts.max==null&&niceTicks.length>1)axis.max=Math.max(axis.max,niceTicks[niceTicks.length-1])}generator=function(axis){var ticks=[],v,i;for(i=0;i1&&/\..*0$/.test((ts[1]-ts[0]).toFixed(extraDec))))axis.tickDecimals=extraDec}}}axis.tickGenerator=generator;if($.isFunction(opts.tickFormatter))axis.tickFormatter=function(v,axis){return""+opts.tickFormatter(v,axis)};else axis.tickFormatter=formatter}function setTicks(axis){var oticks=axis.options.ticks,ticks=[];if(oticks==null||typeof oticks=="number"&&oticks>0)ticks=axis.tickGenerator(axis);else if(oticks){if($.isFunction(oticks))ticks=oticks({min:axis.min,max:axis.max});else ticks=oticks}var i,v;axis.ticks=[];for(i=0;i1)label=t[1]}else v=+t;if(label==null)label=axis.tickFormatter(v,axis);if(!isNaN(v))axis.ticks.push({v:v,label:label})}}function snapRangeToTicks(axis,ticks){if(axis.options.autoscaleMargin&&ticks.length>0){if(axis.options.min==null)axis.min=Math.min(axis.min,ticks[0].v);if(axis.options.max==null&&ticks.length>1)axis.max=Math.max(axis.max,ticks[ticks.length-1].v)}}function draw(){var o=options.grid.offsets;ctx.clearRect(o.left,o.top,canvasWidth-o.left,canvasHeight-o.bottom-o.top);var grid=options.grid;if(grid.show&&grid.backgroundColor)drawBackground();if(grid.show&&!grid.aboveData)drawGrid();for(var i=0;ito){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function drawBackground(){ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.fillStyle=getColorOrGradient(options.grid.backgroundColor,plotHeight,0,"rgba(255, 255, 255, 0)");ctx.fillRect(0,0,plotWidth,plotHeight);ctx.restore()}function drawGrid(){var i;ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var markings=options.grid.markings;if(markings){if($.isFunction(markings)){var axes=plot.getAxes();axes.xmin=axes.xaxis.min;axes.xmax=axes.xaxis.max;axes.ymin=axes.yaxis.min;axes.ymax=axes.yaxis.max;markings=markings(axes)}for(i=0;ixrange.axis.max||yrange.toyrange.axis.max)continue;xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max);yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);if(xrange.from==xrange.to&&yrange.from==yrange.to)continue;xrange.from=xrange.axis.p2c(xrange.from);xrange.to=xrange.axis.p2c(xrange.to);yrange.from=yrange.axis.p2c(yrange.from);yrange.to=yrange.axis.p2c(yrange.to);if(xrange.from==xrange.to||yrange.from==yrange.to){ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=m.lineWidth||options.grid.markingsLineWidth;ctx.moveTo(xrange.from,yrange.from);ctx.lineTo(xrange.to,yrange.to);ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}var axes=allAxes(),bw=options.grid.borderWidth;for(var j=0;jaxis.max||t=="full"&&bw>0&&(v==axis.min||v==axis.max))continue;if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top")yoff=-yoff}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left")xoff=-xoff}if(ctx.lineWidth==1){if(axis.direction=="x")x=Math.floor(x)+.5;else y=Math.floor(y)+.5}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}ctx.restore()}function insertAxisLabels(){placeholder.find(".tickLabels").remove();var html=['
'];var axes=allAxes();for(var j=0;j');for(var i=0;iaxis.max)continue;var pos={},align;if(axis.direction=="x"){align="center";pos.left=Math.round(plotOffset.left+axis.p2c(tick.v)-axis.labelWidth/2);if(axis.position=="bottom")pos.top=box.top+box.padding;else pos.bottom=canvasHeight-(box.top+box.height-box.padding)}else{pos.top=Math.round(plotOffset.top+axis.p2c(tick.v)-axis.labelHeight/2);if(axis.position=="left"){pos.right=canvasWidth-(box.left+box.width-box.padding);align="right"}else{pos.left=box.left+box.padding;align="left"}}pos.width=axis.labelWidth;var style=["position:absolute","text-align:"+align];for(var a in pos)style.push(a+":"+pos[a]+"px");html.push('
'+tick.label+"
")}html.push("
")}html.push("");placeholder.append(html.join(""))}function drawSeries(series){if(series.lines.show)drawSeriesLines(series);if(series.bars.show)drawSeriesBars(series);if(series.points.show)drawSeriesPoints(series)}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i=y2&&y1>axisy.max){if(y2>axisy.max)continue;x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max){if(y1>axisy.max)continue;x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1<=x2&&x1=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(x1!=prevx||y1!=prevy)ctx.moveTo(axisx.p2c(x1)+xoffset,axisy.p2c(y1)+yoffset);prevx=x2;prevy=y2;ctx.lineTo(axisx.p2c(x2)+xoffset,axisy.p2c(y2)+yoffset)}ctx.stroke()}function plotLineArea(datapoints,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,bottom=Math.min(Math.max(0,axisy.min),axisy.max),i=0,top,areaOpen=false,ypos=1,segmentStart=0,segmentEnd=0;while(true){if(ps>0&&i>points.length+ps)break;i+=ps;var x1=points[i-ps],y1=points[i-ps+ypos],x2=points[i],y2=points[i+ypos];if(areaOpen){if(ps>0&&x1!=null&&x2==null){segmentEnd=i;ps=-ps;ypos=2;continue}if(ps<0&&i==segmentStart+ps){ctx.fill();areaOpen=false;ps=-ps;ypos=1;i=segmentStart=segmentEnd+ps;continue}}if(x1==null||x2==null)continue;if(x1<=x2&&x1=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(!areaOpen){ctx.beginPath();ctx.moveTo(axisx.p2c(x1),axisy.p2c(bottom));areaOpen=true}if(y1>=axisy.max&&y2>=axisy.max){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.max));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.max));continue}else if(y1<=axisy.min&&y2<=axisy.min){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.min));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.min));continue}var x1old=x1,x2old=x2;if(y1<=y2&&y1=axisy.min){x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else if(y2<=y1&&y2=axisy.min){x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}if(y1>=y2&&y1>axisy.max&&y2<=axisy.max){x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max&&y1<=axisy.max){x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1!=x1old){ctx.lineTo(axisx.p2c(x1old),axisy.p2c(y1))}ctx.lineTo(axisx.p2c(x1),axisy.p2c(y1));ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));if(x2!=x2old){ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));ctx.lineTo(axisx.p2c(x2old),axisy.p2c(y2))}}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var lw=series.lines.lineWidth,sw=series.shadowSize;if(lw>0&&sw>0){ctx.lineWidth=sw;ctx.strokeStyle="rgba(0,0,0,0.1)";var angle=Math.PI/18;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/2),Math.cos(angle)*(lw/2+sw/2),series.xaxis,series.yaxis);ctx.lineWidth=sw/2;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/4),Math.cos(angle)*(lw/2+sw/4),series.xaxis,series.yaxis)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;var fillStyle=getFillStyle(series.lines,series.color,0,plotHeight);if(fillStyle){ctx.fillStyle=fillStyle;plotLineArea(series.datapoints,series.xaxis,series.yaxis)}if(lw>0)plotLine(series.datapoints,0,0,series.xaxis,series.yaxis);ctx.restore()}function drawSeriesPoints(series){function plotPoints(datapoints,radius,fillStyle,offset,shadow,axisx,axisy,symbol){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;iaxisx.max||yaxisy.max)continue;ctx.beginPath();x=axisx.p2c(x);y=axisy.p2c(y)+offset;if(symbol=="circle")ctx.arc(x,y,radius,0,shadow?Math.PI:Math.PI*2,false);else symbol(ctx,x,y,radius,shadow);ctx.closePath();if(fillStyle){ctx.fillStyle=fillStyle;ctx.fill()}ctx.stroke()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var lw=series.points.lineWidth,sw=series.shadowSize,radius=series.points.radius,symbol=series.points.symbol;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";plotPoints(series.datapoints,radius,null,w+w/2,true,series.xaxis,series.yaxis,symbol);ctx.strokeStyle="rgba(0,0,0,0.2)";plotPoints(series.datapoints,radius,null,w/2,true,series.xaxis,series.yaxis,symbol)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;plotPoints(series.datapoints,radius,getFillStyle(series.points,series.color),0,false,series.xaxis,series.yaxis,symbol);ctx.restore()}function drawBar(x,y,b,barLeft,barRight,offset,fillStyleCallback,axisx,axisy,c,horizontal,lineWidth){var left,right,bottom,top,drawLeft,drawRight,drawTop,drawBottom,tmp;if(horizontal){drawBottom=drawRight=drawTop=true;drawLeft=false;left=b;right=x;top=y+barLeft;bottom=y+barRight;if(rightaxisx.max||topaxisy.max)return;if(leftaxisx.max){right=axisx.max;drawRight=false}if(bottomaxisy.max){top=axisy.max;drawTop=false}left=axisx.p2c(left);bottom=axisy.p2c(bottom);right=axisx.p2c(right);top=axisy.p2c(top);if(fillStyleCallback){c.beginPath();c.moveTo(left,bottom);c.lineTo(left,top);c.lineTo(right,top);c.lineTo(right,bottom);c.fillStyle=fillStyleCallback(bottom,top);c.fill()}if(lineWidth>0&&(drawLeft||drawRight||drawTop||drawBottom)){c.beginPath();c.moveTo(left,bottom+offset);if(drawLeft)c.lineTo(left,top+offset);else c.moveTo(left,top+offset);if(drawTop)c.lineTo(right,top+offset);else c.moveTo(right,top+offset);if(drawRight)c.lineTo(right,bottom+offset);else c.moveTo(right,bottom+offset);if(drawBottom)c.lineTo(left,bottom+offset);else c.moveTo(left,bottom+offset);c.stroke()}}function drawSeriesBars(series){function plotBars(datapoints,barLeft,barRight,offset,fillStyleCallback,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i");fragments.push("");rowStarted=true}if(lf)label=lf(label,s);fragments.push('
'+''+label+"")}if(rowStarted)fragments.push("");if(fragments.length==0)return;var table=''+fragments.join("")+"
";if(options.legend.container!=null)$(options.legend.container).html(table);else{var pos="",p=options.legend.position,m=options.legend.margin;if(m[0]==null)m=[m,m];if(p.charAt(0)=="n")pos+="top:"+(m[1]+plotOffset.top)+"px;";else if(p.charAt(0)=="s")pos+="bottom:"+(m[1]+plotOffset.bottom)+"px;";if(p.charAt(1)=="e")pos+="right:"+(m[0]+plotOffset.right)+"px;";else if(p.charAt(1)=="w")pos+="left:"+(m[0]+plotOffset.left)+"px;";var legend=$('
'+table.replace('style="','style="position:absolute;'+pos+";")+"
").appendTo(placeholder);if(options.legend.backgroundOpacity!=0){var c=options.legend.backgroundColor;if(c==null){c=options.grid.backgroundColor;if(c&&typeof c=="string")c=$.color.parse(c);else c=$.color.extract(legend,"background-color");c.a=1;c=c.toString()}var div=legend.children();$('
').prependTo(legend).css("opacity",options.legend.backgroundOpacity)}}}var highlights=[],redrawTimeout=null;function findNearbyItem(mouseX,mouseY,seriesFilter){var maxDistance=options.grid.mouseActiveRadius,smallestDistance=maxDistance*maxDistance+1,item=null,foundPoint=false,i,j;for(i=series.length-1;i>=0;--i){if(!seriesFilter(series[i]))continue;var s=series[i],axisx=s.xaxis,axisy=s.yaxis,points=s.datapoints.points,ps=s.datapoints.pointsize,mx=axisx.c2p(mouseX),my=axisy.c2p(mouseY),maxx=maxDistance/axisx.scale,maxy=maxDistance/axisy.scale;if(axisx.options.inverseTransform)maxx=Number.MAX_VALUE;if(axisy.options.inverseTransform)maxy=Number.MAX_VALUE;if(s.lines.show||s.points.show){for(j=0;jmaxx||x-mx<-maxx||y-my>maxy||y-my<-maxy)continue;var dx=Math.abs(axisx.p2c(x)-mouseX),dy=Math.abs(axisy.p2c(y)-mouseY),dist=dx*dx+dy*dy;if(dist=Math.min(b,x)&&my>=y+barLeft&&my<=y+barRight:mx>=x+barLeft&&mx<=x+barRight&&my>=Math.min(b,y)&&my<=Math.max(b,y))item=[i,j/ps]}}}if(item){i=item[0];j=item[1];ps=series[i].datapoints.pointsize;return{datapoint:series[i].datapoints.points.slice(j*ps,(j+1)*ps),dataIndex:j,series:series[i],seriesIndex:i}}return null}function onMouseMove(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return s["hoverable"]!=false})}function onMouseLeave(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return false})}function onClick(e){triggerClickHoverEvent("plotclick",e,function(s){return s["clickable"]!=false})}function triggerClickHoverEvent(eventname,event,seriesFilter){var offset=eventHolder.offset(),canvasX=event.pageX-offset.left-plotOffset.left,canvasY=event.pageY-offset.top-plotOffset.top,pos=canvasToAxisCoords({left:canvasX,top:canvasY});pos.pageX=event.pageX;pos.pageY=event.pageY;var item=findNearbyItem(canvasX,canvasY,seriesFilter);if(item){item.pageX=parseInt(item.series.xaxis.p2c(item.datapoint[0])+offset.left+plotOffset.left);item.pageY=parseInt(item.series.yaxis.p2c(item.datapoint[1])+offset.top+plotOffset.top)}if(options.grid.autoHighlight){for(var i=0;iaxisx.max||yaxisy.max)return;var pointRadius=series.points.radius+series.points.lineWidth/2;octx.lineWidth=pointRadius;octx.strokeStyle=$.color.parse(series.color).scale("a",.5).toString();var radius=1.5*pointRadius,x=axisx.p2c(x),y=axisy.p2c(y);octx.beginPath();if(series.points.symbol=="circle")octx.arc(x,y,radius,0,2*Math.PI,false);else series.points.symbol(octx,x,y,radius,false);octx.closePath();octx.stroke()}function drawBarHighlight(series,point){octx.lineWidth=series.bars.lineWidth;octx.strokeStyle=$.color.parse(series.color).scale("a",.5).toString();var fillStyle=$.color.parse(series.color).scale("a",.5).toString();var barLeft=series.bars.align=="left"?0:-series.bars.barWidth/2;drawBar(point[0],point[1],point[2]||0,barLeft,barLeft+series.bars.barWidth,0,function(){return fillStyle},series.xaxis,series.yaxis,octx,series.bars.horizontal,series.bars.lineWidth)}function getColorOrGradient(spec,bottom,top,defaultColor){if(typeof spec=="string")return spec;else{var gradient=ctx.createLinearGradient(0,top,0,bottom);for(var i=0,l=spec.colors.length;i12){hours=hours-12}else if(hours==0){hours=12}}for(var i=0;i=0?"":"-";val=Math.abs(val);var before=Math.floor(log10(val))+1;if(before>n){val=val.toPrecision(n);return sign+val.replace("+","")}else if(before<0){var mantissa=val*pow(10,abs(before)+1);before-=1;return sign+mantissa.toFixed(n-1)+"e"+before}else{return sign+val.toFixed(n-before)}}function init(plot){var crosshair={x:-1,y:-1,locked:false};plot.setCrosshair=function setCrosshair(pos){if(!pos)crosshair.x=-1;else{var o=plot.p2c(pos);crosshair.x=Math.max(0,Math.min(o.left,plot.width()));crosshair.y=Math.max(0,Math.min(o.top,plot.height()))}plot.triggerRedrawOverlay()};plot.clearCrosshair=plot.setCrosshair;plot.lockCrosshair=function lockCrosshair(pos){if(pos)plot.setCrosshair(pos);crosshair.locked=true};plot.unlockCrosshair=function unlockCrosshair(){crosshair.locked=false};function onMouseOut(e){if(crosshair.locked)return;if(crosshair.x!=-1){crosshair.x=-1;plot.triggerRedrawOverlay()}}function onMouseMove(e){if(crosshair.locked)return;if(plot.getSelection&&plot.getSelection()){crosshair.x=-1;return}var offset=plot.offset();crosshair.x=Math.max(0,Math.min(e.pageX-offset.left,plot.width()));crosshair.y=Math.max(0,Math.min(e.pageY-offset.top,plot.height()));plot.triggerRedrawOverlay()}plot.hooks.bindEvents.push(function(plot,eventHolder){if(!plot.getOptions().crosshair.mode)return;eventHolder.mouseout(onMouseOut);eventHolder.mousemove(onMouseMove)});plot.hooks.drawOverlay.push(function(plot,ctx){var c=plot.getOptions().crosshair;if(!c.mode)return;var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);if(crosshair.x!=-1){var pos=plot.c2p({left:crosshair.x,top:crosshair.y});text=format_number(pos.x,3)+","+format_number(pos.y,3);var fontheight=13;ctx.fillStyle=c.color;ctx.font=fontheight+"px Verdana";var twidth=ctx.measureText(text).width;var dx=0,dy=0;if(crosshair.yplot.width()-(twidth+5)){ctx.textAlign="right";dx+=-3}else{ctx.textAlign="left";dx+=3}ctx.fillText(text,crosshair.x+dx,crosshair.y+dy);ctx.strokeStyle=c.color;ctx.lineWidth=c.lineWidth;ctx.lineJoin="round";ctx.beginPath();if(c.mode.indexOf("x")!=-1){ctx.moveTo(crosshair.x,0);ctx.lineTo(crosshair.x,plot.height())}if(c.mode.indexOf("y")!=-1){ctx.moveTo(0,crosshair.y);ctx.lineTo(plot.width(),crosshair.y)}ctx.stroke()}ctx.restore()});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mouseout",onMouseOut);eventHolder.unbind("mousemove",onMouseMove)})}$.plot.plugins.push({init:init,options:options,name:"crosshair_GS",version:"1.0"})})(jQuery);function assert(condition,message){if(!condition){throw new Error(message||"Assert Failed")}}var Node=function(p,t){this.point=p;this.triangle=t||null;this.next=null;this.prev=null;this.value=p.x};var AdvancingFront=function(head,tail){this.head_=head;this.tail_=tail;this.search_node_=head};AdvancingFront.prototype.head=function(){return this.head_};AdvancingFront.prototype.setHead=function(node){this.head_=node};AdvancingFront.prototype.tail=function(){return this.tail_};AdvancingFront.prototype.setTail=function(node){this.tail_=node};AdvancingFront.prototype.search=function(){return this.search_node_};AdvancingFront.prototype.setSearch=function(node){this.search_node_=node};AdvancingFront.prototype.findSearchNode=function(){return this.search_node_};AdvancingFront.prototype.locateNode=function(x){var node=this.search_node_;if(x=node.value){this.search_node_=node;return node}}}else{while(node=node.next){if(x-EPSILON&&val0){return Orientation.CCW}else{return Orientation.CW}}function inScanArea(pa,pb,pc,pd){var oadb=(pa.x-pb.x)*(pd.y-pb.y)-(pd.x-pb.x)*(pa.y-pb.y);if(oadb>=-EPSILON){return false}var oadc=(pa.x-pc.x)*(pd.y-pc.y)-(pd.x-pc.x)*(pa.y-pc.y);if(oadc<=EPSILON){return false}return true}function isAngleObtuse(pa,pb,pc){var ax=pb.x-pa.x;var ay=pb.y-pa.y;var bx=pc.x-pa.x;var by=pc.y-pa.y;return ax*bx+ay*by<0}function triangulate(tcx){tcx.initTriangulation();tcx.createAdvancingFront();sweepPoints(tcx);finalizationPolygon(tcx)}function sweepPoints(tcx){var i,len=tcx.pointCount();for(i=1;iedge.q.x;if(isEdgeSideOfTriangle(node.triangle,edge.p,edge.q)){return}fillEdgeEvent(tcx,edge,node);edgeEventByPoints(tcx,edge.p,edge.q,node.triangle,edge.q)}function edgeEventByPoints(tcx,ep,eq,triangle,point){if(isEdgeSideOfTriangle(triangle,ep,eq)){return}var p1=triangle.pointCCW(point);var o1=orient2d(eq,p1,ep);if(o1===Orientation.COLLINEAR){throw new PointError("poly2tri EdgeEvent: Collinear not supported!",[eq,p1,ep])}var p2=triangle.pointCW(point);var o2=orient2d(eq,p2,ep);if(o2===Orientation.COLLINEAR){throw new PointError("poly2tri EdgeEvent: Collinear not supported!",[eq,p2,ep])}if(o1===o2){if(o1===Orientation.CW){triangle=triangle.neighborCCW(point)}else{triangle=triangle.neighborCW(point)}edgeEventByPoints(tcx,ep,eq,triangle,point)}else{flipEdgeEvent(tcx,ep,eq,triangle,point)}}function isEdgeSideOfTriangle(triangle,ep,eq){var index=triangle.edgeIndex(ep,eq);if(index!==-1){triangle.markConstrainedEdgeByIndex(index);var t=triangle.getNeighbor(index);if(t){t.markConstrainedEdgeByPoints(ep,eq)}return true}return false}function newFrontTriangle(tcx,point,node){var triangle=new Triangle(point,node.point,node.next.point);triangle.markNeighbor(node.triangle);tcx.addToMap(triangle);var new_node=new Node(point);new_node.next=node.next;new_node.prev=node;node.next.prev=new_node;node.next=new_node;if(!legalize(tcx,triangle)){tcx.mapTriangleToNodes(triangle)}return new_node}function fill(tcx,node){var triangle=new Triangle(node.prev.point,node.point,node.next.point);triangle.markNeighbor(node.prev.triangle);triangle.markNeighbor(node.triangle);tcx.addToMap(triangle);node.prev.next=node.next;node.next.prev=node.prev;if(!legalize(tcx,triangle)){tcx.mapTriangleToNodes(triangle)}}function fillAdvancingFront(tcx,n){var node=n.next;while(node.next){if(isAngleObtuse(node.point,node.next.point,node.prev.point)){break}fill(tcx,node);node=node.next}node=n.prev;while(node.prev){if(isAngleObtuse(node.point,node.next.point,node.prev.point)){break}fill(tcx,node);node=node.prev}if(n.next&&n.next.next){if(isBasinAngleRight(n)){fillBasin(tcx,n)}}}function isBasinAngleRight(node){var ax=node.point.x-node.next.next.point.x;var ay=node.point.y-node.next.next.point.y;assert(ay>=0,"unordered y");return ax>=0||Math.abs(ax)0}function rotateTrianglePair(t,p,ot,op){var n1,n2,n3,n4;n1=t.neighborCCW(p);n2=t.neighborCW(p);n3=ot.neighborCCW(op);n4=ot.neighborCW(op);var ce1,ce2,ce3,ce4;ce1=t.getConstrainedEdgeCCW(p);ce2=t.getConstrainedEdgeCW(p);ce3=ot.getConstrainedEdgeCCW(op);ce4=ot.getConstrainedEdgeCW(op);var de1,de2,de3,de4;de1=t.getDelaunayEdgeCCW(p);de2=t.getDelaunayEdgeCW(p);de3=ot.getDelaunayEdgeCCW(op);de4=ot.getDelaunayEdgeCW(op);t.legalize(p,op);ot.legalize(op,p);ot.setDelaunayEdgeCCW(p,de1);t.setDelaunayEdgeCW(p,de2);t.setDelaunayEdgeCCW(op,de3);ot.setDelaunayEdgeCW(op,de4);ot.setConstrainedEdgeCCW(p,ce1);t.setConstrainedEdgeCW(p,ce2);t.setConstrainedEdgeCCW(op,ce3);ot.setConstrainedEdgeCW(op,ce4);t.clearNeighbors();ot.clearNeighbors();if(n1){ot.markNeighbor(n1)}if(n2){t.markNeighbor(n2)}if(n3){t.markNeighbor(n3)}if(n4){ot.markNeighbor(n4)}t.markNeighbor(ot)}function fillBasin(tcx,node){if(orient2d(node.point,node.next.point,node.next.next.point)===Orientation.CCW){tcx.basin.left_node=node.next.next}else{tcx.basin.left_node=node.next}tcx.basin.bottom_node=tcx.basin.left_node;while(tcx.basin.bottom_node.next&&tcx.basin.bottom_node.point.y>=tcx.basin.bottom_node.next.point.y){tcx.basin.bottom_node=tcx.basin.bottom_node.next}if(tcx.basin.bottom_node===tcx.basin.left_node){return}tcx.basin.right_node=tcx.basin.bottom_node;while(tcx.basin.right_node.next&&tcx.basin.right_node.point.ytcx.basin.right_node.point.y;fillBasinReq(tcx,tcx.basin.bottom_node)}function fillBasinReq(tcx,node){if(isShallow(tcx,node)){return}fill(tcx,node);var o;if(node.prev===tcx.basin.left_node&&node.next===tcx.basin.right_node){return}else if(node.prev===tcx.basin.left_node){o=orient2d(node.point,node.next.point,node.next.next.point);if(o===Orientation.CW){return}node=node.next}else if(node.next===tcx.basin.right_node){o=orient2d(node.point,node.prev.point,node.prev.prev.point);if(o===Orientation.CCW){return}node=node.prev}else{if(node.prev.point.yheight){return true}return false}function fillEdgeEvent(tcx,edge,node){if(tcx.edge_event.right){fillRightAboveEdgeEvent(tcx,edge,node)}else{fillLeftAboveEdgeEvent(tcx,edge,node)}}function fillRightAboveEdgeEvent(tcx,edge,node){while(node.next.point.xedge.p.x){if(orient2d(edge.q,node.prev.point,edge.p)===Orientation.CW){fillLeftBelowEdgeEvent(tcx,edge,node)}else{node=node.prev}}}function fillLeftBelowEdgeEvent(tcx,edge,node){if(node.point.x>edge.p.x){if(orient2d(node.point,node.prev.point,node.prev.prev.point)===Orientation.CW){fillLeftConcaveEdgeEvent(tcx,edge,node)}else{fillLeftConvexEdgeEvent(tcx,edge,node);fillLeftBelowEdgeEvent(tcx,edge,node)}}}function fillLeftConvexEdgeEvent(tcx,edge,node){if(orient2d(node.prev.point,node.prev.prev.point,node.prev.prev.prev.point)===Orientation.CW){fillLeftConcaveEdgeEvent(tcx,edge,node.prev)}else{if(orient2d(edge.q,node.prev.prev.point,edge.p)===Orientation.CW){fillLeftConvexEdgeEvent(tcx,edge,node.prev)}else{}}}function fillLeftConcaveEdgeEvent(tcx,edge,node){fill(tcx,node.prev);if(node.prev.point!==edge.p){if(orient2d(edge.q,node.prev.point,edge.p)===Orientation.CW){if(orient2d(node.point,node.prev.point,node.prev.prev.point)===Orientation.CW){fillLeftConcaveEdgeEvent(tcx,edge,node)}else{}}}}function flipEdgeEvent(tcx,ep,eq,t,p){var ot=t.neighborAcross(p);assert(ot,"FLIP failed due to missing triangle!");var op=ot.oppositePoint(t,p);if(t.getConstrainedEdgeAcross(p)){var index=t.index(p);throw new PointError("poly2tri Intersecting Constraints",[p,op,t.getPoint((index+1)%3),t.getPoint((index+2)%3)])}if(inScanArea(p,t.pointCCW(p),t.pointCW(p),op)){rotateTrianglePair(t,p,ot,op);tcx.mapTriangleToNodes(t);tcx.mapTriangleToNodes(ot);if(p===eq&&op===ep){if(eq===tcx.edge_event.constrained_edge.q&&ep===tcx.edge_event.constrained_edge.p){t.markConstrainedEdgeByPoints(ep,eq);ot.markConstrainedEdgeByPoints(ep,eq);legalize(tcx,t);legalize(tcx,ot)}else{}}else{var o=orient2d(eq,op,ep);t=nextFlipTriangle(tcx,o,t,ot,p,op);flipEdgeEvent(tcx,ep,eq,t,p)}}else{var newP=nextFlipPoint(ep,eq,ot,op);flipScanEdgeEvent(tcx,ep,eq,t,ot,newP);edgeEventByPoints(tcx,ep,eq,t,p)}}function nextFlipTriangle(tcx,o,t,ot,p,op){var edge_index;if(o===Orientation.CCW){edge_index=ot.edgeIndex(p,op);ot.delaunay_edge[edge_index]=true;legalize(tcx,ot);ot.clearDelaunayEdges();return t}edge_index=t.edgeIndex(p,op);t.delaunay_edge[edge_index]=true;legalize(tcx,t);t.clearDelaunayEdges();return ot}function nextFlipPoint(ep,eq,ot,op){var o2d=orient2d(eq,op,ep);if(o2d===Orientation.CW){return ot.pointCCW(op)}else if(o2d===Orientation.CCW){return ot.pointCW(op)}else{throw new PointError("poly2tri [Unsupported] nextFlipPoint: opposing point on constrained edge!",[eq,op,ep])}}function flipScanEdgeEvent(tcx,ep,eq,flip_triangle,t,p){var ot=t.neighborAcross(p);assert(ot,"FLIP failed due to missing triangle");var op=ot.oppositePoint(t,p);if(inScanArea(eq,flip_triangle.pointCCW(eq),flip_triangle.pointCW(eq),op)){flipEdgeEvent(tcx,eq,op,ot,op)}else{var newP=nextFlipPoint(ep,eq,ot,op);flipScanEdgeEvent(tcx,ep,eq,flip_triangle,ot,newP)}}var kAlpha=.3;var Edge=function(p1,p2){this.p=p1;this.q=p2;if(p1.y>p2.y){this.q=p1;this.p=p2}else if(p1.y===p2.y){if(p1.x>p2.x){this.q=p1;this.p=p2}else if(p1.x===p2.x){throw new PointError("poly2tri Invalid Edge constructor: repeated points!",[p1])}}if(!this.q._p2t_edge_list){this.q._p2t_edge_list=[]}this.q._p2t_edge_list.push(this)};var Basin=function(){this.left_node=null;this.bottom_node=null;this.right_node=null;this.width=0;this.left_highest=false};Basin.prototype.clear=function(){this.left_node=null;this.bottom_node=null;this.right_node=null;this.width=0;this.left_highest=false};var EdgeEvent=function(){this.constrained_edge=null;this.right=false};var SweepContext=function(contour,options){options=options||{};this.triangles_=[];this.map_=[];this.points_=options.cloneArrays?contour.slice(0):contour;this.edge_list=[];this.pmin_=this.pmax_=null;this.front_=null;this.head_=null;this.tail_=null;this.af_head_=null;this.af_middle_=null;this.af_tail_=null;this.basin=new Basin;this.edge_event=new EdgeEvent;this.initEdges(this.points_)};SweepContext.prototype.addHole=function(polyline){this.initEdges(polyline);var i,len=polyline.length;for(i=0;ixmax&&(xmax=p.x);p.xymax&&(ymax=p.y);p.y>>=1;return bit}function tinf_read_bits(d,num,base){if(!num)return base;while(d.bitcount<24){d.tag|=d.source[d.sourceIndex++]<>>16-num;d.tag>>>=num;d.bitcount-=num;return val+base}function tinf_decode_symbol(d,t){while(d.bitcount<24){d.tag|=d.source[d.sourceIndex++]<>>=1;++len;sum+=t.table[len];cur-=t.table[len]}while(cur>=0);d.tag=tag;d.bitcount-=len;return t.trans[sum+cur]}function tinf_decode_trees(d,lt,dt){var hlit,hdist,hclen;var i,num,length;hlit=tinf_read_bits(d,5,257);hdist=tinf_read_bits(d,5,1);hclen=tinf_read_bits(d,4,4);for(i=0;i<19;++i)lengths[i]=0;for(i=0;i8){d.sourceIndex--;d.bitcount-=8}length=d.source[d.sourceIndex+1];length=256*length+d.source[d.sourceIndex];invlength=d.source[d.sourceIndex+3];invlength=256*invlength+d.source[d.sourceIndex+2];if(length!==(~invlength&65535))return TINF_DATA_ERROR;d.sourceIndex+=4;for(i=length;i;--i)d.dest[d.destLen++]=d.source[d.sourceIndex++];d.bitcount=0;return TINF_OK}function tinf_uncompress(source,dest){var d=new Data(source,dest);var bfinal,btype,res;do{bfinal=tinf_getbit(d);btype=tinf_read_bits(d,2,0);switch(btype){case 0:res=tinf_inflate_uncompressed_block(d);break;case 1:res=tinf_inflate_block_data(d,sltree,sdtree);break;case 2:tinf_decode_trees(d,d.ltree,d.dtree);res=tinf_inflate_block_data(d,d.ltree,d.dtree);break;default:res=TINF_DATA_ERROR}if(res!==TINF_OK)throw new Error("Data error")}while(!bfinal);if(d.destLen0,"No English "+name+" specified.")}assertNamePresent("fontFamily");assertNamePresent("weightName");assertNamePresent("manufacturer");assertNamePresent("copyright");assertNamePresent("version");assert(this.unitsPerEm>0,"No unitsPerEm specified.")};Font.prototype.toTables=function(){return sfnt.fontToTable(this)};Font.prototype.toBuffer=function(){console.warn("Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.");return this.toArrayBuffer()};Font.prototype.toArrayBuffer=function(){var sfntTable=this.toTables();var bytes=sfntTable.encode();var buffer=new ArrayBuffer(bytes.length);var intArray=new Uint8Array(buffer);for(var i=0;i=0&&i>0){s+=" "}s+=floatToString(v)}return s}var d="";for(var i=0;i>4;var n2=b&15;if(n1===eof){break}s+=lookup[n1];if(n2===eof){break}s+=lookup[n2]}return parseFloat(s)}function parseOperand(parser,b0){var b1;var b2;var b3;var b4;if(b0===28){b1=parser.parseByte();b2=parser.parseByte();return b1<<8|b2}if(b0===29){b1=parser.parseByte();b2=parser.parseByte();b3=parser.parseByte();b4=parser.parseByte();return b1<<24|b2<<16|b3<<8|b4}if(b0===30){return parseFloatOperand(parser)}if(b0>=32&&b0<=246){return b0-139}if(b0>=247&&b0<=250){b1=parser.parseByte();return(b0-247)*256+b1+108}if(b0>=251&&b0<=254){b1=parser.parseByte();return-(b0-251)*256-b1-108}throw new Error("Invalid b0 "+b0)}function entriesToObject(entries){var o={};for(var i=0;i>1;stack.length=0;haveWidth=true}function parse(code){var b1;var b2;var b3;var b4;var codeIndex;var subrCode;var jpx;var jpy;var c3x;var c3y;var c4x;var c4y;var i=0;while(i1&&!haveWidth){width=stack.shift()+font.nominalWidthX;haveWidth=true}y+=stack.pop();newContour(x,y);break;case 5:while(stack.length>0){x+=stack.shift();y+=stack.shift();p.lineTo(x,y)}break;case 6:while(stack.length>0){x+=stack.shift();p.lineTo(x,y);if(stack.length===0){break}y+=stack.shift();p.lineTo(x,y)}break;case 7:while(stack.length>0){y+=stack.shift();p.lineTo(x,y);if(stack.length===0){break}x+=stack.shift();p.lineTo(x,y)}break;case 8:while(stack.length>0){c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y+stack.shift();p.curveTo(c1x,c1y,c2x,c2y,x,y)}break;case 10:codeIndex=stack.pop()+font.subrsBias;subrCode=font.subrs[codeIndex];if(subrCode){parse(subrCode)}break;case 11:return;case 12:v=code[i];i+=1;switch(v){case 35:c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();jpx=c2x+stack.shift();jpy=c2y+stack.shift();c3x=jpx+stack.shift();c3y=jpy+stack.shift();c4x=c3x+stack.shift();c4y=c3y+stack.shift();x=c4x+stack.shift();y=c4y+stack.shift();stack.shift();p.curveTo(c1x,c1y,c2x,c2y,jpx,jpy);p.curveTo(c3x,c3y,c4x,c4y,x,y);break;case 34:c1x=x+stack.shift();c1y=y;c2x=c1x+stack.shift();c2y=c1y+stack.shift();jpx=c2x+stack.shift();jpy=c2y;c3x=jpx+stack.shift();c3y=c2y;c4x=c3x+stack.shift();c4y=y;x=c4x+stack.shift();p.curveTo(c1x,c1y,c2x,c2y,jpx,jpy);p.curveTo(c3x,c3y,c4x,c4y,x,y);break;case 36:c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();jpx=c2x+stack.shift();jpy=c2y;c3x=jpx+stack.shift();c3y=c2y;c4x=c3x+stack.shift();c4y=c3y+stack.shift();x=c4x+stack.shift();p.curveTo(c1x,c1y,c2x,c2y,jpx,jpy);p.curveTo(c3x,c3y,c4x,c4y,x,y);break;case 37:c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();jpx=c2x+stack.shift();jpy=c2y+stack.shift();c3x=jpx+stack.shift();c3y=jpy+stack.shift();c4x=c3x+stack.shift();c4y=c3y+stack.shift();if(Math.abs(c4x-x)>Math.abs(c4y-y)){x=c4x+stack.shift()}else{y=c4y+stack.shift()}p.curveTo(c1x,c1y,c2x,c2y,jpx,jpy);p.curveTo(c3x,c3y,c4x,c4y,x,y);break;default:console.log("Glyph "+glyph.index+": unknown operator "+1200+v);stack.length=0}break;case 14:if(stack.length>0&&!haveWidth){width=stack.shift()+font.nominalWidthX;haveWidth=true}if(open){p.closePath();open=false}break;case 18:parseStems();break;case 19:case 20:parseStems();i+=nStems+7>>3;break;case 21:if(stack.length>2&&!haveWidth){width=stack.shift()+font.nominalWidthX;haveWidth=true}y+=stack.pop();x+=stack.pop();newContour(x,y);break;case 22:if(stack.length>1&&!haveWidth){width=stack.shift()+font.nominalWidthX;haveWidth=true}x+=stack.pop();newContour(x,y);break;case 23:parseStems();break;case 24:while(stack.length>2){c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y+stack.shift();p.curveTo(c1x,c1y,c2x,c2y,x,y)}x+=stack.shift();y+=stack.shift();p.lineTo(x,y);break;case 25:while(stack.length>6){x+=stack.shift();y+=stack.shift();p.lineTo(x,y)}c1x=x+stack.shift();c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y+stack.shift();p.curveTo(c1x,c1y,c2x,c2y,x,y);break;case 26:if(stack.length%2){x+=stack.shift()}while(stack.length>0){c1x=x;c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x;y=c2y+stack.shift();p.curveTo(c1x,c1y,c2x,c2y,x,y)}break;case 27:if(stack.length%2){y+=stack.shift()}while(stack.length>0){c1x=x+stack.shift();c1y=y;c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y;p.curveTo(c1x,c1y,c2x,c2y,x,y)}break;case 28:b1=code[i];b2=code[i+1];stack.push((b1<<24|b2<<16)>>16);i+=2;break;case 29:codeIndex=stack.pop()+font.gsubrsBias;subrCode=font.gsubrs[codeIndex];if(subrCode){parse(subrCode)}break;case 30:while(stack.length>0){c1x=x;c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y+(stack.length===1?stack.shift():0);p.curveTo(c1x,c1y,c2x,c2y,x,y);if(stack.length===0){break}c1x=x+stack.shift();c1y=y;c2x=c1x+stack.shift();c2y=c1y+stack.shift();y=c2y+stack.shift();x=c2x+(stack.length===1?stack.shift():0);p.curveTo(c1x,c1y,c2x,c2y,x,y)}break;case 31:while(stack.length>0){c1x=x+stack.shift();c1y=y;c2x=c1x+stack.shift();c2y=c1y+stack.shift();y=c2y+stack.shift();x=c2x+(stack.length===1?stack.shift():0);p.curveTo(c1x,c1y,c2x,c2y,x,y);if(stack.length===0){break}c1x=x;c1y=y+stack.shift();c2x=c1x+stack.shift();c2y=c1y+stack.shift();x=c2x+stack.shift();y=c2y+(stack.length===1?stack.shift():0);p.curveTo(c1x,c1y,c2x,c2y,x,y)}break;default:if(v<32){console.log("Glyph "+glyph.index+": unknown operator "+v)}else if(v<247){stack.push(v-139)}else if(v<251){b1=code[i];i+=1;stack.push((v-247)*256+b1+108)}else if(v<255){b1=code[i];i+=1;stack.push(-(v-251)*256-b1-108)}else{b1=code[i];b2=code[i+1];b3=code[i+2];b4=code[i+3];i+=4;stack.push((b1<<24|b2<<16|b3<<8|b4)/65536)}}}}parse(code);glyph.advanceWidth=width;return p}function calcCFFSubroutineBias(subrs){var bias;if(subrs.length<1240){bias=107}else if(subrs.length<33900){bias=1131}else{bias=32768}return bias}function parseCFFTable(data,start,font){font.tables.cff={};var header=parseCFFHeader(data,start);var nameIndex=parseCFFIndex(data,header.endOffset,bytesToString);var topDictIndex=parseCFFIndex(data,nameIndex.endOffset);var stringIndex=parseCFFIndex(data,topDictIndex.endOffset,bytesToString);var globalSubrIndex=parseCFFIndex(data,stringIndex.endOffset);font.gsubrs=globalSubrIndex.objects;font.gsubrsBias=calcCFFSubroutineBias(font.gsubrs);var topDictData=new DataView(new Uint8Array(topDictIndex.objects[0]).buffer);var topDict=parseCFFTopDict(topDictData,stringIndex.objects);font.tables.cff.topDict=topDict;var privateDictOffset=start+topDict["private"][1];var privateDict=parseCFFPrivateDict(data,privateDictOffset,topDict["private"][0],stringIndex.objects);font.defaultWidthX=privateDict.defaultWidthX;font.nominalWidthX=privateDict.nominalWidthX;if(privateDict.subrs!==0){var subrOffset=privateDictOffset+privateDict.subrs;var subrIndex=parseCFFIndex(data,subrOffset);font.subrs=subrIndex.objects;font.subrsBias=calcCFFSubroutineBias(font.subrs)}else{font.subrs=[];font.subrsBias=0}var charStringsIndex=parseCFFIndex(data,start+topDict.charStrings);font.nGlyphs=charStringsIndex.objects.length;var charset=parseCFFCharset(data,start+topDict.charset,font.nGlyphs,stringIndex.objects);if(topDict.encoding===0){font.cffEncoding=new CffEncoding(cffStandardEncoding,charset)}else if(topDict.encoding===1){font.cffEncoding=new CffEncoding(cffExpertEncoding,charset)}else{font.cffEncoding=parseCFFEncoding(data,start+topDict.encoding,charset)}font.encoding=font.encoding||font.cffEncoding;font.glyphs=new GlyphSet(font);for(var i=0;i=0){sid=i}i=strings.indexOf(s);if(i>=0){sid=i+cffStandardStrings.length}else{sid=cffStandardStrings.length+strings.length;strings.push(s)}return sid}function makeHeader(){return new table.Record("Header",[{name:"major",type:"Card8",value:1},{name:"minor",type:"Card8",value:0},{name:"hdrSize",type:"Card8",value:4},{name:"major",type:"Card8",value:1}])}function makeNameIndex(fontNames){var t=new table.Record("Name INDEX",[{name:"names",type:"INDEX",value:[]}]);t.names=[];for(var i=0;i>1;p.skip("uShort",3);cmap.glyphIndexMap={};var endCountParser=new Parser(data,start+offset+14);var startCountParser=new Parser(data,start+offset+16+segCount*2);var idDeltaParser=new Parser(data,start+offset+16+segCount*4);var idRangeOffsetParser=new Parser(data,start+offset+16+segCount*6);var glyphIndexOffset=start+offset+16+segCount*8;for(i=0;i0){v=p.parseByte();if((flag&sameBitMask)===0){v=-v}v=previousValue+v}else{if((flag&sameBitMask)>0){v=previousValue}else{v=previousValue+p.parseShort()}}return v}function parseGlyph(glyph,data,start){var p=new Parser(data,start);glyph.numberOfContours=p.parseShort();glyph.xMin=p.parseShort();glyph.yMin=p.parseShort();glyph.xMax=p.parseShort();glyph.yMax=p.parseShort();var flags;var flag;if(glyph.numberOfContours>0){var i;var endPointIndices=glyph.endPointIndices=[];for(i=0;i0){var repeatCount=p.parseByte();for(var j=0;j0){var points=[];var point;if(numberOfCoordinates>0){for(i=0;i=0;points.push(point)}var px=0;for(i=0;i0){component.dx=p.parseShort();component.dy=p.parseShort()}else{component.dx=p.parseChar();component.dy=p.parseChar()}if((flags&8)>0){component.xScale=component.yScale=p.parseF2Dot14()}else if((flags&64)>0){component.xScale=p.parseF2Dot14();component.yScale=p.parseF2Dot14()}else if((flags&128)>0){component.xScale=p.parseF2Dot14();component.scale01=p.parseF2Dot14();component.scale10=p.parseF2Dot14();component.yScale=p.parseF2Dot14()}glyph.components.push(component);moreComponents=!!(flags&32)}}}function transformPoints(points,transform){var newPoints=[];for(var i=0;i>1;if(glyphID=range.begin&&unicode=1){os2.ulCodePageRange1=p.parseULong();os2.ulCodePageRange2=p.parseULong()}if(os2.version>=2){os2.sxHeight=p.parseShort();os2.sCapHeight=p.parseShort();os2.usDefaultChar=p.parseUShort();os2.usBreakChar=p.parseUShort();os2.usMaxContent=p.parseUShort()}return os2}function makeOS2Table(options){return new table.Table("OS/2",[{name:"version",type:"USHORT",value:3},{name:"xAvgCharWidth",type:"SHORT",value:0},{name:"usWeightClass",type:"USHORT",value:0},{name:"usWidthClass",type:"USHORT",value:0},{name:"fsType",type:"USHORT",value:0},{name:"ySubscriptXSize",type:"SHORT",value:650},{name:"ySubscriptYSize",type:"SHORT",value:699},{name:"ySubscriptXOffset",type:"SHORT",value:0},{name:"ySubscriptYOffset",type:"SHORT",value:140},{name:"ySuperscriptXSize",type:"SHORT",value:650},{name:"ySuperscriptYSize",type:"SHORT",value:699},{name:"ySuperscriptXOffset",type:"SHORT",value:0},{name:"ySuperscriptYOffset",type:"SHORT",value:479},{name:"yStrikeoutSize",type:"SHORT",value:49},{name:"yStrikeoutPosition",type:"SHORT",value:258},{name:"sFamilyClass",type:"SHORT",value:0},{name:"bFamilyType",type:"BYTE",value:0},{name:"bSerifStyle",type:"BYTE",value:0},{name:"bWeight",type:"BYTE",value:0},{name:"bProportion",type:"BYTE",value:0},{name:"bContrast",type:"BYTE",value:0},{name:"bStrokeVariation",type:"BYTE",value:0},{name:"bArmStyle",type:"BYTE",value:0},{name:"bLetterform",type:"BYTE",value:0},{name:"bMidline",type:"BYTE",value:0},{name:"bXHeight",type:"BYTE",value:0},{name:"ulUnicodeRange1",type:"ULONG",value:0},{name:"ulUnicodeRange2",type:"ULONG",value:0},{name:"ulUnicodeRange3",type:"ULONG",value:0},{name:"ulUnicodeRange4",type:"ULONG",value:0},{name:"achVendID",type:"CHARARRAY",value:"XXXX"},{name:"fsSelection",type:"USHORT",value:0},{name:"usFirstCharIndex",type:"USHORT",value:0},{name:"usLastCharIndex",type:"USHORT",value:0},{name:"sTypoAscender",type:"SHORT",value:0},{name:"sTypoDescender",type:"SHORT",value:0},{name:"sTypoLineGap",type:"SHORT",value:0},{name:"usWinAscent",type:"USHORT",value:0},{name:"usWinDescent",type:"USHORT",value:0},{name:"ulCodePageRange1",type:"ULONG",value:0},{name:"ulCodePageRange2",type:"ULONG",value:0},{name:"sxHeight",type:"SHORT",value:0},{name:"sCapHeight",type:"SHORT",value:0},{name:"usDefaultChar",type:"USHORT",value:0},{name:"usBreakChar",type:"USHORT",value:0},{name:"usMaxContext",type:"USHORT",value:0}],options)}function parsePostTable(data,start){var post={};var p=new Parser(data,start);var i;post.version=p.parseVersion();post.italicAngle=p.parseFixed();post.underlinePosition=p.parseShort();post.underlineThickness=p.parseShort();post.isFixedPitch=p.parseULong();post.minMemType42=p.parseULong();post.maxMemType42=p.parseULong();post.minMemType1=p.parseULong();post.maxMemType1=p.parseULong();switch(post.version){case 1:post.names=standardNames.slice();break;case 2:post.numberOfGlyphs=p.parseUShort();post.glyphNameIndex=new Array(post.numberOfGlyphs);for(i=0;i=standardNames.length){var nameLength=p.parseChar();post.names.push(p.parseString(nameLength))}}break;case 2.5:post.numberOfGlyphs=p.parseUShort();post.offset=new Array(post.numberOfGlyphs);for(i=0;ir2.value.tag){return 1}else{return-1}});sfnt.fields=sfnt.fields.concat(recordFields);sfnt.fields=sfnt.fields.concat(tableFields);return sfnt}function metricsForChar(font,chars,notFoundMetrics){for(var i=0;i0){var glyph=font.glyphs.get(glyphIndex);return glyph.getMetrics()}}return notFoundMetrics}function average(vs){var sum=0;for(var i=0;iunicode||firstCharIndex===null){firstCharIndex=unicode}if(lastCharIndex 123 are reserved for internal usage")}if(glyph.name===".notdef")continue;var metrics=glyph.getMetrics();xMins.push(metrics.xMin);yMins.push(metrics.yMin);xMaxs.push(metrics.xMax);yMaxs.push(metrics.yMax);leftSideBearings.push(metrics.leftSideBearing);rightSideBearings.push(metrics.rightSideBearing);advanceWidths.push(glyph.advanceWidth)}var globals={xMin:Math.min.apply(null,xMins),yMin:Math.min.apply(null,yMins),xMax:Math.max.apply(null,xMaxs),yMax:Math.max.apply(null,yMaxs),advanceWidthMax:Math.max.apply(null,advanceWidths),advanceWidthAvg:average(advanceWidths),minLeftSideBearing:Math.min.apply(null,leftSideBearings),maxLeftSideBearing:Math.max.apply(null,leftSideBearings),minRightSideBearing:Math.min.apply(null,rightSideBearings)};globals.ascender=font.ascender;globals.descender=font.descender;var headTable=head.make({flags:3,unitsPerEm:font.unitsPerEm,xMin:globals.xMin,yMin:globals.yMin,xMax:globals.xMax,yMax:globals.yMax,lowestRecPPEM:3});var hheaTable=hhea.make({ascender:globals.ascender,descender:globals.descender,advanceWidthMax:globals.advanceWidthMax,minLeftSideBearing:globals.minLeftSideBearing,minRightSideBearing:globals.minRightSideBearing,xMaxExtent:globals.maxLeftSideBearing+(globals.xMax-globals.xMin),numberOfHMetrics:font.glyphs.length});var maxpTable=maxp.make(font.glyphs.length);var os2Table=os2.make({xAvgCharWidth:Math.round(globals.advanceWidthAvg),usWeightClass:500,usWidthClass:5,usFirstCharIndex:firstCharIndex,usLastCharIndex:lastCharIndex,ulUnicodeRange1:ulUnicodeRange1,ulUnicodeRange2:ulUnicodeRange2,ulUnicodeRange3:ulUnicodeRange3,ulUnicodeRange4:ulUnicodeRange4,fsSelection:64,sTypoAscender:globals.ascender,sTypoDescender:globals.descender,sTypoLineGap:0,usWinAscent:globals.yMax,usWinDescent:Math.abs(globals.yMin),ulCodePageRange1:1,sxHeight:metricsForChar(font,"xyvw",{yMax:Math.round(globals.ascender/2)}).yMax,sCapHeight:metricsForChar(font,"HIKLEFJMNTZBDPRAGOQSUVWXY",globals).yMax,usDefaultChar:font.hasChar(" ")?32:0,usBreakChar:font.hasChar(" ")?32:0});var hmtxTable=hmtx.make(font.glyphs);var cmapTable=cmap.make(font.glyphs);var englishFamilyName=font.getEnglishName("fontFamily");var englishStyleName=font.getEnglishName("fontSubfamily");var englishFullName=englishFamilyName+" "+englishStyleName;var postScriptName=font.getEnglishName("postScriptName");if(!postScriptName){postScriptName=englishFamilyName.replace(/\s/g,"")+"-"+englishStyleName}var names={};for(var n in font.names){names[n]=font.names[n]}if(!names.uniqueID){names.uniqueID={en:font.getEnglishName("manufacturer")+":"+englishFullName}}if(!names.postScriptName){names.postScriptName={en:postScriptName}}if(!names.preferredFamily){names.preferredFamily=font.names.fontFamily}if(!names.preferredSubfamily){names.preferredSubfamily=font.names.fontSubfamily}var languageTags=[];var nameTable=makeNameTable(names,languageTags);var ltagTable=languageTags.length>0?ltag.make(languageTags):undefined;var postTable=post.make();var cffTable=cff.make(font.glyphs,{version:font.getEnglishName("version"),fullName:englishFullName,familyName:englishFamilyName,weightName:englishStyleName,postScriptName:postScriptName,unitsPerEm:font.unitsPerEm,fontBBox:[0,globals.yMin,globals.ascender,globals.advanceWidthMax]});var tables=[headTable,hheaTable,maxpTable,os2Table,nameTable,cmapTable,postTable,cffTable,hmtxTable];if(ltagTable){tables.push(ltagTable)}var sfntTable=makeSfntTable(tables);var bytes=sfntTable.encode();var checkSum=computeCheckSum(bytes);var tableFields=sfntTable.fields;var checkSumAdjusted=false;for(i=0;i=0&&v<=255,"Byte value should be between 0 and 255.");return[v]};sizeOf.BYTE=constant(1);encode.CHAR=function(v){return[v.charCodeAt(0)]};sizeOf.CHAR=constant(1);encode.CHARARRAY=function(v){var b=[];for(var i=0;i>8&255,v&255]};sizeOf.USHORT=constant(2);encode.SHORT=function(v){if(v>=LIMIT16){v=-(2*LIMIT16-v)}return[v>>8&255,v&255]};sizeOf.SHORT=constant(2);encode.UINT24=function(v){return[v>>16&255,v>>8&255,v&255]};sizeOf.UINT24=constant(3);encode.ULONG=function(v){return[v>>24&255,v>>16&255,v>>8&255,v&255]};sizeOf.ULONG=constant(4);encode.LONG=function(v){if(v>=LIMIT32){v=-(2*LIMIT32-v)}return[v>>24&255,v>>16&255,v>>8&255,v&255]};sizeOf.LONG=constant(4);encode.FIXED=encode.ULONG;sizeOf.FIXED=sizeOf.ULONG;encode.FWORD=encode.SHORT;sizeOf.FWORD=sizeOf.SHORT;encode.UFWORD=encode.USHORT;sizeOf.UFWORD=sizeOf.USHORT;encode.LONGDATETIME=function(){return[0,0,0,0,0,0,0,0]};sizeOf.LONGDATETIME=constant(8);encode.TAG=function(v){assert(v.length===4,"Tag should be exactly 4 ASCII characters.");return[v.charCodeAt(0),v.charCodeAt(1),v.charCodeAt(2),v.charCodeAt(3)]};sizeOf.TAG=constant(4);encode.Card8=encode.BYTE;sizeOf.Card8=sizeOf.BYTE;encode.Card16=encode.USHORT;sizeOf.Card16=sizeOf.USHORT;encode.OffSize=encode.BYTE;sizeOf.OffSize=sizeOf.BYTE;encode.SID=encode.USHORT;sizeOf.SID=sizeOf.USHORT;encode.NUMBER=function(v){if(v>=-107&&v<=107){return[v+139]}else if(v>=108&&v<=1131){v=v-108;return[(v>>8)+247,v&255]}else if(v>=-1131&&v<=-108){v=-v-108;return[(v>>8)+251,v&255]}else if(v>=-32768&&v<=32767){return encode.NUMBER16(v)}else{return encode.NUMBER32(v)}};sizeOf.NUMBER=function(v){return encode.NUMBER(v).length};encode.NUMBER16=function(v){return[28,v>>8&255,v&255]};sizeOf.NUMBER16=constant(3);encode.NUMBER32=function(v){return[29,v>>24&255,v>>16&255,v>>8&255,v&255]};sizeOf.NUMBER32=constant(5);encode.REAL=function(v){var value=v.toString();var m=/\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);if(m){var epsilon=parseFloat("1e"+((m[2]?+m[2]:0)+m[1].length));value=(Math.round(v*epsilon)/epsilon).toString()}var nibbles="";var i;var ii;for(i=0,ii=value.length;i>8&255);b.push(codepoint&255)}return b};sizeOf.UTF16=function(v){return v.length*2};var eightBitMacEncodings={"x-mac-croatian":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø"+"¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ","x-mac-cyrillic":"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ"+"јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю","x-mac-gaelic":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø"+"ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ","x-mac-greek":"Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ"+"άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ­","x-mac-icelandic":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø"+"¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-inuit":"ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ"+"ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł","x-mac-ce":"ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ"+"ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ",macintosh:"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø"+"¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-romanian":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș"+"¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-turkish":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø"+"¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ"};opentype_decode.MACSTRING=function(dataView,offset,dataLength,encoding){var table=eightBitMacEncodings[encoding];if(table===undefined){return undefined}var result="";for(var i=0;i=128){c=table[c];if(c===undefined){return undefined}}result.push(c)}return result};sizeOf.MACSTRING=function(str,encoding){var b=encode.MACSTRING(str,encoding);if(b!==undefined){return b.length}else{return 0}};encode.INDEX=function(l){var i;var offset=1;var offsets=[offset];var data=[];var dataSize=0;for(i=0;i>8;d[o+1]=offset&255;d=d.concat(subtables[i])}return d};sizeOf.TABLE=function(table){var numBytes=0;var length=table.fields.length;for(var i=0;i=1){if(dest!=quat){dest[0]=quat[0];dest[1]=quat[1];dest[2]=quat[2];dest[3]=quat[3]}return dest}var halfTheta=Math.acos(cosHalfTheta);var sinHalfTheta=Math.sqrt(1-cosHalfTheta*cosHalfTheta);if(Math.abs(sinHalfTheta)<.001){dest[0]=quat[0]*.5+quat2[0]*.5;dest[1]=quat[1]*.5+quat2[1]*.5;dest[2]=quat[2]*.5+quat2[2]*.5;dest[3]=quat[3]*.5+quat2[3]*.5;return dest}var ratioA=Math.sin((1-slerp)*halfTheta)/sinHalfTheta;var ratioB=Math.sin(slerp*halfTheta)/sinHalfTheta;dest[0]=quat[0]*ratioA+quat2[0]*ratioB;dest[1]=quat[1]*ratioA+quat2[1]*ratioB;dest[2]=quat[2]*ratioA+quat2[2]*ratioB;dest[3]=quat[3]*ratioA+quat2[3]*ratioB;return dest};quat4.str=function(quat){return"["+quat[0]+", "+quat[1]+", "+quat[2]+", "+quat[3]+"]"};WebGLUtils=function(){var makeFailHTML=function(msg){return""+''+'
'+'
'+'
'+msg+"
"+"
"+"
"};var GET_A_WEBGL_BROWSER=""+"This page requires a browser that supports WebGL.
"+'Click here to upgrade your browser.';var OTHER_PROBLEM=""+"It doesn't appear your computer can support WebGL.
"+'Click here for more information.';var setupWebGL=function(canvas,opt_attribs){function showLink(str){var container=canvas.parentNode;if(container){container.innerHTML=makeFailHTML(str)}}if(!window.WebGLRenderingContext){showLink(GET_A_WEBGL_BROWSER);return null}var context=create3DContext(canvas,opt_attribs);if(!context){showLink(OTHER_PROBLEM)}return context};var create3DContext=function(canvas,opt_attribs){var names=["webgl","experimental-webgl","webkit-3d","moz-webgl"];var context=null;for(var ii=0;ii1e-6){var rotaxis,newup;if(Math.abs(angle-Math.PI)<1e-6)newup=parent.__up.multiply(-1);else{rotaxis=cross(oldaxis,newaxis);newup=parent.__up.rotate({angle:angle,axis:rotaxis})}parent.__up.__x=newup.x;parent.__up.__y=newup.y;parent.__up.__z=newup.z}}function adjust_axis(parent,oldup,newup){parent.__change();if(newup.mag2===0){if(parent.__oldup===undefined)parent.__oldup=oldup}if(parent.__oldup!==undefined){oldup=parent.__oldup;parent.__oldup=undefined}if(newup.dot(parent.__axis)===0)return;var angle=oldup.diff_angle(newup);if(angle>1e-6){var rotaxis,newaxis;if(Math.abs(angle-Math.PI)<1e-6)newaxis=parent.__axis.multiply(-1);else{rotaxis=cross(oldup,newup);newaxis=parent.__axis.rotate({angle:angle,axis:rotaxis})}parent.__axis.__x=newaxis.x;parent.__axis.__y=newaxis.y;parent.__axis.__z=newaxis.z}}function vec(x,y,z){if(!(this instanceof vec)){if(y===undefined)if(z===undefined)return new vec(x.x,x.y,x.z);return new vec(x,y,z)}if(z===undefined||y===undefined)throw new Error("vector() requires 3 arguments: x, y, and z.");this.x=x;this.y=y;this.z=z}function attributeVector(parent,x,y,z){this.__parent=parent;this.__x=x;this.__y=y;this.__z=z;if(parent){parent.__change()}}attributeVector.prototype=new vec(0,0,0);attributeVector.prototype.constructor=attributeVector;function attributeVectorPos(parent,x,y,z){this.__parent=parent;this.__x=x;this.__y=y;this.__z=z;if(parent){parent.__change();parent._pos_set=true;if(parent.__make_trail)parent.__update_trail(vec(x,y,z))}}attributeVectorPos.prototype=new vec(0,0,0);attributeVectorPos.prototype.constructor=attributeVectorPos;function attributeVectorAxis(parent,x,y,z){var oldaxis;this.__parent=parent;if(parent)oldaxis=norm(parent.__axis);this.__x=x;this.__y=y;this.__z=z;if(parent){if(parent.__sizing)parent.__size.__x=Math.sqrt(x*x+y*y+z*z);if(window.__adjustupaxis)adjust_up(parent,oldaxis,this);parent.__change()}}attributeVectorAxis.prototype=new vec(1,0,0);attributeVectorAxis.prototype.constructor=attributeVectorAxis;function attributeVectorSize(parent,x,y,z){this.__parent=parent;this.__x=x;this.__y=y;this.__z=z;if(parent){if(parent.__sizing){var v=parent.__axis.norm().multiply(x);parent.__axis.__x=v.x;parent.__axis.__y=v.y;parent.__axis.__z=v.z}parent.__change()}}attributeVectorSize.prototype=new vec(1,1,1);attributeVectorSize.prototype.constructor=attributeVectorSize;function attributeVectorUp(parent,x,y,z){var oldup;this.__parent=parent;if(parent)oldup=norm(parent.__up);this.__x=x;this.__y=y;this.__z=z;if(parent){if(window.__adjustupaxis)adjust_axis(parent,oldup,this);parent.__change()}}attributeVectorUp.prototype=new vec(0,1,0);attributeVectorUp.prototype.constructor=attributeVectorUp;Object.defineProperty(attributeVector.prototype,"__x",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVector.prototype,"x",{enumerable:true,get:function(){return this.__x},set:function(value){this.__x=value;this.__parent.__change()}});Object.defineProperty(attributeVector.prototype,"__y",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVector.prototype,"y",{enumerable:true,get:function(){return this.__y},set:function(value){this.__y=value;this.__parent.__change()}});Object.defineProperty(attributeVector.prototype,"__z",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVector.prototype,"z",{enumerable:true,get:function(){return this.__z},set:function(value){this.__z=value;this.__parent.__change()}});Object.defineProperty(attributeVectorPos.prototype,"__x",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorPos.prototype,"x",{enumerable:true,get:function(){return this.__x},set:function(value){this.__x=value;this.__parent.__change();this.__parent._pos_set=true;if(this.__parent.__make_trail)this.__parent.__update_trail(vec(this.__x,this.__y,this.__z))}});Object.defineProperty(attributeVectorPos.prototype,"__y",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorPos.prototype,"y",{enumerable:true,get:function(){return this.__y},set:function(value){this.__y=value;this.__parent.__change();this.__parent._pos_set=true;if(this.__parent.__make_trail)this.__parent.__update_trail(vec(this.__x,this.__y,this.__z))}});Object.defineProperty(attributeVectorPos.prototype,"__z",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorPos.prototype,"z",{enumerable:true,get:function(){return this.__z},set:function(value){this.__z=value;this.__parent.__change();this.__parent._pos_set=true;if(this.__parent.__make_trail)this.__parent.__update_trail(vec(this.__x,this.__y,this.__z))}});Object.defineProperty(attributeVectorAxis.prototype,"__x",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorAxis.prototype,"x",{enumerable:true,get:function(){return this.__x},set:function(value){var oldaxis=norm(this.__parent.__axis);this.__x=value;if(this.__parent.__sizing)this.__parent.__size.x=this.mag;adjust_up(this.__parent,oldaxis,this)}});Object.defineProperty(attributeVectorAxis.prototype,"__y",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorAxis.prototype,"y",{enumerable:true,get:function(){return this.__y},set:function(value){var oldaxis=norm(this.__parent.__axis);this.__y=value;if(this.__parent.__sizing)this.__parent.__size.x=this.mag;adjust_up(this.__parent,oldaxis,this)}});Object.defineProperty(attributeVectorAxis.prototype,"__z",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorAxis.prototype,"z",{enumerable:true,get:function(){return this.__z},set:function(value){var oldaxis=norm(this.__parent.__axis);this.__z=value;if(this.__parent.__sizing)this.__parent.__size.x=this.mag;adjust_up(this.__parent,oldaxis,this)}});Object.defineProperty(attributeVectorSize.prototype,"__x",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorSize.prototype,"x",{enumerable:true,get:function(){return this.__x},set:function(value){this.__x=value;if(this.__parent.__sizing){var v=this.__parent.__axis.norm().multiply(value);this.__parent.__axis.__x=v.x;this.__parent.__axis.__y=v.y;this.__parent.__axis.__z=v.z}this.__parent.__change()}});Object.defineProperty(attributeVectorSize.prototype,"__y",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorSize.prototype,"y",{enumerable:true,get:function(){return this.__y},set:function(value){this.__y=value;this.__parent.__change()}});Object.defineProperty(attributeVectorSize.prototype,"__z",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorSize.prototype,"z",{enumerable:true,get:function(){return this.__z},set:function(value){this.__z=value;this.__parent.__change()}});Object.defineProperty(attributeVectorUp.prototype,"__x",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorUp.prototype,"x",{enumerable:true,get:function(){return this.__x},set:function(value){var oldup=norm(this.__parent.__up);this.__x=value;adjust_axis(parent,oldup,this)}});Object.defineProperty(attributeVectorUp.prototype,"__y",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorUp.prototype,"y",{enumerable:true,get:function(){return this.__y},set:function(value){var oldup=norm(this.__parent.__up);this.__y=value;adjust_axis(parent,oldup,this)}});Object.defineProperty(attributeVectorUp.prototype,"__z",{enumerable:false,writable:true,value:0});Object.defineProperty(attributeVectorUp.prototype,"z",{enumerable:true,get:function(){return this.__z},set:function(value){var oldup=norm(this.__parent.__up);this.__z=value;adjust_axis(parent,oldup,this)}});vec.prototype.toString=function(){var input=[this.x,this.y,this.z];var output=[];for(var i=0;i<3;i++){output.push(__convert(input[i]))}return"< "+output[0]+", "+output[1]+", "+output[2]+" >"};vec.prototype.add=function(v){return new vec(this.x+v.x,this.y+v.y,this.z+v.z)};vec.prototype.sub=function(v){return new vec(this.x-v.x,this.y-v.y,this.z-v.z)};vec.prototype.multiply=function(r){return new vec(this.x*r,this.y*r,this.z*r)};vec.prototype.divide=function(r){return new vec(this.x/r,this.y/r,this.z/r)};property.declare(vec.prototype,{mag:{get:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},set:function(value){var v=this.norm().multiply(value);this.x=v.x;this.y=v.y;this.z=v.z}},mag2:{get:function(){return this.x*this.x+this.y*this.y+this.z*this.z},set:function(value){var v=this.norm().multiply(Math.sqrt(value));this.x=v.x;this.y=v.y;this.z=v.z}},hat:{get:function(){return this.norm()},set:function(value){var v=value.hat.multiply(this.mag);this.x=v.x;this.y=v.y;this.z=v.z}}});vec.prototype.norm=function(){var r=this.mag;if(r==0)return new vec(0,0,0);return new vec(this.x/r,this.y/r,this.z/r)};vec.prototype.dot=function(v){return this.x*v.x+this.y*v.y+this.z*v.z};vec.prototype.equals=function(v){if(v===null)return false;return this.x===v.x&&this.y===v.y&&this.z===v.z};vec.prototype.proj=function(v){var B=norm(v);return B.multiply(this.dot(B))};vec.prototype.comp=function(v){return this.dot(norm(v))};vec.prototype.cross=function(v){return new vec(this.y*v.z-this.z*v.y,this.z*v.x-this.x*v.z,this.x*v.y-this.y*v.x)};vec.prototype.diff_angle=function(v){var a=this.norm().dot(v.norm());if(a>1)return 0;if(a<-1)return Math.PI;return Math.acos(a)};vec.prototype.rotate=function(args){var angle,axis;var L=arguments.length;if(L<1||L>2)throw new Error("vector.rotate takes 1 or 2 arguments");for(var i=0;i3)throw new Error("rotate(vector, ...) takes 1 to 3 arguments");v=arguments[0];for(var i=1;i=65536)return null;if(bias<0)this.index.push(offset+bias);else{if(xmin===null||object.__pos.xxmax)xmax=object.__pos.x;if(ymin===null||object.__pos.yymax)ymax=object.__pos.y;if(zmin===null||object.__pos.zzmax)zmax=object.__pos.z;this.pos.push(object.__pos.x,object.__pos.y,object.__pos.z);this.normal.push(object.__normal.x,object.__normal.y,object.__normal.z);this.color.push(object.__color.x,object.__color.y,object.__color.z);if(object.__opacity<1)this.model_transparent=true;this.opacity.push(object.__opacity);this.shininess.push(object.__shininess);this.emissive.push(object.__emissive);this.texpos.push(object.__texpos.x,object.__texpos.y);this.bumpaxis.push(object.__bumpaxis.x,object.__bumpaxis.y,object.__bumpaxis.z);this.index.push(offset)}}else{if(offset+otherMesh.pos.length/3>=65536)return null;var c=[object.__color.x,object.__color.y,object.__color.z];for(var j=0;jxmax)xmax=otherMesh.pos[j]}else if(j%3===1){if(ymin===null||otherMesh.pos[j]ymax)ymax=otherMesh.pos[j]}else if(j%3===2){if(zmin===null||otherMesh.pos[j]zmax)zmax=otherMesh.pos[j]}this.pos.push(otherMesh.pos[j])}for(var j=0;j=Nlong)break;s=i*offset;if(j","?",")","!","@","#","$","%","^","&","*","(",":",":","<","=",">","?","@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","{","|","}","^","_","~","","","","","","","","","","*","+","","","","","f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","","{","|","}","~","delete"];_shifted[187]="+";_shifted[189]="_";_shifted[192]="~";_shifted[219]="{";_shifted[220]="|";_shifted[221]="}";_shifted[186]=":";_shifted[222]='"';_shifted[188]="<";_shifted[190]=">";_shifted[191]="?";var shiftlock=false;var __waitfor;var __waitfor_canvas;var __waitfor__expecting_key;$.fn.extend({gsmenubar:function(cmd){if(!this.is("ul")){alert("MenuBar top level must be unordered list, i.e.
    .");return}this.addClass("gsmenubar");this.children("li").children("ul").each(function(){$(this).menu()})}});$.fn.waitfor=function(eventTypes,callback){var self=this;function cb(ev){self.unbind(eventTypes,cb);__waitfor="";__waitfor_canvas.__expecting_key=__waitfor__expecting_key;callback(null,ev)}this.bind(eventTypes,cb)};$.fn.pause=function(prompt,callback){var self=this;function cb(ev){prompt.visible=false;self.unbind("click",cb);__waitfor="";__waitfor_canvas.__expecting_key=__waitfor__expecting_key;callback(null,ev)}this.bind("click",cb)};window.print_anchor=$("
    ").css("white-space","pre").appendTo($("body"));window.print_anchor.css({float:"left"});var __id=0;function canvas(options){if(!(this instanceof canvas))return new canvas(options);if(!options)options={};canvas.activated=[];canvas.selected=this;canvas.hasmouse=null;this.__title_anchor=$("
    ");this.__caption_anchor=$("
    ");this.__titletext="";this.__captiontext="";if("title"in options){this.__titletext=options.title;delete options.title}if("caption"in options){this.__captiontext=options.caption;delete options.caption}this.__align="none";if("align"in options){this.__align=options.align;delete options.align}this.__lastevent=null;this.__autoscale=true;if("autoscale"in options){this.__autoscale=options.autoscale;delete options.autoscale}this.__range=10;if("width"in options){this.__width=options.width;delete options.width}if("height"in options){this.__height=options.height;delete options.height}for(var id in options)this[id]=options[id];this.hasmouse=false;this.__needs_update=false;this.events=$("
    ");this.wrapper=$("
    ");this.menu=$("
    ");this.__canvas_element=document.createElement("canvas");this.__overlay_element=document.createElement("canvas");this.elements=$([this.__canvas_element,this.__overlay_element]);this.__overlay_objects={objects:[],__changed:false};this.__visiblePrimitives={};this.lights=[];distant_light({direction:vec(.22,.44,.88),color:vec(.8,.8,.8)});distant_light({direction:vec(-.88,-.22,-.44),color:vec(.3,.3,.3)});this.trails=[];this.arrows=[];this.billboards=[];this.update_billboards=false;this.__points_objects=[];this.__opaque_objects={};this.__transparent_objects={};this.vertex_id=1;var N=100;this.__vertices={Nalloc:N,pos:new Float32Array(3*N),normal:new Float32Array(3*N),color:new Float32Array(3*N),opacity:new Float32Array(N),shininess:new Float32Array(N),emissive:new Float32Array(N),texpos:new Float32Array(2*N),bumpaxis:new Float32Array(3*N),index:new Uint16Array(N),model_transparent:false,object_info:{},available:[]};this.__vertices.normal[2]=1;this.__sort_objects={opaque:{plain:{},textures:{},bumpmaps:{},textures_and_bumpmaps:{}},transparent:{plain:{},textures:{},bumpmaps:{},textures_and_bumpmaps:{}}};this.camera=orbital_camera(this);this.mouse=new Mouse(this);this.mouse.pos=vec(0,0,0);this.mouse.ray=vec(0,0,1);this.textures={};this.textures_requested={};this.__changed={};this.__vertex_changed={};this.visible=true;this.waitfor_textures=false;__waitfor="";this.__expecting_key=false;this.center=this.center;this.forward=this.forward;this.up=this.up}property.declare(canvas.prototype,{__activate:function(){this.__activated=true;this.__activate=function(){};this.__id=__id;__id++;var container=canvas.container;this.__title_anchor.css("white-space","pre").appendTo(container);this.menu.css("white-space","pre").appendTo(container);this.wrapper.addClass("glowscript-canvas-wrapper").css("display","inline-block").appendTo(container);this.__caption_anchor.css("white-space","pre").appendTo(container);this.wrapper.css("position","relative");var cv=this.__canvas_element;cv.style.position="absolute";var overlay=this.__overlay_element;overlay.style.position="relative";overlay.style.backgroundColor="transparent";this.width=this.__width;this.height=this.__height;this.wrapper.append(this.__canvas_element);this.wrapper.append(this.__overlay_element);this.wrapper.resizable({alsoResize:[this.__canvas_element,this.__overlay_element],resize:function(ev,ui){this.__canvas_element.width=this.__canvas_element.style.width=this.__overlay_element.width=this.__overlay_element.style.width=this.__width=ui.size.width;this.__canvas_element.height=this.__canvas_element.style.height=this.__overlay_element.height=this.__overlay_element.style.height=this.__height=ui.size.height;this.trigger("resize",{event:"resize"})}.bind(this)});if(!this.resizable)this.wrapper.resizable("disable");this.wrapper.css("float",this.__align);if(this.camera.__activate)this.camera.__activate();this.__handleEvents();if(this.__titletext)this.title=this.__titletext;if(this.__captiontext)this.caption=this.__captiontext;this.__renderer=new WebGLRenderer(this,cv,overlay);canvas.activated.push(this)},remove:function(){for(var id in this.__visiblePrimitives)this.__visiblePrimitives[id].visible=false;for(var id in this.__overlay_objects.objects)this.__overlay_objects.objects[id].visible=false;if(this.__activated)canvas.activated[this.__id]=null;this.wrapper.remove()},__handleEvents:function(){var canvas=this;var elements=canvas.elements;elements.bind("mouseenter mouseleave",function(ev){canvas.trigger("mouse",ev)});var keys={shift:16,ctrl:17,alt:18};$(document).bind("keydown keyup",function(ev){for(var k in keys){if(keys[k]==ev.which){canvas.mouse[k]=ev.type=="keydown";break}}ev.shift=canvas.mouse.shift||shiftlock;ev.key=_unshifted[ev.which];if(shiftlock&&(65<=ev.which&&ev.which<=90))ev.key=_shifted[ev.which];else if(canvas.mouse.shift)ev.key=_shifted[ev.which];var n=keysdownlist.indexOf(ev.key);if(ev.type=="keydown"){if(n<0)keysdownlist.push(ev.key)}else if(n>=0){keysdownlist.splice(n,1)}if(!canvas.__expecting_key)return;ev.event=ev.type;if(ev.which==20&&ev.type=="keydown")shiftlock=!shiftlock;ev.alt=canvas.mouse.alt;ev.ctrl=canvas.mouse.ctrl;canvas.trigger(ev.type,ev)})},capture:function(filename){if(filename.constructor!==String)throw new Error("A capture file name must be a string.");if(filename.indexOf(".png")<0)filename+=".png";this.__renderer.screenshot(function(err,img){if(!err){$(img).load(function(){var a=document.createElement("a");a.href=img.src;a.download=filename;a.click();a.remove()})}})},waitfor:function(eventTypes,callback){__waitfor_canvas=this;__waitfor__expecting_key=this.__expecting_key;if(eventTypes.search("key")>=0){__waitfor=eventTypes;this.__expecting_key=true}else{__waitfor="";this.__expecting_key=false}if(eventTypes=="textures")this.waitfor_textures=true;return this.events.waitfor(eventTypes,callback)},pause:function(args){__waitfor_canvas=this;__waitfor__expecting_key=this.__expecting_key;__waitfor="";var prompt="",callback;if(arguments.length==1)callback=arguments[0];else{prompt=arguments[0];callback=arguments[1]}if(prompt.length>0){if(this.__prompt==undefined){this.__prompt=label({canvas:this,align:"right",pixel_pos:true,height:14,color:color.black,background:color.white,opacity:1,box:false})}this.__prompt.pos=vec(this.__width,this.__height-12,0);this.__prompt.text=prompt;this.__prompt.visible=true;this.events.pause(this.__prompt,callback)}else{if(this.__draw==undefined)this.__draw=draw({canvas:this});var x=this.width-5,y=this.height-20;this.__draw.points=[vec(x,y,0),vec(x-30,y-13,0),vec(x-30,y+15,0),vec(x,y,0)];this.__draw.opacity=1;this.__draw.color=color.black;this.__draw.fillcolor=color.white;this.__draw.visible=true;this.events.pause(this.__draw,callback)}},select:function(){window.__context.canvas_selected=this},title_anchor:{get:function(){if(!this.__activated)this.__activate();return this.__title_anchor},set:function(value){throw new Error("Cannot change title_anchor")}},caption_anchor:{get:function(){if(!this.__activated)this.__activate();return this.__caption_anchor},set:function(value){throw new Error("Cannot change caption_anchor")}},title:{get:function(){return this.__titletext},set:function(value){this.__titletext=value;this.__title_anchor.html(value)}},caption:{get:function(){return this.__captiontext},set:function(value){this.__captiontext=value;this.__caption_anchor.html(value)}},append_to_title:function(args){var s="";var L=arguments.length;for(var i=0;i=0)this.__expecting_key=true;return this.events.bind(eventTypes,callback)},unbind:function(eventTypes,callback){if(eventTypes.search("key")>=0)this.__expecting_key=false;return this.events.unbind(eventTypes,callback)},one:function(eventTypes,callback){return this.events.one(eventTypes,callback)},trigger:function(type,ev){if(ev===undefined)ev={type:type,event:event};if(type=="mouse"){type=ev.type;var ev={type:type,pageX:ev.pageX,pageY:ev.pageY,which:1};this.mouse.__update(ev);ev.event=ev.type;ev.pos=this.mouse.pos;if(ev.type=="mousedown"){ev.press="left";ev.release=null}else if(ev.type=="mousemove"){ev.press=null;ev.release=null}else if(ev.type=="mouseup"){ev.press=null;ev.release="left"}else if(ev.type=="mouseenter"||ev.type=="mouseleave"){ev.press=null;ev.release=null}else if(ev.type=="click"){ev.press=null;ev.release="left"}}if(ev!==null)ev.canvas=this;var nev=new $.Event(type,ev);this.events.trigger(nev)},background:new vec(0,0,0),opacity:1,ambient:new vec(.2,.2,.2),__change:function(){if(this.__lastevent!==null&&this.hasmouse)this.mouse.__update(this.__lastevent)},center:new attributeVector(null,0,0,0),forward:new attributeVector(null,0,0,-1),up:new attributeVector(null,0,1,0),__last_forward:null,__last_center:null,__activated:false,userzoom:true,userspin:true,userpan:true,fov:60*Math.PI/180,width:{value:640,onchanged:function(){this.__canvas_element.width=this.__canvas_element.style.width=this.__overlay_element.width=this.__overlay_element.style.width=this.__width}},height:{value:400,onchanged:function(){this.__canvas_element.height=this.__canvas_element.style.height=this.__overlay_element.height=this.__overlay_element.style.height=this.wrapper[0].style.height=this.__height}},align:{get:function(){return this.__align},set:function(value){if(value=="left"||value=="right"||value=="none"){this.__align=value}else throw new Error("align must be 'left', 'right', or 'none' (the default).")}},resizable:{value:true,onchanged:function(){if(this.__activated){this.wrapper.resizable((this.resizable?"en":"dis")+"able")}}},autoscale:{get:function(){return this.__autoscale},set:function(value){if(this.__autoscale&&!value)Autoscale.compute_autoscale(this);this.__autoscale=value}},range:{get:function(){if(this.__autoscale){Autoscale.compute_autoscale(this)}return this.__range},set:function(value){this.__autoscale=false;this.__range=value;if(this.__lastevent!==null)this.mouse.__update(this.__lastevent)}},pixel_to_world:{get:function(){var w=this.__width;var h=this.__height;var d=2*this.range;if(w>=h){return d/h}else{return d/w}},set:function(value){throw new Error("Cannot assign a value to pixel_to_world.")}},objects:{get:function(){var all=[];for(var id in this.__visiblePrimitives){var v=this.__visiblePrimitives[id];if(v.__obj){if(v==v.__obj.__components[0]&&v.__obj.visible)all.push(v.__obj)}else all.push(v)}for(var id in this.__overlay_objects.objects){var obj=this.__overlay_objects.objects[id];if(obj instanceof label)all.push(obj)}return all}}});property.declare(canvas,{selected:{get:function(){return window.__context.canvas_selected||null},set:function(value){window.__context.canvas_selected=value}},get_selected:function(){return window.__context.canvas_selected||null},all:{get:function(){var v=window.__context.canvas_all;if(v===undefined)v=window.__context.canvas_all=[];return v}},container:{get:function(){return window.__context.glowscript_container||null},set:function(value){window.__context.glowscript_container=$(value)}}});function Mouse(canvas){this.canvas=canvas}property.declare(Mouse.prototype,{canvas:null,pos:null,ray:null,__pickx:null,__picky:null,pick:function(){return this.canvas.__renderer.render(1)},project:function(args){if(args.normal===undefined)throw new Error("scene.mouse.project() must specify a normal");var normal=args.normal;var dist;if(args.d===undefined&&args.point===undefined)dist=normal.dot(this.canvas.__center);else if(args.d!==undefined){dist=args.d}else if(args.point!==undefined){dist=normal.dot(args.point)}var ndc=normal.dot(this.canvas.camera.pos)-dist;var ndr=normal.dot(this.ray);if(ndr==0)return null;var t=-ndc/ndr;return this.canvas.camera.pos.add(this.ray.multiply(t))},alt:false,ctrl:false,shift:false,__update:function(ev){var cv=this.canvas,factor;if(cv.__width>cv.__height)factor=2*cv.__range/cv.__height;else factor=2*cv.__range/cv.__width;var o=$(cv.__canvas_element).offset();this.__pickx=ev.pageX-o.left;this.__picky=cv.__height-(ev.pageY-o.top);var mx=(this.__pickx-cv.__width/2)*factor;var my=(this.__picky-cv.__height/2)*factor;var xaxis=cv.__forward.norm().cross(cv.__up).norm();var yaxis=xaxis.cross(cv.__forward.norm());this.pos=cv.__center.add(xaxis.multiply(mx).add(yaxis.multiply(my)));this.ray=this.pos.sub(cv.camera.pos).norm();canvas.hasmouse=cv;cv.__lastevent=ev}});var exports={canvas:canvas,keysdown:keysdown};Export(exports)})();(function(){"use strict";function orbital_camera(canvas,args){if(!(this instanceof orbital_camera))return new orbital_camera(canvas,args);this.canvas=canvas;this.follower=null}property.declare(orbital_camera.prototype,{pos:{get:function(){var c=this.canvas;return c.center.sub(c.forward.norm().multiply(c.range/Math.tan(c.fov/2)))},set:function(val){var c=this.canvas;c.center=val.add(this.axis)}},axis:{get:function(){var c=this.canvas;return c.forward.norm().multiply(c.range/Math.tan(c.fov/2))},set:function(val){var c=this.canvas;c.center=this.pos.add(val);c.forward=norm(val);c.range=mag(val)*Math.tan(c.fov/2)}},rotate:function(args){if(args===undefined||args.angle===undefined){throw new Error("object.rotate() requires an angle")}var angle=args.angle;var rotaxis,origin;if(args.axis===undefined){rotaxis=this.axis.norm()}else rotaxis=args.axis.norm();if(args.origin===undefined){origin=this.pos}else origin=args.origin;this.pos=origin.add(this.pos.sub(origin).rotate({angle:angle,axis:rotaxis}));this.axis=this.axis.rotate({angle:angle,axis:rotaxis})},follow:function(objectOrFunction){this.follower=objectOrFunction},__activate:function(){var canvas=this.canvas;var camera=this;var contextMenuDisabled=false;var lastX=[null,null],lastY=[null,null];var downX=[null,null],downY=[null,null];var lastpos=null;var angleX=0,angleY=0;var afterdown=false;var rotating,zrotating,zooming,panning;var leftButton=false,rightButton=false,mouseWheel=false;var lastSep=null;var lastAngle=null;var fingers=0;var nomove=false;var tstart;var zoompos=[null,null];var saveEvent;var zoom=function(delta){var z=Math.exp(-delta*.05);canvas.range=canvas.range*z};var zrotate=function(dtheta){canvas.up=canvas.up.rotate({angle:2*dtheta,axis:canvas.__forward})};var spin=function(ev){var dx=ev.pageX-lastX[0];var dy=ev.pageY-lastY[0];angleX+=dx*.01;angleY+=dy*.01;if(angleY<-1.4)angleY=-1.4;if(angleY>1.4)angleY=1.4;canvas.__forward=canvas.__forward.rotate({angle:-.01*dx,axis:canvas.up});var max_vertical_angle=canvas.up.diff_angle(canvas.__forward.multiply(-1));var vertical_angle=.01*dy;if(!(vertical_angle>=max_vertical_angle||vertical_angle<=max_vertical_angle-Math.PI)){canvas.__forward=canvas.__forward.rotate({angle:-vertical_angle,axis:canvas.__forward.cross(canvas.__up)})}};var pan=function(ev){var csave=vec(canvas.__last_center);canvas.mouse.__update(ev);var c=canvas.mouse.pos;var xaxis=canvas.__forward.cross(canvas.__up).hat;var yaxis=xaxis.cross(canvas.__forward).hat;var d=c.sub(lastpos);var dx=d.dot(xaxis);var dy=d.dot(yaxis);lastpos=c.sub(d);canvas.__center=canvas.__center.sub(xaxis.multiply(dx).add(yaxis.multiply(dy)));canvas.__last_center=csave};$(document).bind("contextmenu",function(e){return!contextMenuDisabled});canvas.elements.mousewheel(function(ev,delta){if(canvas.userzoom)zoom(delta);return false});canvas.elements.mousedown(function(ev){if(ev.which==1)leftButton=true;if(ev.which==3)rightButton=true;rotating=canvas.userspin&&(ev.which==3||ev.which==1&&canvas.mouse.ctrl&&!canvas.mouse.alt);zooming=canvas.userzoom&&(ev.which==2||ev.which==1&&canvas.mouse.alt&&!canvas.mouse.ctrl||leftButton&&rightButton);panning=canvas.userpan&&ev.which==1&&canvas.mouse.shift;if(ev.which==3&&!(rotating||zooming))return;downX[0]=lastX[0]=ev.pageX;downY[0]=lastY[0]=ev.pageY;if(rotating||zooming||panning)contextMenuDisabled=true;else if(ev.which==1)canvas.trigger("mouse",ev);if(panning){canvas.autoscale=false;canvas.mouse.__update(ev);lastpos=canvas.mouse.pos}afterdown=true;ev.preventDefault();ev.stopPropagation();return false});canvas.elements.mousemove(function(ev){if(ev.pageX===lastX[0]&&ev.pageY===lastY[0])return;if(!afterdown){canvas.mouse.__update(ev);return}if(zooming){var dy=lastY[0]-ev.pageY;if(dy!==0)zoom(.1*dy)}else if(rotating){spin(ev)}else if(panning){pan(ev)}else if(ev.which==1)canvas.trigger("mouse",ev);if(!panning){lastX[0]=ev.pageX;lastY[0]=ev.pageY}});canvas.elements.mouseup(function(ev){if(ev.which==1)leftButton=false;if(ev.which==3)rightButton=false;if(!afterdown)return;if(ev.which==3&&contextMenuDisabled)setTimeout(function(){contextMenuDisabled=false},0);if(!(rotating||zooming||panning)){if(ev.which==1){canvas.trigger("mouse",ev);if(abs(ev.pageX-downX[0])<=5&&abs(ev.pageY-downY[0])<=5){ev.type="click";canvas.trigger("mouse",ev)}}else if(ev.which==3){contextMenuDisabled=true;return}}rotating=zooming=panning=afterdown=false;lastX=[null,null];lastY=[null,null]});canvas.elements.bind("touchstart",function(ev){rotating=zooming=nomove=false;lastSep=lastAngle=null;var pt;var data=ev.originalEvent.targetTouches;if(data.length>2)return;if(data.length==2&&!(canvas.userspin||canvas.userzoom))return;fingers++;for(var i=0;i2)return;var pt;var newx=[null,null],newy=[null,null];var relx=[0,0],rely=[0,0];for(var i=0;i15||dzoom[1].mag>15){saveEvent=null;zooming=true;var r=zoompos[1].sub(zoompos[0]).norm();var angmom=r.cross(dzoom[1]).sub(r.cross(dzoom[0])).mag;if(angmom>10){zrotating=canvas.userspin;if(!canvas.userspin)zooming=false}}else return}}if(saveEvent!==null){if(data.length==2){saveEvent=null}else{var near=relx[0]<=5&&rely[0]<=5;if(!rotating&&t>150&&near){canvas.trigger("mouse",saveEvent);saveEvent=null}else if(!near){rotating=canvas.userspin;saveEvent=null}}}else{if(newx[0]===lastX[0]&&newy[0]===lastY[0]&&newx[1]===lastX[1]&&newy[1]===lastY[1])return;ev.pageX=newx[0];ev.pageY=newy[0];ev.type="mousemove";if(rotating)spin(ev);else if(zooming){var xx=newx[1]-newx[0];var yy=newy[1]-newy[0];if(zrotating){var angle=Math.atan2(yy,xx);if(lastAngle!==null){var dangle;var va=vec(Math.cos(lastAngle),Math.sin(lastAngle),0);var vb=vec(Math.cos(angle),Math.sin(angle),0);var vc=va.cross(vb);var amag=Math.abs(Math.asin(vc.mag));if(vc.z>=0)dangle=-amag;else dangle=amag;zrotate(dangle)}lastAngle=angle}else if(canvas.userzoom){var sep=Math.sqrt(xx*xx+yy*yy);if(lastSep!==null&&sep!=lastSep)zoom(.2*(sep-lastSep));lastSep=sep}}else canvas.trigger("mouse",ev)}lastX[0]=newx[0];lastX[1]=newx[1];lastY[0]=newy[0];lastY[1]=newy[1]});canvas.elements.bind("touchend",function(ev){fingers--;if(saveEvent!==null&&!(rotating||zooming)){canvas.trigger("mouse",saveEvent);saveEvent=null}var data=ev.originalEvent.changedTouches;ev.pageX=data[0].clientX;ev.pageY=data[0].clientY;if(!(rotating||zooming)){ev.type="mouseup";canvas.trigger("mouse",ev);if(abs(ev.pageX-downX[0])<=5&&abs(ev.pageY-downY[0])<=5){ev.type="click";canvas.trigger("mouse",ev)}}if(zooming){if(fingers>0)nomove=true;else zooming=nomove=false}rotating=false;lastX=[null,null];lastY=[null,null];lastSep=lastAngle=null})}});var exports={orbital_camera:orbital_camera};Export(exports)})();(function(){"use strict";function extent(){}$.extend(extent.prototype,{xmin:null,ymin:null,zmin:null,xmax:null,ymax:null,zmax:null,zx_camera:0,zy_camera:0,last_zx_camera:-1,last_zy_camera:-1,point_extent:function(obj,p){this.xmin=Math.min(p.x,this.xmin);this.ymin=Math.min(p.y,this.ymin);this.zmin=Math.min(p.z,this.zmin);this.xmax=Math.max(p.x,this.xmax);this.ymax=Math.max(p.y,this.ymax);this.zmax=Math.max(p.z,this.zmax);obj.__xmin=Math.min(p.x,obj.__xmin);obj.__ymin=Math.min(p.y,obj.__ymin);obj.__zmin=Math.min(p.z,obj.__zmin);obj.__xmax=Math.max(p.x,obj.__xmax);obj.__ymax=Math.max(p.y,obj.__ymax);obj.__zmax=Math.max(p.z,obj.__zmax)}});var exports={Autoscale:{compute_autoscale:function compute_autoscale(canvas){var ext=canvas.__extent;if(!ext)ext=canvas.__extent=new extent;var ctrx=canvas.center.x,ctry=canvas.center.y,ctrz=canvas.center.z;var all=canvas.__visiblePrimitives;ext.zx_camera=0;ext.zy_camera=0;var cot_hfov=1/Math.tan(canvas.__fov/2);ext.__cot_hfov=cot_hfov;ext.__centerx=canvas.center.x;ext.__centery=canvas.center.y;ext.__centerz=canvas.center.z;var check=false;var obj;for(var id in all){obj=all[id];if(obj.constructor.name=="point")continue;if(obj.constructor.name=="points")continue;check=true;if(canvas.__changed[obj.__id]||obj.__zx_camera===null||obj.__zy_camera===null){obj.__get_extent(ext);if(obj.__xmin===null)continue;var xx=Math.max(Math.abs(obj.__xmin-ctrx),Math.abs(obj.__xmax-ctrx));var yy=Math.max(Math.abs(obj.__ymin-ctry),Math.abs(obj.__ymax-ctry));var zz=Math.max(Math.abs(obj.__zmin-ctrz),Math.abs(obj.__zmax-ctrz));obj.__zx_camera=xx*cot_hfov+zz;obj.__zy_camera=yy*cot_hfov+zz}ext.zx_camera=Math.max(ext.zx_camera,obj.__zx_camera);ext.zy_camera=Math.max(ext.zy_camera,obj.__zy_camera)}if(check){if(ext.zx_camera>ext.last_zx_camera||ext.zx_cameraext.last_zy_camera||ext.zy_cameraext.zy_camera){if(canvas.__width>=canvas.__height){canvas.__range=1.1*(canvas.__height/canvas.__width)*ext.zx_camera/cot_hfov}else{canvas.__range=1.1*ext.zx_camera/cot_hfov}}else{if(canvas.__width>=canvas.__height){canvas.__range=1.1*ext.zy_camera/cot_hfov}else{canvas.__range=1.1*(canvas.__width/canvas.__height)*ext.zy_camera/cot_hfov}}ext.last_zx_camera=ext.zx_camera;ext.last_zy_camera=ext.zy_camera}}},find_extent:function find_extent(obj,ext){if(obj.constructor.name=="points")return;if((obj.constructor.name=="simple_sphere"||obj.constructor.name=="vp_simple_sphere")&&obj.__pixels){for(var a=0;a<8;a++)ext.point_extent(obj,obj.__pos);return}var size=obj.__size;var sizex=size.x,sizey=size.y,sizez=size.z;var start=obj.__pos;var startx=start.x,starty=start.y,startz=start.z;var center_pos=obj.__hasPosAtCenter;var length;if(center_pos)length=Math.sqrt(sizex*sizex+sizey*sizey+sizez*sizez)/2;else length=Math.sqrt(sizex*sizex+sizey*sizey/4+sizez*sizez/4);var px=startx-ext.__centerx;var py=starty-ext.__centery;var pz=startz-ext.__centerz;var zzx=(Math.abs(px)+length)*ext.__cot_hfov+Math.abs(pz)+length;var zzy=(Math.abs(py)+length)*ext.__cot_hfov+Math.abs(pz)+length;if(zzx>i}return x+1}function handleLoadedTexture(image,obj,bump){var name,t0,ref;if(bump){name=obj.__tex.bumpmap;ref=obj.__tex.bumpmap_ref;t0=obj.__tex.bumpmap_t0}else{name=obj.__tex.file;ref=obj.__tex.texture_ref;t0=obj.__tex.texture_t0}var tf=msclock();tf=tf-t0;if(name in canvas.textures){ref.reference=canvas.textures[name]}else{canvas.textures[name]=ref.reference=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,ref.reference);gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,true);if(!isPowerOfTwo(image.width)||!isPowerOfTwo(image.height)){var c=document.createElement("canvas");c.width=nextHighestPowerOfTwo(image.width);c.height=nextHighestPowerOfTwo(image.height);var ctx=c.getContext("2d");ctx.drawImage(image,0,0,c.width,c.height);image=c}gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,image);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR_MIPMAP_NEAREST);gl.generateMipmap(gl.TEXTURE_2D);gl.bindTexture(gl.TEXTURE_2D,null)}if(name in canvas.textures_requested){var done=canvas.textures_requested[name];while(done.length>0){var data=done.pop();if(data[1]){data[0].__tex.bumpmap_ref.reference=ref.reference}else{data[0].__tex.texture_ref.reference=ref.reference}data[0].__change()}}}this.initTexture=function(name,obj,bump){if(bump)obj.__tex.bumpmap=name;else obj.__tex.file=name;if(name in canvas.textures){if(bump)obj.__tex.bumpmap_ref.reference=canvas.textures[name];else obj.__tex.texture_ref.reference=canvas.textures[name];return}if(name in canvas.textures_requested){canvas.textures_requested[name].push([obj,bump]);return}else canvas.textures_requested[name]=[[obj,bump]];var t0=msclock();if(bump)obj.__tex.bumpmap_t0=t0;else obj.__tex.texture_t0=t0;var image=new Image;image.crossOrigin="anonymous";image.src=name;image.onload=function(){handleLoadedTexture(image,obj,bump)}};var update_vertices=0;canvas.__last_width=-1;canvas.__last_height=-1;canvas.__last_forward=canvas.forward;canvas.__last_up=canvas.up;var ktexture=1;var peels={C0:null,D0:null,C1:null,D1:null,C2:null,D2:null,C3:null,D3:null,C4:null,EXTENT_TEXTURE:null};var fullpeels=gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)>=16;var textureN={C0:gl.TEXTURE2,D0:gl.TEXTURE3,C1:gl.TEXTURE4,D1:gl.TEXTURE5,C2:gl.TEXTURE6,D2:gl.TEXTURE7,C3:gl.TEXTURE8,D3:gl.TEXTURE9,C4:gl.TEXTURE10,EXTENT_TEXTURE:gl.TEXTURE11};function makeTexture(T){gl.activeTexture(textureN[T]);gl.bindTexture(gl.TEXTURE_2D,peels[T]);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);if(false&&T=="EXTENT_TEXTURE")gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,3,3,0,gl.RGBA,gl.UNSIGNED_BYTE,null);else gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,ktexture*canvas.__width,ktexture*canvas.__height,0,gl.RGBA,gl.UNSIGNED_BYTE,null);gl.bindTexture(gl.TEXTURE_2D,null)}for(var T in peels){peels[T]=gl.createTexture();makeTexture(T)}var peelFramebuffer=gl.createFramebuffer();gl.bindFramebuffer(gl.FRAMEBUFFER,peelFramebuffer);var peelRenderbuffer=gl.createRenderbuffer();gl.bindRenderbuffer(gl.RENDERBUFFER,peelRenderbuffer);gl.renderbufferStorage(gl.RENDERBUFFER,gl.DEPTH_COMPONENT16,ktexture*canvas.__width,ktexture*canvas.__height);gl.framebufferRenderbuffer(gl.FRAMEBUFFER,gl.DEPTH_ATTACHMENT,gl.RENDERBUFFER,peelRenderbuffer);gl.bindRenderbuffer(gl.RENDERBUFFER,null);gl.bindFramebuffer(gl.FRAMEBUFFER,null);var data=new Uint8Array(3);var tex1=gl.createTexture();gl.activeTexture(gl.TEXTURE1);gl.bindTexture(gl.TEXTURE_2D,tex1);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,1,1,0,gl.RGB,gl.UNSIGNED_BYTE,data);var tex0=gl.createTexture();gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,tex0);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,1,1,0,gl.RGB,gl.UNSIGNED_BYTE,data);this.render=function(mode){if(mode==RENDER){if(canvas.waitfor_textures){var check_objects=canvas.objects;for(var o in check_objects){var obj=check_objects[o];if(obj.__tex===undefined)continue;if(!obj.ready)return}canvas.waitfor_textures=false;canvas.trigger("textures",null)}}if(!canvas.visible){if(mode==RENDER)return;return null}if(canvas.__width!=canvas.__last_width||canvas.__height!=canvas.__last_height){for(var T in peels){makeTexture(T)}gl.bindFramebuffer(gl.FRAMEBUFFER,peelFramebuffer);gl.bindRenderbuffer(gl.RENDERBUFFER,peelRenderbuffer);gl.renderbufferStorage(gl.RENDERBUFFER,gl.DEPTH_COMPONENT16,ktexture*canvas.__width,ktexture*canvas.__height);gl.framebufferRenderbuffer(gl.FRAMEBUFFER,gl.DEPTH_ATTACHMENT,gl.RENDERBUFFER,peelRenderbuffer);gl.bindRenderbuffer(gl.RENDERBUFFER,null);gl.bindFramebuffer(gl.FRAMEBUFFER,null)}if(mode==RENDER){for(var i in canvas.arrows){var pos;var a=canvas.arrows[i];if(!a.run)continue;if(a.obj!==undefined){if(a.obj.pos!==undefined)pos=a.obj.pos;else continue}a.arrow.pos=pos;if(a.obj[a.attr]!==undefined){if(!a.arrow.visible)a.arrow.visible=true;if(window.__GSlang=="vpython")a.arrow.axis=a.obj[a.attr].multiply(a.scale);else a.arrow.axis_and_length=a.obj[a.attr].multiply(a.scale)}}for(var i in canvas.trails){var pos;var a=canvas.trails[i];if(!a.__run)continue;var obj=a.__obj;if(obj===undefined)continue;if(typeof obj==="string"){pos=a[obj];if(pos===undefined)continue}else if(typeof obj!=="function"){if(obj!==undefined&&obj.visible){if(!obj._pos_set)continue;if(obj.__interval>0)continue;if(obj.__pos!==undefined)pos=obj.__pos;else continue}else continue}else pos=obj();if(a.__last_pos!==null&&pos.equals(a.__last_pos))continue;if(a.pps>0){var tnow=msclock();if(a.__last_time===null)a.last_time=tnow;if(tnow-a.__last_time>1e3/a.pps)a.__last_time=tnow;else if(tnow!=a.__last_time)continue}a.__trail.push({pos:pos,color:a.color,radius:a.radius,retain:a.retain});a.__last_pos=vec(pos)}if(canvas.update_billboards||!canvas.__forward.equals(canvas.__last_forward)||!canvas.__up.equals(canvas.__last_up)){canvas.update_billboards=false;for(var i=0;i=canvasElement.clientHeight)camera.distance=canvas.__range/Math.tan(canvas.__fov/2);else camera.distance=canvas.__range*(canvasElement.clientHeight/canvasElement.clientWidth)/Math.tan(canvas.__fov/2);camera.pos=mat4.multiplyVec3(mat4.rotateX(mat4.rotateY(mat4.identity(mat4.create()),-camera.angleX),-camera.angleY),vec3.create([0,0,camera.distance]));camera.pos=vec3.create([canvas.__center.x+camera.pos[0],canvas.__center.y+camera.pos[1],canvas.__center.z+camera.pos[2]]);camera.zNear=camera.distance/100;camera.zFar=camera.distance*10;var projMatrix=mat4.perspective(camera.fovy,canvasElement.clientWidth/canvasElement.clientHeight,camera.zNear,camera.zFar);var viewMatrix=mat4.lookAt(camera.pos,camera.target,camera.up);for(var i=0;i0){var scale=2*canvas.__range/canvas.__width;for(var i=0;i0&&(canvas.__overlay_objects.__changed||!(canvas.__forward.equals(canvas.__last_forward)&&canvas.__center.equals(canvas.__last_center)&&canvas.__up.equals(canvas.__last_up)&&canvas.__width==canvas.__last_width&&canvas.__height==canvas.__last_height&&canvas.__range==canvas.__last_range))){canvas.__overlay_objects.__changed=false;var ctx=canvas.overlay_context;ctx.clearRect(0,0,canvas.__width,canvas.__height);for(var i=0;iPEEL_D0)gl.uniform2fv(prog.uniforms.canvas_size,canvas_size);if(minormode!=MERGE){if(mode==RENDER||minormode==PEEL_C0||minormode==PEEL_C1||minormode==PEEL_C2||minormode==PEEL_C3||minormode==PEEL_C4){gl.uniform1i(prog.uniforms.light_count,light_count);gl.uniform4fv(prog.uniforms.light_pos,light_pos);gl.uniform3fv(prog.uniforms.light_color,light_color);gl.uniform3fv(prog.uniforms.light_ambient,light_ambient);gl.enableVertexAttribArray(prog.attributes.normal);if(prog!=curve_program){gl.enableVertexAttribArray(prog.attributes.color);gl.enableVertexAttribArray(prog.attributes.opacity);gl.enableVertexAttribArray(prog.attributes.shininess);gl.enableVertexAttribArray(prog.attributes.emissive);gl.enableVertexAttribArray(prog.attributes.texpos);gl.enableVertexAttribArray(prog.attributes.bumpaxis);gl.uniform1i(prog.uniforms.texmap,0);gl.uniform1i(prog.uniforms.bumpmap,1)}}gl.uniformMatrix4fv(prog.uniforms.viewMatrix,false,viewMatrix);gl.uniformMatrix4fv(prog.uniforms.projMatrix,false,projMatrix)}if(minormode==MERGE){gl.uniform1i(prog.uniforms.C0,2);gl.uniform1i(prog.uniforms.C1,4);if(fullpeels){gl.uniform1i(prog.uniforms.C2,6);gl.uniform1i(prog.uniforms.C3,8);gl.uniform1i(prog.uniforms.C4,10)}}else if(minormode>PEEL_D0){gl.uniform1i(prog.uniforms.D0,3);if(minormode==PEEL_C2||minormode==PEEL_D2)gl.uniform1i(prog.uniforms.D1,5);else if(minormode==PEEL_C3||minormode==PEEL_D3)gl.uniform1i(prog.uniforms.D2,7);else if(minormode==PEEL_C4)gl.uniform1i(prog.uniforms.D3,9)}}function subrender(minormode,T,Trefs){if(mode==RENDER_TEXTURE&&Trefs.length>0){for(var i=0;iPEEL_C0)gl.clearColor(0,0,0,0);else if(mode==EXTENT)gl.clearColor(0,0,0,1);else gl.clearColor(canvas.__background.x,canvas.__background.y,canvas.__background.z,canvas.__opacity);if(mode==EXTENT){gl.depthFunc(gl.GREATER);gl.clearDepth(0)}else{gl.depthFunc(gl.LEQUAL);gl.clearDepth(1)}gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);function render_curves(){var model=object_models.curve;var objs=model.id_object;var elements=model.elementType;var model_length=model.index.length;var setup=true;for(var id in objs){if(!objs[id].visible)break;if(minormode>PEEL_D0)break;if(setup){if(minormode==RENDER||minormode==PEEL_C0){if(curve_program==null)curve_program=shaderProgram(shaders.opaque_render_fragment,shaders.curve_render_vertex,gl);useProgram(curve_program,minormode)}else if(minormode==PEEL_D0){if(curve_peel_depth_programD0==null)curve_peel_depth_programD0=shaderProgram(shaders.peel_depth_fragmentD0,shaders.curve_peel_depth_vertex,gl);useProgram(curve_peel_depth_programD0,minormode)}else if(minormode==PICK){if(curve_pick_program==null)curve_pick_program=shaderProgram(shaders.pick_fragment,shaders.curve_pick_vertex,gl);useProgram(curve_pick_program,minormode)}gl.bindBuffer(gl.ARRAY_BUFFER,model.posBuffer);gl.vertexAttribPointer(program.attributes.pos,4,gl.FLOAT,false,0,0);if(minormode!=PICK&&minormodePEEL_D0){if(op=="opaque")continue}else{if(op=="transparent")continue}var first=true;for(var sort_type in sort[op]){for(var sort_list in sort[op][sort_type]){if(first){first=false;switch(minormode){case RENDER:case PEEL_C0:if(triangle_program===null)triangle_program=shaderProgram(shaders.opaque_render_fragment,shaders.tri_render_vertex,gl);useProgram(triangle_program,minormode);break;case EXTENT:if(extent_program===null)extent_program=shaderProgram(shaders.pick_fragment,shaders.extent_vertex,gl);useProgram(extent_program,minormode);break;case PEEL_D0:if(tri_peel_depth_programD0===null)tri_peel_depth_programD0=shaderProgram(shaders.peel_depth_fragmentD0,shaders.tri_peel_depth_vertex,gl);useProgram(tri_peel_depth_programD0,minormode);break;case PEEL_D1:if(tri_peel_depth_programD1===null)tri_peel_depth_programD1=shaderProgram(shaders.peel_depth_fragmentD1,shaders.tri_peel_depth_vertex,gl);useProgram(tri_peel_depth_programD1,minormode);break;case PEEL_D2:if(tri_peel_depth_programD2===null)tri_peel_depth_programD2=shaderProgram(shaders.peel_depth_fragmentD2,shaders.tri_peel_depth_vertex,gl);useProgram(tri_peel_depth_programD2,minormode);break;case PEEL_D3:if(tri_peel_depth_programD3===null)tri_peel_depth_programD3=shaderProgram(shaders.peel_depth_fragmentD3,shaders.tri_peel_depth_vertex,gl);useProgram(tri_peel_depth_programD3,minormode);break;case PEEL_C1:if(tri_peel_color_programC1===null)tri_peel_color_programC1=shaderProgram(shaders.peel_color_fragmentC1,shaders.tri_render_vertex,gl);useProgram(tri_peel_color_programC1,minormode);break;case PEEL_C2:if(tri_peel_color_programC2===null)tri_peel_color_programC2=shaderProgram(shaders.peel_color_fragmentC2,shaders.tri_render_vertex,gl);useProgram(tri_peel_color_programC2,minormode);break;case PEEL_C3:if(tri_peel_color_programC3===null)tri_peel_color_programC3=shaderProgram(shaders.peel_color_fragmentC3,shaders.tri_render_vertex,gl);useProgram(tri_peel_color_programC3,minormode);break;case PEEL_C4:if(tri_peel_color_programC4===null)tri_peel_color_programC4=shaderProgram(shaders.peel_color_fragmentC4,shaders.tri_render_vertex,gl);useProgram(tri_peel_color_programC4,minormode);break}gl.bindBuffer(gl.ARRAY_BUFFER,model.posBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.pos,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.pos,3,gl.FLOAT,false,0,0);if(mode==RENDER||minormode==PEEL_C0||minormode==PEEL_C1||minormode==PEEL_C2||minormode==PEEL_C3||minormode==PEEL_C4){gl.bindBuffer(gl.ARRAY_BUFFER,model.normalBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.normal,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.normal,3,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.colorBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.color,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.color,3,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.opacityBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.opacity,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.opacity,1,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.shininessBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.shininess,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.shininess,1,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.emissiveBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.emissive,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.emissive,1,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.texposBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.texpos,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.texpos,2,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.bumpaxisBuffer);if(update_vertices)gl.bufferData(gl.ARRAY_BUFFER,model_arrays.bumpaxis,gl.DYNAMIC_DRAW);gl.vertexAttribPointer(program.attributes.bumpaxis,3,gl.FLOAT,false,0,0);update_vertices=0}}var indices=sort[op][sort_type][sort_list];var tbobj=indices[0];var model_index=new Uint16Array(indices.slice(1));var model_length=model_index.length;gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,model.indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,model_index,gl.DYNAMIC_DRAW);if(mode==EXTENT)elements=gl.POINTS;var Tdata=0,Bdata=0;if(sort_type=="textures"){if((mode==RENDER||mode==RENDER_TEXTURE)&&tbobj.__tex.file!==null){if(tbobj.__tex.texture_ref.reference!==null){gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,tbobj.__tex.texture_ref.reference);Tdata=1}else continue}}else if(sort_type=="bumpmaps"){if((mode==RENDER||mode==RENDER_TEXTURE)&&tbobj.__tex.bumpmap!==null){if(tbobj.__tex.bumpmap_ref.reference!==null){gl.activeTexture(gl.TEXTURE1);gl.bindTexture(gl.TEXTURE_2D,tbobj.__tex.bumpmap_ref.reference);Bdata=1}else continue}}else if(sort_type=="textures_and_bumpmaps"){if((mode==RENDER||mode==RENDER_TEXTURE)&&tbobj.__tex.file!==null){if(tbobj.__tex.texture_ref.reference!==null){gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,tbobj.__tex.texture_ref.reference);Tdata=1}else continue}if((mode==RENDER||mode==RENDER_TEXTURE)&&tbobj.__tex.bumpmap!==null){if(tbobj.__tex.bumpmap_ref.reference!==null){gl.activeTexture(gl.TEXTURE1);gl.bindTexture(gl.TEXTURE_2D,tbobj.__tex.bumpmap_ref.reference);Bdata=1}else continue}}gl.uniform1f(program.uniforms.T,Tdata);gl.uniform1f(program.uniforms.B,Bdata);gl.drawElements(elements,model_length,gl.UNSIGNED_SHORT,0)}}}}function render_merge(){var model=object_models.quad;var elements=model.elementType;var model_length=model.index.length;if(fullpeels){if(merge_program==null)merge_program=shaderProgram(shaders.merge_fragment,shaders.merge_vertex,gl);useProgram(merge_program,minormode)}else{if(merge_program2==null)merge_program2=shaderProgram(shaders.merge_fragment2,shaders.merge_vertex,gl);useProgram(merge_program2,minormode)}gl.bindBuffer(gl.ARRAY_BUFFER,model.posBuffer);gl.vertexAttribPointer(program.attributes.pos,3,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,model.indexBuffer);gl.drawElements(elements,model_length,gl.UNSIGNED_SHORT,0)}for(var m in object_models){if(minormode>=MERGE){render_merge();break}if(m=="quad"||m=="triangle"){if(triangles_exist)render_triangles();continue}else if(m=="curve"){render_curves();continue}var model=object_models[m];var elements=model.elementType;var model_length=model.index.length;var objs;if(minormode>PEEL_D0){if(canvas.__transparent_objects[m]===undefined)continue;objs=canvas.__transparent_objects[m]}else{if(canvas.__opaque_objects[m]===undefined)continue;objs=canvas.__opaque_objects[m]}var gotobjects=false;for(var id in objs){gotobjects=true;break}if(!gotobjects)continue;var ringobject=m=="ring"||m=="vp_ring";switch(minormode){case RENDER:case PEEL_C0:if(ringobject){if(ring_program===null)ring_program=shaderProgram(shaders.opaque_render_fragment,shaders.ring_render_vertex,gl);useProgram(ring_program,minormode)}else{if(standard_program===null)standard_program=shaderProgram(shaders.opaque_render_fragment,shaders.render_vertex,gl);useProgram(standard_program,minormode)}break;case PICK:if(ringobject){if(ring_pick_program===null)ring_pick_program=shaderProgram(shaders.pick_fragment,shaders.ring_pick_vertex,gl);useProgram(ring_pick_program,minormode)}else{if(pick_program===null)pick_program=shaderProgram(shaders.pick_fragment,shaders.pick_vertex,gl);useProgram(pick_program,minormode)}break;case EXTENT:if(extent_program===null)extent_program=shaderProgram(shaders.pick_fragment,shaders.extent_vertex,gl);useProgram(extent_program,minormode);break;case PEEL_D0:if(ringobject){if(ring_peel_depth_programD0===null)ring_peel_depth_programD0=shaderProgram(shaders.peel_depth_fragmentD0,shaders.ring_peel_depth_vertex,gl);useProgram(ring_peel_depth_programD0,minormode)}else{if(peel_depth_programD0===null)peel_depth_programD0=shaderProgram(shaders.peel_depth_fragmentD0,shaders.peel_depth_vertex,gl);useProgram(peel_depth_programD0,minormode)}break;case PEEL_D1:if(ringobject){if(ring_peel_depth_programD1===null)ring_peel_depth_programD1=shaderProgram(shaders.peel_depth_fragmentD1,shaders.ring_peel_depth_vertex,gl);useProgram(ring_peel_depth_programD1,minormode)}else{if(peel_depth_programD1===null)peel_depth_programD1=shaderProgram(shaders.peel_depth_fragmentD1,shaders.peel_depth_vertex,gl);useProgram(peel_depth_programD1,minormode)}break;case PEEL_D2:if(ringobject){if(ring_peel_depth_programD2===null)ring_peel_depth_programD2=shaderProgram(shaders.peel_depth_fragmentD2,shaders.ring_peel_depth_vertex,gl);useProgram(ring_peel_depth_programD2,minormode)}else{if(peel_depth_programD2===null)peel_depth_programD2=shaderProgram(shaders.peel_depth_fragmentD2,shaders.peel_depth_vertex,gl);useProgram(peel_depth_programD2,minormode)}break;case PEEL_D3:if(ringobject){if(ring_peel_depth_programD3===null)ring_peel_depth_programD3=shaderProgram(shaders.peel_depth_fragmentD3,shaders.ring_peel_depth_vertex,gl);useProgram(ring_peel_depth_programD3,minormode)}else{if(peel_depth_programD3===null)peel_depth_programD3=shaderProgram(shaders.peel_depth_fragmentD3,shaders.peel_depth_vertex,gl);useProgram(peel_depth_programD3,minormode)}break;case PEEL_C1:if(ringobject){if(ring_peel_color_programC1===null)ring_peel_color_programC1=shaderProgram(shaders.peel_color_fragmentC1,shaders.ring_render_vertex,gl);useProgram(ring_peel_color_programC1,minormode)}else{if(peel_color_programC1===null)peel_color_programC1=shaderProgram(shaders.peel_color_fragmentC1,shaders.render_vertex,gl);useProgram(peel_color_programC1,minormode)}break;case PEEL_C2:if(ringobject){if(ring_peel_color_programC2===null)ring_peel_color_programC2=shaderProgram(shaders.peel_color_fragmentC2,shaders.ring_render_vertex,gl);useProgram(ring_peel_color_programC2,minormode)}else{if(peel_color_programC2===null)peel_color_programC2=shaderProgram(shaders.peel_color_fragmentC2,shaders.render_vertex,gl);useProgram(peel_color_programC2,minormode)}break;case PEEL_C3:if(ringobject){if(ring_peel_color_programC3===null)ring_peel_color_programC3=shaderProgram(shaders.peel_color_fragmentC3,shaders.ring_render_vertex,gl);useProgram(ring_peel_color_programC3,minormode)}else{if(peel_color_programC3===null)peel_color_programC3=shaderProgram(shaders.peel_color_fragmentC3,shaders.render_vertex,gl);useProgram(peel_color_programC3,minormode)}break;case PEEL_C4:if(ringobject){if(ring_peel_color_programC4===null)ring_peel_color_programC4=shaderProgram(shaders.peel_color_fragmentC4,shaders.ring_render_vertex,gl);useProgram(ring_peel_color_programC4,minormode)}else{if(peel_color_programC4===null)peel_color_programC4=shaderProgram(shaders.peel_color_fragmentC4,shaders.render_vertex,gl);useProgram(peel_color_programC4,minormode)}break}gl.bindBuffer(gl.ARRAY_BUFFER,model.posBuffer);gl.vertexAttribPointer(program.attributes.pos,3,gl.FLOAT,false,0,0);if(mode!=PICK&&(mode==RENDER||minormode==PEEL_C0||minormode==PEEL_C1||minormode==PEEL_C2||minormode==PEEL_C3||minormode==PEEL_C4)){gl.bindBuffer(gl.ARRAY_BUFFER,model.normalBuffer);gl.vertexAttribPointer(program.attributes.normal,3,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.colorBuffer);gl.vertexAttribPointer(program.attributes.color,3,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.opacityBuffer);gl.vertexAttribPointer(program.attributes.opacity,1,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.shininessBuffer);gl.vertexAttribPointer(program.attributes.shininess,1,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.emissiveBuffer);gl.vertexAttribPointer(program.attributes.emissive,1,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.texposBuffer);gl.vertexAttribPointer(program.attributes.texpos,2,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,model.bumpaxisBuffer);gl.vertexAttribPointer(program.attributes.bumpaxis,3,gl.FLOAT,false,0,0)}else if(ringobject){gl.bindBuffer(gl.ARRAY_BUFFER,model.normalBuffer);gl.vertexAttribPointer(program.attributes.normal,3,gl.FLOAT,false,0,0)}gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,model.indexBuffer);if(mode==EXTENT)elements=gl.POINTS;for(var id in objs){var obj=objs[id];var data=obj.__data;if(minormode128){exponent=-(exponent-128);mantissa=-mantissa}var extent;if(mantissa==0&&exponent==0)extent=0;else extent=Math.exp(mantissa+exponent);return null}else if(mode==PICK){gl.readPixels(canvas.mouse.__pickx,canvas.mouse.__picky,1,1,gl.RGBA,gl.UNSIGNED_BYTE,pixels);var id=16777216*pixels[0]+65536*pixels[1]+256*pixels[2]+pixels[3];var obj=canvas.__visiblePrimitives[id];if(!obj)return null;else if(obj.__obj&&obj.__obj.pickable)return obj.__obj;else if(obj.constructor.name=="point"){if(!obj.__curve.pickable||!obj.pickable)return null;var pts=obj.__curve.__points;var L=pts.length;for(var i=0;i0;var t=msclock();var elapsed=0;if(doAverage)elapsed=t-lastStartRedraw;lastStartRedraw=t;canvas.trigger("redraw",{dt:elapsed});renderer.render(RENDER);t=msclock();elapsed=0;if(doAverage)elapsed=t-lastEndRedraw;lastEndRedraw=t;if(doAverage){renderMS=renderMS*.95+(t-lastStartRedraw)*.05;fps=fps*.95+1e3/elapsed*.05}else{renderMS=t-lastStartRedraw;fps=0}var total=fps*renderMS;$("#fps").text(fps.toFixed(1)+" renders/s * "+renderMS.toFixed(1)+" ms/render = "+total.toFixed(1)+" ms rendering/s");canvas.trigger("draw_complete",{dt:elapsed});canvas.__last_center=canvas.__center;canvas.__last_forward=canvas.__forward;canvas.__last_range=canvas.__range;canvas.__last_up=canvas.__up;canvas.__last_width=canvas.__width;canvas.__last_height=canvas.__height}this.reset();trigger_render()}var desired_fps=60;var N=0;var enditers;function rate(iters,cb){var dt,timer;if(cb===undefined)throw new Error("rate(iterations_per_second, wait) called without wait");if(N>0){N--;timer=msclock();if(timer>enditers)N=1;if(N>1){cb()}else{N=0;var dt=enditers-Math.ceil(timer);if(dt<5)dt=0;setTimeout(cb,dt)}}else{if(iters<=120){dt=Math.ceil(1e3/iters);setTimeout(cb,dt)}else{timer=msclock();N=Math.ceil(iters/desired_fps);enditers=msclock()+Math.ceil(1e3/desired_fps);cb()}}}var exports={WebGLRenderer:WebGLRenderer,rate:rate};Export(exports)})();(function(){"use strict";function log10(val){return Math.log(val)/Math.LN10}var eformat=false;var nformat=0;var nmax=0;function format_number(val,axis){if(axis.ticks.length==0){var delta=axis.tickSize;var amin=axis.min,amax=axis.max;var nticks=Math.floor((amax-amin)/delta+.5)+1;var vmax,test;for(var i=0;ivmax&&test!=0)vmax=test}nmax=Math.floor(log10(vmax))+1;var n=Math.floor(log10(delta))+1;if(n>3){eformat=true;nformat=n}else if(n>0){eformat=false;nformat=0}else if(n<0){eformat=true;nformat=n;if(nmax>=0){eformat=false;nformat=-n+1}}else{eformat=false;nformat=1}}if(val==0)return"0";if(eformat){var nf,nexp;var mantissa=val*pow(10,-nformat+1);nf=0;nexp=nformat-1;if(nmax>nformat){mantissa*=.1;nf+=1;nexp+=1}return mantissa.toFixed(nf)+"e"+nexp}else{return val.toFixed(nformat)}}var fontsize=16;var graphid=0;function graph(options){graph.activated=[];graph.__selected=null;graph.get_selected=function(){return graph.__selected};if(!(this instanceof graph))return new graph(options);options=options||{};if(options.x!==undefined)delete options.x;if(options.y!==undefined)delete options.y;this.fast=true;if(options.fast!==undefined){this.fast=options.fast;delete options.fast}this.scroll=false;if(options.scroll!==undefined){this.scroll=options.scroll;delete options.scroll}this.graph_options={};if(this.fast){this.graph_options={series:{shadowSize:0},crosshair:{mode:"xy",color:"rgba(0,0,0,1)"},xaxis:{min:null,max:null,tickFormatter:format_number},yaxis:{min:null,max:null,tickFormatter:format_number}}}this.__lock=false;this.__id="graph"+graphid;graphid++;this.container=$('
    ');this.__activated=false;this.__deleted=false;this.graph_series=[];this.__todo_list=[];graph.__selected=this;this.__width=640;this.__height=400;this.__plot=null;this.__xmin=this.__ymin=null;this.__xmax=this.__ymax=null;this.__xmin_actual=this.__ymin_actual=null;this.__xmax_actual=this.__ymax_actual=null;this.__title=this.__xtitle=this.__ytitle="";this.__made_title=false;this.__made_xtitle=false;this.__made_ytitle=false;if(options.width!==undefined){this.__width=options.width;delete options.width}if(options.height!==undefined){this.__height=options.height;delete options.height}this.__align="none";if(options.align!==undefined){this.__align=options.align;delete options.align}if(options.title!==undefined){this.__title=print_to_string(options.title);delete options.title}if(options.xtitle!==undefined){this.__xtitle=print_to_string(options.xtitle);delete options.xtitle}if(options.ytitle!==undefined){this.__ytitle=print_to_string(options.ytitle);delete options.ytitle}this.__foreground=color.black;this.__background=color.white;if(options.foreground!==undefined){var v=options.foreground;if(!(v instanceof vec))throw new Error("graph foreground must be a vector.");this.__foreground=v;delete options.foreground}if(options.background!==undefined){var v=options.background;if(!(v instanceof vec))throw new Error("graph background must be a vector.");var hsv=color.rgb_to_hsv(v);if(hsv.z<.5)hsv.z=.5;this.__background=color.hsv_to_rgb(hsv);delete options.background}var minmax=0;if(options.xmin!==undefined){this.__xmin=options.xmin;if(this.fast)this.graph_options.xaxis.min=options.xmin;minmax++;delete options.xmin}if(options.xmax!==undefined){this.__xmax=options.xmax;if(this.fast)this.graph_options.xaxis.max=options.xmax;minmax++;delete options.xmax}if(this.scroll){if(minmax!=2)throw new Error("For a scrolling graph, both xmin and xmax must be specified.");if(this.__xmax<=this.xmin)throw new Error("For a scrolling graph, xmax must be greater than xmin.")}if(!this.fast&&minmax==1){if(this.__xmin===null&&this.__xmax>0)this.__xmin=0;else if(this.__xmin<0&&this.__xmax===null)this.__xmax=0;else throw new Error("You must specify both xmin and xmax.")}minmax=0;if(options.ymin!==undefined){this.__ymin=options.ymin;if(this.fast)this.graph_options.yaxis.min=options.ymin;minmax++;delete options.ymin}if(options.ymax!==undefined){this.__ymax=options.ymax;if(this.fast)this.graph_options.yaxis.max=options.ymax;minmax++;delete options.ymax}if(!this.fast&&minmax==1){if(this.__ymin===null&&this.__ymax>0)this.__ymin=0;else if(this.__ymin<0&&this.__ymax===null)this.__ymax=0;else throw new Error("You must specify both ymin and ymax.")}this.__logx=this.__logy=false;if(options.logx!==undefined){this.__logx=this.graph_options.logx=options.logx;delete options.logx}if(options.logy!==undefined){this.__logy=this.graph_options.logy=options.logy;delete options.logy}if(this.fast){if(this.__logx){this.graph_options.xaxis.transform=function(v){return log10(v)};this.graph_options.xaxis.inverseTransform=function(v){return pow(10,v)}}if(this.__logy){this.graph_options.yaxis.transform=function(v){return log10(v)};this.graph_options.yaxis.inverseTransform=function(v){return pow(10,v)}}}var err="",count=0;for(var attr in options){count+=1;err+=attr+", "}if(err.length>0){if(count==1)throw new Error(err.slice(0,err.length-2)+" is not an attribute of a graph");else throw new Error("These are not attributes of a graph: "+err.slice(0,err.length-2))}function compute_offset(T){if(T==null||T=="")return 0;T=T.replace("
    ","\n");T=T.replace("
    ","\n");T=T.split("\n");if(T.length==1)return fontsize;return fontsize+1.3*fontsize*(T.length-1)}if(this.fast){var top=0,left=0,right=0,bottom=0;if(this.__align=="right")right=40;var d=10;if(this.__title!=="")top=d+compute_offset(this.__title);if(this.__ytitle!==""){left=compute_offset(this.__ytitle);if(left>fontsize)throw new Error("graph ytitle must not contain line breaks.");left+=d}if(this.__xtitle!==null&&this.__xtitle!==""){bottom=compute_offset(this.__xtitle);if(bottom>fontsize)throw new Error("graph xtitle must not contain line breaks.");bottom+=d}this.graph_options.grid={color:color.to_html(this.__foreground),backgroundColor:color.to_html(this.__background),offsets:{left:left,right:right,top:top,bottom:bottom}}}}property.declare(graph.prototype,{type:{get:function(){return this.__type},set:function(){throw new Error("Cannot change the type of a graph.")}},select:function(){graph.__selected=this},__todo:function(info,id){if(!this.__lock)Plotly.restyle(this.__id,info,id);else this.__todo_list.push([info,id])},__changed:false,remove:function(){if(this.__activated){if(!this.fast)Plotly.purge(this.__id);this.__deleted=true;this.container.remove()}},title:{get:function(){return this.__title},set:function(value){var m=value.match(/([^\n])*/);value=m[0];this.__title=value;if(this.__activated){if(this.fast){this.make_title(value)}else{Plotly.relayout(this.__id,{title:value})}}}},xtitle:{get:function(){return this.__xtitle},set:function(value){var m=value.match(/([^\n])*/);value=m[0];this.__xtitle=value;if(this.__activated){if(this.fast){this.make_xtitle(value)}else{Plotly.relayout(this.__id,{"xaxis.title":value})}}}},ytitle:{get:function(){return this.__ytitle},set:function(value){var m=value.match(/([^\n])*/);value=m[0];this.__ytitle=value;if(this.__activated){if(this.fast){this.make_ytitle(value)}else{Plotly.relayout(this.__id,{"yaxis.title":value})}}}},width:{get:function(){return this.__width},set:function(value){this.__width=value;if(this.fast){this.container.css("width",value);var plot=$.plot(this.container,[],this.graph_options);plot.resize();plot.setupGrid()}else{if(this.__activated)Plotly.relayout(this.__id,{width:value})}}},height:{get:function(){return this.__height},set:function(value){this.__height=value;if(this.fast){this.container.css("height",value);var plot=$.plot(this.container,[],this.graph_options);plot.resize();plot.setupGrid()}else{if(this.__activated)Plotly.relayout(this.__id,{height:value})}}},align:{get:function(){return this.__align},set:function(value){if(this.__activated)throw new Error("Cannot change align after the graph is activated.");if(value=="left"||value=="right"||value=="none"){this.__align=value}else throw new Error("align must be 'left', 'right', or 'none' (the default).")}},xmin:{get:function(){return this.__xmin},set:function(value){this.__xmin=value;if(this.fast)this.graph_options.xaxis.min=value;else if(this.__activated)Plotly.relayout(this.__id,{"xaxis.range":[value,this.__xmax]})}},xmax:{get:function(){return this.__xmax},set:function(value){this.__xmax=value;if(this.fast)this.graph_options.xaxis.max=value;else if(this.__activated)Plotly.relayout(this.__id,{"xaxis.range":[this.__xmin,value]})}},ymin:{get:function(){return this.__ymin},set:function(value){this.__ymin=value;if(this.fast)this.graph_options.yaxis.min=value;else if(this.__activated)Plotly.relayout(this.__id,{"yaxis.range":[value,this.__ymax]})}},ymax:{get:function(){return this.__ymax},set:function(value){this.__ymax=value;if(this.fast)this.graph_options.yaxis.max=value;else if(this.__activated)Plotly.relayout(this.__id,{"yaxis.range":[this.__ymin,value]})}},logx:{get:function(){return this.__logx},set:function(value){this.__logx=value;if(this.__logx==value)return;if(this.fast){if(value){this.graph_options.xaxis.transform=function(v){return log10(v)};this.graph_options.xaxis.inverseTransform=function(v){return pow(10,v)}}else{delete this.graph_options.xaxis.transform;delete this.graph_options.xaxis.inverseTransform}}else if(this.__activated){if(value)Plotly.relayout(this.__id,{"xaxis.type":"log"});else Plotly.relayout(this.__id,{"xaxis.type":null})}}},logy:{get:function(){return this.__logy},set:function(value){this.__logy=value;if(this.__logy==value)return;if(this.fast){if(value){this.graph_options.yaxis.transform=function(v){return log10(v)};this.graph_options.yaxis.inverseTransform=function(v){return pow(10,v)}}else{delete this.graph_options.yaxis.transform;delete this.graph_options.yaxis.inverseTransform}}else if(this.__activated){if(value)Plotly.relayout(this.__id,{"yaxis.type":"log"});else Plotly.relayout(this.__id,{"yaxis.type":null})}}},foreground:{get:function(){return this.__foreground},set:function(value){if(!(value instanceof vec))throw new Error("graph foreground color must be a vector.");this.__foreground=value;var col=color.to_html(value);if(this.fast)this.graph_options.grid.color=col;else if(this.__activated)Plotly.relayout(this.__id,{paper_bgcolor:col})}},background:{get:function(){return this.__background},set:function(value){if(!(value instanceof vec))throw new Error("graph background color must be a vector.");this.__background=value;var col=color.to_html(value);if(this.fast)this.graph_options.grid.backgroundColor=col;else if(this.__activated)Plotly.relayout(this.__id,{plot_bgcolor:col})}},make_title:function(title){var o=this.graph_options.grid.offsets;if(o.top===0)throw new Error("Cannot change a graph title if it does not already have a title.");var ctx=this.__plot.getCanvas().getContext("2d");ctx.fillStyle=color.to_html_rgba(vec(1,1,1),1);ctx.fillRect(0,0,this.__width,o.top);var font="Arial";if(title!==null&&title!==""){var y0=15;var x0=o.left+(this.__width-o.left-o.right)/2;var info=parse_html({ctx:ctx,text:title.toString(),x:x0,y:y0,align:"center",font:font,fontsize:fontsize,angle:0});display_2D(info)}},make_xtitle:function(title){var o=this.graph_options.grid.offsets;if(o.bottom===0)throw new Error("Cannot change a graph xtitle if it does not already have an xtitle.");var ctx=this.__plot.getCanvas().getContext("2d");ctx.fillStyle=color.to_html_rgba(vec(1,1,1),1);ctx.fillRect(o.left,this.__height-o.bottom,this.__width-o.right,this.__height);var font="Arial";if(title!==null&&title!==""){var x0=o.left+(this.__width-o.left-o.right)/2;var y0=this.__height+o.top-5;var info=parse_html({ctx:ctx,text:title.toString(),x:x0,y:y0,align:"center",font:font,fontsize:fontsize,angle:0});display_2D(info)}},make_ytitle:function(title){var o=this.graph_options.grid.offsets;if(o.left===0)throw new Error("Cannot change a graph ytitle if it does not already have a ytitle.");var ctx=this.__plot.getCanvas().getContext("2d");ctx.fillStyle=color.to_html_rgba(vec(1,1,1),1);ctx.fillRect(0,o.top,o.left,this.__height-o.top-o.bottom);var font="Arial";if(title!==null&&title!==""){var x0=15;var y0=o.top+(this.__height-o.bottom-15)/2;var info=parse_html({ctx:ctx,text:title.toString(),x:x0,y:y0,align:"center",font:font,fontsize:fontsize,angle:-Math.PI/2});display_2D(info)}},add_to_graph:function(obj){obj.__id=this.graph_series.length;obj.__activated=false;obj.__scrolldata=[];this.graph_series.push(obj)},__activate:function(dheight){if(this.__activated)return;if(!this.fast){this.container.addClass("glowscript-graph").css("width",this.__width).css("height",this.__height+dheight).appendTo(canvas.container);if(this.__align!="none")this.container.css("float",this.__align)}else{this.container.addClass("glowscript-graph").css("width",this.__width).css("height",this.__height+this.graph_options.grid.offsets.top).appendTo(canvas.container);this.container.css("float",this.__align)}graph.activated.push(this);this.__activated=true},__update:function(){if(!this.__changed||this.__deleted)return;if(this.__lock)return;var already_activated=this.__activated;function compute_offset(s){var m=s.match(/
    /g);if(m===null)return 0;return m.length}var xmax=this.__xmax;if(!this.fast){for(var i=0;i0){layout.title=this.title;var n=compute_offset(this.title);t=65;if(n>0)t+=45*(n-1);dh+=45*(n+1);layout.height+=dh}var l=65;if(this.ytitle!==undefined&&this.ytitle.length>0){layout.yaxis.title=this.ytitle;var d=compute_offset(this.ytitle);if(d>0)throw new Error("A ytitle must not contain
    .");l=80}var b=45;if(this.xtitle!==undefined&&this.xtitle.length>0){layout.xaxis.title=this.xtitle;var d=compute_offset(this.xtitle);if(d>0)throw new Error("An xtitle must not contain
    .")}layout.margin={l:l,r:20,b:b,t:t,pad:4};this.__activate(dh);this.__activated=true}var indices=[];for(var i=0;ixmax)xmax=xx}}xs.push(x);ys.push(y);s.__newdata=[]}if(this.scroll&&xmax>this.__xmax){var d=xmax-this.__xmax;this.xmin+=d;this.xmax+=d}if(!this.__lock){this.__lock=true;var self=this;Plotly.extendTraces(this.__id,{x:xs,y:ys},indices).then(function(){self.__lock=false})}}else{var info=[];for(var i=0;i0){var x=s.__data[L-1][0];if(x>xmax)xmax=x}info.push(s.options);if(s.__linemarker!==null&&s.__markers){s.__linemarker.data=s.__data;s.__linemarker.color=s.__linemarker.points.fillColor;s.__linemarker.points.radius=s.__radius;info.push(s.__linemarker)}if(s.__dot&&s.__realtype=="lines"&&s.__data.length>0){var dotdisplay={points:{show:true}};if(s.__dot_radius!==null)dotdisplay.points.radius=s.__dot_radius;else dotdisplay.points.radius=s.__width+1;if(s.__dot_color!==null)dotdisplay.color=color.to_html(s.__dot_color);else dotdisplay.color=color.to_html(s.__color);dotdisplay.points.fillColor=dotdisplay.color;dotdisplay.data=[s.options.data[s.options.data.length-1]];info.push(dotdisplay)}}if(this.scroll&&xmax>this.__xmax){var d=xmax-this.__xmax;this.xmin+=d;this.xmax+=d}if(info.length>0){this.__activate(0);this.__plot=$.plot(this.container,info,this.graph_options);this.__plot.draw()}if(!already_activated){if(this.__title!==null&&this.__title!=="")this.make_title(this.__title);if(this.__xtitle!==null&&this.__xtitle!=="")this.make_xtitle(this.__xtitle);if(this.__ytitle!==null&&this.__ytitle!=="")this.make_ytitle(this.__ytitle)}}this.__changed=false;if(!already_activated)new render_graph(this)}});var to_slow_type={lines:"lines",scatter:"markers",markers:"markers",bar:"bar"};var to_fast_type={lines:"lines",scatter:"points",markers:"points",bar:"bars"};function gobject(options){options=options||{};this.__data=[];this.__newdata=[];this.__color=vec(0,0,0);this.__marker_color=vec(0,0,0);this.__linemarker=null;this.__lineobj=null;this.__markerobj=null;this.__label=null;this.__save_label=null;this.__delta=1;this.__width=2;this.__radius=3;this.__horizontal=false;this.__dot=false;this.__xmin=null;this.__xmax=null;this.__ymin=null;this.__ymax=null;this.__label="";this.__legend=false;this.__markers=false;this.__interval=-1;var fast=true;if(options.fast!==undefined){fast=options.fast;delete options.fast}if(options.graph!==undefined){this.__graph=options.graph;delete options.graph}else if(options.gdisplay!==undefined){this.__graph=options.gdisplay;delete options.gdisplay}else{try{this.__graph=graph.get_selected()}catch(err){this.__graph=graph({fast:fast})}}this.__type="lines";if(options.type!==undefined){this.__type=options["type"];delete options["type"]}if(this.__graph.fast)this.__realtype=to_fast_type[this.__type];else this.__realtype=to_slow_type[this.__type];if(!this.__realtype)throw new Error("Unknown series type: "+this.__type);this.options={};var ftype=this.__realtype;var initialdata=[];if(options.data!==undefined){initialdata=options.data;delete options.data}if(options.color!==undefined){var c=options.color;if(!(c instanceof vec))throw new Error("graph color must be a vector.");this.__color=c;this.__marker_color=c;delete options.color}if(this.__graph.fast){if(ftype=="lines")this.options[ftype]={show:true,lineWidth:this.__width};else if(ftype=="points")this.options[ftype]={show:true,radius:this.__radius,fill:true,lineWidth:0};else if(ftype=="bars")this.options[ftype]={show:true,align:"center",horizontal:false,barWidth:1,lineWidth:1,fill:.5}}this.options.color=color.to_html(this.__color);if(this.__graph.fast){if(this.__realtype=="points")this.options[ftype].fillColor=this.options.color;else this.options.fillColor=this.options.color}if(options.marker_color!==undefined){var c=options.marker_color;if(!(c instanceof vec))throw new Error("graph color must be a vector.");this.__marker_color=c;delete options.marker_color}if(options.width!==undefined){this.__width=options.width;if(this.__graph.fast)this.options[ftype].lineWidth=options.width;delete options.width}if(options.markers!==undefined){if(this.__realtype!="lines")throw new Error("One can add markers only to graph curves.");if(options.markers===true){this.__markers=options.markers;var r=this.__width/2+2;if(options.radius!==undefined)r=options.radius;if(this.__graph.fast)this.__linemarker={points:{show:true,radius:r,lineWidth:0,fillColor:color.to_html(this.__marker_color),fill:true}};else this.__radius=r}delete options.markers}if(options.radius!==undefined){this.__radius=options.radius;if(this.__graph.fast)this.options[ftype].radius=options.radius;delete options.radius}if(options.size!==undefined){this.__radius=options.size/2;if(this.__graph.fast)this.options[ftype].radius=options.size/2;delete options.size}if(options.horizontal!==undefined){this.__horizontal=options.horizontal;if(this.__graph.fast)this.options[ftype].horizontal=options.horizontal;delete options.horizontal}if(options.delta!==undefined){this.__delta=options.delta;if(this.__graph.fast)this.options[ftype].barWidth=options.delta;delete options.delta}if(options.label!==undefined){this.__label=options.label;this.__save_label=options.label;if(this.__graph.fast)this.options.label=options.label;if(options.label.length>0)this.__legend=true;delete options.label}if(options.legend!==undefined){this.__legend=options.legend;if(this.__graph.fast&&!options.legend&&this.__label!==null)delete this.options.label;delete options.legend}if(options.__lineobj!==undefined){this.__lineobj=options.__lineobj;delete options.__lineobj}if(options.__markerobj!==undefined){this.__markerobj=options.__markerobj;delete options.__markerobj}if(options.dot!==undefined){if(this.__realtype!="lines")throw new Error('Can add a moving dot only to a gcurve or "lines" object');this.__dot=options.dot;this.__dot_radius=this.__width/2+4;this.__dot_color=this.__color;delete options.dot}if(options.dot_radius!==undefined){this.__dot_radius=options.dot_radius;delete options.dot_radius}this.__dot_color=this.__color;if(options.dot_color!==undefined){var v=options.dot_color;if(!(v instanceof vec))throw new Error("graph dot_color must be a vector.");this.__dot_color=v;delete options.dot_color}if(options.interval!==undefined){this.__interval=options.interval;this.__ninterval=options.interval;delete options.interval}this.__visible=true;if(options.visible!==undefined){this.__visible=options.visible;delete options.visible}var err="",count=0;for(var attr in options){count+=1;err+=attr+", "}if(err.length>0){if(count==1)throw new Error(err.slice(0,err.length-2)+" is not an attribute of a series");else throw new Error("These are not attributes of a graph object: "+err.slice(0,err.length-2))}this.__graph.add_to_graph(this);this.remove=function(){this.data=[]};var toType=function(obj){return{}.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()};var resolveargs=function(args){var v,ret;ret=[];if(toType(arguments[0][0])!="array"){for(var i=0;i0){this.__ninterval++;if(this.__ninterval>=this.__interval)this.__ninterval=0;else continue}if(this.__graph.fast)this.__data.push(data[i]);else this.__newdata.push(data[i]);if(!this.__graph.fast&&this.__realtype=="lines"&&this.__newdata.length>0){var d=this.__newdata[this.__newdata.length-1];if(this.__dot)this.__dotobject.data=[data[i]];if(this.__markers)this.__markerobject.__newdata.push(data[i])}}}else{var x,y,dx,dy,lastx,lasty,dt,xy,wx,wy;var g=this.__graph;var xmin=g.__xmin;var xmax=g.__xmax;var ymin=g.__ymin;var ymax=g.__ymax;var xmin_actual=g.__xmin_actual;var xmax_actual=g.__xmax_actual;var ymin_actual=g.__ymin_actual;var ymax_actual=g.__ymax_actual;dt=Math.floor(msclock()-this.__lasttime);this.__lasttime=msclock();lastx=lasty=null;if(this.__data.length>0){xy=this.__data[this.__data.length-1];lastx=xy[0];lasty=xy[1]}for(var i=0;i0){this.__ninterval++;if(this.__ninterval>=this.__interval)this.__ninterval=0;else continue}xy=data[i];x=xy[0];y=xy[1];checkval(x);checkval(y);if(g.scroll){g.__xmin_actual=xmin_actual=g.__xmin;g.__xmax_actual=xmax_actual=g.__xmax}else{if(xmin_actual===null||xxmax_actual)g.__xmax_actual=xmax_actual=x}if(ymin_actual===null||yymax_actual)g.__ymax_actual=ymax_actual=y;if(lastx!==null){wx=xmax_actual-xmin_actual;dx=wx===0?0:Math.abs(x-lastx)/wx;wy=ymax_actual-ymin_actual;dy=wy===0?0:Math.abs(y-lasty)/wy;if(dx<.01&&dy<.01)continue}if(this.__graph.fast)this.__data.push(xy);else this.__newdata.push(xy);lastx=x;lasty=y}if(this.__dot&&!this.__graph.fast&&this.__newdata.length>0){this.__dotobject.data=[this.__newdata[this.__newdata.length-1]]}}this.__graph.__changed=true;if(!this.__graph.__activated)this.__graph.__update()};if(initialdata.length>0)this.plot(initialdata)}property.declare(gobject.prototype,{graph:{get:function(){return this.__graph},set:function(value){throw new Error("Cannot change the choice of graph for an existing graphing object.")}},type:{get:function(){return this.__type},set:function(value){throw new Error("Cannot change the type of an existing graphing object.")}},data:{get:function(){return this.__data.concat(this.__newdata)},set:function(value){this.__data=[];this.options.data=[];this.__newdata=[];if(!this.__graph.fast){if(this.__realtype=="lines"){if(this.__dot){this.__dotobject.__data=[];this.__dotobject.__newdata=[];if(this.__graph.__activated)this.__graph.__todo({x:[[]],y:[[]]},this.__dotobject.__id)}if(this.__markers){this.__markerobject.__data=[];this.__markerobject.__newdata=[];if(this.__graph.__activated)this.__graph.__todo({x:[[]],y:[[]]},this.__markerobject.__id)}}if(this.__graph.__activated)this.__graph.__todo({x:[[]],y:[[]]},this.__id)}this.__graph.__changed=true;if(value.length>0)this.plot(value)}},markers:{get:function(){return this.__markers},set:function(value){if(this.__realtype!="lines")throw new Error("One can add markers only to graph curves.");if(this.__markers===value)return;this.__markers=value;var r=this.__radius+2;if(this.__activated){if(value){var up={};up["marker.color"]=color.to_html(this.__color);if(this.__graph.fast)this.options.points={show:true,radius:r};else this.__graph.__todo(up,this.__markerobject.__id)}else{var up={};up["marker.size"]=.1;if(this.__graph.fast)this.options.points={show:false,radius:r};else this.__graph.__todo(up,this.__markerobject.__id);this.__graph.__changed=true}}}},color:{get:function(){return this.__color},set:function(value){if(!(value instanceof vec))throw new Error("graphing color must be a vector.");if(this.__color.equals(value))return;this.__color=value;var col=color.to_html(value);if(this.__graph.fast)this.options.color=col;if(this.__activated){if(this.__graph.fast){if(this.__realtype=="points")this.options[ftype].fillColor=this.options.color;else this.options.fillColor=this.options.color;this.__graph.__changed=true}else{var up={};if(this.__realtype=="bar"){up["marker.color"]=color.to_html_rgba(value,.5);up["marker.line.color"]=color.to_html_rgba(value,1)}else if(this.__realtype=="markers"){up["marker.color"]=col}else if(this.__realtype=="lines"){up["line.color"]=col;up["marker.color"]=col}this.__graph.__todo(up,this.__id)}}}},marker_color:{get:function(){return this.__marker_color},set:function(value){if(!(value instanceof vec))throw new Error("graphing marker_color must be a vector.");if(this.__marker_color.equals(value))return;this.__marker_color=value;var col=color.to_html(value);if(this.__graph.fast)this.options.marker_color=col;if(this.__activated){if(this.__graph.fast){if(this.__realtype=="lines")this.__linemarker.points.fillColor=col;else this.options.color=col;this.__graph.__changed=true}else{if(this.__markerobject.__activated){var up={};up["marker.color"]=col;this.__graph.__todo(up,this.__markerobject.__id)}}}}},label:{get:function(){if(this.__label===undefined)return"";return this.__label},set:function(value){var m=value.match(/([^\n])*/);value=m[0];if(this.__label==value)return;this.__label=value;if(this.__graph.fast)this.options.label=value;if(value.length>0)this.__legend=true;if(this.__activated){if(this.__graph.fast){this.options.label=value;this.__graph.__changed=true}else this.__graph.__todo({name:value,showlegend:this.__legend},this.__id)}}},legend:{get:function(){return this.__legend},set:function(value){if(this.__legend==value)return;this.__legend=value;if(this.__graph.fast)this.options.legend=value;if(this.__activated){if(this.__graph.fast){if(this.options.label!==null){if(value)this.options.label=this.__save_label;else delete this.options.label}this.options.legend=value;this.__graph.__changed=true}else this.__graph.__todo({showlegend:value},this.__id)}}},delta:{get:function(){return this.__delta},set:function(value){if(this.__delta==value)return;this.__delta=value;if(this.__graph.fast)this.options[this.__realtype].barWidth=value;if(this.__activated){if(this.__graph.fast){this.__graph.__changed=true}else this.__graph.__todo({width:value},this.__id)}}},width:{get:function(){return this.__width},set:function(value){if(this.__width==value)return;this.__width=value;if(this.__graph.fast)this.options[this.__realtype].lineWidth=value;if(this.__activated){if(this.__graph.fast){this.__graph.__changed=true}else{var up={};if(this.__realtype=="bar"){return}else if(this.__realtype=="lines"){up["line.width"]=value}else if(this.__realtype=="markers"){return}this.__graph.__todo(up,this.__id)}}}},radius:{get:function(){return this.__radius},set:function(value){if(this.__radius==value)return;this.__radius=value;if(this.__graph.fast)this.options[this.__realtype].radius=value;if(this.__activated){if(this.__graph.fast){this.__graph.__changed=true}else{var up={};up["marker.size"]=2*value;if(this.__realtype=="lines"){if(!this.__markers)return;this.__graph.__todo(up,this.__markerobject.__id)}else if(this.__realtype=="markers"){this.__graph.__todo(up,this.__id)}}}}},size:{get:function(){return 2*this.__radius},set:function(value){if(2*this.__radius==value)return;this.__radius=value/2;if(this.__graph.fast)this.options[this.__realtype].radius=value/2;if(this.__activated){if(this.__graph.fast){this.__graph.__changed=true}else{var up={};if(this.__realtype=="bar"){return}else if(this.__realtype=="lines"){up["line.width"]=value;up["marker.size"]=value}else if(this.__realtype=="markers"){up["marker.size"]=value}this.__graph.__todo(up,this.__id)}}}},horizontal:{get:function(){return this.__horizontal},set:function(value){if(this.__horizontal==value)return;this.__horizontal=value;if(this.__graph.fast)this.options[this.__realtype].horizontal=value;if(this.__activated){if(this.__graph.fast){this.__graph.__changed=true}else{if(value)this.__graph.__todo({orientation:"h"},this.__id);else this.__graph.__todo({orientation:"v"},this.__id)}}}},orientation:{get:function(){var ret=this.__horizontal?"h":"v";return ret},set:function(value){var m=value.match(/([^\n])*/);value=m[0];this.__orientation=value;if(value=="v")this.__horizontal=false;else if(value=="h")this.__horizontal=true;else throw new Error("orientation must be either 'v' for vertical or 'h' for horizontal");if(this.__activated){if(this.__graph.fast)this.__graph.__changed=true;else this.__graph.__todo({orientation:value},this.__id)}}},dot:{get:function(){return this.__dot},set:function(value){if(this.__activated)throw new Error("Cannot change gcurve dot after the gcurve has been activated.");if(this.__dot==value)return;this.__dot=value}},dot_color:{get:function(){return this.__dot_color},set:function(value){if(!(value instanceof vec))throw new Error("graph dot_color must be a vector.");if(this.__dot_color.equals(value))return;this.__dot_color=value;var col=color.to_html(value);if(this.__graph.fast){this.options.dot_color=col;this.__graph.__changed=true}else if(this.__dotobject.__activated){var up={};up["marker.color"]=col;this.__graph.__todo(up,this.__dotobject.__id)}}},dot_radius:{get:function(){return this.__dot_radius},set:function(value){if(this.__dot_radius==value)return;this.__dot_radius=value;if(this.__graph.fast){this.options.dot_radius=value;this.__graph.__changed=true}else if(this.__dotobject.__activated){var up={};up["marker.size"]=2*value;this.__graph.__todo(up,this.__dotobject.__id)}}},visible:{get:function(){return this.__visible},set:function(value){if(this.__visible==value)return;this.__visible=value;if(this.__activated){if(!this.__graph.fast)this.__graph.__todo({visible:value},this.__id);this.__graph.__changed=true}}}});function render_graph(grf){function grender(){window.requestAnimationFrame(grender);grf.__update()}grender()}function series(options){var ret=new gobject(options);if(ret.dot){options={type:"scatter",color:ret.__dot_color,radius:ret.__dot_radius};ret.__dotobject=new gobject(options)}return ret}function gcurve(options){options=options||{};options.type="lines";if(options.pos!==undefined){options.data=options.pos;delete options.pos}if(options.size!==undefined){options.dot_radius=options.size/2;delete options.size}var ret=new gobject(options);if(!ret.__graph.fast){if(ret.dot)ret.__dotobject=gdots({color:ret.__dot_color,radius:ret.__dot_radius,__lineobj:ret});if(ret.markers)ret.__markerobject=gdots({color:ret.__marker_color,radius:ret.__radius,__markerobj:ret})}return ret}function gdots(options){options=options||{};options.type="scatter";if(options.pos!==undefined){options.data=options.pos;delete options.pos}return new gobject(options)}function gvbars(options){options=options||{};options.type="bar";options.horizontal=false;if(options.pos!==undefined){options.data=options.pos;delete options.pos}return new gobject(options)}function ghbars(options){options=options||{};options.type="bar";options.horizontal=true;if(options.pos!==undefined){options.data=options.pos;delete options.pos}return new gobject(options)}function ghistogram(options){throw new Error("ghistogram is not currently implemented in GlowScript.")}var exports={graph:graph,vp_graph:graph,gdisplay:graph,series:series,gcurve:gcurve,gdots:gdots,gvbars:gvbars,ghbars:ghbars,ghistogram:ghistogram};Export(exports)})();(function(){"use strict";var color={red:vec(1,0,0),green:vec(0,1,0),blue:vec(0,0,1),yellow:vec(1,1,0),orange:vec(1,.6,0),cyan:vec(0,1,1),magenta:vec(1,0,1),purple:vec(.4,.2,.6),white:vec(1,1,1),black:vec(0,0,0),gray:function(g){return vec(g,g,g)},hsv_to_rgb:function(hsv){var h=hsv.x;var s=hsv.y;var v=hsv.z;if(s==0){return vec(v,v,v)}var i=Math.floor(6*h);var f=6*h-i;var p=v*(1-s);var q=v*(1-s*f);var t=v*(1-s*(1-f));var i=i%6;switch(i){case 0:return vec(v,t,p);case 1:return vec(q,v,p);case 2:return vec(p,v,t);case 3:return vec(p,q,v);case 4:return vec(t,p,v);case 5:return vec(v,p,q)}},rgb_to_hsv:function(rgb){var r=rgb.x;var g=rgb.y;var b=rgb.z;var maxc=Math.max(r,g,b);var minc=Math.min(r,g,b);var v=maxc;if(minc==maxc){return vec(0,0,v)}var s=(maxc-minc)/maxc;var rc=(maxc-r)/(maxc-minc);var gc=(maxc-g)/(maxc-minc);var bc=(maxc-b)/(maxc-minc);var h;if(r==maxc){h=bc-gc}else if(g==maxc){h=2+rc-bc}else{h=4+gc-rc}h=h/6;if(h<0)h++;return vec(h,s,v)},to_html:function(color){var r=Math.floor(255*color.x);var g=Math.floor(255*color.y);var b=Math.floor(255*color.z);return"rgb("+r+","+g+","+b+")"},to_html_rgba:function(color,opacity){var r=Math.floor(255*color.x);var g=Math.floor(255*color.y);var b=Math.floor(255*color.z);return"rgba("+r+","+g+","+b+","+opacity+")"}};var exports={color:color};Export(exports)})();(function(){"use strict";var npdefault=64;function shape_object(){}shape_object.prototype.roundc=function roundc(cps,args){var cp=[],i;for(i=0;i0){outer=this.roundc(outer,{roundness:args.roundness,invert:args.invert});inner=this.roundc(inner,{roundness:args.roundness,invert:args.invert})}return[outer,inner]};shape_object.prototype.rectangle=function rectangle(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];if(args.width===undefined)args.width=1;if(args.height===undefined)args.height=null;if(args.rotate===undefined)args.rotate=0;if(args.thickness===undefined)args.thickness=0;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;var w2,h2,cp;if(args.height===null){args.height=args.width}if(args.thickness===0){cp=[];w2=args.width/2;h2=args.height/2;cp=[[w2,-h2],[w2,h2],[-w2,h2],[-w2,-h2],[w2,-h2]];cp=this.addpos(args.pos,cp);if(args.rotate!==0)cp=this.rotatecp(cp,args.pos,args.rotate);if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1)cp=this.scale(cp,args.xscale,args.yscale);if(args.roundness>0)cp=this.roundc(cp,{roundness:args.roundness,invert:args.invert})}else{cp=this.rframe(args)}return cp};shape_object.prototype.cross=function cross(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];if(args.width===undefined)args.width=1;if(args.rotate===undefined)args.rotate=0;if(args.thickness===undefined)args.thickness=.2;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;var wtp,w2,t2,cp;wtp=(args.width+args.thickness)/2;w2=args.width/2;t2=args.thickness/2;cp=[[w2,-t2],[w2,t2],[t2,t2],[t2,w2],[-t2,w2],[-t2,t2],[-w2,t2],[-w2,-t2],[-t2,-t2],[-t2,-w2],[t2,-w2],[t2,-t2],[w2,-t2]];cp=this.addpos(args.pos,cp);if(rotate!==0)cp=this.rotatecp(cp,args.pos,args.rotate);if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1)cp=this.scale(cp,args.xscale,args.yscale);if(args.roundness>0)cp=this.roundc(cp,{roundness:args.roundness,invert:args.invert});return cp};shape_object.prototype.trframe=function trframe(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];if(args.width===undefined)args.width=2;if(args.height===undefined)args.height=1;if(args.top===undefined)args.top=null;if(args.rotate===undefined)args.rotate=0;if(args.thickness===undefined)args.thickness=0;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;var angle,db,outer,inner;if(args.top===null){args.top=width/2}if(args.thickness===null){args.thickness=min(args.height,args.top)*.2}else{args.thickness=min(args.height,args.top)*args.thickness*2}outer=this.trapezoid({pos:args.pos,width:args.width,height:args.height,top:args.top});angle=Math.atan((args.width-args.top)/2/args.height);db=args.thickness/Math.cos(angle);inner=this.trapezoid({pos:args.pos,width:args.width-db-args.thickness*Math.tan(angle),height:args.height-args.thickness,top:args.top-(db-args.thickness*Math.tan(angle))});outer=this.addpos(args.pos,outer);inner=this.addpos(args.pos,inner);if(args.rotate!==0){outer=this.rotatecp(outer,args.pos,args.rotate);inner=this.rotatecp(inner,args.pos,args.rotate)}if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1){outer=this.scale(outer,args.xscale,args.yscale);inner=this.scale(inner,args.xscale,args.yscale)}if(args.roundness>0){outer=this.roundc(outer,{roundness:args.roundness,invert:args.invert});inner=this.roundc(inner,{roundness:args.roundness,invert:args.invert})}return[outer,inner]};shape_object.prototype.trapezoid=function trapezoid(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];if(args.width===undefined)args.width=2;if(args.height===undefined)args.height=1;if(args.top===undefined)args.top=null;if(args.rotate===undefined)args.rotate=0;if(args.thickness===undefined)args.thickness=0;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;var w2,h2,t2,cp;w2=args.width/2;h2=args.height/2;if(args.top===null){args.top=w2}t2=args.top/2;if(args.thickness===0){cp=[[w2,-h2],[t2,h2],[-t2,h2],[-w2,-h2],[w2,-h2]];cp=this.addpos(args.pos,cp);if(args.rotate!==0)cp=this.rotatecp(cp,args.pos,args.rotate);if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1)cp=this.scale(cp,args.xscale,args.yscale);if(args.roundness>0)cp=this.roundc(cp,{roundness:args.roundness,invert:args.invert})}else{cp=this.trframe(args)}return cp};shape_object.prototype.circframe=function circframe(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];if(args.radius===undefined)args.radius=.5;if(args.iradius===undefined)args.iradius=null;if(args.np===undefined)args.np=npdefault;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;if(args.angle1===undefined)args.angle1=0;if(args.angle2===undefined)args.angle2=2*Math.PI;if(args.rotate===undefined)args.rotate=0;args.thickness=0;var outer,inner;if(args.iradius===null)args.iradius=args.radius*.8;outer=this.circle(args);if(args.angle1===0&&args.angle2==2*Math.PI){args.radius=args.iradius}else{var t=args.radius-args.iradius;var angle=(args.angle1+args.angle2)/2;var offset=t/Math.sin((args.angle2-args.angle1)/2);args.corner=[args.pos[0]+offset*Math.cos(angle),args.pos[1]+offset*Math.sin(angle)];var dangle=Math.asin(t/args.iradius);args.angle1=args.angle1+dangle;args.angle2=args.angle2-dangle;args.radius=args.iradius}inner=this.circle(args);if(args.rotate!==0){outer=this.rotatecp(outer,args.pos,args.rotate);inner=this.rotatecp(inner,args.pos,args.rotate)}if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1){outer=this.scale(outer,args.xscale,args.yscale);inner=this.scale(inner,args.xscale,args.yscale)}return[outer,inner]};shape_object.prototype.circle=function circle(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];var corner=args.pos;if(args.corner!==undefined)corner=args.corner;if(args.radius===undefined)args.radius=.5;if(args.np===undefined)args.np=npdefault;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;if(args.thickness===undefined)args.thickness=0;if(args.angle1===undefined)args.angle1=0;if(args.angle2===undefined)args.angle2=2*Math.PI;if(args.rotate===undefined)args.rotate=0;var seg,nseg,dc,ds,x0,y0,c2,s2,c,s,i,cp;cp=[];if(args.thickness>0){args.iradius=args.radius-args.radius*args.thickness;cp=this.circframe(args)}else{if(args.angle1!==0||args.angle2!==2*Math.PI){cp.push([corner[0],corner[1]])}seg=2*Math.PI/args.np;nseg=Math.floor(Math.abs((args.angle2-args.angle1)/seg+.5));seg=(args.angle2-args.angle1)/nseg;if(args.angle1!==0||args.angle2!==2*Math.PI){nseg+=1}c=args.radius*Math.cos(args.angle1);s=args.radius*Math.sin(args.angle1);dc=Math.cos(seg);ds=Math.sin(seg);x0=args.pos[0];y0=args.pos[1];cp.push([x0+c,y0+s]);for(i=0;i0){outer=this.roundc(outer,{roundness:args.roundness,invert:args.invert});inner=this.roundc(inner,{roundness:args.roundness,invert:args.invert})}return[outer,inner]};shape_object.prototype.ngon=function ngon(args){args=args||{};if(args.thickness===undefined)args.thickness=0;if(args.pos===undefined)args.pos=[0,0];if(args.length===undefined)args.length=1;if(args.rotate===undefined)args.rotate=0;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;if(args.np===undefined)args.np=3;var seg,x,y,angle,radius,i,cp;cp=[];if(args.np<3)throw Error("number of sides can not be less than 3");angle=2*Math.PI/args.np;radius=args.length/2/Math.sin(angle/2);if(args.thickness===0){seg=2*Math.PI/args.np;angle=0;for(i=0;i0)cp=this.roundc(cp,{roundness:args.roundness,invert:args.invert})}else{cp=this.nframe(args)}return cp};shape_object.prototype.triangle=function triangle(args){args=args||{};if(args.rotate===undefined)args.rotate=0;args.np=3;args.rotate=args.rotate-Math.PI/6;return this.ngon(args)};shape_object.prototype.pentagon=function pentagon(args){args=args||{};if(args.rotate===undefined)args.rotate=0;args.np=5;args.rotate=args.rotate+Math.PI/10;return this.ngon(args)};shape_object.prototype.hexagon=function hexagon(args){args=args||{};if(args.rotate===undefined)args.rotate=0;args.np=6;return this.ngon(args)};shape_object.prototype.octagon=function octagon(args){args=args||{};if(args.rotate===undefined)args.rotate=0;args.np=8;args.rotate=args.rotate+Math.PI/8;return this.ngon(args)};shape_object.prototype.sframe=function sframe(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];if(args.radius===undefined)args.radius=1;if(args.n===undefined)args.n=5;if(args.iradius===undefined)args.iradius=null;if(args.rotate===undefined)args.rotate=0;if(args.thickness===undefined)args.thickness=0;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;var outer,inner,cp;if(args.iradius===null){args.iradius=.5*args.radius}if(args.thickness===null){args.thickness=.2*args.radius}else{args.thickness=args.thickness*2*args.iradius}outer=this.star({pos:args.pos,n:args.n,radius:args.radius,iradius:args.iradius});inner=this.star({pos:args.pos,n:args.n,radius:args.radius-args.thickness,iradius:(args.radius-args.thickness)*args.iradius/args.radius});if(args.rotate!==0){outer=this.rotatecp(outer,args.pos,args.rotate);inner=this.rotatecp(inner,args.pos,args.rotate)}if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1){outer=this.scale(outer,args.xscale,args.yscale);inner=this.scale(inner,args.xscale,args.yscale)}if(args.roundness>0){outer=this.roundc(outer,{roundness:args.roundness,invert:args.invert});inner=this.roundc(inner,{roundness:args.roundness,invert:args.invert})}return[outer,inner]};shape_object.prototype.star=function star(args){args=args||{};if(args.pos===undefined)args.pos=[0,0];if(args.radius===undefined)args.radius=1;if(args.n===undefined)args.n=5;if(args.iradius===undefined)args.iradius=null;if(args.rotate===undefined)args.rotate=0;if(args.thickness===undefined)args.thickness=0;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;var dtheta,theta,i,cp;if(args.iradius===null)args.iradius=args.radius*.5;if(args.thickness===0){cp=[];dtheta=Math.PI/args.n;theta=0;for(i=0;i<2*args.n+1;i++){if(i%2===0){cp.push([-args.radius*Math.sin(theta),args.radius*Math.cos(theta)])}else{cp.push([-args.iradius*Math.sin(theta),args.iradius*Math.cos(theta)])}theta+=dtheta}cp=this.addpos(args.pos,cp);cp[cp.length-1]=cp[0];if(args.rotate!==0)cp=this.rotatecp(cp,args.pos,args.rotate);if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1)cp=this.scale(cp,args.xscale,args.yscale);if(args.roundness>0)cp=this.roundc(cp,{roundness:args.roundness,invert:args.invert})}else{cp=this.sframe(args)}return cp};shape_object.prototype.points=function points(args){args=args||{};var path=false;if(args.pos===undefined)args.pos=[];if(args.rotate===undefined)args.rotate=0;if(args.roundness===undefined)args.roundness=0;if(args.invert===undefined)args.invert=false;if(args.scale===undefined)args.scale=1;if(args.xscale===undefined)args.xscale=1;if(args.yscale===undefined)args.yscale=1;if(args.path===undefined)path=args.path;var closed,cp;cp=args.pos;closed=cp[cp.length-1][0]===cp[0][0]&&cp[cp.length-1][1]===cp[0][1];if(!closed&&!path)cp.push(cp[0]);if(cp.length&&args.rotate!==0)cp=this.rotatecp(cp,cp[0],args.rotate);if(args.scale!==1)args.xscale=args.yscale=args.scale;if(args.xscale!==1||args.yscale!==1)cp=this.scale(cp,args.xscale,args.yscale);if(args.roundness>0)cp=this.roundc(cp,{roundness:args.roundness,invert:args.invert});return cp};shape_object.prototype.ToothOutline=function ToothOutline(args){args=args||{};if(args.n===undefined)args.n=30;if(args.res===undefined)args.res=1;if(args.phi===undefined)args.phi=20;if(args.radius===undefined)args.radius=50;if(args.addendum===undefined)args.addendum=.4;if(args.dedendum===undefined)args.dedendum=.5;if(args.fradius===undefined)args.fradius=.1;if(args.bevel===undefined)args.bevel=.05;var TOOTHGEO,R,DiametralPitch,ToothThickness,CircularPitch,U1,U2,ThetaA1,ThetaA2,ThetaA3;var A,pts,normals,i,Aw,r,u,xp,yp,auxth,m,rA,xc,yc,P0,Ra,th,N,P,V;TOOTHGEO={PitchRadius:args.radius,TeethN:args.n,PressureAng:args.phi,Addendum:args.addendum,Dedendum:args.dedendum,Fillet:args.fradius,Bevel:args.bevel,Resolution:args.res};R={Bottom:TOOTHGEO["PitchRadius"]-TOOTHGEO["Dedendum"]-TOOTHGEO["Fillet"],Ded:TOOTHGEO["PitchRadius"]-TOOTHGEO["Dedendum"],Base:TOOTHGEO["PitchRadius"]*cos(TOOTHGEO["PressureAng"]*Math.PI/180),Bevel:TOOTHGEO["PitchRadius"]+TOOTHGEO["Addendum"]-TOOTHGEO["Bevel"],Add:TOOTHGEO["PitchRadius"]+TOOTHGEO["Addendum"]};DiametralPitch=TOOTHGEO["TeethN"]/(2*TOOTHGEO["PitchRadius"]);ToothThickness=Math.PI/2/DiametralPitch;CircularPitch=Math.PI/DiametralPitch;U1=sqrt((1-Math.cos(TOOTHGEO["PressureAng"]*Math.PI/1800))/Math.cos(TOOTHGEO["PressureAng"]*Math.PI/180));U2=sqrt(R["Bevel"]*R["Bevel"]/(R["Ded"]*R["Ded"])-1);ThetaA1=Math.atan((sin(U1)-U1*Math.cos(U1))/(Math.cos(U1)+U1*Math.sin(U1)));ThetaA2=Math.atan((sin(U2)-U2*Math.cos(U2))/(Math.cos(U2)+U2*Math.sin(U2)));ThetaA3=ThetaA1+ToothThickness/(TOOTHGEO["PitchRadius"]*2);A={Theta0:CircularPitch/(TOOTHGEO["PitchRadius"]*2),Theta1:ThetaA3+TOOTHGEO["Fillet"]/R["Ded"],Theta2:ThetaA3,Theta3:ThetaA3-ThetaA2,Theta4:ThetaA3-ThetaA2-TOOTHGEO["Bevel"]/R["Add"]};N=TOOTHGEO["Resolution"];pts=[];normals=[];for(i=0;i<2*N;i++){th=(A["Theta1"]-A["Theta0"])*i/(2*N-1)+A["Theta0"];pts.push([R["Bottom"]*Math.cos(th),R["Bottom"]*Math.sin(th)]);normals.push([-Math.cos(th),-Math.sin(th)])}xc=R["Ded"]*Math.cos(A["Theta1"]);yc=R["Ded"]*Math.sin(A["Theta1"]);Aw=Math.PI/2+A["Theta2"]-A["Theta1"];for(i=0;iright)right=x;if(ytop)top=y}center=[(left+right)/2,(bottom+top)/2];dx=args.pos[0]-center[0];dy=args.pos[1]-center[1];gear2=[];for(i=0;i0;axis=up0.cross(args.up);p=[];for(i=0;i=16777216){R=Math.floor(N/16777216);N-=R*16777216}if(N>=65536){G=Math.floor(N/65536);N-=G*65536}if(N>=256){B=Math.floor(N/256);N-=B*256}return[R/255,G/255,B/255,N/255]}function RSdict_to_JSobjectliteral(args){return args}function makeTrail(obj,args){obj.__interval=-1;if(obj.constructor!=curve&&obj.constructor!=points&&args.make_trail!==undefined){obj.__make_trail=args.make_trail;delete args.make_trail;obj.__trail_type="curve";if(args.trail_type!==undefined){if(args.trail_type!="curve"&&args.trail_type!="points"&&args.trail_type!="spheres")throw new Error("trail_type = "+args.trail_type+" but must be 'curve' or 'points' (or 'spheres').");obj.__trail_type=args.trail_type;delete args.trail_type}if(args.interval!==undefined){obj.__interval=args.interval;delete args.interval}else if(obj.__trail_type!="curve"){obj.__interval=1}if(args.retain!==undefined){obj.__retain=args.retain;delete args.retain}else obj.__retain=-1;obj.__trail_color=color.white;if(obj.color!==undefined)obj.__trail_color=obj.color;if(args.trail_color!==undefined){obj.__trail_color=args.trail_color;delete args.trail_color}obj.__trail_radius=0;if(args.trail_radius!==undefined){obj.__trail_radius=args.trail_radius;delete args.trail_radius}else{if(obj.__trail_type=="points")obj.__trail_radius=.1*obj.__size.y}obj.__pps=0;if(args.pps!==undefined){if(obj.__interval>0){if(obj.__trail_type!="curve")throw new Error("pps cannot be used with a "+obj.__trail_type+"-type trail");else throw new Error("pps cannot be used with interval > 0")}obj.__pps=args.pps;delete args.pps}obj.__trail_object=attach_trail(obj,{type:obj.__trail_type,color:obj.__trail_color,radius:obj.__trail_radius,pps:obj.__pps,retain:obj.__retain});if(args.pos!==undefined&&obj.__make_trail)obj.__trail_object.__trail.push(args.pos);if(!obj.__make_trail)obj.__trail_object.stop();obj.__ninterval=0;obj._pos_set=args.pos!==undefined}}function init(obj,args){if(obj.constructor==text)return;if(window.__GSlang=="vpython"&&args.display!==undefined){args.canvas=args.display;delete args.display}if(args.canvas!==undefined){obj.canvas=args.canvas;delete args.canvas}else{obj.canvas=canvas.selected}if(obj.canvas){if(!(obj.constructor==distant_light||obj.constructor==local_light))obj.canvas.__activate();obj.__model=obj.__get_model()}if(args.__obj){obj.__obj=args.__obj;delete args.__obj}if(args.radius!==undefined){obj.radius=args.radius;delete args.radius}if(args.size_units!==undefined){obj.size_units=args.size_units;delete args.size_units}if(args.axis!==undefined){if(args.axis.mag2===0)obj.__oldaxis=vec(1,0,0);obj.axis=args.axis;delete args.axis}if(args.size!==undefined){obj.size=args.size;delete args.size}if(args.up!==undefined){if(args.up.mag2===0)obj.__oldup=vec(0,1,0);obj.up=args.up;delete args.up}if(args.color!==undefined){obj.color=args.color;delete args.color}if(args.make_trail!==undefined)makeTrail(obj,args);for(var id in args)obj[id]=args[id];if(args.visible===undefined&&obj.canvas!==null)obj.visible=true}function initObject(obj,constructor,args){if(!(obj instanceof constructor))return new constructor(args);args=args||{};obj.__tex={file:null,bumpmap:null,texture_ref:{reference:null},bumpmap_ref:{reference:null},left:false,right:false,sides:false,flipx:false,flipy:false,turn:0,flags:0};if(constructor==curve)obj.origin=obj.origin;if(constructor!=curve&&constructor!=points&&constructor!=text){obj.pos=obj.pos}if(constructor!=points&&constructor!=text){if(constructor==arrow){if(args.axis!==undefined)throw new Error("arrow does not have axis; replace with axis_and_length");else obj.axis_and_length=obj.axis_and_length}else if(constructor==vp_arrow&&args.axis_and_length!==undefined){throw new Error("VPython arrow does not have axis_and_length; replace with axis")}else obj.axis=obj.axis;obj.up=obj.up;obj.size=obj.size;obj.color=obj.color}obj.__sizing=false;if(window.__GSlang=="vpython"&&!(constructor==vp_sphere||constructor==vp_simple_sphere||constructor==vp_ring||constructor==text||constructor==vp_compound||constructor==compound))obj.__sizing=true;if(args.opacity===undefined)obj.__opacity=1;obj.__opacity_change=true;init(obj,args)}var nextVisibleId=1;var textures={flower:":flower_texture.jpg",granite:":granite_texture.jpg",gravel:":gravel_texture.jpg",earth:":earth_texture.jpg",metal:":metal_texture.jpg",rock:":rock_texture.jpg",rough:":rough_texture.jpg",rug:":rug_texture.jpg",stones:":stones_texture.jpg",stucco:":stucco_texture.jpg",wood:":wood_texture.jpg",wood_old:":wood_old_texture.jpg"};var bumpmaps={gravel:":gravel_bumpmap.jpg",rock:":rock_bumpmap.jpg",stones:":stones_bumpmap.jpg",stucco:":stucco_bumpmap.jpg",wood_old:":wood_old_bumpmap.jpg"};function setup_texture(name,obj,isbump){if(name.slice(0,1)==":"){var jv=window.Jupyter_VPython;if(jv!==undefined){name=jv+name.slice(1)}else{if(navigator.onLine)name="https://s3.amazonaws.com/glowscript/textures/"+name.slice(1);else name="../lib/FilesInAWS/"+name.slice(1)}}obj.canvas.__renderer.initTexture(name,obj,isbump)}function Primitive(){}property.declare(Primitive.prototype,{__id:null,__hasPosAtCenter:false,__deleted:false,__zx_camera:null,__zy_camera:null,__xmin:null,__ymin:null,__zmin:null,__xmax:null,__ymax:null,__zmax:null,pos:new attributeVectorPos(null,0,0,0),size:new attributeVector(null,1,1,1),axis:new attributeVectorAxis(null,1,0,0),up:new attributeVectorUp(null,0,1,0),color:new attributeVector(null,1,1,1),opacity:{get:function(){return this.__opacity},set:function(value){if(value==this.__opacity)return;if(this.__opacity<1&&value==1||this.__opacity==1&&value<1){this.__opacity_change=true}this.__opacity=value;this.__change()}},x:{get:function(){throw new Error('"object.x" is not supported; perhaps you meant "object.pos.x"')},set:function(value){throw new Error('"object.x" is not supported; perhaps you meant "object.pos.x"')}},y:{get:function(){throw new Error('"object.y" is not supported; perhaps you meant "object.pos.y"')},set:function(value){throw new Error('"object.y" is not supported; perhaps you meant "object.pos.y"')}},z:{get:function(){throw new Error('"object.z" is not supported; perhaps you meant "object.pos.z"')},set:function(value){throw new Error('"object.z" is not supported; perhaps you meant "object.pos.z"')}},__opacity_change:false,__prev_opacity:null,shininess:{value:.6,onchanged:function(){this.__change()}},emissive:{value:false,onchanged:function(){this.__change()}},pickable:{value:true,onchanged:function(){this.__change()}},ready:{get:function(){return this.__tex.file===null||this.__tex.texture_ref.reference!==null&&this.__tex.bumpmap===null||this.__tex.bumpmap_ref.reference!==null}},make_trail:{get:function(){return this.__make_trail},set:function(value){if(this.__make_trail!==value){if(value){this.__trail_object.start();this.__trail_object.__trail.push(this.__pos)}else this.__trail_object.stop();this.__make_trail=value}}},retain:{get:function(){return this.__retain},set:function(value){this.__retain=value;if(this.__trail_object!==undefined)this.__trail_object.retain=value}},trail_type:{get:function(){if(this.__trail_type=="curve")return"curve";else if(this.__trail_type=="spheres")return"points";else return this.__trail_type},set:function(value){throw new Error('"trail_type" cannot be changed.')}},trail_color:{get:function(){return this.__color},set:function(value){this.__trail_color=value;if(this.__trail_object!==undefined)this.__trail_object.color=value}},trail_radius:{get:function(){return this.__radius},set:function(value){this.__trail_radius=value;if(this.__trail_object!==undefined)this.__trail_object.radius=value}},pps:{get:function(){return this.__pps},set:function(value){this.__pps=value;if(this.__trail_object!==undefined)this.__trail_object.pps=value}},clear_trail:function(){if(this.__trail_object!==undefined)this.__trail_object.clear()},__update_trail:function(v){if(!this.__trail_object.__run||!this.visible)return;if(this.__interval===-1)return;this.__ninterval++;var update=false;if(this.__ninterval>=this.__interval){this.__ninterval=0;update=true}else if(this.__ninterval==1&&this.__trail_object.__trail.__points.length===0)update=true;if(update){if(this.__retain==-1)this.__trail_object.__trail.push({pos:v,color:this.__trail_color,radius:this.__trail_radius});else this.__trail_object.__trail.push({pos:v,color:this.__trail_color,radius:this.__trail_radius,retain:this.__retain})}},texture:{get:function(){return{file:this.__tex.file,bumpmap:this.__tex.bumpmap,left:this.__tex.left,right:this.__tex.right,sides:this.__tex.sides,flipx:this.__tex.flipx,flipy:this.__tex.flipy,turn:this.__tex.turn}},set:function(args){this.__tex={file:null,bumpmap:null,texture_ref:{reference:null},bumpmap_ref:{reference:null},left:false,right:false,sides:false,flipx:false,flipy:false,turn:0,flags:0};if(args===null){}else if(typeof args==="string"){this.__tex.left=this.__tex.right=this.__tex.sides=true;setup_texture(args,this,false)}else{args=RSdict_to_JSobjectliteral(args);if(args.file!==undefined&&typeof args.file==="string"){setup_texture(args.file,this,false)}else throw new Error("You must specify a file name for a texture.");if(args.bumpmap!==undefined){if(args.bumpmap!==null){if(typeof args.bumpmap!=="string")throw new Error("You must specify a file name for a bumpmap.");setup_texture(args.bumpmap,this,true)}}if(args.flipx!==undefined)this.__tex.flipx=args.flipx;if(args.flipy!==undefined)this.__tex.flipy=args.flipy;if(args.turn!==undefined)this.__tex.turn=Math.round(args.turn);if(args.place!==undefined){if(typeof args.place==="string")args.place=[args.place];for(var i=0;i3)throw new Error("object.rotate takes 1 to 3 arguments");var args={};for(var i=0;i1e-6){if(isarrow)obj.axis_and_length=obj.axis_and_length.rotate({angle:angle,axis:rotaxis});else obj.axis=obj.__axis.rotate({angle:angle,axis:rotaxis});obj.up=obj.__up.rotate({angle:angle,axis:rotaxis})}else{obj.up=obj.__up.rotate({angle:angle,axis:rotaxis})}window.__adjustupaxis=true},getTransformedMesh:function(){var X=this.__axis.norm();var Y=this.__up.norm();var Z=X.cross(Y);var T=this.__pos;if(this instanceof ring||this instanceof vp_ring){var m=Mesh.makeRing_compound(this.__size);var matrix=[X.x,X.y,X.z,0,Y.x,Y.y,Y.z,0,Z.x,Z.y,Z.z,0,T.x,T.y,T.z,1];return m.transformed(matrix)}else{X=X.multiply(this.__size.x);Y=Y.multiply(this.__size.y);Z=Z.multiply(this.__size.z);var matrix=[X.x,X.y,X.z,0,Y.x,Y.y,Y.z,0,Z.x,Z.y,Z.z,0,T.x,T.y,T.z,1];return this.__model.mesh.transformed(matrix)}}});function box(args){return initObject(this,box,args)}subclass(box,Primitive);box.prototype.__hasPosAtCenter=true;function cylinder(args){return initObject(this,cylinder,args)}subclass(cylinder,Primitive);function cone(args){return initObject(this,cone,args)}subclass(cone,cylinder);function pyramid(args){return initObject(this,pyramid,args)}subclass(pyramid,box);function sphere(args){return initObject(this,sphere,args)}subclass(sphere,Primitive);sphere.prototype.__hasPosAtCenter=true;function simple_sphere(args){return initObject(this,simple_sphere,args)}subclass(simple_sphere,sphere);property.declare(simple_sphere.prototype,{radius:{get:function(){return this.__size.__y/2},set:function(value){this.size=vec(2*value,2*value,2*value);this.__change()}}});function vp_box(args){return initObject(this,vp_box,args)}subclass(vp_box,box);property.declare(vp_box.prototype,{size:new attributeVectorSize(null,1,1,1),length:{get:function(){return this.__size.__x},set:function(value){this.axis=this.__axis.norm().multiply(value);this.__change()}},height:{get:function(){return this.__size.__y},set:function(value){this.__size.__y=value;this.__change()}},width:{get:function(){return this.__size.__z},set:function(value){this.__size.__z=value;this.__change()}},red:{get:function(){return this.__color.__x},set:function(value){this.__color.__x=value;this.__change()}},green:{get:function(){return this.__color.__y},set:function(value){this.__color.__y=value;this.__change()}},blue:{get:function(){return this.__color.__z},set:function(value){this.__color.__z=value;this.__change()}}});function vp_pyramid(args){return initObject(this,vp_pyramid,args)}subclass(vp_pyramid,vp_box);function vp_sphere(args){return initObject(this,vp_sphere,args)}subclass(vp_sphere,sphere);property.declare(vp_sphere.prototype,{axis:new attributeVectorAxis(null,1,0,0),size:new attributeVector(null,2,2,2),radius:{get:function(){return this.__size.__y/2},set:function(value){this.size=vec(2*value,2*value,2*value);this.__change()}}});function vp_simple_sphere(args){return initObject(this,vp_simple_sphere,args)}subclass(vp_simple_sphere,simple_sphere);property.declare(vp_simple_sphere.prototype,{axis:new attributeVectorAxis(null,1,0,0),size:new attributeVector(null,2,2,2)});function vp_ellipsoid(args){return initObject(this,vp_ellipsoid,args)}subclass(vp_ellipsoid,vp_box);property.declare(vp_ellipsoid.prototype,{radius:{get:function(){throw new Error("An ellipsoid does not have a radius attribute.")},set:function(value){throw new Error("An ellipsoid does not have a radius attribute.")}}});function vp_cylinder(args){return initObject(this,vp_cylinder,args)}subclass(vp_cylinder,vp_box);property.declare(vp_cylinder.prototype,{size:new attributeVectorSize(null,1,2,2),radius:{get:function(){return this.__size.__y/2},set:function(value){this.__size.__y=this.__size.__z=2*value;this.__change()}}});function vp_cone(args){return initObject(this,vp_cone,args)}subclass(vp_cone,vp_cylinder);function arrow_update(obj,vp){var pos=obj.__pos;var color=obj.__color;var axis;if(vp)axis=obj.__axis;else axis=obj.__axis_and_length;var L=mag(axis);var A=axis.norm();var sw=obj.__shaftwidth||L*.1;var hw=obj.__headwidth||sw*2;var hl=obj.__headlength||sw*3;if(swL*.5){var scale=L*.5/hl;if(!obj.__shaftwidth)sw*=scale;if(!obj.__headwidth)hw*=scale;if(!obj.__headlength)hl*=scale}var components=obj.__components;if(!components){if(vp)components=obj.__components=[vp_box({canvas:obj.canvas,__obj:obj}),vp_pyramid({canvas:obj.canvas,__obj:obj})];else components=obj.__components=[box({canvas:obj.canvas,__obj:obj}),pyramid({canvas:obj.canvas,__obj:obj})]}var shaft=components[0];var tip=components[1];shaft.pos=pos.add(A.multiply(.5*(L-hl)));tip.pos=pos.add(A.multiply(L-hl));window.__adjustupaxis=false;shaft.axis=tip.axis=axis;shaft.up=tip.up=obj.__up;window.__adjustupaxis=true;shaft.size=vec(L-hl,sw,sw);tip.size=vec(hl,hw,hw);shaft.color=tip.color=obj.color;shaft.opacity=tip.opacity=obj.opacity;shaft.pickable=tip.pickable=obj.pickable;obj.size=vec(L,hw,hw);shaft.__update();tip.__update()}function arrow(args){if(!(this instanceof arrow))return new arrow(args);this.__shaftwidth=0;this.__headwidth=0;this.__headlength=0;return initObject(this,arrow,args)}subclass(arrow,box);property.declare(arrow.prototype,{__primitiveCount:2,shaftwidth:{get:function(){return this.__shaftwidth},set:function(value){this.__shaftwidth=value;this.__change()}},headwidth:{get:function(){return this.__headwidth},set:function(value){this.__headwidth=value;this.__change()}},headlength:{get:function(){return this.__headlength},set:function(value){this.__headlength=value;this.__change()}},axis_and_length:new attributeVectorAxis(null,1,0,0),axis:{get:function(){new Error("arrow has an axis_and_length attribute but no axis attribute")},set:function(value){new Error("arrow has an axis_and_length attribute but no axis attribute")}},__change:function(){if(this.__components){this.__components[0].__change();this.__components[1].__change()}},__update:function(){arrow_update(this,false)},__get_extent:function(ext){if(!this.__components)this.__update();Autoscale.find_extent(this.__components[0],ext);Autoscale.find_extent(this.__components[1],ext)}});function vp_arrow(args){if(!(this instanceof vp_arrow))return new vp_arrow(args);this.__shaftwidth=0;this.__headwidth=0;this.__headlength=0;return initObject(this,vp_arrow,args)}subclass(vp_arrow,arrow);property.declare(vp_arrow.prototype,{axis:new attributeVectorAxis(null,1,0,0),axis_and_length:{get:function(){new Error("arrow has an axis attribute but no axis_and_length attribute")},set:function(value){new Error("arrow has an axis attribute but no axis_and_length attribute")}},length:{get:function(){return mag(this.__axis)},set:function(val){this.axis=this.__axis.norm().multiply(val)}},__update:function(){arrow_update(this,true)}});function text(args){if(!(this instanceof text))return new text(args);args=args||{};if(args.canvas===null)return;if(args.text===undefined||args.text.length===0)throw new Error("A text object needs non-empty text.");if(args.length!==undefined)throw new Error("The length cannot be specified when constructing 3D text.");if(args.size!==undefined)throw new Error("A text object does not have a size attribute.");args.text=print_to_string(args.text);this.pos=this.pos;this.axis=this.axis;this.up=this.up;this.color=this.color;var cloning=args.__comp!==undefined;if(!cloning){var ret=text3D(args);this.__id=nextVisibleId;nextVisibleId++;this.__comp=ret[0];args=ret[1]}else{this.__id=nextVisibleId;nextVisibleId++}for(var attr in args)this[attr]=args[attr];if(cloning)this.__comp.__pos=this.__pos.add(this.__offset);this.__pseudosize=vec(this.__comp.size);this.__height_fraction=this.__height/this.__comp.size.y;this.__descender_fraction=this.__descender/this.__comp.size.y;args={};initObject(this,text,{});if(this.__billboard){this.canvas.update_billboards=true;this.canvas.billboards.push(this)}if(!cloning){this.__offset=this.__comp.__pos;this.__offset.x/=this.length;this.__offset.y/=this.height;this.__offset.z/=this.depth}}subclass(text,Primitive);property.declare(text.prototype,{size:{get:function(){throw new Error("A text object does not have a size attribute.")},set:function(value){throw new Error("A text object does not have a size attribute.")}},visible:{get:function(){return this.__comp.visible},set:function(value){this.__comp.visible=value}},__update:function(){window.__adjustupaxis=false;this.__comp.axis=this.axis;this.__comp.up=this.up;window.__adjustupaxis=true;var yheight=this.__pseudosize.y;this.__comp.size=vec(this.length,yheight,Math.abs(this.depth));this.__comp.color=this.color;var dz=cross(this.__axis,this.__up).norm();this.__comp.pos=this.__pos.add(this.__axis.norm().multiply(this.__offset.x*this.length)).add(this.__up.norm().multiply(this.__offset.y*this.height)).add(dz.multiply(this.__offset.z*this.depth))},length:{get:function(){return this.__pseudosize.x},set:function(value){if(value===this.__pseudosize.x)return;this.__pseudosize.x=value;this.__change()}},height:{get:function(){return this.__height_fraction*this.__pseudosize.y},set:function(value){this.__pseudosize.y=value/this.__height_fraction;this.__change()}},depth:{get:function(){if(this.__depth>=0)return this.__pseudosize.z;else return-this.__pseudosize.z},set:function(value){var d=this.__depth;if(Math.abs(value)<.01*d){if(value<0)value=-.01*d;else value=.01*d}this.__depth=value;this.__size.z=Math.abs(value);this.__change()}},descender:{get:function(){return this.__descender_fraction*this.__pseudosize.y},set:function(value){throw new Error("descender is read-only")}},opacity:{get:function(){return this.__opacity},set:function(value){this.__comp.opacity=this.__opacity=value;this.__change()}},shininess:{get:function(){return this.__shininess},set:function(value){this.__shininess=this.__comp.shininess=value;this.__change()}},emissive:{get:function(){return this.__emissive},set:function(value){this.__comp.emissive=this.__emissive=value;this.__change()}},texture:{get:function(){throw new Error("Cannot currently apply a texture to a text object")},set:function(value){throw new Error("Cannot currently apply a texture to a text object")}},text:{get:function(){return this.__text},set:function(value){throw new Error("text is read-only")}},font:{get:function(){return this.__font},set:function(value){throw new Error("font is read-only")}},align:{get:function(){return this.__align},set:function(value){throw new Error("align is read-only")}},billboard:{get:function(){return this.__billboard},set:function(value){throw new Error("billboard is read-only")}},show_start_face:{get:function(){return this.__show_start_face},set:function(value){throw new Error("show_start_face is read-only")}},show_end_face:{get:function(){return this.__show_end_face},set:function(value){throw new Error("show_end_face is read-only")}},start_face_color:{get:function(){return this.__start_face_color},set:function(value){throw new Error("start_face_color is read-only")}},end_face_color:{get:function(){return this.__end_face_color},set:function(value){throw new Error("end_face_color is read-only")}},start:{get:function(){return this.upper_left.sub(this.up.norm().multiply(this.height))},set:function(value){throw new Error("start is read-only")}},end:{get:function(){return this.upper_right.sub(this.up.norm().multiply(this.height))},set:function(value){throw new Error("end is read-only")}},vertical_spacing:{get:function(){return 1.5*this.height},set:function(value){throw new Error("vertical_spacing is read-only")}},upper_left:{get:function(){var dx=0;if(this.__align=="right")dx=-this.length;else if(this.__align=="center")dx=-this.length/2;return this.pos.add(this.up.norm().multiply(this.height)).add(this.axis.norm().multiply(dx))},set:function(value){throw new Error("upper_left is read-only")}},upper_right:{get:function(){return this.upper_left.add(this.axis.norm().multiply(this.length))},set:function(value){throw new Error("upper_right is read-only")}},lower_left:{get:function(){return this.upper_left.add(this.up.norm().multiply(-this.height-this.descender-1.5*this.height*(this.__lines-1)))},set:function(value){throw new Error("lower_left is read-only")}},lower_right:{get:function(){return this.lower_left.add(this.axis.norm().multiply(this.length))},set:function(value){throw new Error("lower_right is read-only")}},lines:{get:function(){return this.__lines},set:function(value){throw new Error("lines is read-only")}}});function vertex(args){if(!(this instanceof vertex)){return new vertex(args)}args=args||{};if(args.canvas!==undefined){this.canvas=args.canvas}else if(args.display!==undefined){this.canvas=args.display}else{this.canvas=canvas.selected}for(var attr in args){if(attr=="canvas"||attr=="display")continue;this[attr]=args[attr]}if(this.opacity===undefined)this.opacity=1;if(this.__texpos.z!==0)throw new Error("In a vertex the z component of texpos must be zero.");if(this.canvas.vertex_id>=65536)throw new Error("Currently the number of vertices is limited to 65536.");var lengths={pos:3,normal:3,color:3,opacity:1,shininess:1,emissive:1,texpos:2,bumpaxis:3};this.__id=this.canvas.__vertices.available.pop();if(this.__id===undefined){this.__id=this.canvas.vertex_id;var c=this.canvas.__vertices;if(this.canvas.vertex_id%c.Nalloc===0){var temp;var L=this.canvas.vertex_id+c.Nalloc;for(var t in lengths){temp=new Float32Array(lengths[t]*L);temp.set(c[t],0);c[t]=temp}}this.canvas.vertex_id++}this.canvas.__vertices.object_info[this.__id]={};this.__change()}property.declare(vertex.prototype,{__id:null,__hasPosAtCenter:true,pos:new attributeVector(null,0,0,0),normal:new attributeVector(null,0,0,1),color:new attributeVector(null,1,1,1),opacity:{get:function(){return this.__opacity},set:function(value){if(value==this.__opacity)return;if(this.__opacity<1&&value==1||this.__opacity==1&&value<1){var users=this.canvas.__vertices.object_info[this.__id];for(var u in users){users[u].__change();users[u].__opacity_change=true}}this.__opacity=value;this.canvas.__vertex_changed[this.__id]=this}},texpos:new attributeVector(null,0,0,0),bumpaxis:new attributeVector(null,1,0,0),shininess:{value:.6,onchanged:function(){this.__change()}},emissive:{value:false,onchanged:function(){this.__change()}},__change:function(){if(this.__id){this.canvas.__vertex_changed[this.__id]=this;if(this.canvas.__autoscale){var users=this.canvas.__vertices.object_info[this.__id];for(var u in users)users[u].__change()}}},rotate:function(args){if(args.angle===undefined){throw new Error("vertex.rotate() requires angle:...")}var angle=args.angle;if(args.axis===undefined){throw new Error("vertex.rotate() requires axis:...")}var axis=args.axis.norm();var origin;if(args.origin===undefined){origin=vec(0,0,0)}else origin=args.origin;this.pos=origin.add(this.__pos.sub(origin).rotate({angle:angle,axis:axis}));this.__change()}});function tri_quad_error(object_type,attribute){throw new Error("A "+object_type+" has no "+attribute+" attribute.")}function triangle(args){if(!(this instanceof triangle))return new triangle(args);args=args||{};var vnames=["v0","v1","v2"];if(args.vs===undefined){for(var i=0;i<3;i++)if(args[vnames[i]]===undefined)throw new Error("A triangle must have a vertex "+vnames[i]+".")}init(this,args);var vtemp=this.vs;for(var i=0;i<3;i++)this.canvas.__vertices.object_info[vtemp[i].__id][this.__id]=this}subclass(triangle,box);property.declare(triangle.prototype,{v0:{get:function(){return this.__v0},set:function(value){if(!(value instanceof vertex))throw new Error("v0 must be a vertex object.");this.__v0=value;this.__change()}},v1:{get:function(){return this.__v1},set:function(value){if(!(value instanceof vertex))throw new Error("v1 must be a vertex object.");this.__v1=value;this.__change()}},v2:{get:function(){return this.__v2},set:function(value){if(!(value instanceof vertex))throw new Error("v2 must be a vertex object.");this.__v2=value;this.__change()}},vs:{get:function(){return[this.__v0,this.__v1,this.__v2]},set:function(value){if(toType(value)!="array"||value.length!=3)throw new Error("triangle.vs must be a list of 3 vertex objects.");for(var i=0;i<3;i++)if(!(value[i]instanceof vertex))throw new Error("triangle.vs must contain vertex objects.");this.__v0=value[0];this.__v1=value[1];this.__v2=value[2];this.__change()}},pos:{get:function(){tri_quad_error("triangle","pos")},set:function(value){tri_quad_error("triangle","pos")}},color:{get:function(){tri_quad_error("triangle","color")},set:function(value){tri_quad_error("triangle","color")}},size:{get:function(){tri_quad_error("triangle","size")},set:function(value){tri_quad_error("triangle","size")}},axis:{get:function(){tri_quad_error("triangle","axis")},set:function(value){tri_quad_error("triangle","axis")}},up:{get:function(){tri_quad_error("triangle","up")},set:function(value){tri_quad_error("triangle","up")}},opacity:{get:function(){tri_quad_error("triangle","opacity")},set:function(value){tri_quad_error("triangle","opacity")}},shininess:{get:function(){tri_quad_error("triangle","shininess")},set:function(value){tri_quad_error("triangle","shininess")}},emissive:{get:function(){tri_quad_error("triangle","emissive")},set:function(value){tri_quad_error("triangle","emissive")}},__prev_texture:null,__prev_bumpmap:null,__update:function(){this.__model.id_object[this.__id]=this},__get_extent:function(ext){var vnames=["__v0","__v1","__v2"];for(var i=0;i<3;i++)ext.point_extent(this,this[vnames[i]].pos)},rotate:function(args){throw new Error("A triangle has no rotate method; rotate the vertices instead.")}});function quad(args){if(!(this instanceof quad))return new quad(args);args=args||{};var vnames=["v0","v1","v2","v3"];if(args.vs===undefined){for(var i=0;i<4;i++)if(args[vnames[i]]===undefined)throw new Error("A quad must have a vertex "+vnames[i]+".")}this.__tex={file:null,bumpmap:null,texture_ref:{reference:null},bumpmap_ref:{reference:null},left:false,right:false,sides:false,flipx:false,flipy:false,turn:0,flags:0};init(this,args);var vtemp=this.vs;for(var i=0;i<4;i++)this.canvas.__vertices.object_info[vtemp[i].__id][this.__id]=this}subclass(quad,box);property.declare(quad.prototype,{v0:{get:function(){return this.__v0},set:function(value){if(!(value instanceof vertex))throw new Error("v0 must be a vertex object.");this.__v0=value;this.__change()}},v1:{get:function(){return this.__v1},set:function(value){if(!(value instanceof vertex))throw new Error("v1 must be a vertex object.");this.__v1=value;this.__change()}},v2:{get:function(){return this.__v2},set:function(value){if(!(value instanceof vertex))throw new Error("v2 must be a vertex object.");this.__v2=value;this.__change()}},v3:{get:function(){return this.__v3},set:function(value){if(!(value instanceof vertex))throw new Error("v3 must be a vertex object.");this.__v3=value;this.__change()}},vs:{get:function(){return[this.__v0,this.__v1,this.__v2,this.__v3]},set:function(value){if(toType(value)!="array"||value.length!=4)throw new Error("quad.vs must be a list of 4 vertex objects.");for(var i=0;i<4;i++)if(!(value[i]instanceof vertex))throw new Error("quad.vs must contain vertex objects.");this.__v0=value[0];this.__v1=value[1];this.__v2=value[2];this.__v3=value[3];this.__change()}},pos:{get:function(){tri_quad_error("quad","pos")},set:function(value){tri_quad_error("quad","pos")}},color:{get:function(){tri_quad_error("quad","color")},set:function(value){tri_quad_error("quad","color")}},size:{get:function(){tri_quad_error("quad","size")},set:function(value){tri_quad_error("quad","size")}},axis:{get:function(){tri_quad_error("quad","axis")},set:function(value){tri_quad_error("quad","axis")}},up:{get:function(){tri_quad_error("quad","up")},set:function(value){tri_quad_error("quad","up")}},opacity:{get:function(){tri_quad_error("quad","opacity")},set:function(value){tri_quad_error("quad","opacity")}},shininess:{get:function(){tri_quad_error("quad","shininess")},set:function(value){tri_quad_error("quad","shininess")}},__prev_texture:null,__prev_bumpmap:null,__update:function(){this.__model.id_object[this.__id]=this},__get_extent:function(ext){var vnames=["__v0","__v1","__v2","__v3"];for(var i=0;i<4;i++)ext.point_extent(this,this[vnames[i]].pos)},rotate:function(args){throw new Error("A quad has no rotate method; rotate the vertices instead.")}});var compound_id=0;function make_compound(objects,parameters){function update_extent(c,extent){if(extent.__xmin===null)return;for(var ext in extent){var value=extent[ext];if(ext.slice(-3)=="min"){if(c[ext]===null||valuec[ext])c[ext]=value}}}function compound_error(n){if(n>0)throw new Error("Compounding objects 0 through "+n+" in the list exceeds the vertex limit.");else throw new Error("The first object in the list exceeds the vertex limit")}var self=parameters.self;delete parameters.self;if(parameters.origin!==undefined){this.__origin=parameters.origin;delete parameters.origin}var mesh=new Mesh;var release=[];var temp;for(var i=0;i0)this.__push_and_append(pos,{})}}subclass(curve,Primitive);property.declare(curve.prototype,{origin:new attributeVectorPos(null,0,0,0),pos:{get:function(){throw new Error("Use methods to read curve or points pos attribute.")},set:function(value){throw new Error("Use methods to change curve or points pos attribute.")}},radius:{get:function(){return this.__radius},set:function(value){this.__radius=value;this.__change()}},retain:{get:function(){return this.__retain},set:function(value){this.__retain=value;this.__change()}},npoints:{get:function(){return this.__points.length},set:function(value){throw new Error("Cannot change curve or points npoints.")}},__no_autoscale:false,__get_extent:function(ext){if(this.__no_autoscale)return;var xmin=null,ymin=null,zmin=null,xmax=null,ymax=null,zmax=null;var length=this.__points.length;var pnt=this.__points;var org=this.__origin;var p;for(var i=0;ixmax)xmax=p.x+org.x;if(ymax===null||p.y+org.y>ymax)ymax=p.y+org.y;if(zmax===null||p.z+org.z>zmax)zmax=p.z+org.z}var center=vec((xmin+xmax)/2,(ymin+ymax)/2,(zmin+zmax)/2);var pseudosize=vec(xmax-xmin,ymax-ymin,zmax-zmin);var savepos=this.__pos,savesize=this.__size;this.__pos=center;this.__size=pseudosize;this.__hasPosAtCenter=true;ext.xmin=xmin;ext.xmax=xmax;ext.ymin=ymin;ext.ymax=ymax;ext.zmin=zmin;ext.zmax=zmax;Autoscale.find_extent(this,ext);this.__pos=savepos;this.__size=savesize},__setup:function(iargs){var pos=[];var specs={};var haspos=false;if(iargs instanceof vec){haspos=true;pos=[iargs]}else if(iargs.length>1){haspos=true;for(var i=0;i-1&&this.__points.length>=retain){var N=this.__points.length-retain;for(var d=0;d-1&&this.__points.length>=retain){var N=this.__points.length-retain;for(var d=0;d=0&&n0){this.__points[n-1].__nextsegment=p.__nextsegment;this.__points[n-1].__change()}if(n0)this.__push_and_append(pos,{});if(this.__pixels)this.canvas.__points_objects.push(this)}}subclass(points,curve);property.declare(points.prototype,{origin:{get:function(){throw new Error("The points object has no origin attribute.")},set:function(value){throw new Error("The points object has no origin attribute.")}},color:{get:function(){return this.__color},set:function(value){this.__color=value;if(!this.__pixels){for(var i=0;i=this.canvas.__height)var factor=2*this.canvas.__range/this.canvas.__height;else var factor=2*this.canvas.__range/this.canvas.__width;var viewMatrix=mat4.lookAt(camera.pos,camera.target,camera.up);var vnew=mat4.multiplyVec3(viewMatrix,vec3.create([this.pos.x,this.pos.y,this.pos.z]));if(vnew[2]>-camera.zNear||vnew[2]<-camera.zFar)return;var d=camera.distance;var k=-d/(vnew[2]*factor);posx=Math.round(k*vnew[0]+this.canvas.__width/2);posy=Math.round(-k*vnew[1]+this.canvas.__height/2)}var h=this.__height;var upperchar=.4*this.__height;var f=this.__font;if(this.__font=="sans")f="Arial";else if(this.__font=="serif")f="Georgia";ctx.textBaseline="middle";ctx.lineWidth=this.__linewidth;var default_color=vec(1,1,1);if(this.canvas.__background.equals(vec(1,1,1)))default_color=vec(0,0,0);ctx.strokeStyle=color.to_html(this.__linecolor||this.__color||default_color);var info=parse_html({ctx:ctx,text:print_to_string(this.__text),x:posx,y:posy,align:this.__align||"center",font:f,fontsize:h,color:this.__color||default_color});var nlines=info.lines.length;var width=info.maxwidth;var dh=1.3*this.__height;var height=this.__height+(nlines-1)*dh;var xbase;var ybase;var border=this.__border;if(xoffset||yoffset){if(Math.abs(yoffset)>Math.abs(xoffset)){if(yoffset>0){xbase=posx+xoffset-width/2;ybase=posy-yoffset-height-border+upperchar}else{xbase=posx+xoffset-width/2;ybase=posy-yoffset+border+upperchar}}else if(Math.abs(xoffset)>0){ybase=posy-yoffset-height/2+upperchar;if(xoffset>0){xbase=posx+xoffset+border}else if(xoffset<0){xbase=posx+xoffset-width-border}}}else{ybase=posy;switch(this.__align){case null:case"center":xbase=posx-width/2;break;case"right":xbase=posx-width;break;case"left":xbase=posx;break}}var bcolor;if(this.__background==null)bcolor=this.canvas.__background;else bcolor=this.__background;ctx.fillStyle=color.to_html_rgba(bcolor,this.__opacity);ctx.fillRect(xbase-border,ybase-upperchar-border,width+2*border,height+2*border);if((xoffset||yoffset)&&this.__line){ctx.beginPath();if(this.space>0){var v=vec(xoffset,-yoffset,0).norm().multiply(this.space);v=v.add(vec(posx,posy,0));ctx.moveTo(v.x,v.y)}else ctx.moveTo(posx,posy);ctx.lineTo(posx+xoffset,posy-yoffset);ctx.stroke()}if(this.__box){ctx.beginPath();ctx.moveTo(xbase-border,ybase-upperchar-border);ctx.lineTo(xbase+width+border,ybase-upperchar-border);ctx.lineTo(xbase+width+border,ybase-upperchar+height+border);ctx.lineTo(xbase-border,ybase-upperchar+height+border);ctx.closePath();ctx.stroke()}info.x=xbase;info.y=ybase;switch(this.__align){case null:case"center":info.x+=width/2;break;case"right":info.x+=width;break;case"left":info.x+=0;break}display_2D(info)},__change:function(){if(this.canvas!==undefined)this.canvas.__overlay_objects.__changed=true}});function attach_trail(objectOrFunction,options){if(!(this instanceof attach_trail))return new attach_trail(objectOrFunction,options);if(options===undefined)options={};this.__options={};this.__obj=objectOrFunction;if(options.canvas!==undefined)this.canvas=options.canvas;else this.canvas=canvas.selected;var radius=0;if(options.type===undefined){this.type="curve"}else{switch(options.type){case"curve":this.type=options.type;break;case"spheres":case"points":this.type="points";this.__options["size_units"]="world";break;default:throw new Error("attach_trail type must be 'curve' or 'points' (or 'spheres')")}}if(typeof objectOrFunction!=="function"&&typeof objectOrFunction!=="string"){this.canvas=objectOrFunction.canvas;this.__options["color"]=objectOrFunction.color;if(options.radius===undefined){if(this.type=="points")radius=.1*objectOrFunction.size.y}else radius=options.radius}else{if(options.radius!==undefined)radius=options.radius}this.__options["radius"]=this.__radius=radius;this.__options["canvas"]=this.canvas;if(options.color!==undefined){this.__options["color"]=options.color}else this.__options["color"]=vec(1,1,1);this.__options["retain"]=-1;if(options.retain!==undefined){this.__options["retain"]=options.retain}this.pps=0;if(options.pps!==undefined){this.pps=options.pps}this.__options["pickable"]=false;var send={};for(var a in this.__options)send[a]=this.__options[a];if(this.type=="curve")this.__trail=curve(send);else this.__trail=points(send);this.__trails=[this.__trail];this.canvas.trails.push(this);this.__last_pos=null;this.__last_time=null;this.__run=true;this.__elements=0}property.declare(attach_trail.prototype,{color:{get:function(){return this.__options.color},set:function(value){this.__options.color=value}},radius:{get:function(){return this.__options.radius},set:function(value){this.__options.radius=value}},pps:{get:function(){return this.__options.pps},set:function(value){this.__options.pps=value}},retain:{get:function(){return this.__options.retain},set:function(value){this.__options.retain=value}},start:function(){this.__run=true;var send={};for(var a in this.__options)send[a]=this.__options[a];if(this.type==="curve")this.__trail=curve(send);else this.__trail=points(send);this.__trails.push(this.__trail)},stop:function(){this.__run=false},clear:function(){this.__last_pos=null;this.__last_time=null;this.__elements=0;for(var i=0;i').css({width:"16px",height:"16px"}).appendTo(attrs.pos).click(function(){attrs.checked=!attrs.checked;$(attrs.jradio).prop("checked",attrs.checked);attrs.bind(cradio)});$(" "+attrs.text+"").appendTo(attrs.pos);var cradio={get disabled(){return attrs.disabled},set disabled(value){attrs.disabled=value;$(attrs.jradio).attr("disabled",attrs.disabled);if(attrs.disabled)$("#"+attrs._id).css({color:rgb_to_css(vec(.7,.7,.7))});else $("#"+attrs._id).css({color:rgb_to_css(vec(0,0,0))})},get checked(){return attrs.checked},set checked(value){attrs.checked=value;$(attrs.jradio).prop("checked",value)},get text(){return attrs.text},set text(value){attrs.text=value;$("#"+attrs._id).html(" "+value)},remove:function(){$(attrs.jradio).remove();$("#"+attrs._id).remove()}};for(var a in args){cradio[a]=args[a]}cradio.checked=attrs.checked;return cradio}function checkbox(args){if(!(this instanceof checkbox))return new checkbox(args);var cvs=canvas.get_selected();var attrs={pos:cvs.caption_anchor,checked:false,text:"",disabled:false};if(args.bind!==undefined){attrs.bind=args.bind;delete args.bind}else throw new Error("A checkbox must have a bind attribute.");for(a in attrs){if(args[a]!==undefined){attrs[a]=args[a];delete args[a]}}widgetid++;attrs._id=widgetid.toString();attrs.jcheckbox=$('').css({width:"16px",height:"16px"}).appendTo(attrs.pos).click(function(){attrs.checked=!attrs.checked;$(attrs.jcheckbox).prop("checked",attrs.checked);attrs.bind(ccheckbox)});$(" "+attrs.text+"").appendTo(attrs.pos);var ccheckbox={get disabled(){return attrs.disabled},set disabled(value){attrs.disabled=value;$(attrs.jcheckbox).attr("disabled",attrs.disabled);if(attrs.disabled)$("#"+attrs._id).css({color:rgb_to_css(vec(.7,.7,.7))});else $("#"+attrs._id).css({color:rgb_to_css(vec(0,0,0))})},get checked(){return attrs.checked},set checked(value){attrs.checked=value;$(attrs.jcheckbox).prop("checked",value)},get text(){return attrs.text},set text(value){attrs.text=value;$("#"+attrs._id).html(" "+value)},remove:function(){$(attrs.jcheckbox).remove();$("#"+attrs._id).remove()}};for(var a in args){ccheckbox[a]=args[a]}ccheckbox.checked=attrs.checked;return ccheckbox}function wtext(args){if(!(this instanceof wtext))return new wtext(args);args=args||{};var cvs=canvas.get_selected();var attrs={pos:cvs.caption_anchor,text:""};for(var a in attrs){if(args[a]!==undefined){attrs[a]=args[a];delete args[a]}}widgetid++;attrs._id=widgetid.toString();$(""+print_to_string(attrs.text)+"").appendTo(attrs.pos);var cwtext={get text(){return attrs.text},set text(value){attrs.text=value;$("#"+attrs._id).html(print_to_string(value))},remove:function(){$("#"+attrs._id).remove()}};for(var a in args){cwtext[a]=args[a]}return cwtext}var mathfunctions=["e","E","abs","sqrt","sin","cos","tan","asin","acos","atan","atan2","exp","log","pow","pi","ceil","floor","round","random","factorial","combin","radians","degrees"];function winput(args){if(!(this instanceof winput))return new winput(args);args=args||{};var cvs=canvas.get_selected();var attrs={pos:cvs.caption_anchor,text:"",width:100,height:20,type:"numeric",number:null,disabled:false};if(args.bind!==undefined){attrs.bind=args.bind;delete args.bind}else throw new Error("A winput must have a bind attribute.");for(var a in attrs){if(args[a]!==undefined){attrs[a]=args[a];delete args[a]}}if(attrs.type!="numeric"&&attrs.type!="string")throw new Error('A winput type must be "numeric" or "string".');function checksafe(s){var name="";for(var i=0;i0){if(mathfunctions.indexOf(name)<0)return false;name=""}}}if(name.length>0&&mathfunctions.indexOf(name)<0)return false;return true}widgetid++;attrs._id=widgetid.toString();attrs.jwinput=$('').val(attrs.text).appendTo(attrs.pos).css({width:attrs.width.toString()+"px",height:attrs.height.toString()+"px","font-size":(attrs.height-5).toString()+"px","font-family":"sans-serif"}).keypress(function(k){if(k.keyCode==13){attrs.text=attrs.jwinput.val();attrs.number=null;if(attrs.type=="numeric"){var ok=false;if(checksafe(attrs.text)){try{attrs.number=Function('"use strict";return('+attrs.text+")")();ok=true}catch(err){ok=false}}if(!ok){attrs.number=null;alert("Numeric input error");return}}attrs.bind(cwinput)}});var cwinput={get disabled(){return attrs.disabled},set disabled(value){attrs.disabled=value;$(attrs.jwinput).attr("disabled",attrs.disabled);if(attrs.disabled)$("#"+attrs._id).css({color:rgb_to_css(vec(.7,.7,.7))});else $("#"+attrs._id).css({color:rgb_to_css(vec(0,0,0))})},get text(){return attrs.text},set text(value){attrs.text=value;$("#"+attrs._id).val(value)},get number(){return attrs.number},remove:function(){$(attrs.jwinput).remove()}};for(var a in args){cwinput[a]=args[a]}return cwinput}function button(args){if(!(this instanceof button))return new button(args);var cvs=canvas.get_selected();var attrs={pos:cvs.caption_anchor,text:" ",color:vec(0,0,0),background:vec(1,1,1),disabled:false};if(args.bind!==undefined){attrs.bind=args.bind;delete args.bind}else throw new Error("A button must have a bind attribute.");for(a in attrs){if(args[a]!==undefined){attrs[a]=args[a];delete args[a]}}attrs.jbutton=$("