├── .nojekyll ├── content ├── data │ ├── matplotlib.png │ ├── fasta-example.fasta │ ├── bar.vl.json │ ├── iris.csv │ └── Museums_in_DC.geojson ├── z3.ipynb ├── pyodide │ ├── renderers.ipynb │ ├── pyb2d │ │ ├── color_mixing.ipynb │ │ ├── newtons_cradle.ipynb │ │ ├── gauss_machine.ipynb │ │ ├── games │ │ │ ├── billiard.ipynb │ │ │ ├── rocket.ipynb │ │ │ ├── angry_shapes.ipynb │ │ │ └── goo.ipynb │ │ └── 0_tutorial.ipynb │ ├── plotly.ipynb │ ├── folium.ipynb │ ├── ipycanvas.ipynb │ ├── altair.ipynb │ ├── interactive-widgets.ipynb │ └── ipyleaflet.ipynb ├── javascript.ipynb ├── p5.ipynb └── python.ipynb ├── repl └── jupyter-lite.json ├── README.md ├── .github └── workflows │ └── deploy.yml ├── requirements.txt └── .gitignore /.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /content/data/matplotlib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philzook58/jupyterlite-demo/main/content/data/matplotlib.png -------------------------------------------------------------------------------- /repl/jupyter-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter-lite-schema-version": 0, 3 | "jupyter-config-data": { 4 | "disabledExtensions": [ 5 | "@jupyterlab/drawio-extension", 6 | "jupyterlab-kernel-spy", 7 | "jupyterlab-tour" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /content/data/fasta-example.fasta: -------------------------------------------------------------------------------- 1 | >SEQUENCE_1 2 | MTEITAAMVKELRESTGAGMMDCKNALSETNGDFDKAVQLLREKGLGKAAKKADRLAAEG 3 | LVSVKVSDDFTIAAMRPSYLSYEDLDMTFVENEYKALVAELEKENEERRRLKDPNKPEHK 4 | IPQFASRKQLSDAILKEAEEKIKEELKAQGKPEKIWDNIIPGKMNSFIADNSQLDSKLTL 5 | MGQFYVMDDKKTVEQVIAEKEKEFGGKIKIVEFICFEVGEGLEKKTEDFAAEVAAQL 6 | >SEQUENCE_2 7 | SATVSEINSETDFVAKNDQFIALTKDTTAHIQSNSLQSVEELHSSTINGVKFEEYLKSQI 8 | ATIGENLVVRRFATLKAGANGVVNGYIHTNGRVGVVIAAACDSAEVASKSRDLLRQICMH -------------------------------------------------------------------------------- /content/data/bar.vl.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "values": [ 4 | { 5 | "a": "A", 6 | "b": 28 7 | }, 8 | { 9 | "a": "B", 10 | "b": 55 11 | }, 12 | { 13 | "a": "C", 14 | "b": 43 15 | }, 16 | { 17 | "a": "D", 18 | "b": 91 19 | }, 20 | { 21 | "a": "E", 22 | "b": 81 23 | }, 24 | { 25 | "a": "F", 26 | "b": 53 27 | }, 28 | { 29 | "a": "G", 30 | "b": 19 31 | }, 32 | { 33 | "a": "H", 34 | "b": 87 35 | }, 36 | { 37 | "a": "I", 38 | "b": 52 39 | } 40 | ] 41 | }, 42 | "description": "A simple bar chart with embedded data.", 43 | "encoding": { 44 | "x": { 45 | "field": "a", 46 | "type": "ordinal" 47 | }, 48 | "y": { 49 | "field": "b", 50 | "type": "quantitative" 51 | } 52 | }, 53 | "mark": "bar" 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JupyterLite Demo 2 | 3 | [![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://jupyterlite.github.io/demo) 4 | 5 | JupyterLite deployed as a static site to GitHub Pages, for demo purposes. 6 | 7 | ## ✨ Try it in your browser ✨ 8 | 9 | ➡️ **https://jupyterlite.github.io/demo** 10 | 11 | ![github-pages](https://user-images.githubusercontent.com/591645/120649478-18258400-c47d-11eb-80e5-185e52ff2702.gif) 12 | 13 | ## Requirements 14 | 15 | JupyterLite is being tested against modern web browsers: 16 | 17 | - Firefox 90+ 18 | - Chromium 89+ 19 | 20 | ## Deploy your JupyterLite website on GitHub Pages 21 | 22 | Check out the guide on the JupyterLite documentation: https://jupyterlite.readthedocs.io/en/latest/quickstart/deploy.html 23 | 24 | ## Further Information and Updates 25 | 26 | For more info, keep an eye on the JupyterLite documentation: 27 | 28 | - How-to Guides: https://jupyterlite.readthedocs.io/en/latest/howto/index.html 29 | - Reference: https://jupyterlite.readthedocs.io/en/latest/reference/index.html 30 | -------------------------------------------------------------------------------- /content/z3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "\n", 8 | " wheels are starting to go into CI actions\n" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": { 15 | "vscode": { 16 | "languageId": "plaintext" 17 | } 18 | }, 19 | "outputs": [], 20 | "source": [ 21 | "import micropip\n", 22 | "await micropip.install(\"https://microsoft.github.io/z3guide/z3_solver-4.13.4.0-py3-none-pyodide_2024_0_wasm32.whl\")" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": { 29 | "vscode": { 30 | "languageId": "plaintext" 31 | } 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "import z3\n", 36 | "s = z3.Solver()\n", 37 | "x,y,z = z3.Reals(\"x y z\")\n", 38 | "s.add(x >= 4)\n", 39 | "s.check()\n", 40 | "s.model()" 41 | ] 42 | } 43 | ], 44 | "metadata": { 45 | "language_info": { 46 | "name": "python" 47 | } 48 | }, 49 | "nbformat": 4, 50 | "nbformat_minor": 2 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Setup Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.11' 21 | - name: Install the dependencies 22 | run: | 23 | python -m pip install -r requirements.txt 24 | - name: Build the JupyterLite site 25 | run: | 26 | cp README.md content 27 | jupyter lite build --contents content --output-dir dist 28 | - name: Upload artifact 29 | uses: actions/upload-pages-artifact@v3 30 | with: 31 | path: ./dist 32 | 33 | deploy: 34 | needs: build 35 | if: github.ref == 'refs/heads/main' 36 | permissions: 37 | pages: write 38 | id-token: write 39 | 40 | environment: 41 | name: github-pages 42 | url: ${{ steps.deployment.outputs.page_url }} 43 | 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Deploy to GitHub Pages 47 | id: deployment 48 | uses: actions/deploy-pages@v4 49 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Core modules (mandatory) 2 | jupyterlite-core==0.4.0 3 | jupyterlab~=4.2.4 4 | notebook~=7.2.1 5 | 6 | 7 | # Python kernel (optional) 8 | jupyterlite-pyodide-kernel==0.4.1 9 | 10 | # JavaScript kernel (optional) 11 | jupyterlite-javascript-kernel==0.3.0 12 | 13 | # Language support (optional) 14 | jupyterlab-language-pack-fr-FR 15 | jupyterlab-language-pack-zh-CN 16 | 17 | # P5 kernel (optional) 18 | jupyterlite-p5-kernel==0.1.0 19 | 20 | # JupyterLab: Fasta file renderer (optional) 21 | jupyterlab-fasta>=3.3.0,<4 22 | # JupyterLab: Geojson file renderer (optional) 23 | jupyterlab-geojson>=3.4.0,<4 24 | # JupyterLab: guided tour (optional) 25 | # TODO: re-enable after https://github.com/jupyterlab-contrib/jupyterlab-tour/issues/82 26 | # jupyterlab-tour 27 | # JupyterLab: dark theme 28 | jupyterlab-night 29 | # JupyterLab: Miami nights theme (optional) 30 | jupyterlab_miami_nights 31 | 32 | # Python: ipywidget library for Jupyter notebooks (optional) 33 | ipywidgets>=8.1.3,<9 34 | # Python: ipyevents library for Jupyter notebooks (optional) 35 | ipyevents>=2.0.1 36 | # Python: interative Matplotlib library for Jupyter notebooks (optional) 37 | ipympl>=0.8.2 38 | # Python: ipycanvas library for Jupyter notebooks (optional) 39 | ipycanvas>=0.9.1 40 | # Python: ipyleaflet library for Jupyter notebooks (optional) 41 | ipyleaflet 42 | 43 | # Python: plotting libraries (optional) 44 | plotly>=5,<6 45 | bqplot 46 | 47 | jupyterlab-open-url-parameter 48 | -------------------------------------------------------------------------------- /content/pyodide/renderers.ipynb: -------------------------------------------------------------------------------- 1 | {"metadata":{"language_info":{"codemirror_mode":{"name":"python","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.8"},"kernelspec":{"name":"python","display_name":"Pyolite","language":"python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# JupyterLab Renderers","metadata":{}},{"cell_type":"markdown","source":"## FASTA","metadata":{}},{"cell_type":"code","source":"def Fasta(data=''):\n bundle = {}\n bundle['application/vnd.fasta.fasta'] = data\n bundle['text/plain'] = data\n display(bundle, raw=True)\n\nFasta(\"\"\">SEQUENCE_1\nMTEITAAMVKELRESTGAGMMDCKNALSETNGDFDKAVQLLREKGLGKAAKKADRLAAEG\nLVSVKVSDDFTIAAMRPSYLSYEDLDMTFVENEYKALVAELEKENEERRRLKDPNKPEHK\nIPQFASRKQLSDAILKEAEEKIKEELKAQGKPEKIWDNIIPGKMNSFIADNSQLDSKLTL\nMGQFYVMDDKKTVEQVIAEKEKEFGGKIKIVEFICFEVGEGLEKKTEDFAAEVAAQL\n>SEQUENCE_2\nSATVSEINSETDFVAKNDQFIALTKDTTAHIQSNSLQSVEELHSSTINGVKFEEYLKSQI\nATIGENLVVRRFATLKAGANGVVNGYIHTNGRVGVVIAAACDSAEVASKSRDLLRQICMH\"\"\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## GeoJSON","metadata":{}},{"cell_type":"code","source":"def GeoJSON(data):\n bundle = {}\n bundle['application/geo+json'] = data\n bundle['text/plain'] = data\n display(bundle, raw=True)\n \nGeoJSON({\n \"type\": \"Feature\",\n \"geometry\": {\n \"type\": \"Point\",\n \"coordinates\": [125.6, 10.1]\n },\n \"properties\": {\n \"name\": \"Dinagat Islands\"\n }\n})","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | .yarn-packages/ 5 | *.egg-info/ 6 | .ipynb_checkpoints 7 | *.tsbuildinfo 8 | 9 | # Created by https://www.gitignore.io/api/python 10 | # Edit at https://www.gitignore.io/?templates=python 11 | 12 | ### Python ### 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | .spyproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # Mr Developer 94 | .mr.developer.cfg 95 | .project 96 | .pydevproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | .dmypy.json 104 | dmypy.json 105 | 106 | # Pyre type checker 107 | .pyre/ 108 | 109 | # OS X stuff 110 | *.DS_Store 111 | 112 | # End of https://www.gitignore.io/api/python 113 | 114 | # jupyterlite 115 | *.doit.db 116 | _output 117 | -------------------------------------------------------------------------------- /content/javascript.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "language_info": { 4 | "codemirror_mode": { 5 | "name": "javascript" 6 | }, 7 | "file_extension": ".js", 8 | "mimetype": "text/javascript", 9 | "name": "javascript", 10 | "nbconvert_exporter": "javascript", 11 | "pygments_lexer": "javascript", 12 | "version": "es2017" 13 | }, 14 | "kernelspec": { 15 | "name": "javascript", 16 | "display_name": "JavaScript", 17 | "language": "javascript" 18 | }, 19 | "toc-showcode": true 20 | }, 21 | "nbformat_minor": 4, 22 | "nbformat": 4, 23 | "cells": [ 24 | { 25 | "cell_type": "markdown", 26 | "source": "# JavaScript in `JupyterLite`\n\n![](https://jupyterlite.readthedocs.io/en/latest/_static/kernelspecs/javascript.svg)", 27 | "metadata": {} 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "source": "## Standard streams", 32 | "metadata": {} 33 | }, 34 | { 35 | "cell_type": "code", 36 | "source": "console.log('hello world')", 37 | "metadata": { 38 | "trusted": true 39 | }, 40 | "execution_count": null, 41 | "outputs": [] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "source": "console.error('error')", 46 | "metadata": { 47 | "trusted": true 48 | }, 49 | "execution_count": null, 50 | "outputs": [] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "source": "## JavaScript specific constructs", 55 | "metadata": {} 56 | }, 57 | { 58 | "cell_type": "code", 59 | "source": "const delay = 2000;\n\nsetTimeout(() => {\n console.log('done');\n}, delay);", 60 | "metadata": { 61 | "trusted": true 62 | }, 63 | "execution_count": null, 64 | "outputs": [] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "source": "var str = \"hello world\"\nstr.split('').forEach(c => {\n console.log(c)\n})", 69 | "metadata": { 70 | "trusted": true 71 | }, 72 | "execution_count": null, 73 | "outputs": [] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "source": "## Markdown cells", 78 | "metadata": {} 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "source": "Lorenz system of differential equations\n\n$$\n\\begin{aligned}\n\\dot{x} & = \\sigma(y-x) \\\\\n\\dot{y} & = \\rho x - y - xz \\\\\n\\dot{z} & = -\\beta z + xy\n\\end{aligned}\n$$\n", 83 | "metadata": {} 84 | } 85 | ] 86 | } -------------------------------------------------------------------------------- /content/pyodide/pyb2d/color_mixing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "if \"pyodide\" in sys.modules:\n", 11 | " import piplite\n", 12 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "from b2d.testbed import TestbedBase\n", 22 | "import random\n", 23 | "import numpy\n", 24 | "import b2d\n", 25 | "\n", 26 | "class ColorMixing(TestbedBase):\n", 27 | "\n", 28 | " name = \"ColorMixing\"\n", 29 | "\n", 30 | " def __init__(self, settings=None):\n", 31 | " super(ColorMixing, self).__init__(settings=settings)\n", 32 | " dimensions = [30, 30]\n", 33 | "\n", 34 | " # the outer box\n", 35 | " box_shape = b2d.ChainShape()\n", 36 | " box_shape.create_loop(\n", 37 | " [\n", 38 | " (0, 0),\n", 39 | " (0, dimensions[1]),\n", 40 | " (dimensions[0], dimensions[1]),\n", 41 | " (dimensions[0], 0),\n", 42 | " ]\n", 43 | " )\n", 44 | " box = self.world.create_static_body(position=(0, 0), shape=box_shape)\n", 45 | "\n", 46 | " fixtureA = b2d.fixture_def(\n", 47 | " shape=b2d.circle_shape(1), density=2.2, friction=0.2, restitution=0.5\n", 48 | " )\n", 49 | " body = self.world.create_dynamic_body(position=(13, 10), fixtures=fixtureA)\n", 50 | "\n", 51 | " pdef = b2d.particle_system_def(\n", 52 | " viscous_strength=0.9,\n", 53 | " spring_strength=0.0,\n", 54 | " damping_strength=0.5,\n", 55 | " pressure_strength=0.5,\n", 56 | " color_mixing_strength=0.008,\n", 57 | " density=2,\n", 58 | " )\n", 59 | " psystem = self.world.create_particle_system(pdef)\n", 60 | " psystem.radius = 0.3\n", 61 | " psystem.damping = 1.0\n", 62 | "\n", 63 | " colors = [\n", 64 | " (255, 0, 0, 255),\n", 65 | " (0, 255, 0, 255),\n", 66 | " (0, 0, 255, 255),\n", 67 | " (255, 255, 0, 255),\n", 68 | " ]\n", 69 | " posiitons = [(6, 10), (20, 10), (20, 20), (6, 20)]\n", 70 | " for color, pos in zip(colors, posiitons):\n", 71 | "\n", 72 | " shape = b2d.polygon_shape(box=(5, 5), center=pos, angle=0)\n", 73 | " pgDef = b2d.particle_group_def(\n", 74 | " flags=b2d.ParticleFlag.waterParticle\n", 75 | " | b2d.ParticleFlag.colorMixingParticle,\n", 76 | " # group_flags=b2d.ParticleGroupFlag.solidParticleGroup,\n", 77 | " shape=shape,\n", 78 | " strength=1.0,\n", 79 | " color=color,\n", 80 | " )\n", 81 | " group = psystem.create_particle_group(pgDef)\n", 82 | "\n" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 92 | "\n", 93 | "s = JupyterAsyncGui.Settings()\n", 94 | "s.resolution = [1000,500]\n", 95 | "s.scale = 8\n", 96 | "s.fps = 40\n", 97 | "\n", 98 | "tb = b2d.testbed.run(ColorMixing, backend=JupyterAsyncGui, gui_settings=s)" 99 | ] 100 | } 101 | ], 102 | "metadata": { 103 | "kernelspec": { 104 | "display_name": "Python 3 (ipykernel)", 105 | "language": "python", 106 | "name": "python3" 107 | }, 108 | "language_info": { 109 | "codemirror_mode": { 110 | "name": "ipython", 111 | "version": 3 112 | }, 113 | "file_extension": ".py", 114 | "mimetype": "text/x-python", 115 | "name": "python", 116 | "nbconvert_exporter": "python", 117 | "pygments_lexer": "ipython3", 118 | "version": "3.10.4" 119 | } 120 | }, 121 | "nbformat": 4, 122 | "nbformat_minor": 4 123 | } 124 | -------------------------------------------------------------------------------- /content/pyodide/pyb2d/newtons_cradle.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "82ae535a-a041-40f0-8c40-e689b894b0bc", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import sys\n", 11 | "if \"pyodide\" in sys.modules:\n", 12 | " import piplite\n", 13 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.0')" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "id": "7381ff66-7924-4216-b141-8f7b15ed038a", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "from b2d.testbed import TestbedBase\n", 24 | "import b2d\n", 25 | "\n", 26 | "\n", 27 | "class NewtonsCradle(TestbedBase):\n", 28 | "\n", 29 | " name = \"newton's cradle\"\n", 30 | "\n", 31 | " def __init__(self, settings=None):\n", 32 | " super(NewtonsCradle, self).__init__(settings=settings)\n", 33 | "\n", 34 | " # radius of the circles\n", 35 | " r = 1.0\n", 36 | " # length of the rope\n", 37 | " l = 10.0\n", 38 | " # how many balls\n", 39 | " n = 10\n", 40 | "\n", 41 | " offset = (l + r, 2 * r)\n", 42 | " dynamic_circles = []\n", 43 | " static_bodies = []\n", 44 | " for i in range(n):\n", 45 | " if i + 1 == n:\n", 46 | " position = (offset[0] + i * 2 * r + l, offset[1] + l)\n", 47 | " else:\n", 48 | " position = (offset[0] + i * 2 * r, offset[1])\n", 49 | "\n", 50 | " circle = self.world.create_dynamic_body(\n", 51 | " position=position,\n", 52 | " fixtures=b2d.fixture_def(\n", 53 | " shape=b2d.circle_shape(radius=r * 0.90),\n", 54 | " density=1.0,\n", 55 | " restitution=1.0,\n", 56 | " friction=0.0,\n", 57 | " ),\n", 58 | " linear_damping=0.01,\n", 59 | " angular_damping=1.0,\n", 60 | " fixed_rotation=True,\n", 61 | " )\n", 62 | " dynamic_circles.append(circle)\n", 63 | "\n", 64 | " static_body = self.world.create_static_body(\n", 65 | " position=(offset[0] + i * 2 * r, offset[1] + l)\n", 66 | " )\n", 67 | "\n", 68 | " self.world.create_distance_joint(\n", 69 | " static_body,\n", 70 | " circle,\n", 71 | " local_anchor_a=(0, 0),\n", 72 | " local_anchor_b=(0, 0),\n", 73 | " max_length=l,\n", 74 | " stiffness=0,\n", 75 | " )\n", 76 | "\n", 77 | " static_bodies.append(static_body)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 3, 83 | "id": "6f694d32-8b23-40cf-91c3-0edd6abb658d", 84 | "metadata": {}, 85 | "outputs": [ 86 | { 87 | "data": { 88 | "application/vnd.jupyter.widget-view+json": { 89 | "model_id": "b24ad88f6cfd4db19fc3145000e79635", 90 | "version_major": 2, 91 | "version_minor": 0 92 | }, 93 | "text/plain": [ 94 | "Output()" 95 | ] 96 | }, 97 | "metadata": {}, 98 | "output_type": "display_data" 99 | } 100 | ], 101 | "source": [ 102 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 103 | "s = JupyterAsyncGui.Settings()\n", 104 | "s.resolution = [1000,300]\n", 105 | "b2d.testbed.run(NewtonsCradle, backend=JupyterAsyncGui, gui_settings=s);" 106 | ] 107 | } 108 | ], 109 | "metadata": { 110 | "kernelspec": { 111 | "display_name": "Python 3 (ipykernel)", 112 | "language": "python", 113 | "name": "python3" 114 | }, 115 | "language_info": { 116 | "codemirror_mode": { 117 | "name": "ipython", 118 | "version": 3 119 | }, 120 | "file_extension": ".py", 121 | "mimetype": "text/x-python", 122 | "name": "python", 123 | "nbconvert_exporter": "python", 124 | "pygments_lexer": "ipython3", 125 | "version": "3.10.4" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 5 130 | } 131 | -------------------------------------------------------------------------------- /content/p5.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata":{ 3 | "kernelspec":{ 4 | "name":"p5js", 5 | "display_name":"p5.js", 6 | "language":"javascript" 7 | }, 8 | "language_info":{ 9 | "codemirror_mode":{ 10 | "name":"javascript" 11 | }, 12 | "file_extension":".js", 13 | "mimetype":"text/javascript", 14 | "name":"p5js", 15 | "nbconvert_exporter":"javascript", 16 | "pygments_lexer":"javascript", 17 | "version":"es2017" 18 | } 19 | }, 20 | "nbformat_minor":4, 21 | "nbformat":4, 22 | "cells":[ 23 | { 24 | "cell_type":"markdown", 25 | "source":"# p5 notebook\n\nA minimal Jupyter notebook UI for [p5.js](https://p5js.org) kernels.", 26 | "metadata":{ 27 | 28 | } 29 | }, 30 | { 31 | "cell_type":"markdown", 32 | "source":"First let's define a couple of variables:", 33 | "metadata":{ 34 | 35 | } 36 | }, 37 | { 38 | "cell_type":"code", 39 | "source":"var n = 4;\nvar speed = 1;", 40 | "metadata":{ 41 | "trusted":true 42 | }, 43 | "execution_count":null, 44 | "outputs":[ 45 | 46 | ] 47 | }, 48 | { 49 | "cell_type":"markdown", 50 | "source":"## The `setup` function\n\nThe usual p5 setup function, which creates the canvas.", 51 | "metadata":{ 52 | 53 | } 54 | }, 55 | { 56 | "cell_type":"code", 57 | "source":"function setup () {\n createCanvas(innerWidth, innerHeight);\n rectMode(CENTER);\n}", 58 | "metadata":{ 59 | "trusted":true 60 | }, 61 | "execution_count":null, 62 | "outputs":[ 63 | 64 | ] 65 | }, 66 | { 67 | "cell_type":"markdown", 68 | "source":"## The `draw` function\n\nFrom the [p5.js documentation](https://p5js.org/reference/#/p5/draw):\n\n> The `draw()` function continuously executes the lines of code contained inside its block until the program is stopped or `noLoop()` is called.", 69 | "metadata":{ 70 | 71 | } 72 | }, 73 | { 74 | "cell_type":"code", 75 | "source":"function draw() {\n background('#ddd');\n translate(innerWidth / 2, innerHeight / 2);\n for (let i = 0; i < n; i++) {\n push();\n rotate(frameCount * speed / 1000 * (i + 1));\n fill(i * 5, i * 100, i * 150);\n const s = 200 - i * 10;\n rect(0, 0, s, s);\n pop();\n }\n}", 76 | "metadata":{ 77 | "trusted":true 78 | }, 79 | "execution_count":null, 80 | "outputs":[ 81 | 82 | ] 83 | }, 84 | { 85 | "cell_type":"markdown", 86 | "source":"## Show the sketch\n\nNow let's show the sketch by using the `%show` magic:", 87 | "metadata":{ 88 | 89 | } 90 | }, 91 | { 92 | "cell_type":"code", 93 | "source":"%show", 94 | "metadata":{ 95 | "trusted":true 96 | }, 97 | "execution_count":null, 98 | "outputs":[ 99 | 100 | ] 101 | }, 102 | { 103 | "cell_type":"markdown", 104 | "source":"## Tweak the values\n\nWe can also tweak some values in real time:", 105 | "metadata":{ 106 | 107 | } 108 | }, 109 | { 110 | "cell_type":"code", 111 | "source":"speed = 3", 112 | "metadata":{ 113 | "trusted":true 114 | }, 115 | "execution_count":null, 116 | "outputs":[ 117 | 118 | ] 119 | }, 120 | { 121 | "cell_type":"code", 122 | "source":"n = 20", 123 | "metadata":{ 124 | "trusted":true 125 | }, 126 | "execution_count":null, 127 | "outputs":[ 128 | 129 | ] 130 | }, 131 | { 132 | "cell_type":"markdown", 133 | "source":"We can also show the sketch a second time taking into account the new values:", 134 | "metadata":{ 135 | 136 | } 137 | }, 138 | { 139 | "cell_type":"code", 140 | "source":"%show", 141 | "metadata":{ 142 | "trusted":true 143 | }, 144 | "execution_count":null, 145 | "outputs":[ 146 | 147 | ] 148 | } 149 | ] 150 | } -------------------------------------------------------------------------------- /content/pyodide/plotly.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Plotly in JupyterLite\n", 8 | "\n", 9 | "`plotly.py` is an interactive, open-source, and browser-based graphing library for Python: https://plotly.com/python/" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "%pip install -q nbformat plotly" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": { 24 | "tags": [] 25 | }, 26 | "source": [ 27 | "## Basic Figure" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "import plotly.graph_objects as go\n", 37 | "fig = go.Figure()\n", 38 | "fig.add_trace(go.Scatter(y=[2, 1, 4, 3]))\n", 39 | "fig.add_trace(go.Bar(y=[1, 4, 3, 2]))\n", 40 | "fig.update_layout(title = 'Hello Figure')\n", 41 | "fig.show()" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": { 47 | "tags": [] 48 | }, 49 | "source": [ 50 | "## Basic Table with a Pandas DataFrame" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "import plotly.graph_objects as go\n", 60 | "import pandas as pd\n", 61 | "\n", 62 | "from js import fetch\n", 63 | "\n", 64 | "URL = \"https://raw.githubusercontent.com/plotly/datasets/master/2014_usa_states.csv\"\n", 65 | "\n", 66 | "res = await fetch(URL)\n", 67 | "text = await res.text()\n", 68 | "\n", 69 | "filename = 'data.csv'\n", 70 | "\n", 71 | "with open(filename, 'w') as f:\n", 72 | " f.write(text)\n", 73 | "\n", 74 | "df = pd.read_csv(filename)\n", 75 | "\n", 76 | "fig = go.Figure(data=[go.Table(\n", 77 | " header=dict(values=list(df.columns),\n", 78 | " fill_color='paleturquoise',\n", 79 | " align='left'),\n", 80 | " cells=dict(values=[df.Rank, df.State, df.Postal, df.Population],\n", 81 | " fill_color='lavender',\n", 82 | " align='left'))\n", 83 | "])\n", 84 | "\n", 85 | "fig.show()" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "## Quiver Plot with Points" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "import plotly.figure_factory as ff\n", 102 | "import plotly.graph_objects as go\n", 103 | "\n", 104 | "import numpy as np\n", 105 | "\n", 106 | "x,y = np.meshgrid(np.arange(-2, 2, .2),\n", 107 | " np.arange(-2, 2, .25))\n", 108 | "z = x*np.exp(-x**2 - y**2)\n", 109 | "v, u = np.gradient(z, .2, .2)\n", 110 | "\n", 111 | "# Create quiver figure\n", 112 | "fig = ff.create_quiver(x, y, u, v,\n", 113 | " scale=.25,\n", 114 | " arrow_scale=.4,\n", 115 | " name='quiver',\n", 116 | " line_width=1)\n", 117 | "\n", 118 | "# Add points to figure\n", 119 | "fig.add_trace(go.Scatter(x=[-.7, .75], y=[0,0],\n", 120 | " mode='markers',\n", 121 | " marker_size=12,\n", 122 | " name='points'))\n", 123 | "\n", 124 | "fig.show()" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [] 133 | } 134 | ], 135 | "metadata": { 136 | "kernelspec": { 137 | "display_name": "Python (Pyodide)", 138 | "language": "python", 139 | "name": "python" 140 | }, 141 | "language_info": { 142 | "codemirror_mode": { 143 | "name": "python", 144 | "version": 3 145 | }, 146 | "file_extension": ".py", 147 | "mimetype": "text/x-python", 148 | "name": "python", 149 | "nbconvert_exporter": "python", 150 | "pygments_lexer": "ipython3", 151 | "version": "3.8" 152 | }, 153 | "orig_nbformat": 4, 154 | "toc-showcode": false 155 | }, 156 | "nbformat": 4, 157 | "nbformat_minor": 4 158 | } 159 | -------------------------------------------------------------------------------- /content/data/iris.csv: -------------------------------------------------------------------------------- 1 | sepal_length,sepal_width,petal_length,petal_width,species 2 | 5.1,3.5,1.4,0.2,se 3 | 4.9,3,1.4,0.2,setosa 4 | 4.7,3.2,1.3,0.2,setosa 5 | 4.6,3.1,1.5,0.2,setosa 6 | 5,3.6,1.4,0.2,setosa 7 | 5.4,3.9,1.7,0.4,setosa 8 | 4.6,3.4,1.4,0.3,setosa 9 | 5,3.4,1.5,0.2,setosa 10 | 4.4,2.9,1.4,0.2,setosa 11 | 4.9,3.1,1.5,0.1,setosa 12 | 5.4,3.7,1.5,0.2,setosa 13 | 4.8,3.4,1.6,0.2,setosa 14 | 4.8,3,1.4,0.1,setosa 15 | 4.3,3,1.1,0.1,setosa 16 | 5.8,4,1.2,0.2,setosa 17 | 5.7,4.4,1.5,0.4,setosa 18 | 5.4,3.9,1.3,0.4,setosa 19 | 5.1,3.5,1.4,0.3,setosa 20 | 5.7,3.8,1.7,0.3,setosa 21 | 5.1,3.8,1.5,0.3,setosa 22 | 5.4,3.4,1.7,0.2,setosa 23 | 5.1,3.7,1.5,0.4,setosa 24 | 4.6,3.6,1,0.2,setosa 25 | 5.1,3.3,1.7,0.5,setosa 26 | 4.8,3.4,1.9,0.2,setosa 27 | 5,3,1.6,0.2,setosa 28 | 5,3.4,1.6,0.4,setosa 29 | 5.2,3.5,1.5,0.2,setosa 30 | 5.2,3.4,1.4,0.2,setosa 31 | 4.7,3.2,1.6,0.2,setosa 32 | 4.8,3.1,1.6,0.2,setosa 33 | 5.4,3.4,1.5,0.4,setosa 34 | 5.2,4.1,1.5,0.1,setosa 35 | 5.5,4.2,1.4,0.2,setosa 36 | 4.9,3.1,1.5,0.1,setosa 37 | 5,3.2,1.2,0.2,setosa 38 | 5.5,3.5,1.3,0.2,setosa 39 | 4.9,3.1,1.5,0.1,setosa 40 | 4.4,3,1.3,0.2,setosa 41 | 5.1,3.4,1.5,0.2,setosa 42 | 5,3.5,1.3,0.3,setosa 43 | 4.5,2.3,1.3,0.3,setosa 44 | 4.4,3.2,1.3,0.2,setosa 45 | 5,3.5,1.6,0.6,setosa 46 | 5.1,3.8,1.9,0.4,setosa 47 | 4.8,3,1.4,0.3,setosa 48 | 5.1,3.8,1.6,0.2,setosa 49 | 4.6,3.2,1.4,0.2,setosa 50 | 5.3,3.7,1.5,0.2,setosa 51 | 5,3.3,1.4,0.2,setosa 52 | 7,3.2,4.7,1.4,versicolor 53 | 6.4,3.2,4.5,1.5,versicolor 54 | 6.9,3.1,4.9,1.5,versicolor 55 | 5.5,2.3,4,1.3,versicolor 56 | 6.5,2.8,4.6,1.5,versicolor 57 | 5.7,2.8,4.5,1.3,versicolor 58 | 6.3,3.3,4.7,1.6,versicolor 59 | 4.9,2.4,3.3,1,versicolor 60 | 6.6,2.9,4.6,1.3,versicolor 61 | 5.2,2.7,3.9,1.4,versicolor 62 | 5,2,3.5,1,versicolor 63 | 5.9,3,4.2,1.5,versicolor 64 | 6,2.2,4,1,versicolor 65 | 6.1,2.9,4.7,1.4,versicolor 66 | 5.6,2.9,3.6,1.3,versicolor 67 | 6.7,3.1,4.4,1.4,versicolor 68 | 5.6,3,4.5,1.5,versicolor 69 | 5.8,2.7,4.1,1,versicolor 70 | 6.2,2.2,4.5,1.5,versicolor 71 | 5.6,2.5,3.9,1.1,versicolor 72 | 5.9,3.2,4.8,1.8,versicolor 73 | 6.1,2.8,4,1.3,versicolor 74 | 6.3,2.5,4.9,1.5,versicolor 75 | 6.1,2.8,4.7,1.2,versicolor 76 | 6.4,2.9,4.3,1.3,versicolor 77 | 6.6,3,4.4,1.4,versicolor 78 | 6.8,2.8,4.8,1.4,versicolor 79 | 6.7,3,5,1.7,versicolor 80 | 6,2.9,4.5,1.5,versicolor 81 | 5.7,2.6,3.5,1,versicolor 82 | 5.5,2.4,3.8,1.1,versicolor 83 | 5.5,2.4,3.7,1,versicolor 84 | 5.8,2.7,3.9,1.2,versicolor 85 | 6,2.7,5.1,1.6,versicolor 86 | 5.4,3,4.5,1.5,versicolor 87 | 6,3.4,4.5,1.6,versicolor 88 | 6.7,3.1,4.7,1.5,versicolor 89 | 6.3,2.3,4.4,1.3,versicolor 90 | 5.6,3,4.1,1.3,versicolor 91 | 5.5,2.5,4,1.3,versicolor 92 | 5.5,2.6,4.4,1.2,versicolor 93 | 6.1,3,4.6,1.4,versicolor 94 | 5.8,2.6,4,1.2,versicolor 95 | 5,2.3,3.3,1,versicolor 96 | 5.6,2.7,4.2,1.3,versicolor 97 | 5.7,3,4.2,1.2,versicolor 98 | 5.7,2.9,4.2,1.3,versicolor 99 | 6.2,2.9,4.3,1.3,versicolor 100 | 5.1,2.5,3,1.1,versicolor 101 | 5.7,2.8,4.1,1.3,versicolor 102 | 6.3,3.3,6,2.5,virginica 103 | 5.8,2.7,5.1,1.9,virginica 104 | 7.1,3,5.9,2.1,virginica 105 | 6.3,2.9,5.6,1.8,virginica 106 | 6.5,3,5.8,2.2,virginica 107 | 7.6,3,6.6,2.1,virginica 108 | 4.9,2.5,4.5,1.7,virginica 109 | 7.3,2.9,6.3,1.8,virginica 110 | 6.7,2.5,5.8,1.8,virginica 111 | 7.2,3.6,6.1,2.5,virginica 112 | 6.5,3.2,5.1,2,virginica 113 | 6.4,2.7,5.3,1.9,virginica 114 | 6.8,3,5.5,2.1,virginica 115 | 5.7,2.5,5,2,virginica 116 | 5.8,2.8,5.1,2.4,virginica 117 | 6.4,3.2,5.3,2.3,virginica 118 | 6.5,3,5.5,1.8,virginica 119 | 7.7,3.8,6.7,2.2,virginica 120 | 7.7,2.6,6.9,2.3,virginica 121 | 6,2.2,5,1.5,virginica 122 | 6.9,3.2,5.7,2.3,virginica 123 | 5.6,2.8,4.9,2,virginica 124 | 7.7,2.8,6.7,2,virginica 125 | 6.3,2.7,4.9,1.8,virginica 126 | 6.7,3.3,5.7,2.1,virginica 127 | 7.2,3.2,6,1.8,virginica 128 | 6.2,2.8,4.8,1.8,virginica 129 | 6.1,3,4.9,1.8,virginica 130 | 6.4,2.8,5.6,2.1,virginica 131 | 7.2,3,5.8,1.6,virginica 132 | 7.4,2.8,6.1,1.9,virginica 133 | 7.9,3.8,6.4,2,virginica 134 | 6.4,2.8,5.6,2.2,virginica 135 | 6.3,2.8,5.1,1.5,virginica 136 | 6.1,2.6,5.6,1.4,virginica 137 | 7.7,3,6.1,2.3,virginica 138 | 6.3,3.4,5.6,2.4,virginica 139 | 6.4,3.1,5.5,1.8,virginica 140 | 6,3,4.8,1.8,virginica 141 | 6.9,3.1,5.4,2.1,virginica 142 | 6.7,3.1,5.6,2.4,virginica 143 | 6.9,3.1,5.1,2.3,virginica 144 | 5.8,2.7,5.1,1.9,virginica 145 | 6.8,3.2,5.9,2.3,virginica 146 | 6.7,3.3,5.7,2.5,virginica 147 | 6.7,3,5.2,2.3,virginica 148 | 6.3,2.5,5,1.9,virginica 149 | 6.5,3,5.2,2,virginica 150 | 6.2,3.4,5.4,2.3,virginica 151 | 5.9,3,5.1,1.8,virginica 152 | -------------------------------------------------------------------------------- /content/pyodide/pyb2d/gauss_machine.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "39a80aae-a990-4ed8-b880-3db2e7f70f16", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import sys\n", 11 | "if \"pyodide\" in sys.modules:\n", 12 | " import piplite\n", 13 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "id": "9d9f8d68-0f3b-49c1-9512-e3e8344e7342", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "from b2d.testbed import TestbedBase\n", 24 | "import random\n", 25 | "import numpy\n", 26 | "import b2d\n", 27 | "\n", 28 | "\n", 29 | "class GaussMachine(TestbedBase):\n", 30 | "\n", 31 | " name = \"Gauss Machine\"\n", 32 | "\n", 33 | " def __init__(self, settings=None):\n", 34 | " super(GaussMachine, self).__init__(settings=settings)\n", 35 | "\n", 36 | " self.box_shape = 30, 20\n", 37 | " box_shape = self.box_shape\n", 38 | "\n", 39 | " # outer box\n", 40 | " verts = numpy.array(\n", 41 | " [(0, box_shape[1]), (0, 0), (box_shape[0], 0), (box_shape[0], box_shape[1])]\n", 42 | " )\n", 43 | " shape = b2d.chain_shape(vertices=numpy.flip(verts, axis=0))\n", 44 | " box = self.world.create_static_body(position=(0, 0), shape=shape)\n", 45 | "\n", 46 | " # \"bins\"\n", 47 | " bin_height = box_shape[1] / 3\n", 48 | " bin_width = 1\n", 49 | " for x in range(0, box_shape[0], bin_width):\n", 50 | " box = self.world.create_static_body(\n", 51 | " position=(0, 0), shape=b2d.two_sided_edge_shape((x, 0), (x, bin_height))\n", 52 | " )\n", 53 | "\n", 54 | " # reflectors\n", 55 | " ref_start_y = int(bin_height + box_shape[1] / 10.0)\n", 56 | " ref_stop_y = int(box_shape[1] * 0.9)\n", 57 | " for x in range(0, box_shape[0] + 1):\n", 58 | "\n", 59 | " for y in range(ref_start_y, ref_stop_y):\n", 60 | " s = [0.5, 0][y % 2 == 0]\n", 61 | " shape = b2d.circle_shape(radius=0.3)\n", 62 | " box = self.world.create_static_body(position=(x + s, y), shape=shape)\n", 63 | "\n", 64 | " # particle system\n", 65 | " pdef = b2d.particle_system_def(\n", 66 | " viscous_strength=0.9,\n", 67 | " spring_strength=0.0,\n", 68 | " damping_strength=100.5,\n", 69 | " pressure_strength=1.0,\n", 70 | " color_mixing_strength=0.05,\n", 71 | " density=2,\n", 72 | " )\n", 73 | "\n", 74 | " psystem = self.world.create_particle_system(pdef)\n", 75 | " psystem.radius = 0.1\n", 76 | " psystem.damping = 0.5\n", 77 | "\n", 78 | " # linear emitter\n", 79 | " emitter_pos = (self.box_shape[0] / 2, self.box_shape[1] + 10)\n", 80 | " emitter_def = b2d.RandomizedLinearEmitterDef()\n", 81 | " emitter_def.emite_rate = 400\n", 82 | " emitter_def.lifetime = 25\n", 83 | " emitter_def.size = (10, 1)\n", 84 | " emitter_def.transform = b2d.Transform(emitter_pos, b2d.Rot(0))\n", 85 | "\n", 86 | " self.emitter = b2d.RandomizedLinearEmitter(psystem, emitter_def)\n", 87 | "\n", 88 | " def pre_step(self, dt):\n", 89 | " self.emitter.step(dt)" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "id": "51e5df1b-f5e6-486a-a6c0-173597198e5d", 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 100 | "s = JupyterAsyncGui.Settings()\n", 101 | "s.resolution = [350,400]\n", 102 | "s.scale = 11\n", 103 | "tb = b2d.testbed.run(GaussMachine, backend=JupyterAsyncGui, gui_settings=s);" 104 | ] 105 | } 106 | ], 107 | "metadata": { 108 | "kernelspec": { 109 | "display_name": "Python 3 (ipykernel)", 110 | "language": "python", 111 | "name": "python3" 112 | }, 113 | "language_info": { 114 | "codemirror_mode": { 115 | "name": "ipython", 116 | "version": 3 117 | }, 118 | "file_extension": ".py", 119 | "mimetype": "text/x-python", 120 | "name": "python", 121 | "nbconvert_exporter": "python", 122 | "pygments_lexer": "ipython3", 123 | "version": "3.10.4" 124 | } 125 | }, 126 | "nbformat": 4, 127 | "nbformat_minor": 5 128 | } 129 | -------------------------------------------------------------------------------- /content/pyodide/folium.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "tags": [] 7 | }, 8 | "source": [ 9 | "# `folium` Interactive Map Demo\n", 10 | "\n", 11 | "Simple demonstration of rendering a map in a `jupyterlite` notebook.\n", 12 | "\n", 13 | "Note that the `folium` package has several dependencies which themselves may have dependencies.\n", 14 | "\n", 15 | "The following code fragement, run in a fresh Python enviroment into which `folium` has already been installed, identifies the packages that are loaded in when `folium` is loaded:\n", 16 | "\n", 17 | "```python\n", 18 | "#https://stackoverflow.com/a/40381601/454773\n", 19 | "import sys\n", 20 | "before = [str(m) for m in sys.modules]\n", 21 | "import folium\n", 22 | "after = [str(m) for m in sys.modules]\n", 23 | "set([m.split('.')[0] for m in after if not m in before and not m.startswith('_')])\n", 24 | "```\n", 25 | "\n", 26 | "The loaded packages are:\n", 27 | "\n", 28 | "```\n", 29 | "{'branca',\n", 30 | " 'certifi',\n", 31 | " 'chardet',\n", 32 | " 'cmath',\n", 33 | " 'csv',\n", 34 | " 'dateutil',\n", 35 | " 'encodings',\n", 36 | " 'folium',\n", 37 | " 'gzip',\n", 38 | " 'http',\n", 39 | " 'idna',\n", 40 | " 'importlib',\n", 41 | " 'jinja2',\n", 42 | " 'markupsafe',\n", 43 | " 'mmap',\n", 44 | " 'numpy',\n", 45 | " 'pandas',\n", 46 | " 'pkg_resources',\n", 47 | " 'pytz',\n", 48 | " 'requests',\n", 49 | " 'secrets',\n", 50 | " 'stringprep',\n", 51 | " 'urllib3',\n", 52 | " 'zipfile'}\n", 53 | " ```\n", 54 | " " 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "The following packages seem to need installing in order load `folium`, along with folium itself:\n", 62 | "\n", 63 | "```\n", 64 | "chardet, certifi, idna, branca, urllib3, Jinja2, requests, Markupsafe\n", 65 | "```\n", 66 | "\n", 67 | "Universal wheels, with filenames of the form `PACKAGE-VERSION-py2.py3-none-any.whl` appearing in the *Download files* area of a PyPi package page ([example](https://pypi.org/project/requests/#files)] are required in order to install the package.\n", 68 | "\n", 69 | "One required package, [`Markupsafe`](https://pypi.org/project/Markupsafe/#files)) *did not* have a universal wheel available, so a wheel was manually built elsewhere (by hacking the [`setup.py` file](https://github.com/pallets/markupsafe/blob/main/setup.py) to force it to build the wheel in a platform and speedup free way) and pushed to a downloadable location in an [*ad hoc* wheelhouse](https://opencomputinglab.github.io/vce-wheelhouse/)." 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": { 76 | "trusted": true 77 | }, 78 | "outputs": [], 79 | "source": [ 80 | "# Install folium requirements\n", 81 | "%pip install -q folium" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "## Demo of `folium` Map\n", 89 | "\n", 90 | "Load in the `folium` package:" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": { 97 | "trusted": true 98 | }, 99 | "outputs": [], 100 | "source": [ 101 | "import folium" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "And render a demo map:" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": { 115 | "trusted": true 116 | }, 117 | "outputs": [], 118 | "source": [ 119 | "m = folium.Map(location=[50.693848, -1.304734], zoom_start=11)\n", 120 | "m" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": null, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [] 129 | } 130 | ], 131 | "metadata": { 132 | "kernelspec": { 133 | "display_name": "Python (Pyodide)", 134 | "language": "python", 135 | "name": "python" 136 | }, 137 | "language_info": { 138 | "codemirror_mode": { 139 | "name": "python", 140 | "version": 3 141 | }, 142 | "file_extension": ".py", 143 | "mimetype": "text/x-python", 144 | "name": "python", 145 | "nbconvert_exporter": "python", 146 | "pygments_lexer": "ipython3", 147 | "version": "3.8" 148 | }, 149 | "orig_nbformat": 4, 150 | "toc-showcode": false 151 | }, 152 | "nbformat": 4, 153 | "nbformat_minor": 4 154 | } 155 | -------------------------------------------------------------------------------- /content/pyodide/ipycanvas.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# ipycanvas: John Conway's Game Of Life" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Some of the following code is adapted from https://jakevdp.github.io/blog/2013/08/07/conways-game-of-life/" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": { 21 | "trusted": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "%pip install -q ipycanvas" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "trusted": true 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "import asyncio\n", 37 | "\n", 38 | "import numpy as np\n", 39 | "\n", 40 | "from ipycanvas import RoughCanvas, hold_canvas" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": { 47 | "trusted": true 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "def life_step(x):\n", 52 | " \"\"\"Game of life step\"\"\"\n", 53 | " nbrs_count = sum(np.roll(np.roll(x, i, 0), j, 1)\n", 54 | " for i in (-1, 0, 1) for j in (-1, 0, 1)\n", 55 | " if (i != 0 or j != 0))\n", 56 | " return (nbrs_count == 3) | (x & (nbrs_count == 2))" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "metadata": { 63 | "trusted": true 64 | }, 65 | "outputs": [], 66 | "source": [ 67 | "def draw(x, canvas, color='black'):\n", 68 | " with hold_canvas(canvas):\n", 69 | " canvas.clear()\n", 70 | " canvas.fill_style = '#FFF0C9'\n", 71 | " canvas.rough_fill_style = 'solid'\n", 72 | " canvas.fill_rect(-10, -10, canvas.width + 10, canvas.height + 10)\n", 73 | " canvas.rough_fill_style = 'cross-hatch'\n", 74 | "\n", 75 | " canvas.fill_style = color\n", 76 | " canvas.stroke_style = color\n", 77 | "\n", 78 | " living_cells = np.where(x)\n", 79 | " \n", 80 | " rects_x = living_cells[1] * n_pixels\n", 81 | " rects_y = living_cells[0] * n_pixels\n", 82 | "\n", 83 | " canvas.fill_rects(rects_x, rects_y, n_pixels)\n", 84 | " canvas.stroke_rects(rects_x, rects_y, n_pixels)" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": { 91 | "trusted": true 92 | }, 93 | "outputs": [], 94 | "source": [ 95 | "glider_gun =\\\n", 96 | "[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],\n", 97 | " [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],\n", 98 | " [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],\n", 99 | " [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],\n", 100 | " [1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n", 101 | " [1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],\n", 102 | " [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],\n", 103 | " [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\n", 104 | " [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]\n", 105 | "\n", 106 | "x = np.zeros((50, 70), dtype=bool)\n", 107 | "x[1:10,1:37] = glider_gun" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": null, 113 | "metadata": { 114 | "trusted": true 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "n_pixels = 15\n", 119 | "\n", 120 | "canvas = RoughCanvas(width=x.shape[1]*n_pixels, height=x.shape[0]*n_pixels)\n", 121 | "canvas.fill_style = '#FFF0C9'\n", 122 | "canvas.rough_fill_style = 'solid'\n", 123 | "canvas.fill_rect(0, 0, canvas.width, canvas.height)\n", 124 | "\n", 125 | "canvas" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": { 132 | "trusted": true 133 | }, 134 | "outputs": [], 135 | "source": [ 136 | "draw(x, canvas, '#5770B3')" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "metadata": { 143 | "trusted": true 144 | }, 145 | "outputs": [], 146 | "source": [ 147 | "for _ in range(300):\n", 148 | " x = life_step(x)\n", 149 | " draw(x, canvas, '#5770B3')\n", 150 | "\n", 151 | " await asyncio.sleep(0.1)" 152 | ] 153 | } 154 | ], 155 | "metadata": { 156 | "kernelspec": { 157 | "display_name": "Python (Pyodide)", 158 | "language": "python", 159 | "name": "python" 160 | }, 161 | "language_info": { 162 | "codemirror_mode": { 163 | "name": "python", 164 | "version": 3 165 | }, 166 | "file_extension": ".py", 167 | "mimetype": "text/x-python", 168 | "name": "python", 169 | "nbconvert_exporter": "python", 170 | "pygments_lexer": "ipython3", 171 | "version": "3.8" 172 | }, 173 | "orig_nbformat": 4, 174 | "toc-showcode": false 175 | }, 176 | "nbformat": 4, 177 | "nbformat_minor": 4 178 | } 179 | -------------------------------------------------------------------------------- /content/pyodide/altair.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Altair in `JupyterLite`\n", 8 | "\n", 9 | "**Altair** is a declarative statistical visualization library for Python.\n", 10 | "\n", 11 | "Most of the examples below are from: https://altair-viz.github.io/gallery" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "## Import the dependencies:" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": { 25 | "trusted": true 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "%pip install -q altair" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "## Simple Bar Chart" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": { 43 | "trusted": true 44 | }, 45 | "outputs": [], 46 | "source": [ 47 | "import altair as alt\n", 48 | "import pandas as pd\n", 49 | "\n", 50 | "source = pd.DataFrame({\n", 51 | " 'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],\n", 52 | " 'b': [28, 55, 43, 91, 81, 53, 19, 87, 52]\n", 53 | "})\n", 54 | "\n", 55 | "alt.Chart(source).mark_bar().encode(\n", 56 | " x='a',\n", 57 | " y='b'\n", 58 | ")" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "## Simple Heatmap" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": { 72 | "trusted": true 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "import altair as alt\n", 77 | "import numpy as np\n", 78 | "import pandas as pd\n", 79 | "\n", 80 | "# Compute x^2 + y^2 across a 2D grid\n", 81 | "x, y = np.meshgrid(range(-5, 5), range(-5, 5))\n", 82 | "z = x ** 2 + y ** 2\n", 83 | "\n", 84 | "# Convert this grid to columnar data expected by Altair\n", 85 | "source = pd.DataFrame({'x': x.ravel(),\n", 86 | " 'y': y.ravel(),\n", 87 | " 'z': z.ravel()})\n", 88 | "\n", 89 | "alt.Chart(source).mark_rect().encode(\n", 90 | " x='x:O',\n", 91 | " y='y:O',\n", 92 | " color='z:Q'\n", 93 | ")\n" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "## Install the Vega Dataset" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": { 107 | "trusted": true 108 | }, 109 | "outputs": [], 110 | "source": [ 111 | "%pip install -q vega_datasets" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "## Interactive Average" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": { 125 | "trusted": true 126 | }, 127 | "outputs": [], 128 | "source": [ 129 | "import altair as alt\n", 130 | "from vega_datasets import data\n", 131 | "\n", 132 | "source = data.seattle_weather()\n", 133 | "brush = alt.selection(type='interval', encodings=['x'])\n", 134 | "\n", 135 | "bars = alt.Chart().mark_bar().encode(\n", 136 | " x='month(date):O',\n", 137 | " y='mean(precipitation):Q',\n", 138 | " opacity=alt.condition(brush, alt.OpacityValue(1), alt.OpacityValue(0.7)),\n", 139 | ").add_selection(\n", 140 | " brush\n", 141 | ")\n", 142 | "\n", 143 | "line = alt.Chart().mark_rule(color='firebrick').encode(\n", 144 | " y='mean(precipitation):Q',\n", 145 | " size=alt.SizeValue(3)\n", 146 | ").transform_filter(\n", 147 | " brush\n", 148 | ")\n", 149 | "\n", 150 | "alt.layer(bars, line, data=source)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "## Locations of US Airports" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": { 164 | "trusted": true 165 | }, 166 | "outputs": [], 167 | "source": [ 168 | "import altair as alt\n", 169 | "from vega_datasets import data\n", 170 | "\n", 171 | "airports = data.airports.url\n", 172 | "states = alt.topo_feature(data.us_10m.url, feature='states')\n", 173 | "\n", 174 | "# US states background\n", 175 | "background = alt.Chart(states).mark_geoshape(\n", 176 | " fill='lightgray',\n", 177 | " stroke='white'\n", 178 | ").properties(\n", 179 | " width=500,\n", 180 | " height=300\n", 181 | ").project('albersUsa')\n", 182 | "\n", 183 | "# airport positions on background\n", 184 | "points = alt.Chart(airports).transform_aggregate(\n", 185 | " latitude='mean(latitude)',\n", 186 | " longitude='mean(longitude)',\n", 187 | " count='count()',\n", 188 | " groupby=['state']\n", 189 | ").mark_circle().encode(\n", 190 | " longitude='longitude:Q',\n", 191 | " latitude='latitude:Q',\n", 192 | " size=alt.Size('count:Q', title='Number of Airports'),\n", 193 | " color=alt.value('steelblue'),\n", 194 | " tooltip=['state:N','count:Q']\n", 195 | ").properties(\n", 196 | " title='Number of airports in US'\n", 197 | ")\n", 198 | "\n", 199 | "background + points\n" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [] 208 | } 209 | ], 210 | "metadata": { 211 | "kernelspec": { 212 | "display_name": "Python (Pyodide)", 213 | "language": "python", 214 | "name": "python" 215 | }, 216 | "language_info": { 217 | "codemirror_mode": { 218 | "name": "python", 219 | "version": 3 220 | }, 221 | "file_extension": ".py", 222 | "mimetype": "text/x-python", 223 | "name": "python", 224 | "nbconvert_exporter": "python", 225 | "pygments_lexer": "ipython3", 226 | "version": "3.8" 227 | } 228 | }, 229 | "nbformat": 4, 230 | "nbformat_minor": 4 231 | } 232 | -------------------------------------------------------------------------------- /content/pyodide/interactive-widgets.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "9ca234f7-84b7-4107-9bcd-74f5a4ffd07d", 6 | "metadata": {}, 7 | "source": [ 8 | "# `ipywidgets` Interactive Demo\n", 9 | "\n", 10 | "Simple demonstration of rendering Interactive widgets in a `jupyterlite` notebook.\n", 11 | "\n", 12 | "`ipywidgets` can be installed in this deployment (it provides the @jupyter-widgets/jupyterlab-manager federated extension), but you will need to make your own deployment to have access to other interactive widgets libraries." 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "d62fba6e", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "%pip install -q ipywidgets" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "id": "3bab23f8-de91-43c9-9cec-84f4924425fc", 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "from ipywidgets import IntSlider" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "id": "a15c5acb-ee72-4005-8761-5693db853f22", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "slider = IntSlider()" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "8ba89682-e0d7-4bd2-961a-f9956850fd5a", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "slider" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "id": "50510ade-668f-4477-8cb2-41574609ac73", 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "slider" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "id": "7bac1ed8-8c77-426b-a781-1c1a6cfad829", 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "slider.value" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "976a70a0-e99d-4c20-b005-f59bbba10f85", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "slider.value = 5" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "id": "3134c76e-cffb-4701-8230-e6c4bfbbfdb9", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "from ipywidgets import IntText, link" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "id": "f7b3fe0a-5695-4ef2-a573-40785e68fbae", 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "text = IntText()" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "id": "5e2fd50e-19e0-4e20-a1f7-ad65400ec636", 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "text" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "id": "bb3bedce-7311-48c0-aeab-8fe3aa554b92", 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "link((slider, 'value'), (text, 'value'));" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "id": "71b68c3e-184e-4320-9513-d0bc72800a85", 128 | "metadata": {}, 129 | "source": [ 130 | "# `bqplot` Interactive Demo\n", 131 | "\n", 132 | "Plotting in JupyterLite\n", 133 | "\n", 134 | "`bqplot` can be installed in this deployment (it provides the bqplot federated extension), but you will need to make your own deployment to have access to other interactive widgets libraries." 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "id": "119eb9a3-ac98-42c3-98d4-1ac460eb75d3", 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "%pip install -q bqplot" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "id": "23b32857-2958-4083-b16a-ac26cd2408d4", 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "from bqplot import *\n", 155 | "\n", 156 | "import numpy as np\n", 157 | "import pandas as pd\n", 158 | "\n", 159 | "np.random.seed(0)\n", 160 | "\n", 161 | "n = 100\n", 162 | "\n", 163 | "x = list(range(n))\n", 164 | "y = np.cumsum(np.random.randn(n)) + 100.\n", 165 | "\n", 166 | "sc_x = LinearScale()\n", 167 | "sc_y = LinearScale()\n", 168 | "\n", 169 | "lines = Lines(\n", 170 | " x=x, y=y,\n", 171 | " scales={'x': sc_x, 'y': sc_y}\n", 172 | ")\n", 173 | "ax_x = Axis(scale=sc_x, label='Index')\n", 174 | "ax_y = Axis(scale=sc_y, orientation='vertical', label='lines')\n", 175 | "\n", 176 | "Figure(marks=[lines], axes=[ax_x, ax_y], title='Lines')" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "id": "ddb6b44e-06a0-4049-a79d-33ffc90d5a03", 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "lines.colors = ['green']" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "id": "e367e7fb-b403-41aa-9629-224827ec3005", 193 | "metadata": {}, 194 | "outputs": [], 195 | "source": [ 196 | "lines.fill = 'bottom'" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": null, 202 | "id": "d4a167f3-07c4-4880-92f5-7fcdea0c61c6", 203 | "metadata": {}, 204 | "outputs": [], 205 | "source": [ 206 | "lines.marker = 'circle'" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "id": "1d1342f7-ec08-4f53-84dc-d712226d9e46", 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "n = 100\n", 217 | "\n", 218 | "x = list(range(n))\n", 219 | "y = np.cumsum(np.random.randn(n))\n", 220 | "\n", 221 | "sc_x = LinearScale()\n", 222 | "sc_y = LinearScale()\n", 223 | "\n", 224 | "bars = Bars(\n", 225 | " x=x, y=y,\n", 226 | " scales={'x': sc_x, 'y': sc_y}\n", 227 | ")\n", 228 | "ax_x = Axis(scale=sc_x, label='Index')\n", 229 | "ax_y = Axis(scale=sc_y, orientation='vertical', label='bars')\n", 230 | "\n", 231 | "Figure(marks=[bars], axes=[ax_x, ax_y], title='Bars', animation_duration=1000)" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": null, 237 | "id": "f86bbcfb-5b02-4700-b8d6-f90068893b55", 238 | "metadata": {}, 239 | "outputs": [], 240 | "source": [ 241 | "bars.y = np.cumsum(np.random.randn(n))" 242 | ] 243 | } 244 | ], 245 | "metadata": { 246 | "kernelspec": { 247 | "display_name": "Python (Pyodide)", 248 | "language": "python", 249 | "name": "python" 250 | }, 251 | "language_info": { 252 | "codemirror_mode": { 253 | "name": "python", 254 | "version": 3 255 | }, 256 | "file_extension": ".py", 257 | "mimetype": "text/x-python", 258 | "name": "python", 259 | "nbconvert_exporter": "python", 260 | "pygments_lexer": "ipython3", 261 | "version": "3.8" 262 | }, 263 | "orig_nbformat": 4, 264 | "toc-showcode": false 265 | }, 266 | "nbformat": 4, 267 | "nbformat_minor": 5 268 | } 269 | -------------------------------------------------------------------------------- /content/pyodide/ipyleaflet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%pip install -q bqplot ipyleaflet" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import os\n", 19 | "from urllib.request import urlopen\n", 20 | "import json\n", 21 | "from datetime import datetime\n", 22 | "\n", 23 | "import numpy as np\n", 24 | "import pandas as pd\n", 25 | "\n", 26 | "from js import fetch\n", 27 | "\n", 28 | "from ipywidgets import Dropdown\n", 29 | "\n", 30 | "from bqplot import Lines, Figure, LinearScale, DateScale, Axis\n", 31 | "\n", 32 | "from ipyleaflet import Map, GeoJSON, WidgetControl" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "URL = \"https://raw.githubusercontent.com/jupyter-widgets/ipyleaflet/master/examples/nations.json\"\n", 42 | "\n", 43 | "res = await fetch(URL)\n", 44 | "text = await res.text()\n", 45 | "\n", 46 | "data = pd.read_json(text)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "def clean_data(data):\n", 56 | " for column in ['income', 'lifeExpectancy', 'population']:\n", 57 | " data = data.drop(data[data[column].apply(len) <= 4].index)\n", 58 | " return data\n", 59 | "\n", 60 | "def extrap_interp(data):\n", 61 | " data = np.array(data)\n", 62 | " x_range = np.arange(1800, 2009, 1.)\n", 63 | " y_range = np.interp(x_range, data[:, 0], data[:, 1])\n", 64 | " return y_range\n", 65 | "\n", 66 | "def extrap_data(data):\n", 67 | " for column in ['income', 'lifeExpectancy', 'population']:\n", 68 | " data[column] = data[column].apply(extrap_interp)\n", 69 | " return data" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "data = clean_data(data)\n", 79 | "data = extrap_data(data)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "data" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "date_start = datetime(1800, 12, 31)\n", 98 | "date_end = datetime(2009, 12, 31)\n", 99 | "\n", 100 | "date_scale = DateScale(min=date_start, max=date_end)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "date_data = pd.date_range(start=date_start, end=date_end, freq='A', normalize=True)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "country_name = 'Angola'\n", 119 | "data_name = 'income'\n", 120 | "\n", 121 | "x_data = data[data.name == country_name][data_name].values[0]" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "x_scale = LinearScale()\n", 131 | "\n", 132 | "lines = Lines(x=date_data, y=x_data, scales={'x': date_scale, 'y': x_scale})\n", 133 | "\n", 134 | "ax_x = Axis(label='Year', scale=date_scale, num_ticks=10, tick_format='%Y')\n", 135 | "ax_y = Axis(label=data_name.capitalize(), scale=x_scale, orientation='vertical', side='left')\n", 136 | "\n", 137 | "figure = Figure(axes=[ax_x, ax_y], title=country_name, marks=[lines], animation_duration=500,\n", 138 | " layout={'max_height': '250px', 'max_width': '400px'})" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [ 147 | "def update_figure(country_name, data_name):\n", 148 | " try:\n", 149 | " lines.y = data[data.name == country_name][data_name].values[0]\n", 150 | " ax_y.label = data_name.capitalize()\n", 151 | " figure.title = country_name\n", 152 | " except IndexError:\n", 153 | " pass" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "URL = \"https://raw.githubusercontent.com/jupyter-widgets/ipyleaflet/master/examples/countries.geo.json\"\n", 163 | "\n", 164 | "res = await fetch(URL)\n", 165 | "text = await res.text()\n", 166 | "\n", 167 | "countries = json.loads(text)" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "m = Map(zoom=3)\n", 177 | "\n", 178 | "geo = GeoJSON(data=countries, style={'fillColor': 'white', 'weight': 0.5}, hover_style={'fillColor': '#1f77b4'}, name='Countries')\n", 179 | "m.add_layer(geo)" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": null, 185 | "metadata": {}, 186 | "outputs": [], 187 | "source": [ 188 | "widget_control1 = WidgetControl(widget=figure, position='bottomright')\n", 189 | "\n", 190 | "m.add_control(widget_control1)\n", 191 | "\n", 192 | "def on_hover(event, feature, **kwargs):\n", 193 | " global country_name\n", 194 | "\n", 195 | " country_name = feature['properties']['name']\n", 196 | " update_figure(country_name, data_name)\n", 197 | "\n", 198 | "geo.on_hover(on_hover)" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "dropdown = Dropdown(\n", 208 | " options=['income', 'population', 'lifeExpectancy'],\n", 209 | " value=data_name,\n", 210 | " description='Plotting:'\n", 211 | ")\n", 212 | "\n", 213 | "def on_click(change):\n", 214 | " global data_name\n", 215 | "\n", 216 | " data_name = change['new']\n", 217 | " update_figure(country_name, data_name)\n", 218 | "\n", 219 | "dropdown.observe(on_click, 'value')\n", 220 | "\n", 221 | "widget_control2 = WidgetControl(widget=dropdown, position='bottomleft')\n", 222 | "\n", 223 | "m.add_control(widget_control2)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": null, 229 | "metadata": {}, 230 | "outputs": [], 231 | "source": [ 232 | "m" 233 | ] 234 | } 235 | ], 236 | "metadata": { 237 | "kernelspec": { 238 | "display_name": "Python (Pyodide)", 239 | "language": "python", 240 | "name": "python" 241 | }, 242 | "language_info": { 243 | "codemirror_mode": { 244 | "name": "python", 245 | "version": 3 246 | }, 247 | "file_extension": ".py", 248 | "mimetype": "text/x-python", 249 | "name": "python", 250 | "nbconvert_exporter": "python", 251 | "pygments_lexer": "ipython3", 252 | "version": "3.8" 253 | }, 254 | "orig_nbformat": 4, 255 | "toc-showcode": false 256 | }, 257 | "nbformat": 4, 258 | "nbformat_minor": 4 259 | } 260 | -------------------------------------------------------------------------------- /content/pyodide/pyb2d/games/billiard.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "if \"pyodide\" in sys.modules:\n", 11 | " import piplite\n", 12 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "import numpy\n", 22 | "import b2d\n", 23 | "import math\n", 24 | "import random\n", 25 | "\n", 26 | "from b2d.testbed import TestbedBase\n", 27 | "\n", 28 | "class Billiard(TestbedBase):\n", 29 | "\n", 30 | " name = \"Billiard\"\n", 31 | "\n", 32 | " def __init__(self, settings=None):\n", 33 | " super(Billiard, self).__init__(gravity=(0, 0), settings=settings)\n", 34 | " dimensions = [30, 50]\n", 35 | " self.dimensions = dimensions\n", 36 | "\n", 37 | " # the outer box\n", 38 | " box_shape = b2d.ChainShape()\n", 39 | " box_shape.create_loop(\n", 40 | " [\n", 41 | " (0, 0),\n", 42 | " (0, dimensions[1]),\n", 43 | " (dimensions[0], dimensions[1]),\n", 44 | " (dimensions[0], 0),\n", 45 | " ]\n", 46 | " )\n", 47 | " self.ball_radius = 1\n", 48 | " box = self.world.create_static_body(\n", 49 | " position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n", 50 | " )\n", 51 | "\n", 52 | " self.place_balls()\n", 53 | " self.place_pockets()\n", 54 | "\n", 55 | " # mouse interaction\n", 56 | " self._selected_ball = None\n", 57 | " self._selected_ball_pos = None\n", 58 | " self._last_pos = None\n", 59 | "\n", 60 | " # balls to be destroyed in the next step\n", 61 | " # since they are in the pocket\n", 62 | " self._to_be_destroyed = []\n", 63 | "\n", 64 | " def place_pockets(self):\n", 65 | " pocket_radius = 1\n", 66 | " self.pockets = []\n", 67 | "\n", 68 | " def place_pocket(position):\n", 69 | " pocket_shape = b2d.circle_shape(radius=pocket_radius / 3)\n", 70 | " pocket = self.world.create_static_body(\n", 71 | " position=position,\n", 72 | " fixtures=b2d.fixture_def(shape=pocket_shape, is_sensor=True),\n", 73 | " user_data=(\"pocket\", None),\n", 74 | " )\n", 75 | " self.pockets.append(pocket)\n", 76 | "\n", 77 | " d = pocket_radius / 2\n", 78 | "\n", 79 | " place_pocket(position=(0 + d, 0 + d))\n", 80 | " place_pocket(position=(self.dimensions[0] - d, 0 + d))\n", 81 | "\n", 82 | " place_pocket(position=(0 + d, self.dimensions[1] / 2))\n", 83 | " place_pocket(position=(self.dimensions[0] - d, self.dimensions[1] / 2))\n", 84 | "\n", 85 | " place_pocket(position=(0 + d, self.dimensions[1] - d))\n", 86 | " place_pocket(position=(self.dimensions[0] - d, self.dimensions[1] - d))\n", 87 | "\n", 88 | " def place_balls(self):\n", 89 | " self.balls = []\n", 90 | "\n", 91 | " base_colors = [\n", 92 | " (1, 1, 0),\n", 93 | " (0, 0, 1),\n", 94 | " (1, 0, 0),\n", 95 | " (1, 0, 1),\n", 96 | " (1, 0.6, 0),\n", 97 | " (0, 1, 0),\n", 98 | " (0.7, 0.4, 0.4),\n", 99 | " ]\n", 100 | " colors = []\n", 101 | " for color in base_colors:\n", 102 | " # ``full`` ball\n", 103 | " colors.append((color, color))\n", 104 | " # ``half`` ball (half white)\n", 105 | " colors.append((color, (1, 1, 1)))\n", 106 | "\n", 107 | " random.shuffle(colors)\n", 108 | " colors.insert(4, ((0, 0, 0), (0, 0, 0))) # black\n", 109 | "\n", 110 | " n_y = 5\n", 111 | " c_x = self.dimensions[0] / 2\n", 112 | " diameter = (self.ball_radius * 2) * 1.01\n", 113 | "\n", 114 | " bi = 0\n", 115 | " for y in range(n_y):\n", 116 | "\n", 117 | " py = y * diameter * 0.5 * math.sqrt(3)\n", 118 | " n_x = y + 1\n", 119 | " ox = diameter * (n_y - y) / 2\n", 120 | " for x in range(y + 1):\n", 121 | " position = (x * diameter + 10 + ox, py + 30)\n", 122 | " self.create_billard_ball(position=position, color=colors[bi])\n", 123 | " bi += 1\n", 124 | "\n", 125 | " self.create_billard_ball(position=(c_x, 10), color=((1, 1, 1), (1, 1, 1)))\n", 126 | "\n", 127 | " def create_billard_ball(self, position, color):\n", 128 | "\n", 129 | " ball = self.world.create_dynamic_body(\n", 130 | " position=position,\n", 131 | " fixtures=b2d.fixture_def(\n", 132 | " shape=b2d.circle_shape(radius=self.ball_radius),\n", 133 | " density=1.0,\n", 134 | " restitution=0.8,\n", 135 | " ),\n", 136 | " linear_damping=0.8,\n", 137 | " user_data=(\"ball\", color),\n", 138 | " fixed_rotation=True,\n", 139 | " )\n", 140 | " self.balls.append(ball)\n", 141 | "\n", 142 | " def begin_contact(self, contact):\n", 143 | " body_a = contact.body_a\n", 144 | " body_b = contact.body_b\n", 145 | "\n", 146 | " ud_a = body_a.user_data\n", 147 | " ud_b = body_b.user_data\n", 148 | " if ud_a is None or ud_b is None:\n", 149 | " return\n", 150 | "\n", 151 | " if ud_b[0] == \"ball\":\n", 152 | " body_a, body_b = body_b, body_a\n", 153 | " ud_a, ud_b = ud_b, ud_a\n", 154 | "\n", 155 | " if ud_a[0] == \"ball\" and ud_b[0] == \"pocket\":\n", 156 | " self._to_be_destroyed.append(body_a)\n", 157 | "\n", 158 | " def pre_step(self, dt):\n", 159 | " for b in self._to_be_destroyed:\n", 160 | " self.balls.remove(b)\n", 161 | " self.world.destroy_body(b)\n", 162 | " self._to_be_destroyed = []\n", 163 | "\n", 164 | " def ball_at_position(self, pos):\n", 165 | " body = self.world.find_body(pos)\n", 166 | " if body is not None:\n", 167 | " user_data = body.user_data\n", 168 | " if user_data is not None and user_data[0] == \"ball\":\n", 169 | " return body\n", 170 | " return None\n", 171 | "\n", 172 | " def on_mouse_down(self, pos):\n", 173 | " body = self.ball_at_position(pos)\n", 174 | " if body is not None:\n", 175 | " self._selected_ball = body\n", 176 | " self._selected_ball_pos = pos\n", 177 | " return True\n", 178 | "\n", 179 | " return False\n", 180 | "\n", 181 | " def on_mouse_move(self, pos):\n", 182 | " if self._selected_ball is not None:\n", 183 | " self._last_pos = pos\n", 184 | " return True\n", 185 | " return False\n", 186 | "\n", 187 | " def on_mouse_up(self, pos):\n", 188 | " if self._selected_ball is not None:\n", 189 | " self._last_pos = pos\n", 190 | " # if the mouse is in the starting ball itself we do nothing\n", 191 | " if self.ball_at_position(pos) != self._selected_ball:\n", 192 | " delta = b2d.vec2(self._selected_ball_pos) - b2d.vec2(self._last_pos)\n", 193 | " delta *= 100.0\n", 194 | " self._selected_ball.apply_linear_impulse(\n", 195 | " delta, self._selected_ball_pos, True\n", 196 | " )\n", 197 | " self._selected_ball = None\n", 198 | " self._selected_ball_pos = None\n", 199 | " self._last_pos = None\n", 200 | " return False\n", 201 | "\n", 202 | " def post_debug_draw(self):\n", 203 | "\n", 204 | " for pocket in self.pockets:\n", 205 | " self.debug_draw.draw_solid_circle(\n", 206 | " pocket.position, self.ball_radius, (1, 0), (1, 1, 1)\n", 207 | " )\n", 208 | "\n", 209 | " for ball in self.balls:\n", 210 | " _, (color0, color1) = ball.user_data\n", 211 | "\n", 212 | " self.debug_draw.draw_solid_circle(\n", 213 | " ball.position, self.ball_radius, (1, 0), color0\n", 214 | " )\n", 215 | " self.debug_draw.draw_solid_circle(\n", 216 | " ball.position, self.ball_radius / 2, (1, 0), color1\n", 217 | " )\n", 218 | " self.debug_draw.draw_circle(\n", 219 | " ball.position, self.ball_radius, (1, 1, 1), line_width=0.1\n", 220 | " )\n", 221 | "\n", 222 | " if self._selected_ball is not None:\n", 223 | "\n", 224 | " # draw circle around selected ball\n", 225 | " self.debug_draw.draw_circle(\n", 226 | " self._selected_ball.position,\n", 227 | " self.ball_radius * 2,\n", 228 | " (1, 1, 1),\n", 229 | " line_width=0.2,\n", 230 | " )\n", 231 | "\n", 232 | " # mark position on selected ball with red dot\n", 233 | " self.debug_draw.draw_solid_circle(\n", 234 | " self._selected_ball_pos, self.ball_radius * 0.2, (1, 0), (1, 0, 0)\n", 235 | " )\n", 236 | "\n", 237 | " # draw the line between marked pos on ball and last pos\n", 238 | " if self._last_pos is not None:\n", 239 | " self.debug_draw.draw_segment(\n", 240 | " self._selected_ball_pos, self._last_pos, (1, 1, 1), line_width=0.2\n", 241 | " )" 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "metadata": {}, 247 | "source": [ 248 | "# Controlls\n", 249 | "* To play this game, click and hold inside a billiard ball, move and release the mouse to shoot the ball.\n", 250 | "* Use the mouse-wheel to zoom in/out, a\n", 251 | "* Click and drag in the empty space to translate the view." 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": null, 257 | "metadata": {}, 258 | "outputs": [], 259 | "source": [ 260 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 261 | "backend = JupyterAsyncGui\n", 262 | "s = backend.Settings()\n", 263 | "s.resolution = [500,600]\n", 264 | "s.scale = 8\n", 265 | "s.fps = 40\n", 266 | "s.translate = [125,100]\n", 267 | "b2d.testbed.run(Billiard, backend=backend, gui_settings=s);" 268 | ] 269 | }, 270 | { 271 | "cell_type": "code", 272 | "execution_count": null, 273 | "metadata": {}, 274 | "outputs": [], 275 | "source": [] 276 | } 277 | ], 278 | "metadata": { 279 | "kernelspec": { 280 | "display_name": "Python 3 (ipykernel)", 281 | "language": "python", 282 | "name": "python3" 283 | }, 284 | "language_info": { 285 | "codemirror_mode": { 286 | "name": "ipython", 287 | "version": 3 288 | }, 289 | "file_extension": ".py", 290 | "mimetype": "text/x-python", 291 | "name": "python", 292 | "nbconvert_exporter": "python", 293 | "pygments_lexer": "ipython3", 294 | "version": "3.10.4" 295 | } 296 | }, 297 | "nbformat": 4, 298 | "nbformat_minor": 4 299 | } 300 | -------------------------------------------------------------------------------- /content/pyodide/pyb2d/games/rocket.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "f7b8452d-61fe-4356-8084-cac603096fef", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import sys\n", 11 | "if \"pyodide\" in sys.modules:\n", 12 | " import piplite\n", 13 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "id": "0bfa61e4-9817-4bea-aa66-6a660a423ae6", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "from b2d.testbed import TestbedBase\n", 24 | "import random\n", 25 | "import numpy\n", 26 | "import b2d\n", 27 | "import math\n", 28 | "\n", 29 | "class Rocket(TestbedBase):\n", 30 | "\n", 31 | " name = \"Rocket\"\n", 32 | "\n", 33 | " def __init__(self, settings=None):\n", 34 | " super(Rocket, self).__init__(gravity=(0, 0), settings=settings)\n", 35 | "\n", 36 | " # gravitational constant\n", 37 | " self.gravitational_constant = 6.0\n", 38 | "\n", 39 | " self.planets = {}\n", 40 | "\n", 41 | " # home planet\n", 42 | " home_planet = self.world.create_kinematic_body(\n", 43 | " position=(10, 0),\n", 44 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=20)),\n", 45 | " user_data=\"home_planet\",\n", 46 | " )\n", 47 | "\n", 48 | " # target planet\n", 49 | " target_planet = self.world.create_kinematic_body(\n", 50 | " position=(100, 100),\n", 51 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=10)),\n", 52 | " user_data=\"target_planet\",\n", 53 | " )\n", 54 | "\n", 55 | " # black hole\n", 56 | " black_hole = self.world.create_kinematic_body(\n", 57 | " position=(0, 400),\n", 58 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1)),\n", 59 | " user_data=\"black_hole\",\n", 60 | " )\n", 61 | "\n", 62 | " self.planets = {\n", 63 | " home_planet: dict(radius=20, density=1, color=(0, 0.2, 1)),\n", 64 | " target_planet: dict(radius=10, density=1, color=(0.7, 0.7, 0.7)),\n", 65 | " black_hole: dict(radius=1, density=10000, color=(0.1, 0.1, 0.1)),\n", 66 | " }\n", 67 | "\n", 68 | " # a tiny rocket\n", 69 | " self.rocket = self.world.create_dynamic_body(\n", 70 | " position=(10, 10),\n", 71 | " fixtures=[\n", 72 | " b2d.fixture_def(shape=b2d.polygon_shape(box=[1, 1]), density=1),\n", 73 | " b2d.fixture_def(\n", 74 | " shape=b2d.polygon_shape(vertices=[(-1, 1), (0, 4), (1, 1)]),\n", 75 | " density=1,\n", 76 | " ),\n", 77 | " ],\n", 78 | " angular_damping=0.5,\n", 79 | " linear_damping=0.2,\n", 80 | " user_data=\"rocket\",\n", 81 | " )\n", 82 | " # check if the rocket is gone\n", 83 | " self.touched_black_hole = False\n", 84 | "\n", 85 | " # particle system\n", 86 | " pdef = b2d.particle_system_def(\n", 87 | " viscous_strength=0.9,\n", 88 | " spring_strength=0.0,\n", 89 | " damping_strength=100.5,\n", 90 | " pressure_strength=1.0,\n", 91 | " color_mixing_strength=0.05,\n", 92 | " density=0.1,\n", 93 | " )\n", 94 | "\n", 95 | " psystem = self.world.create_particle_system(pdef)\n", 96 | " psystem.radius = 0.1\n", 97 | " psystem.damping = 0.5\n", 98 | "\n", 99 | " self.emitters = []\n", 100 | " self.key_map = {\"w\": 0, \"a\": 1, \"d\": 2}\n", 101 | "\n", 102 | " angle_width = (math.pi * 2) / 16\n", 103 | " emitter_def = b2d.RandomizedRadialEmitterDef()\n", 104 | " emitter_def.emite_rate = 2000\n", 105 | " emitter_def.lifetime = 1.0\n", 106 | " emitter_def.enabled = False\n", 107 | " emitter_def.inner_radius = 1\n", 108 | " emitter_def.outer_radius = 1\n", 109 | " emitter_def.velocity_magnitude = 10.0\n", 110 | " emitter_def.start_angle = math.pi / 2 - angle_width / 2.0\n", 111 | " emitter_def.stop_angle = math.pi / 2 + angle_width / 2.0\n", 112 | " emitter_def.body = self.rocket\n", 113 | "\n", 114 | " delta = 0.2\n", 115 | " self.emitter_local_anchors = [\n", 116 | " (0, -delta), # main\n", 117 | " (-delta, -0.5), # left,\n", 118 | " (delta, -0.5), # right\n", 119 | " ]\n", 120 | " self.emitter_local_rot = [math.pi, math.pi / 2, -math.pi / 2] # main\n", 121 | "\n", 122 | " # main trust\n", 123 | " emitter_def.emite_rate = 2000\n", 124 | " world_anchor = self.rocket.get_world_point(self.emitter_local_anchors[0])\n", 125 | " emitter_def.transform = b2d.Transform(\n", 126 | " world_anchor, b2d.Rot(self.emitter_local_rot[0])\n", 127 | " )\n", 128 | " emitter = b2d.RandomizedRadialEmitter(psystem, emitter_def)\n", 129 | " self.emitters.append(emitter)\n", 130 | "\n", 131 | " # left\n", 132 | " emitter_def.emite_rate = 200\n", 133 | " world_anchor = self.rocket.get_world_point(self.emitter_local_anchors[1])\n", 134 | " emitter_def.transform = b2d.Transform(\n", 135 | " world_anchor, b2d.Rot(self.emitter_local_rot[1])\n", 136 | " )\n", 137 | " emitter = b2d.RandomizedRadialEmitter(psystem, emitter_def)\n", 138 | " self.emitters.append(emitter)\n", 139 | "\n", 140 | " # right\n", 141 | " emitter_def.emite_rate = 200\n", 142 | " world_anchor = self.rocket.get_world_point(self.emitter_local_anchors[1])\n", 143 | " emitter_def.transform = b2d.Transform(\n", 144 | " world_anchor, b2d.Rot(self.emitter_local_rot[1])\n", 145 | " )\n", 146 | " emitter = b2d.RandomizedRadialEmitter(psystem, emitter_def)\n", 147 | " self.emitters.append(emitter)\n", 148 | "\n", 149 | " def pre_step(self, dt):\n", 150 | "\n", 151 | " # check if the rocket has died\n", 152 | " if self.touched_black_hole:\n", 153 | " if self.rocket is not None:\n", 154 | " self.world.destroy_body(self.rocket)\n", 155 | " self.rocket = None\n", 156 | " else:\n", 157 | " rocket_center = self.rocket.world_center\n", 158 | " rocket_mass = self.rocket.mass\n", 159 | " # compute gravitational forces\n", 160 | " net_force = numpy.zeros([2])\n", 161 | " for planet, planet_def in self.planets.items():\n", 162 | " radius = planet_def[\"radius\"]\n", 163 | " planet_center = planet.position\n", 164 | " planet_mass = planet_def[\"density\"] * radius ** 2 * math.pi\n", 165 | " delta = rocket_center - planet_center\n", 166 | " distance = delta.normalize()\n", 167 | " f = (\n", 168 | " -self.gravitational_constant\n", 169 | " * rocket_mass\n", 170 | " * planet_mass\n", 171 | " / (distance * distance)\n", 172 | " )\n", 173 | " net_force += delta * f\n", 174 | " f = float(net_force[0]), float(net_force[1])\n", 175 | " self.rocket.apply_force_to_center(f)\n", 176 | "\n", 177 | " # run the rockets engines\n", 178 | " for emitter, local_anchor, local_rotation in zip(\n", 179 | " self.emitters, self.emitter_local_anchors, self.emitter_local_rot\n", 180 | " ):\n", 181 | " world_anchor = self.rocket.get_world_point(local_anchor)\n", 182 | " emitter.position = world_anchor\n", 183 | " emitter.angle = self.rocket.angle + local_rotation\n", 184 | " emitter.step(dt)\n", 185 | "\n", 186 | " def begin_contact(self, contact):\n", 187 | " body_a = contact.body_a\n", 188 | " body_b = contact.body_b\n", 189 | " if body_b.user_data == \"rocket\":\n", 190 | " body_a, body_b = body_b, body_a\n", 191 | "\n", 192 | " user_data_a = body_a.user_data\n", 193 | " user_data_b = body_b.user_data\n", 194 | " if body_a.user_data == \"rocket\":\n", 195 | " if user_data_b == \"black_hole\":\n", 196 | " self.touched_black_hole = True\n", 197 | "\n", 198 | " def on_keyboard_down(self, key):\n", 199 | " if key in self.key_map:\n", 200 | " self.emitters[self.key_map[key]].enabled = True\n", 201 | " return True\n", 202 | " return False\n", 203 | "\n", 204 | " def on_keyboard_up(self, key):\n", 205 | " if key in self.key_map:\n", 206 | " self.emitters[self.key_map[key]].enabled = False\n", 207 | " return False\n", 208 | " return False\n", 209 | "\n", 210 | " def pre_debug_draw(self):\n", 211 | " pass\n", 212 | "\n", 213 | " def post_debug_draw(self):\n", 214 | " for planet, planet_def in self.planets.items():\n", 215 | " pos = planet.position\n", 216 | " self.debug_draw.draw_solid_circle(\n", 217 | " pos, planet_def[\"radius\"] + 0.1, axis=None, color=planet_def[\"color\"]\n", 218 | " )\n", 219 | " if planet.user_data == \"black_hole\":\n", 220 | " self.debug_draw.draw_circle(\n", 221 | " pos, planet_def[\"radius\"] * 5, color=(1, 1, 1), line_width=0.1\n", 222 | " )" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "id": "357866b3-876e-421f-8d2a-77d6697551d3", 228 | "metadata": {}, 229 | "source": [ 230 | "# Controlls\n", 231 | "* To play this game, use 'w','a','s','d' on your keyboard to steer the rocket\n", 232 | "* try to land on the other planet\n", 233 | "* avoid the black hole\n", 234 | "* Use the mouse-wheel to zoom in/out, a\n", 235 | "* Click and drag in the empty space to translate the view." 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "id": "7bab75b7-cec1-4348-b95d-9ffd282ded5c", 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [ 245 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 246 | "s = JupyterAsyncGui.Settings()\n", 247 | "s.resolution = [1000,1000]\n", 248 | "s.scale = 3\n", 249 | "tb = b2d.testbed.run(Rocket, backend=JupyterAsyncGui, gui_settings=s);" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": null, 255 | "id": "674c57c8-b5b1-45a9-b75e-5ddc487f7d9b", 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [] 259 | } 260 | ], 261 | "metadata": { 262 | "kernelspec": { 263 | "display_name": "Python 3 (ipykernel)", 264 | "language": "python", 265 | "name": "python3" 266 | }, 267 | "language_info": { 268 | "codemirror_mode": { 269 | "name": "ipython", 270 | "version": 3 271 | }, 272 | "file_extension": ".py", 273 | "mimetype": "text/x-python", 274 | "name": "python", 275 | "nbconvert_exporter": "python", 276 | "pygments_lexer": "ipython3", 277 | "version": "3.10.4" 278 | } 279 | }, 280 | "nbformat": 4, 281 | "nbformat_minor": 5 282 | } 283 | -------------------------------------------------------------------------------- /content/python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# A Python kernel backed by Pyodide\n", 11 | "\n", 12 | "![](https://raw.githubusercontent.com/pyodide/pyodide/master/docs/_static/img/pyodide-logo.png)" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": { 19 | "trusted": true 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "import pyodide_kernel\n", 24 | "pyodide_kernel.__version__" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "# Simple code execution" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": { 38 | "trusted": true 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "a = 3" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": { 49 | "trusted": true 50 | }, 51 | "outputs": [], 52 | "source": [ 53 | "a" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "trusted": true 61 | }, 62 | "outputs": [], 63 | "source": [ 64 | "b = 89\n", 65 | "\n", 66 | "def sq(x):\n", 67 | " return x * x\n", 68 | "\n", 69 | "sq(b)" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": { 76 | "trusted": true 77 | }, 78 | "outputs": [], 79 | "source": [ 80 | "print" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": { 86 | "tags": [] 87 | }, 88 | "source": [ 89 | "# Redirected streams" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": { 96 | "trusted": true 97 | }, 98 | "outputs": [], 99 | "source": [ 100 | "import sys\n", 101 | "\n", 102 | "print(\"Error !!\", file=sys.stderr)" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "# Error handling" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": { 116 | "scrolled": true, 117 | "trusted": true 118 | }, 119 | "outputs": [], 120 | "source": [ 121 | "\"Hello\"\n", 122 | "\n", 123 | "def dummy_function():\n", 124 | " import missing_module" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": { 131 | "trusted": true 132 | }, 133 | "outputs": [], 134 | "source": [ 135 | "dummy_function()" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "# Code completion" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "### press `tab` to see what is available in `sys` module" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "metadata": { 156 | "trusted": true 157 | }, 158 | "outputs": [], 159 | "source": [ 160 | "from sys import " 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "# Code inspection" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "### using the question mark" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "metadata": { 181 | "trusted": true 182 | }, 183 | "outputs": [], 184 | "source": [ 185 | "?print" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": {}, 191 | "source": [ 192 | "### by pressing `shift+tab`" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": null, 198 | "metadata": { 199 | "trusted": true 200 | }, 201 | "outputs": [], 202 | "source": [ 203 | "print(" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "# Input support" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": null, 216 | "metadata": { 217 | "trusted": true 218 | }, 219 | "outputs": [], 220 | "source": [ 221 | "name = await input('Enter your name: ')" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": null, 227 | "metadata": { 228 | "trusted": true 229 | }, 230 | "outputs": [], 231 | "source": [ 232 | "'Hello, ' + name" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "# Rich representation" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "metadata": { 246 | "trusted": true 247 | }, 248 | "outputs": [], 249 | "source": [ 250 | "from IPython.display import display, Markdown, HTML, JSON, Latex" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "metadata": { 256 | "tags": [] 257 | }, 258 | "source": [ 259 | "## HTML" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": null, 265 | "metadata": { 266 | "trusted": true 267 | }, 268 | "outputs": [], 269 | "source": [ 270 | "print('Before display')\n", 271 | "\n", 272 | "s = '

HTML Title

'\n", 273 | "display(HTML(s))\n", 274 | "\n", 275 | "print('After display')" 276 | ] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "metadata": {}, 281 | "source": [ 282 | "## Markdown" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": null, 288 | "metadata": { 289 | "trusted": true 290 | }, 291 | "outputs": [], 292 | "source": [ 293 | "Markdown('''\n", 294 | "# Title\n", 295 | "\n", 296 | "**in bold**\n", 297 | "\n", 298 | "~~Strikthrough~~\n", 299 | "''')" 300 | ] 301 | }, 302 | { 303 | "cell_type": "markdown", 304 | "metadata": {}, 305 | "source": [ 306 | "## Pandas DataFrame" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": null, 312 | "metadata": { 313 | "trusted": true 314 | }, 315 | "outputs": [], 316 | "source": [ 317 | "import pandas as pd\n", 318 | "import numpy as np\n", 319 | "from string import ascii_uppercase as letters\n", 320 | "from IPython.display import display\n", 321 | "\n", 322 | "df = pd.DataFrame(np.random.randint(0, 100, size=(100, len(letters))), columns=list(letters))\n", 323 | "df" 324 | ] 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "metadata": {}, 329 | "source": [ 330 | "### Show the same DataFrame " 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": null, 336 | "metadata": { 337 | "trusted": true 338 | }, 339 | "outputs": [], 340 | "source": [ 341 | "df" 342 | ] 343 | }, 344 | { 345 | "cell_type": "markdown", 346 | "metadata": {}, 347 | "source": [ 348 | "## IPython.display module" 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": null, 354 | "metadata": { 355 | "trusted": true 356 | }, 357 | "outputs": [], 358 | "source": [ 359 | "from IPython.display import clear_output, display, update_display\n", 360 | "from asyncio import sleep" 361 | ] 362 | }, 363 | { 364 | "cell_type": "markdown", 365 | "metadata": {}, 366 | "source": [ 367 | "### Update display" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": null, 373 | "metadata": { 374 | "trusted": true 375 | }, 376 | "outputs": [], 377 | "source": [ 378 | "class Square:\n", 379 | " color = 'PeachPuff'\n", 380 | " def _repr_html_(self):\n", 381 | " return '''\n", 382 | "
\n", 383 | "
''' % self.color\n", 384 | "square = Square()\n", 385 | "\n", 386 | "display(square, display_id='some-square')" 387 | ] 388 | }, 389 | { 390 | "cell_type": "code", 391 | "execution_count": null, 392 | "metadata": { 393 | "trusted": true 394 | }, 395 | "outputs": [], 396 | "source": [ 397 | "square.color = 'OliveDrab'\n", 398 | "update_display(square, display_id='some-square')" 399 | ] 400 | }, 401 | { 402 | "cell_type": "markdown", 403 | "metadata": {}, 404 | "source": [ 405 | "### Clear output" 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": null, 411 | "metadata": { 412 | "trusted": true 413 | }, 414 | "outputs": [], 415 | "source": [ 416 | "print(\"hello\")\n", 417 | "await sleep(3)\n", 418 | "clear_output() # will flicker when replacing \"hello\" with \"goodbye\"\n", 419 | "print(\"goodbye\")" 420 | ] 421 | }, 422 | { 423 | "cell_type": "code", 424 | "execution_count": null, 425 | "metadata": { 426 | "trusted": true 427 | }, 428 | "outputs": [], 429 | "source": [ 430 | "print(\"hello\")\n", 431 | "await sleep(3)\n", 432 | "clear_output(wait=True) # prevents flickering\n", 433 | "print(\"goodbye\")" 434 | ] 435 | }, 436 | { 437 | "cell_type": "markdown", 438 | "metadata": {}, 439 | "source": [ 440 | "### Display classes" 441 | ] 442 | }, 443 | { 444 | "cell_type": "code", 445 | "execution_count": null, 446 | "metadata": { 447 | "trusted": true 448 | }, 449 | "outputs": [], 450 | "source": [ 451 | "from IPython.display import HTML\n", 452 | "HTML('''\n", 453 | "
\n", 454 | "
''')" 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": null, 460 | "metadata": { 461 | "trusted": true 462 | }, 463 | "outputs": [], 464 | "source": [ 465 | "from IPython.display import Math\n", 466 | "Math(r'F(k) = \\int_{-\\infty}^{\\infty} f(x) e^{2\\pi i k} dx')" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": null, 472 | "metadata": { 473 | "trusted": true 474 | }, 475 | "outputs": [], 476 | "source": [ 477 | "from IPython.display import Latex\n", 478 | "Latex(r\"\"\"\\begin{eqnarray}\n", 479 | "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n", 480 | "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n", 481 | "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n", 482 | "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0 \n", 483 | "\\end{eqnarray}\"\"\")" 484 | ] 485 | }, 486 | { 487 | "cell_type": "code", 488 | "execution_count": null, 489 | "metadata": { 490 | "trusted": true 491 | }, 492 | "outputs": [], 493 | "source": [ 494 | "from IPython.display import ProgressBar\n", 495 | "\n", 496 | "for i in ProgressBar(10):\n", 497 | " await sleep(0.1)" 498 | ] 499 | }, 500 | { 501 | "cell_type": "code", 502 | "execution_count": null, 503 | "metadata": { 504 | "trusted": true 505 | }, 506 | "outputs": [], 507 | "source": [ 508 | "from IPython.display import JSON\n", 509 | "JSON(['foo', {'bar': ('baz', None, 1.0, 2)}], metadata={}, expanded=True, root='test')" 510 | ] 511 | }, 512 | { 513 | "cell_type": "code", 514 | "execution_count": null, 515 | "metadata": { 516 | "trusted": true 517 | }, 518 | "outputs": [], 519 | "source": [ 520 | "from IPython.display import GeoJSON\n", 521 | "GeoJSON(\n", 522 | " data={\n", 523 | " \"type\": \"Feature\",\n", 524 | " \"geometry\": {\n", 525 | " \"type\": \"Point\",\n", 526 | " \"coordinates\": [11.8, -45.04]\n", 527 | " }\n", 528 | " }, url_template=\"http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png\",\n", 529 | " layer_options={\n", 530 | " \"basemap_id\": \"celestia_mars-shaded-16k_global\",\n", 531 | " \"attribution\" : \"Celestia/praesepe\",\n", 532 | " \"tms\": True,\n", 533 | " \"minZoom\" : 0,\n", 534 | " \"maxZoom\" : 5\n", 535 | " }\n", 536 | ")" 537 | ] 538 | }, 539 | { 540 | "cell_type": "markdown", 541 | "metadata": {}, 542 | "source": [ 543 | "## Network requests and JSON" 544 | ] 545 | }, 546 | { 547 | "cell_type": "code", 548 | "execution_count": null, 549 | "metadata": { 550 | "trusted": true 551 | }, 552 | "outputs": [], 553 | "source": [ 554 | "import json\n", 555 | "from js import fetch" 556 | ] 557 | }, 558 | { 559 | "cell_type": "code", 560 | "execution_count": null, 561 | "metadata": { 562 | "trusted": true 563 | }, 564 | "outputs": [], 565 | "source": [ 566 | "res = await fetch('https://httpbin.org/get')\n", 567 | "text = await res.text()\n", 568 | "obj = json.loads(text) \n", 569 | "JSON(obj)" 570 | ] 571 | }, 572 | { 573 | "cell_type": "markdown", 574 | "metadata": {}, 575 | "source": [ 576 | "## Sympy" 577 | ] 578 | }, 579 | { 580 | "cell_type": "code", 581 | "execution_count": null, 582 | "metadata": { 583 | "trusted": true 584 | }, 585 | "outputs": [], 586 | "source": [ 587 | "from sympy import Integral, sqrt, symbols, init_printing\n", 588 | "\n", 589 | "init_printing()\n", 590 | "\n", 591 | "x = symbols('x')\n", 592 | "\n", 593 | "Integral(sqrt(1 / x), x)" 594 | ] 595 | }, 596 | { 597 | "cell_type": "markdown", 598 | "metadata": {}, 599 | "source": [ 600 | "## Magics" 601 | ] 602 | }, 603 | { 604 | "cell_type": "code", 605 | "execution_count": null, 606 | "metadata": { 607 | "trusted": true 608 | }, 609 | "outputs": [], 610 | "source": [ 611 | "import os\n", 612 | "os.listdir()" 613 | ] 614 | }, 615 | { 616 | "cell_type": "code", 617 | "execution_count": null, 618 | "metadata": { 619 | "trusted": true 620 | }, 621 | "outputs": [], 622 | "source": [ 623 | "%cd /home" 624 | ] 625 | }, 626 | { 627 | "cell_type": "code", 628 | "execution_count": null, 629 | "metadata": { 630 | "trusted": true 631 | }, 632 | "outputs": [], 633 | "source": [ 634 | "%pwd" 635 | ] 636 | }, 637 | { 638 | "cell_type": "code", 639 | "execution_count": null, 640 | "metadata": { 641 | "trusted": true 642 | }, 643 | "outputs": [], 644 | "source": [ 645 | "current_path = %pwd\n", 646 | "print(current_path)" 647 | ] 648 | }, 649 | { 650 | "cell_type": "code", 651 | "execution_count": null, 652 | "metadata": { 653 | "trusted": true 654 | }, 655 | "outputs": [], 656 | "source": [ 657 | "%%writefile test.txt\n", 658 | "\n", 659 | "This will create a new file. \n", 660 | "With the text that you see here." 661 | ] 662 | }, 663 | { 664 | "cell_type": "code", 665 | "execution_count": null, 666 | "metadata": { 667 | "trusted": true 668 | }, 669 | "outputs": [], 670 | "source": [ 671 | "%history" 672 | ] 673 | }, 674 | { 675 | "cell_type": "code", 676 | "execution_count": null, 677 | "metadata": { 678 | "trusted": true 679 | }, 680 | "outputs": [], 681 | "source": [ 682 | "import time" 683 | ] 684 | }, 685 | { 686 | "cell_type": "code", 687 | "execution_count": null, 688 | "metadata": { 689 | "trusted": true 690 | }, 691 | "outputs": [], 692 | "source": [ 693 | "%%timeit \n", 694 | "\n", 695 | "time.sleep(0.1)" 696 | ] 697 | } 698 | ], 699 | "metadata": { 700 | "kernelspec": { 701 | "display_name": "Python (Pyodide)", 702 | "language": "python", 703 | "name": "python" 704 | }, 705 | "language_info": { 706 | "codemirror_mode": { 707 | "name": "python", 708 | "version": 3 709 | }, 710 | "file_extension": ".py", 711 | "mimetype": "text/x-python", 712 | "name": "python", 713 | "nbconvert_exporter": "python", 714 | "pygments_lexer": "ipython3", 715 | "version": "3.8" 716 | }, 717 | "orig_nbformat": 4 718 | }, 719 | "nbformat": 4, 720 | "nbformat_minor": 4 721 | } 722 | -------------------------------------------------------------------------------- /content/pyodide/pyb2d/games/angry_shapes.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2859de40-f927-4790-b192-c5b0531058f7", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import sys\n", 11 | "if \"pyodide\" in sys.modules:\n", 12 | " import piplite\n", 13 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "id": "8ac3e93b-3e9e-4cd7-a183-0214b0dcb513", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "from b2d.testbed import TestbedBase\n", 24 | "import math\n", 25 | "import numpy\n", 26 | "import b2d\n", 27 | "\n", 28 | "class AngryShapes(TestbedBase):\n", 29 | "\n", 30 | " name = \"AngryShapes\"\n", 31 | "\n", 32 | " class Settings(TestbedBase.Settings):\n", 33 | " substeps: int = 2\n", 34 | "\n", 35 | " def draw_segment(self, p1, p2, color, line_width=1):\n", 36 | " screen_p1 = self._point(self.world_to_screen(p1))\n", 37 | " screen_p2 = self._point(self.world_to_screen(p2))\n", 38 | " screen_color = self._uint8_color(color)\n", 39 | " screen_line_width = self._line_width(line_width)\n", 40 | "\n", 41 | " cv.line(self._image, screen_p1, screen_p2, screen_color, screen_line_width)\n", 42 | "\n", 43 | " def draw_polygon(self, vertices, color, line_width=1):\n", 44 | " # todo add C++ function for this\n", 45 | " screen_vertices = numpy.array(\n", 46 | " [self._point(self.world_to_screen(v)) for v in vertices], dtype=\"int32\"\n", 47 | " )\n", 48 | " screen_color = self._uint8_color(color)\n", 49 | " screen_line_width = self._line_width(line_width)\n", 50 | "\n", 51 | " cv.polylines(\n", 52 | " self._image, [screen_vertices], True, screen_color, screen_line_width, 8\n", 53 | " )\n", 54 | "\n", 55 | " def draw_solid_polygon(self, vertices, color):\n", 56 | " # todo add C++ function for this\n", 57 | " screen_vertices = numpy.array(\n", 58 | " [self._point(self.world_to_screen(v)) for v in vertices], dtype=\"int32\"\n", 59 | " )\n", 60 | " screen_color = self._uint8_color(color)\n", 61 | "\n", 62 | " cv.fillPoly(self._image, [screen_vertices], screen_color, 8)\n", 63 | "\n", 64 | " def __init__(self, settings=None):\n", 65 | " super(AngryShapes, self).__init__(settings=settings)\n", 66 | "\n", 67 | " self.targets = []\n", 68 | " self.projectiles = []\n", 69 | " self.marked_for_destruction = []\n", 70 | " self.emitter = None\n", 71 | "\n", 72 | " # particle system\n", 73 | " pdef = b2d.particle_system_def(\n", 74 | " viscous_strength=0.9,\n", 75 | " spring_strength=0.0,\n", 76 | " damping_strength=100.5,\n", 77 | " pressure_strength=1.0,\n", 78 | " color_mixing_strength=0.05,\n", 79 | " density=0.1,\n", 80 | " )\n", 81 | "\n", 82 | " self.psystem = self.world.create_particle_system(pdef)\n", 83 | " self.psystem.radius = 1\n", 84 | " self.psystem.damping = 0.5\n", 85 | "\n", 86 | " self.build_outer_box()\n", 87 | " self.build_castle()\n", 88 | " self.build_launcher()\n", 89 | " self.arm_launcher()\n", 90 | " self.build_explosives()\n", 91 | "\n", 92 | " def build_outer_box(self):\n", 93 | " # the outer box\n", 94 | "\n", 95 | " shape = b2d.edge_shape([(100, 0), (600, 0)])\n", 96 | " box = self.world.create_static_body(\n", 97 | " position=(0, 0), fixtures=b2d.fixture_def(shape=shape, friction=1)\n", 98 | " )\n", 99 | "\n", 100 | " def build_target(self, pos):\n", 101 | " t = self.world.create_dynamic_body(\n", 102 | " position=pos,\n", 103 | " fixtures=[\n", 104 | " b2d.fixture_def(shape=b2d.circle_shape(radius=4), density=1.0),\n", 105 | " b2d.fixture_def(\n", 106 | " shape=b2d.circle_shape(radius=2, pos=(3, 3)), density=1.0\n", 107 | " ),\n", 108 | " b2d.fixture_def(\n", 109 | " shape=b2d.circle_shape(radius=2, pos=(-3, 3)), density=1.0\n", 110 | " ),\n", 111 | " ],\n", 112 | " linear_damping=0,\n", 113 | " angular_damping=0,\n", 114 | " user_data=\"target\",\n", 115 | " )\n", 116 | " self.targets.append(t)\n", 117 | "\n", 118 | " def build_castle(self):\n", 119 | " def build_pyramid(offset, bar_shape, n):\n", 120 | " def build_brick(pos, size):\n", 121 | " hsize = [s / 2 for s in size]\n", 122 | " self.world.create_dynamic_body(\n", 123 | " position=(\n", 124 | " pos[0] + hsize[0] + offset[0],\n", 125 | " pos[1] + hsize[1] + offset[1],\n", 126 | " ),\n", 127 | " fixtures=b2d.fixture_def(\n", 128 | " shape=b2d.polygon_shape(box=hsize), density=8\n", 129 | " ),\n", 130 | " user_data=\"brick\",\n", 131 | " )\n", 132 | "\n", 133 | " bar_length = bar_shape[0]\n", 134 | " bar_width = bar_shape[1]\n", 135 | "\n", 136 | " nxm = n\n", 137 | " for y in range(nxm):\n", 138 | " py = y * (bar_length + bar_width)\n", 139 | " nx = nxm - y\n", 140 | " for x in range(nx):\n", 141 | " px = x * bar_length + y * (bar_length) / 2.0\n", 142 | " if y + 1 < nxm - 1:\n", 143 | " if x == 0:\n", 144 | " px += bar_width / 2\n", 145 | " if x + 1 == nx:\n", 146 | " px -= bar_width / 2\n", 147 | "\n", 148 | " build_brick((px, py), (bar_width, bar_length))\n", 149 | " if x < nx - 1:\n", 150 | " self.build_target(\n", 151 | " pos=(\n", 152 | " px + offset[0] + bar_length / 2,\n", 153 | " py + offset[1] + bar_width,\n", 154 | " )\n", 155 | " )\n", 156 | " build_brick(\n", 157 | " (px + bar_width / 2, py + bar_length),\n", 158 | " (bar_length, bar_width),\n", 159 | " )\n", 160 | "\n", 161 | " build_pyramid(offset=(100, 0), bar_shape=[40, 4], n=4)\n", 162 | " build_pyramid(offset=(400, 0), bar_shape=[30, 3], n=4)\n", 163 | "\n", 164 | " def build_launcher(self):\n", 165 | "\n", 166 | " self.launcher_anchor_pos = (30, 0)\n", 167 | " self.launcher_anchor = self.world.create_static_body(\n", 168 | " position=self.launcher_anchor_pos\n", 169 | " )\n", 170 | "\n", 171 | " def arm_launcher(self):\n", 172 | " self.reload_time = None\n", 173 | " self.is_armed = True\n", 174 | " self.projectile_radius = 3\n", 175 | " projectile_pos = (self.launcher_anchor_pos[0], self.launcher_anchor_pos[1] / 2)\n", 176 | "\n", 177 | " self.projectile = self.world.create_dynamic_body(\n", 178 | " position=projectile_pos,\n", 179 | " fixtures=b2d.fixture_def(\n", 180 | " shape=b2d.circle_shape(radius=self.projectile_radius), density=100.0\n", 181 | " ),\n", 182 | " linear_damping=0,\n", 183 | " angular_damping=0,\n", 184 | " user_data=\"projectile\",\n", 185 | " )\n", 186 | " self.projectiles.append(self.projectile)\n", 187 | " self.projectile_joint = self.world.create_distance_joint(\n", 188 | " self.launcher_anchor, self.projectile, length=1, stiffness=10000\n", 189 | " )\n", 190 | " self.mouse_joint = None\n", 191 | "\n", 192 | " def build_explosives(self):\n", 193 | " self.explosives = []\n", 194 | "\n", 195 | " def on_mouse_down(self, p):\n", 196 | " if self.is_armed:\n", 197 | " body = self.world.find_body(pos=p)\n", 198 | " if body is not None and body.user_data is not None:\n", 199 | " print(\"got body\")\n", 200 | " if body.user_data == \"projectile\":\n", 201 | " print(\"got projectile\")\n", 202 | " kwargs = dict(\n", 203 | " body_a=self.groundbody,\n", 204 | " body_b=body,\n", 205 | " target=p,\n", 206 | " max_force=50000.0 * body.mass,\n", 207 | " stiffness=10000.0,\n", 208 | " )\n", 209 | "\n", 210 | " self.mouse_joint = self.world.create_mouse_joint(**kwargs)\n", 211 | " body.awake = True\n", 212 | " return True\n", 213 | "\n", 214 | " return False\n", 215 | "\n", 216 | " def on_mouse_move(self, p):\n", 217 | " if self.is_armed:\n", 218 | " if self.mouse_joint is not None:\n", 219 | " self.mouse_joint.target = p\n", 220 | " return True\n", 221 | " return False\n", 222 | "\n", 223 | " def on_mouse_up(self, p):\n", 224 | " if self.is_armed:\n", 225 | " if self.mouse_joint is not None:\n", 226 | " self.world.destroy_joint(self.mouse_joint)\n", 227 | " if self.projectile_joint is not None:\n", 228 | " self.world.destroy_joint(self.projectile_joint)\n", 229 | " self.projectile_joint = None\n", 230 | " self.mouse_joint = None\n", 231 | " delta = self.launcher_anchor.position - b2d.vec2(p)\n", 232 | " scaled_delta = delta * 50000.0\n", 233 | " print(scaled_delta)\n", 234 | "\n", 235 | " self.projectile.apply_linear_impulse_to_center(scaled_delta, True)\n", 236 | " self.reload_time = self.elapsed_time + 1.0\n", 237 | " self.is_armed = False\n", 238 | " return False\n", 239 | "\n", 240 | " def begin_contact(self, contact):\n", 241 | " body_a = contact.body_a\n", 242 | " body_b = contact.body_b\n", 243 | " ud_a = body_a.user_data\n", 244 | " ud_b = body_b.user_data\n", 245 | " if ud_b == \"projectile\":\n", 246 | " body_a, body_b = body_b, body_a\n", 247 | " ud_a, ud_b = ud_b, ud_a\n", 248 | " if ud_a == \"projectile\":\n", 249 | "\n", 250 | " if ud_b == \"target\" or ud_b == \"brick\":\n", 251 | " self.marked_for_destruction.append(body_a)\n", 252 | " emitter_def = b2d.RandomizedRadialEmitterDef()\n", 253 | " emitter_def.emite_rate = 20000\n", 254 | " emitter_def.lifetime = 0.7\n", 255 | " emitter_def.enabled = True\n", 256 | " emitter_def.inner_radius = 0.0\n", 257 | " emitter_def.outer_radius = 1.0\n", 258 | " emitter_def.velocity_magnitude = 1000.0\n", 259 | " emitter_def.start_angle = 0\n", 260 | " emitter_def.stop_angle = math.pi\n", 261 | " emitter_def.transform = b2d.Transform(body_a.position, b2d.Rot(0))\n", 262 | " self.emitter = b2d.RandomizedRadialEmitter(self.psystem, emitter_def)\n", 263 | " self.emitter_die_time = self.elapsed_time + 0.02\n", 264 | "\n", 265 | " def pre_step(self, dt):\n", 266 | "\n", 267 | " if self.reload_time is not None:\n", 268 | " if self.elapsed_time >= self.reload_time:\n", 269 | " self.arm_launcher()\n", 270 | "\n", 271 | " # delete contact bodies\n", 272 | " for body in self.marked_for_destruction:\n", 273 | " if body in self.projectiles:\n", 274 | " self.projectiles.remove(body)\n", 275 | " self.world.destroy_body(body)\n", 276 | " if body == self.projectile:\n", 277 | " self.reload_time = self.elapsed_time + 1.0\n", 278 | " self.marked_for_destruction = []\n", 279 | "\n", 280 | " # delete bodies which have fallen down\n", 281 | " for body in self.world.bodies:\n", 282 | " if body.position.y < -100:\n", 283 | " if body.user_data == \"projectile\":\n", 284 | " self.projectiles.remove(body)\n", 285 | " if body.user_data == \"target\":\n", 286 | " self.targets.remove(body)\n", 287 | " self.world.destroy_body(body)\n", 288 | "\n", 289 | " # emmiter\n", 290 | " if self.emitter is not None:\n", 291 | " self.emitter.step(dt)\n", 292 | " if self.elapsed_time >= self.emitter_die_time:\n", 293 | " self.emitter = None\n", 294 | "\n", 295 | " def draw_target(self, target):\n", 296 | " center = target.position\n", 297 | " center_l = target.get_world_point((-3, 3))\n", 298 | " center_r = target.get_world_point((3, 3))\n", 299 | " eye_left = target.get_world_point((-1, 1))\n", 300 | " eye_right = target.get_world_point((1, 1))\n", 301 | " pink = [c / 255 for c in (248, 24, 148)]\n", 302 | "\n", 303 | " self.debug_draw.draw_solid_circle(\n", 304 | " center=center, radius=4, axis=None, color=pink\n", 305 | " )\n", 306 | " self.debug_draw.draw_solid_circle(\n", 307 | " center=center_l, radius=2, axis=None, color=pink\n", 308 | " )\n", 309 | " self.debug_draw.draw_solid_circle(\n", 310 | " center=center_r, radius=2, axis=None, color=pink\n", 311 | " )\n", 312 | "\n", 313 | " # schnautze\n", 314 | " nose_center = target.get_world_point((0, -1))\n", 315 | " nose_center_l = target.get_world_point((-0.3, -1))\n", 316 | " nose_center_r = target.get_world_point((0.3, -1))\n", 317 | "\n", 318 | " self.debug_draw.draw_circle(\n", 319 | " center=nose_center,\n", 320 | " radius=2,\n", 321 | " # axis=None,\n", 322 | " color=(1, 1, 1),\n", 323 | " line_width=0.2,\n", 324 | " )\n", 325 | " # eyes\n", 326 | " for nose_center in [nose_center_l, nose_center_r]:\n", 327 | " self.debug_draw.draw_solid_circle(\n", 328 | " center=nose_center, radius=0.6, axis=None, color=(1, 1, 1)\n", 329 | " )\n", 330 | " # eyes\n", 331 | " for eye_center in [eye_left, eye_right]:\n", 332 | " self.debug_draw.draw_solid_circle(\n", 333 | " center=eye_center, radius=1, axis=None, color=(1, 1, 1)\n", 334 | " )\n", 335 | " self.debug_draw.draw_solid_circle(\n", 336 | " center=eye_center, radius=0.7, axis=None, color=(0, 0, 0)\n", 337 | " )\n", 338 | "\n", 339 | " def draw_projectile(self, projectile):\n", 340 | "\n", 341 | " center = projectile.position\n", 342 | " # center_l = target.get_world_point((-3,3))\n", 343 | " # center_r = target.get_world_point(( 3,3))\n", 344 | " eye_left = projectile.get_world_point((-1, 1))\n", 345 | " eye_right = projectile.get_world_point((1, 1))\n", 346 | "\n", 347 | " self.debug_draw.draw_solid_circle(\n", 348 | " center=center,\n", 349 | " radius=self.projectile_radius * 1.1,\n", 350 | " axis=None,\n", 351 | " color=(1, 0, 0),\n", 352 | " )\n", 353 | "\n", 354 | " # eyes\n", 355 | " for eye_center in [eye_left, eye_right]:\n", 356 | " self.debug_draw.draw_solid_circle(\n", 357 | " center=eye_center, radius=1, axis=None, color=(1, 1, 1)\n", 358 | " )\n", 359 | " self.debug_draw.draw_solid_circle(\n", 360 | " center=eye_center, radius=0.7, axis=None, color=(0, 0, 0)\n", 361 | " )\n", 362 | "\n", 363 | " def post_debug_draw(self):\n", 364 | " for target in self.targets:\n", 365 | " self.draw_target(target)\n", 366 | "\n", 367 | " for projectile in self.projectiles:\n", 368 | " self.draw_projectile(projectile)" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "id": "6df7c8b9-216b-4fd2-8ee8-aeec294e149d", 374 | "metadata": {}, 375 | "source": [ 376 | "# Controlls\n", 377 | "* To play this game, click and drag the red ball and release it to shot it.\n", 378 | "* Use the mouse-wheel to zoom in/out, a\n", 379 | "* Click and drag in the empty space to translate the view." 380 | ] 381 | }, 382 | { 383 | "cell_type": "code", 384 | "execution_count": null, 385 | "id": "df412e76-7a9a-4e1d-8bc7-c02e222e10dc", 386 | "metadata": {}, 387 | "outputs": [], 388 | "source": [ 389 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 390 | "s = JupyterAsyncGui.Settings()\n", 391 | "s.resolution = [1000,500]\n", 392 | "s.scale = 2\n", 393 | "s.translate = [100,100]\n", 394 | "tb = b2d.testbed.run(AngryShapes, backend=JupyterAsyncGui, gui_settings=s);" 395 | ] 396 | } 397 | ], 398 | "metadata": { 399 | "kernelspec": { 400 | "display_name": "Python 3 (ipykernel)", 401 | "language": "python", 402 | "name": "python3" 403 | }, 404 | "language_info": { 405 | "codemirror_mode": { 406 | "name": "ipython", 407 | "version": 3 408 | }, 409 | "file_extension": ".py", 410 | "mimetype": "text/x-python", 411 | "name": "python", 412 | "nbconvert_exporter": "python", 413 | "pygments_lexer": "ipython3", 414 | "version": "3.10.4" 415 | } 416 | }, 417 | "nbformat": 4, 418 | "nbformat_minor": 5 419 | } 420 | -------------------------------------------------------------------------------- /content/data/Museums_in_DC.geojson: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"OBJECTID":1,"ADDRESS":"716 MONROE STREET NE","NAME":"AMERICAN POETRY MUSEUM","ADDRESS_ID":309744,"LEGALNAME":"HERITAGE US","ALTNAME":"AMERICAN POETRY MUSEUM","WEBURL":" http://americanpoetrymuseum.org/"},"geometry":{"type":"Point","coordinates":[-76.995003703568,38.9328428790235]}},{"type":"Feature","properties":{"OBJECTID":2,"ADDRESS":"719 6TH STREET NW","NAME":"GERMAN-AMERICAN HERITAGE MUSEUM","ADDRESS_ID":238949,"LEGALNAME":"CORCORAN GALLERY OF ART","ALTNAME":" ","WEBURL":"http://gahmusa.org/"},"geometry":{"type":"Point","coordinates":[-77.01958878310639,38.89911061096782]}},{"type":"Feature","properties":{"OBJECTID":3,"ADDRESS":"1307 NEW HAMPSHIRE AVENUE NW","NAME":"HEURICH HOUSE FOUNDATION","ADDRESS_ID":241060,"LEGALNAME":"U.S. DEPARTMENT OF THE INTERIOR MUSEUM","ALTNAME":"HEURICH HOUSE FOUNDATION","WEBURL":"HTTP://HEURICHHOUSE.ORG"},"geometry":{"type":"Point","coordinates":[-77.04460619923155,38.908030206509885]}},{"type":"Feature","properties":{"OBJECTID":4,"ADDRESS":"950 INDEPENDENCE AVENUE SW","NAME":"NATIONAL MUSEUM OF AFRICAN ART","ADDRESS_ID":293262,"LEGALNAME":"BUILDING PRESERVATION FOUNDATION","ALTNAME":"NATIONAL MUSEUM OF AFRICAN ART","WEBURL":"HTTP://AFRICA.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.02550917725944,38.88796214949963]}},{"type":"Feature","properties":{"OBJECTID":5,"ADDRESS":"740 JACKSON PLACE NW","NAME":"THE WHITE HOUSE ENDOWMENT TRUST","ADDRESS_ID":218748,"LEGALNAME":"NATIONAL BUILDING MUSEUM","ALTNAME":"THE WHITE HOUSE ENDOWMENT TRUST","WEBURL":"HTTP://WWW.WHITEHOUSEHISTORY.ORG"},"geometry":{"type":"Point","coordinates":[-77.03820629325264,38.899842529027275]}},{"type":"Feature","properties":{"OBJECTID":6,"ADDRESS":"921 PENNSYLVANIA AVENUE SE","NAME":"OLD NAVAL HOSPITAL FOUNDATION","ADDRESS_ID":82564,"LEGALNAME":"JEWISH WAR VETERANS NATIONAL MEMORIAL MUSEUM ARCHIVES AND LI","ALTNAME":"OLD NAVAL HOSPITAL FOUNDATION","WEBURL":"http://hillcenterdc.org/home/"},"geometry":{"type":"Point","coordinates":[-76.99314290714912,38.8829885933721]}},{"type":"Feature","properties":{"OBJECTID":7,"ADDRESS":"2201 C STREET NW","NAME":"DIPLOMATIC ROOMS FOUNDATION","ADDRESS_ID":243360,"LEGALNAME":"NATIONAL PLASTICS MUSEUM INC","ALTNAME":"DIPLOMATIC ROOMS FOUNDATION","WEBURL":"https://diplomaticrooms.state.gov/home.aspx"},"geometry":{"type":"Point","coordinates":[-77.04831079505838,38.894135140073566]}},{"type":"Feature","properties":{"OBJECTID":8,"ADDRESS":"4400 MASSACHUSETTS AVENUE NW","NAME":"AMERICAN UNIVERSITY MUSEUM AT THE KATZEN ARTS CENTER","ADDRESS_ID":223994,"LEGALNAME":"VERNISSAGE FOUNDATION","ALTNAME":"AMERICAN UNIVERSITY MUSEUM AT THE KATZEN ARTS CENTER","WEBURL":"HTTP://WWW.AMERICAN.EDU/CAS/MUSEUM/"},"geometry":{"type":"Point","coordinates":[-77.08841712551974,38.9390892139132]}},{"type":"Feature","properties":{"OBJECTID":9,"ADDRESS":"2320 S STREET NW","NAME":"TEXTILE MUSEUM","ADDRESS_ID":243164,"LEGALNAME":"SMITHSONIAN INSTITUTION, S. DILLON RIPLEY CENTER","ALTNAME":"TEXTILE MUSEUM","WEBURL":"HTTP://WWW.TEXTILEMUSEUM.ORG"},"geometry":{"type":"Point","coordinates":[-77.0464284034822,38.89880233850966]}},{"type":"Feature","properties":{"OBJECTID":10,"ADDRESS":"1145 17TH STREET NW","NAME":"NATIONAL GEOGRAPHIC MUSEUM","ADDRESS_ID":290192,"LEGALNAME":"CAPITOL HILL RESTORATION SOCIETY INC","ALTNAME":" ","WEBURL":"HTTP://WWW.NATIONALGEOGRAPHIC.COM"},"geometry":{"type":"Point","coordinates":[-77.03815544194862,38.90519711304962]}},{"type":"Feature","properties":{"OBJECTID":11,"ADDRESS":"3501 NEW YORK AVENUE NE","NAME":"THE NATIONAL BONSAI & PENJING MUSEUM","ADDRESS_ID":293238,"LEGALNAME":"NATIONAL BONSAI FOUNDATION","ALTNAME":" ","WEBURL":"https://www.bonsai-nbf.org/contact-us/"},"geometry":{"type":"Point","coordinates":[-76.96989266812075,38.91241055669072]}},{"type":"Feature","properties":{"OBJECTID":12,"ADDRESS":"2020 O STREET NW","NAME":"O STREET MUSEUM","ADDRESS_ID":243057,"LEGALNAME":"LEPIDOPTERISTS SOCIETY","ALTNAME":" ","WEBURL":"http://www.omuseum.org/museum/"},"geometry":{"type":"Point","coordinates":[-77.04592748104784,38.90839101941751]}},{"type":"Feature","properties":{"OBJECTID":13,"ADDRESS":"2101 CONSTITUTION AVENUE NW","NAME":"NATIONAL ACADEMY OF SCIENCES","ADDRESS_ID":242716,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATURAL HISTORY MUSEUM","ALTNAME":"NATIONAL ACADEMY OF SCIENCES","WEBURL":"WWW.NATIONALACADEMIES.ORG/NAS/ARTS"},"geometry":{"type":"Point","coordinates":[-77.0476448925699,38.89296693766957]}},{"type":"Feature","properties":{"OBJECTID":14,"ADDRESS":"2401 FOXHALL ROAD NW","NAME":"KREEGER MUSEUM","ADDRESS_ID":271251,"LEGALNAME":"CONGRESSIONAL CEMETERY","ALTNAME":"KREEGER MUSEUM","WEBURL":"HTTP://WWW.KREEGERMUSEUM.ORG/"},"geometry":{"type":"Point","coordinates":[-77.08878098790044,38.92191197499568]}},{"type":"Feature","properties":{"OBJECTID":15,"ADDRESS":"1250 NEW YORK AVENUE NW","NAME":"THE NATIONAL MUSEUM OF WOMEN IN THE ART","ADDRESS_ID":279010,"LEGALNAME":"NATIONAL MUSEUM OF HEALTH AND MEDICINE","ALTNAME":"THE NATIONAL MUSEUM OF WOMEN IN THE ART","WEBURL":"HTTP://WWW.NMWA.ORG"},"geometry":{"type":"Point","coordinates":[-77.029163689541,38.90005647268176]}},{"type":"Feature","properties":{"OBJECTID":16,"ADDRESS":"900 JEFFERSON DRIVE SW","NAME":"ARTS AND INDUSTRIES BUILDING","ADDRESS_ID":293260,"LEGALNAME":"ANACOSTIA COMMUNITY MUSEUM","ALTNAME":" ","WEBURL":"http://www.si.edu/Museums/arts-and-industries-building"},"geometry":{"type":"Point","coordinates":[-77.02446647929001,38.888201004559114]}},{"type":"Feature","properties":{"OBJECTID":17,"ADDRESS":"736 SICARD STREET SE","NAME":"NATIONAL MUSEUM OF UNITED STATES NAVY","ADDRESS_ID":311896,"LEGALNAME":"BLACK SPORTS LEGENDS FOUNDATION","ALTNAME":"NATIONAL MUSEUM OF UNITED STATES NAVY","WEBURL":"http://www.history.navy.mil/museums/NationalMuseum/org8-1.htm"},"geometry":{"type":"Point","coordinates":[-76.99526950368147,38.87303084860059]}},{"type":"Feature","properties":{"OBJECTID":18,"ADDRESS":"500 17TH STREET NW","NAME":"CORCORAN GALLERY OF ART","ADDRESS_ID":279802,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATIONAL ZOOLOGICAL PARK","ALTNAME":"CORCORAN GALLERY OF ART","WEBURL":"http://www.corcoran.org/"},"geometry":{"type":"Point","coordinates":[-77.0397427304576,38.895854463821884]}},{"type":"Feature","properties":{"OBJECTID":19,"ADDRESS":"2017 I STREET NW","NAME":"THE ARTS CLUB OF WASHINGTON","ADDRESS_ID":285527,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF AFRICAN AMERICAN HISTORY AND CULTURE","ALTNAME":"THE ARTS CLUB OF WASHINGTON","WEBURL":"HTTP://WWW.ARTSCLUBOFWASHINGTON.ORG"},"geometry":{"type":"Point","coordinates":[-77.04573426864144,38.90157618582308]}},{"type":"Feature","properties":{"OBJECTID":20,"ADDRESS":"701 3RD STREET NW","NAME":"LILLIAN AND ALBERT SMALL JEWISH MUSEUM","ADDRESS_ID":293253,"LEGALNAME":"LILLIAN AND ALBERT SMALL JEWISH MUSEUM","ALTNAME":" ","WEBURL":"http://www.jhsgw.org/"},"geometry":{"type":"Point","coordinates":[-77.01493675564363,38.89857205791096]}},{"type":"Feature","properties":{"OBJECTID":21,"ADDRESS":"320 A STREET NE","NAME":"FREDERICK DOUGLASS MUSEUM","ADDRESS_ID":38979,"LEGALNAME":"COSMOS CLUB HISTORIC PRESERVATION FOUNDATION","ALTNAME":" ","WEBURL":"http://www3.nahc.org/fd/"},"geometry":{"type":"Point","coordinates":[-77.00110470253333,38.891131915241964]}},{"type":"Feature","properties":{"OBJECTID":22,"ADDRESS":"1334 G STREET NW","NAME":"ARMENIAN GENOCIDE MUSEUM AND MEMORIAL","ADDRESS_ID":240658,"LEGALNAME":"GERMAN-AMERICAN HERITAGE MUSEUM","ALTNAME":"ARMENIAN GENOCIDE MUSEUM AND MEMORIAL","WEBURL":"http://www.armeniangenocidemuseum.org/"},"geometry":{"type":"Point","coordinates":[-77.03108432435003,38.89804891426683]}},{"type":"Feature","properties":{"OBJECTID":23,"ADDRESS":"1799 NEW YORK AVENUE NW","NAME":"OCTAGON MUSEUM","ADDRESS_ID":218490,"LEGALNAME":"AMERICAN RED CROSS MUSEUM","ALTNAME":" ","WEBURL":"HTTP://WWW.THEOCTAGON.ORG"},"geometry":{"type":"Point","coordinates":[-77.04141820048949,38.89635375607101]}},{"type":"Feature","properties":{"OBJECTID":24,"ADDRESS":"1901 FORT PLACE SE","NAME":"ANACOSTIA COMMUNITY MUSEUM","ADDRESS_ID":286524,"LEGALNAME":"FAUNA & FLORA INTERNATIONAL INC","ALTNAME":"ANACOSTIA COMMUNITY MUSEUM","WEBURL":"HTTP://ANACOSTIA.SI.EDU"},"geometry":{"type":"Point","coordinates":[-76.97678467186984,38.8565826636904]}},{"type":"Feature","properties":{"OBJECTID":25,"ADDRESS":"2312 CALIFORNIA STREET NW","NAME":"NATIONAL MUSEUM OF THE JEWISH PEOPLE","ADDRESS_ID":234961,"LEGALNAME":"GREENSEED COMMUNITY GARDEN LAND TRUST","ALTNAME":" ","WEBURL":"http://www.nsideas.com/archive/nmjh/"},"geometry":{"type":"Point","coordinates":[-77.05118108814123,38.91537084189858]}},{"type":"Feature","properties":{"OBJECTID":26,"ADDRESS":"430 17TH STREET NW","NAME":"AMERICAN RED CROSS MUSEUM","ADDRESS_ID":300987,"LEGALNAME":"DOUBLE M MANAGEMENT","ALTNAME":"AMERICAN RED CROSS MUSEUM","WEBURL":"http://www.redcross.org/"},"geometry":{"type":"Point","coordinates":[-77.04020705622152,38.89482654014118]}},{"type":"Feature","properties":{"OBJECTID":27,"ADDRESS":"1600 21ST STREET NW","NAME":"THE PHILLIPS COLLECTION","ADDRESS_ID":243333,"LEGALNAME":"SMITHSONIAN INSTITUTION, RENWICK GALLERY","ALTNAME":"THE PHILLIPS COLLECTION","WEBURL":"HTTP://WWW.PHILLIPSCOLLECTION.ORG"},"geometry":{"type":"Point","coordinates":[-77.04685454590388,38.91150979086159]}},{"type":"Feature","properties":{"OBJECTID":28,"ADDRESS":"800 F STREET NW","NAME":"INTERNATIONAL SPY MUSEUM","ADDRESS_ID":238378,"LEGALNAME":"CONFEDERATE MEMORIAL HALL ASSOCIATION","ALTNAME":"INTERNATIONAL SPY MUSEUM","WEBURL":"HTTP://WWW.SPYMUSEUM.ORG/"},"geometry":{"type":"Point","coordinates":[-77.02328618491306,38.896986480912865]}},{"type":"Feature","properties":{"OBJECTID":29,"ADDRESS":"100 RAOUL WALLENBERG PLACE SW","NAME":"UNITED STATES HOLOCAUST MEMORIAL MUSEUM","ADDRESS_ID":293186,"LEGALNAME":"NATIONAL MUSIC CENTER AND MUSEUM FOUNDATION","ALTNAME":"UNITED STATES HOLOCAUST MEMORIAL MUSEUM","WEBURL":"HTTP://WWW.USHMM.ORG"},"geometry":{"type":"Point","coordinates":[-77.03268853739414,38.88668873773371]}},{"type":"Feature","properties":{"OBJECTID":30,"ADDRESS":"801 K STREET NW","NAME":"HISTORICAL SOCIETY OF WASHINGTON DC","ADDRESS_ID":238956,"LEGALNAME":"Historical Society of Washington, D.C","ALTNAME":" ","WEBURL":"http://www.dchistory.org/"},"geometry":{"type":"Point","coordinates":[-77.02294505078932,38.90262956584554]}},{"type":"Feature","properties":{"OBJECTID":31,"ADDRESS":"1849 C STREET NW","NAME":"INTERIOR MUSEUM","ADDRESS_ID":293214,"LEGALNAME":"VICE PRESIDENTS RESIDENCE FOUNDATION","ALTNAME":"INTERIOR MUSEUM","WEBURL":"HTTP://WWW.DOI.GOV/INTERIORMUSEUM"},"geometry":{"type":"Point","coordinates":[-77.04260256434321,38.89445283458921]}},{"type":"Feature","properties":{"OBJECTID":32,"ADDRESS":"4155 LINNEAN AVENUE NW","NAME":"HILLWOOD MUSEUM & GARDENS","ADDRESS_ID":284839,"LEGALNAME":"SMITHSONIAN INSTITUTION, NATIONAL GALLERY OF ART","ALTNAME":"HILLWOOD MUSEUM & GARDENS","WEBURL":"WWW.HILLWOODMUSEUM.ORG"},"geometry":{"type":"Point","coordinates":[-77.0526196505072,38.94364171194315]}},{"type":"Feature","properties":{"OBJECTID":33,"ADDRESS":"1318 VERMONT AVENUE NW","NAME":"BETHUNE MEMORIAL MUSEUM","ADDRESS_ID":225385,"LEGALNAME":"NATIONAL MUSEUM OF WOMEN IN THE ARTS INC","ALTNAME":" ","WEBURL":"http://www.nps.gov/mamc/index.htm"},"geometry":{"type":"Point","coordinates":[-77.03086564182146,38.90817580546652]}},{"type":"Feature","properties":{"OBJECTID":34,"ADDRESS":"1500 MASSACHUSETTS AVENUE NW","NAME":"NATIONAL MUSEUM OF CATHOLIC ART AND LIBRARY","ADDRESS_ID":242324,"LEGALNAME":"KREEGER MUSEUM","ALTNAME":" ","WEBURL":"http://nmcal.org/nmcah_exhibition_in_washington.html"},"geometry":{"type":"Point","coordinates":[-77.03551120800971,38.90651019329394]}},{"type":"Feature","properties":{"OBJECTID":35,"ADDRESS":"1 MASSACHUSETTS AVENUE NW","NAME":"NATIONAL GUARD MEMORIAL MUSEUM","ADDRESS_ID":238009,"LEGALNAME":"CARL SCHMITT FOUNDATION INC","ALTNAME":" ","WEBURL":"HTTP://WWW.NGEF.ORG"},"geometry":{"type":"Point","coordinates":[-77.00956143652462,38.89812580681995]}},{"type":"Feature","properties":{"OBJECTID":36,"ADDRESS":"1811 R STREET NW","NAME":"NATIONAL MUSEUM OF AMERICAN JEWISH MILITARY HISTORY","ADDRESS_ID":243292,"LEGALNAME":"CITY TAVERN PRESERVATION FOUNDATION","ALTNAME":"JEWISH WAR VETERANS NATIONAL MEMORIAL MUSEUM ARCHIVES AND LIBRARY","WEBURL":"http://www.nmajmh.org/"},"geometry":{"type":"Point","coordinates":[-77.04211577477285,38.91282059721026]}},{"type":"Feature","properties":{"OBJECTID":37,"ADDRESS":"3900 HAREWOOD ROAD NE","NAME":"POPE JOHN PAUL II CULTURAL CENTER","ADDRESS_ID":288031,"LEGALNAME":"AMERICAN POETRY MUSEUM","ALTNAME":" ","WEBURL":"HTTP://WWW.JP2CC.ORG"},"geometry":{"type":"Point","coordinates":[-77.00466710351098,38.93776654366721]}},{"type":"Feature","properties":{"OBJECTID":38,"ADDRESS":"700 PENNSYLVANIA AVENUE NW","NAME":"NATIONAL ARCHIVES MUSEUM","ADDRESS_ID":293251,"LEGALNAME":"PHILLIPS COLLECTION","ALTNAME":"NATIONAL ARCHIVES MUSEUM","WEBURL":"https://www.archives.gov/dc-metro/washington/"},"geometry":{"type":"Point","coordinates":[-77.0228592459719,38.89285370583677]}},{"type":"Feature","properties":{"OBJECTID":39,"ADDRESS":"201 18TH STREET NW","NAME":"ART MUSEUM OF THE AMERICAS","ADDRESS_ID":294191,"LEGALNAME":"Art Museum of the Americas","ALTNAME":" ","WEBURL":"http://www.museum.oas.org/"},"geometry":{"type":"Point","coordinates":[-77.04147388756545,38.892799844291474]}},{"type":"Feature","properties":{"OBJECTID":40,"ADDRESS":"9 HILLYER COURT NW","NAME":"INTERNATIONAL ARTS & ARTISTS","ADDRESS_ID":279975,"LEGALNAME":"THE INTERNATIONAL SPY MUSEUM","ALTNAME":"INTERNATIONAL ARTS & ARTISTS","WEBURL":"WWW.ARTSANDARTISTS.ORG"},"geometry":{"type":"Point","coordinates":[-77.04730884101534,38.91222144699389]}},{"type":"Feature","properties":{"OBJECTID":41,"ADDRESS":"2 MASSACHUSETTS AVENUE NE","NAME":"NATIONAL POSTAL MUSEUM","ADDRESS_ID":293217,"LEGALNAME":"BEAD SOCIETY OF GREATER WASHINGTON","ALTNAME":"NATIONAL POSTAL MUSEUM","WEBURL":"HTTP://POSTALMUSEUM.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.00819124512859,38.8981463599396]}},{"type":"Feature","properties":{"OBJECTID":42,"ADDRESS":"1519 MONROE STREET NW","NAME":"POWHATAN MUSEUM","ADDRESS_ID":234557,"LEGALNAME":"AMERICAN UNIVERSITY MUSEUM","ALTNAME":" ","WEBURL":"http://www.powhatanmuseum.com/"},"geometry":{"type":"Point","coordinates":[-77.03550660261739,38.93243814726252]}},{"type":"Feature","properties":{"OBJECTID":43,"ADDRESS":"144 CONSTITUTION AVENUE NE","NAME":"SEWALL-BELMONT HOUSE AND MUSEUM","ADDRESS_ID":286201,"LEGALNAME":"AMERICAN MUSEUM OF PEACE INC","ALTNAME":" ","WEBURL":"HTTP://WWW.SEWALLBELMONT.ORG"},"geometry":{"type":"Point","coordinates":[-77.00375845550963,38.89219466787653]}},{"type":"Feature","properties":{"OBJECTID":44,"ADDRESS":"802 MASSACHUSETTS AVENUE NE","NAME":"SHOOK MUSEUM FOUNDATION","ADDRESS_ID":79669,"LEGALNAME":"GREENPEACE FUND","ALTNAME":" ","WEBURL":"SHOOKMUSEUM.ORG"},"geometry":{"type":"Point","coordinates":[-76.9944246526475,38.891834530779185]}},{"type":"Feature","properties":{"OBJECTID":45,"ADDRESS":"1400 CONSTITUTION AVENUE NW","NAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF NATURAL HISTORY","ADDRESS_ID":310702,"LEGALNAME":"B'NAI B'RITH KLUTZNICK MUSEUM","ALTNAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF NATURAL HISTORY","WEBURL":"http://www.mnh.si.edu/"},"geometry":{"type":"Point","coordinates":[-77.02591603234607,38.89121850995097]}},{"type":"Feature","properties":{"OBJECTID":46,"ADDRESS":"500 HOWARD PLACE NW","NAME":"HOWARD UNIVERSITY MUSEUM","ADDRESS_ID":243398,"LEGALNAME":"COLLECTONS STRIES AMRCN MSLIMS","ALTNAME":" ","WEBURL":"http://www.coas.howard.edu/msrc/museum.html"},"geometry":{"type":"Point","coordinates":[-77.0196991986925,38.922360224748935]}},{"type":"Feature","properties":{"OBJECTID":47,"ADDRESS":"8TH STREET NW AND F ST NW","NAME":"NATIONAL PORTRAIT GALLERY","ADDRESS_ID":294248,"LEGALNAME":"BOHEMIA ARTS","ALTNAME":"NATIONAL PORTRAIT GALLERY","WEBURL":"HTTP://WWW.NPG.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.02295571583119,38.89815890118559]}},{"type":"Feature","properties":{"OBJECTID":48,"ADDRESS":"14TH STREET NW AND CONSTITUTION AVENUE NW","NAME":"NATIONAL MUSEUM OF AFRICAN AMERICAN HISTORY AND CULTURE","ADDRESS_ID":903110,"LEGALNAME":"AMERICANS FOR BATTLEFIELD PRESERVATION","ALTNAME":"NATIONAL MUSEUM OF AFRICAN AMERICAN HISTORY AND CULTURE","WEBURL":"HTTP://WWW.NMAAHC.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.03271597832732,38.89119983415094]}},{"type":"Feature","properties":{"OBJECTID":49,"ADDRESS":"4TH STREET SW AND INDEPENDENCE AVENUE SW","NAME":"NATIONAL MUSEUM OF AMERICAN INDIAN","ADDRESS_ID":294429,"LEGALNAME":"BLAIR HOUSE RESTORATION FUND","ALTNAME":" ","WEBURL":"WWW.NMAI.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.01672595283219,38.88826561652]}},{"type":"Feature","properties":{"OBJECTID":50,"ADDRESS":"6TH STREET SW AND INDEPENDENCE AVENUE SW","NAME":"NATIONAL AIR AND SPACE MUSEUM","ADDRESS_ID":301565,"LEGALNAME":"BETHUNE MEMORIAL MUSEUM","ALTNAME":"NATIONAL AIR AND SPACE MUSEUM","WEBURL":"HTTP://WWW.NASM.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.01979999825605,38.888161175521944]}},{"type":"Feature","properties":{"OBJECTID":51,"ADDRESS":"7THB STREET AND INDEPENDENCE AVENUE SW","NAME":"HIRSHHORN MUSEUM AND SCULPTURE GARDEN","ADDRESS_ID":294428,"LEGALNAME":"D.C. OFFICE OF PUBLIC RECORDS AND ARCHIVES","ALTNAME":"HIRSHHORN MUSEUM AND SCULPTURE GARDEN","WEBURL":"HTTP://HIRSHHORN.SI.EDU/"},"geometry":{"type":"Point","coordinates":[-77.02294902891254,38.88843565656003]}},{"type":"Feature","properties":{"OBJECTID":52,"ADDRESS":"MADISON DRIVE NW AND 12TH STREET NW","NAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF AMERICAN HISTORY","ADDRESS_ID":293200,"LEGALNAME":null,"ALTNAME":"SMITHSONIAN INSTITUTION, NATIONAL MUSEUM OF AMERICAN HISTORY","WEBURL":"HTTP://AMERICANHISTORY.SI.EDU"},"geometry":{"type":"Point","coordinates":[-77.03005156534492,38.89123181993075]}},{"type":"Feature","properties":{"OBJECTID":53,"ADDRESS":"4TH STREET NW AND MADISON DRIVE NW","NAME":"NATIONAL GALLERY OF ART - EAST BUILDING","ADDRESS_ID":293209,"LEGALNAME":null,"ALTNAME":null,"WEBURL":"http://www.nga.gov/content/ngaweb/visit/maps-and-information/east-building.html"},"geometry":{"type":"Point","coordinates":[-77.01668919569053,38.89125721273486]}},{"type":"Feature","properties":{"OBJECTID":54,"ADDRESS":"4TH STREET NW AND MADISON DRIVE NW","NAME":"NATIONAL GALLERY OF ART - WEST BUILDING","ADDRESS_ID":293249,"LEGALNAME":null,"ALTNAME":null,"WEBURL":"http://www.nga.gov/content/ngaweb/visit/maps-and-information/west-building.html"},"geometry":{"type":"Point","coordinates":[-77.01989150273015,38.891313914429645]}},{"type":"Feature","properties":{"OBJECTID":55,"ADDRESS":"1000 JEFFERSON DRIVE SW","NAME":"SMITHSONIAN INSTITUTION - CASTLE","ADDRESS_ID":293187,"LEGALNAME":null,"ALTNAME":null,"WEBURL":"http://www.si.edu/Museums/smithsonian-institution-building"},"geometry":{"type":"Point","coordinates":[-77.02597189316775,38.88879577572046]}},{"type":"Feature","properties":{"OBJECTID":56,"ADDRESS":"1050 INDEPENDENCE AVENUE SW","NAME":"SACKLER GALLERY","ADDRESS_ID":293191,"LEGALNAME":"ARTHUR M. SACKLER GALLERY","ALTNAME":null,"WEBURL":"http://www.asia.si.edu/"},"geometry":{"type":"Point","coordinates":[-77.02645343758842,38.88796502751886]}},{"type":"Feature","properties":{"OBJECTID":57,"ADDRESS":"JEFFERSON DRIVE SW AND 12TH STREET SW","NAME":"FREER GALLERY","ADDRESS_ID":294417,"LEGALNAME":"FREER GALLERY OF ART","ALTNAME":null,"WEBURL":"http://www.asia.si.edu/"},"geometry":{"type":"Point","coordinates":[-77.02736845485786,38.8882746680144]}}]} -------------------------------------------------------------------------------- /content/pyodide/pyb2d/0_tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "b07a3b47-2262-4135-a1d2-52e8392b44eb", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import sys\n", 11 | "if \"pyodide\" in sys.modules:\n", 12 | " import piplite\n", 13 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')\n" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "id": "49c3f9ea-23ce-4c5c-b3fe-44f1cecadf20", 19 | "metadata": {}, 20 | "source": [ 21 | "pyb2d is imported as b2d" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "dff93359-2c68-467a-9239-478a0e550a4b", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "import b2d\n", 32 | "# import pyb2d_jupyterlite_backend\n", 33 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 34 | "import numpy as np\n", 35 | "import matplotlib.pylab as plt" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "id": "bc977c4e-75ee-4349-9408-650c3dcd01e0", 41 | "metadata": {}, 42 | "source": [ 43 | "# Tutorial 0: A free falling body\n", 44 | "The first step with Box2D is the creation of the world. The world is parametrized by a gravity vector." 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "id": "4ff914a6-eb18-45a1-b1ed-e8ad7ab0d298", 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "# the world\n", 55 | "gravity = (0, -10)\n", 56 | "world = b2d.World(gravity)" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "id": "3afdbb2a-e694-4779-b95e-73a5b38d34b6", 62 | "metadata": {}, 63 | "source": [ 64 | "Create a circle-shaped body" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "id": "99837a63-4628-483c-8f2d-cc4aec9cb1d5", 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "# the body def\n", 75 | "body_def = b2d.BodyDef()\n", 76 | "body_def.type = b2d.BodyType.dynamic\n", 77 | "body_def.position = (0, 0)\n", 78 | "\n", 79 | "# the body\n", 80 | "body = world.create_body(body_def)\n", 81 | "\n", 82 | "# shape\n", 83 | "circle_shape = b2d.CircleShape()\n", 84 | "circle_shape.radius = 1.0\n", 85 | "\n", 86 | "# the fixture\n", 87 | "fixture_def = b2d.FixtureDef()\n", 88 | "fixture_def.shape = circle_shape\n", 89 | "fixture_def.density = 1.0\n", 90 | "\n", 91 | "# create and add the fixture to the body\n", 92 | "fixture = body.create_fixture(fixture_def)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "id": "bf9758a6-fb6e-4f9c-b15f-783f9488cf7e", 98 | "metadata": {}, 99 | "source": [ 100 | "We can now have a look at the world: We render the world st. each meter in the Box2D world will be 100 pixels in the image:" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "id": "8b433892-3c82-43be-a085-eda3e4279b2c", 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "# from b2d.plot import render_world\n", 111 | "b2d.plot.plot_world(world, ppm=100)" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "id": "5e1db1f1-6e47-454c-9ea9-86262d7da309", 117 | "metadata": {}, 118 | "source": [ 119 | "Lets run the world for a total of 5 seconds. \n", 120 | "Usually one wants to run the world at a certain frame rate.\n", 121 | "With the frame rate and the total time we can compute the delta for each iteration and how many steps we need" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "id": "41a232a9-a3c5-425d-9aed-d3adb90d6314", 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "t = 5\n", 132 | "fps = 40\n", 133 | "dt = 1.0 / fps\n", 134 | "n_steps = int(t / dt + 0.5)\n", 135 | "print(f\"t={t} fps={fps} dt={dt} n_steps={n_steps}\")" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "id": "4d458acb-6d5c-47ba-bcbf-d15ea2cf2537", 141 | "metadata": {}, 142 | "source": [ 143 | "in each step we query the bodies position and velocity and store then for later plotting" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": null, 149 | "id": "4e042c7b-07a7-445f-ba04-e38173b46c0f", 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "positions = np.zeros([n_steps, 2])\n", 154 | "velocites = np.zeros([n_steps, 2])\n", 155 | "timepoints = np.zeros([n_steps])\n", 156 | "\n", 157 | "t_elapsed = 0.0\n", 158 | "for i in range(n_steps):\n", 159 | "\n", 160 | " # get the bodies center of mass\n", 161 | " positions[i, :] = body.world_center\n", 162 | "\n", 163 | " # get the bodies velocity\n", 164 | " velocites[i, :] = body.linear_velocity\n", 165 | "\n", 166 | " timepoints[i] = t_elapsed\n", 167 | "\n", 168 | " world.step(time_step=dt, velocity_iterations=1, position_iterations=1)\n", 169 | " t_elapsed += dt" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "id": "0ec7d66c-c979-40fa-8af3-9e99873ec105", 175 | "metadata": {}, 176 | "source": [ 177 | "plot the y-position against the time. We can see that the body is falling down in an accelerating way:" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "id": "434cb907-1b76-414e-bb5e-6ea32dd1f829", 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [ 187 | "plt.plot(timepoints, positions[:, 1])\n", 188 | "plt.ylabel('y-poistion [meter]')\n", 189 | "plt.xlabel('t [sec]')\n", 190 | "plt.show()" 191 | ] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "id": "b7f58954-4ea1-49f9-b0b0-7df38336860d", 196 | "metadata": {}, 197 | "source": [ 198 | "as expected the x position is not changing since the gravity vector is non-zero only in the x direction" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "id": "39573eed-e6c8-45bf-8e35-4251b660ce3f", 205 | "metadata": { 206 | "tags": [] 207 | }, 208 | "outputs": [], 209 | "source": [ 210 | "plt.plot(timepoints, positions[:, 0])\n", 211 | "plt.ylabel('x-poistion [meter]')\n", 212 | "plt.xlabel('t [sec]')\n", 213 | "plt.show()" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "id": "cdb98dc5-3bc8-4933-91a0-a1db3afb9c34", 219 | "metadata": {}, 220 | "source": [ 221 | "# Tutorial 1: A falling body in a box, more pythonic\n", 222 | "Create a world, but in a more pythonic way, and animate the world" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": null, 228 | "id": "d58c2639-21da-490b-8dcd-205962f63dfc", 229 | "metadata": {}, 230 | "outputs": [], 231 | "source": [ 232 | "# the world\n", 233 | "world = b2d.world(gravity=(0, -10))\n", 234 | "\n", 235 | "# create the dynamic body\n", 236 | "body = world.create_dynamic_body(\n", 237 | " position=(5, 5),\n", 238 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.75),\n", 239 | ")\n", 240 | "\n", 241 | "# create a box\n", 242 | "box_shape = b2d.ChainShape()\n", 243 | "box_shape.create_loop([(0, 0), (0, 10),(10,10),(10, 0)])\n", 244 | "box = world.create_static_body(\n", 245 | " position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n", 246 | ")\n", 247 | "b2d.plot.animate_world(world, ppm=20, t=10)" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "id": "10dbb85a-84b0-4820-8fb3-6108d9c0fe00", 253 | "metadata": {}, 254 | "source": [ 255 | "note that when we animate that world again, the body has already been fallen" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": null, 261 | "id": "c919702c-7c62-4d87-bf1f-df5027d72a83", 262 | "metadata": {}, 263 | "outputs": [], 264 | "source": [ 265 | "b2d.plot.animate_world(world, ppm=20, t=2)" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "id": "7322e9c5-8608-4375-81ed-766cbb2af927", 271 | "metadata": {}, 272 | "source": [ 273 | "# Tutorial 2: Interactive worlds\n", 274 | "While animating the world already is already nice, interacting with the world is even better.\n", 275 | "pyb2d has a framwork to interact with the world for multiple backends.\n", 276 | "This framework is called `TestbedBase` since you can \"test\" your world in an interactive way" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "id": "97cddd47-5a88-4cae-8543-cfcdf658255a", 283 | "metadata": {}, 284 | "outputs": [], 285 | "source": [ 286 | "from b2d.testbed import TestbedBase\n", 287 | "\n", 288 | "class InteractiveExample(TestbedBase):\n", 289 | " def __init__(self, settings=None):\n", 290 | " super(InteractiveExample, self).__init__(settings=settings)\n", 291 | " # create two balls\n", 292 | " body = self.world.create_dynamic_body(position=(5, 5),\n", 293 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.5),\n", 294 | " )\n", 295 | " body = self.world.create_dynamic_body(position=(8, 5),\n", 296 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1, restitution=0.8),\n", 297 | " )\n", 298 | " # create a box\n", 299 | " box_shape = b2d.ChainShape()\n", 300 | " box_shape.create_loop([(0, 0), (0, 10),(10,10),(10, 0)])\n", 301 | " box = self.world.create_static_body(\n", 302 | " position=(0, 0), fixtures=b2d.fixture_def(shape=box_shape, friction=0)\n", 303 | " )\n", 304 | " \n", 305 | "s = JupyterAsyncGui.Settings()\n", 306 | "s.resolution = [300,300]\n", 307 | "b2d.testbed.run(InteractiveExample, backend=JupyterAsyncGui, gui_settings=s);" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "id": "64bf55d1-4117-4af0-8f1f-65de33751743", 313 | "metadata": { 314 | "tags": [] 315 | }, 316 | "source": [ 317 | "# Tutorial 3: Joints" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "id": "07147cab-23be-4406-85f7-4b3d174e3954", 323 | "metadata": { 324 | "tags": [] 325 | }, 326 | "source": [ 327 | "## Tutorial 3.1: Prismatic Joint" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": null, 333 | "id": "9a2d178d-33d7-4c51-b0ff-f66c98cac673", 334 | "metadata": {}, 335 | "outputs": [], 336 | "source": [ 337 | "world = b2d.world(gravity=(0, -10))\n", 338 | "anchor_body = world.create_static_body(position=(0, 0))\n", 339 | "b = world.create_dynamic_body(\n", 340 | " position=(10, 10),\n", 341 | " fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n", 342 | " linear_damping=0.0,\n", 343 | " angular_damping=0.0,\n", 344 | ")\n", 345 | "world.create_prismatic_joint(anchor_body, b, local_axis_a=(1, 1))\n", 346 | "b2d.plot.animate_world(world, ppm=20, t=3, bounding_box=((0,0),(10,10)))" 347 | ] 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "id": "07971d69-b2ef-4d74-8c1c-48f38dcc708c", 352 | "metadata": { 353 | "tags": [] 354 | }, 355 | "source": [ 356 | "## Tutorial 3.2: Pully Joint" 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": null, 362 | "id": "39b7fef2-4b4b-4904-9899-1a34f1039693", 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "world = b2d.world(gravity=(0, -10))\n", 367 | "\n", 368 | "\n", 369 | "a = world.create_dynamic_body(\n", 370 | " position=(-5, 0),\n", 371 | " fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.8]), density=1),\n", 372 | " linear_damping=0.0,\n", 373 | " angular_damping=0.0,\n", 374 | ")\n", 375 | "b = world.create_dynamic_body(\n", 376 | " position=(5, 0),\n", 377 | " fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n", 378 | " linear_damping=0.0,\n", 379 | " angular_damping=0.0,\n", 380 | ")\n", 381 | "world.create_pully_joint(\n", 382 | " a,\n", 383 | " b,\n", 384 | " length_a=10,\n", 385 | " length_b=10,\n", 386 | " ground_anchor_a=(-5, 10),\n", 387 | " ground_anchor_b=(5, 10),\n", 388 | " local_anchor_a=(0, 0),\n", 389 | " local_anchor_b=(0, 0),\n", 390 | ")\n", 391 | "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((-10,-12),(10,12)))" 392 | ] 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "id": "7e8fb17d-1dda-45cb-98df-6b98be5b4e6c", 397 | "metadata": {}, 398 | "source": [ 399 | "## Tutorial 3.3: Revolute Joint" 400 | ] 401 | }, 402 | { 403 | "cell_type": "code", 404 | "execution_count": null, 405 | "id": "a2686e5f-3fa2-412d-8a40-2e9a67123d43", 406 | "metadata": {}, 407 | "outputs": [], 408 | "source": [ 409 | "world = b2d.world(gravity=(0, -10))\n", 410 | "bodies = []\n", 411 | "b = world.create_static_body(position=(0, 15))\n", 412 | "bodies.append(b)\n", 413 | "for i in range(5):\n", 414 | " b = world.create_dynamic_body(\n", 415 | " position=(i * 4 + 2, 15),\n", 416 | " fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[2, 0.5]), density=1),\n", 417 | " linear_damping=0.0,\n", 418 | " angular_damping=0.0,\n", 419 | " )\n", 420 | " bodies.append(b)\n", 421 | "world.create_revolute_joint(\n", 422 | " bodies[0], bodies[1], local_anchor_a=(0, 0), local_anchor_b=(-2, 0.0)\n", 423 | ")\n", 424 | "for i in range(1, len(bodies) - 1):\n", 425 | " a = bodies[i]\n", 426 | " b = bodies[i + 1]\n", 427 | " world.create_revolute_joint(a, b, local_anchor_a=(2, 0.0), local_anchor_b=(-2, 0.0))\n", 428 | "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((-20,-10),(20,20)))" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "id": "9caa18f0-4eb7-4e72-8445-8f6d096d9465", 434 | "metadata": { 435 | "tags": [] 436 | }, 437 | "source": [ 438 | "## Tutorial 3.4: Weld Joint" 439 | ] 440 | }, 441 | { 442 | "cell_type": "code", 443 | "execution_count": null, 444 | "id": "3d74fb2f-7da5-4ad7-8f21-fba1f97acee2", 445 | "metadata": {}, 446 | "outputs": [], 447 | "source": [ 448 | "# the world\n", 449 | "world = b2d.world(gravity=(0, -10))\n", 450 | "\n", 451 | "\n", 452 | "bodies = []\n", 453 | "\n", 454 | "# create a static body as anchor\n", 455 | "b = world.create_static_body(\n", 456 | " position=(0, 4), fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[0.3, 0.5]))\n", 457 | ")\n", 458 | "bodies.append(b)\n", 459 | "\n", 460 | "for i in range(4):\n", 461 | " b = world.create_dynamic_body(\n", 462 | " position=(i + 1.0, 4),\n", 463 | " fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[0.3, 0.5]), density=0.1),\n", 464 | " linear_damping=2.5,\n", 465 | " angular_damping=2.5,\n", 466 | " )\n", 467 | " bodies.append(b)\n", 468 | "\n", 469 | "for i in range(len(bodies) - 1):\n", 470 | " a = bodies[i]\n", 471 | " b = bodies[i + 1]\n", 472 | " world.create_weld_joint(\n", 473 | " a,\n", 474 | " b,\n", 475 | " local_anchor_a=(0.5, 0.5),\n", 476 | " local_anchor_b=(-0.5, 0.5),\n", 477 | " damping=0.1,\n", 478 | " reference_angle=0,\n", 479 | " stiffness=20,\n", 480 | " )\n", 481 | " world.create_weld_joint(\n", 482 | " a,\n", 483 | " b,\n", 484 | " local_anchor_a=(0.5, -0.5),\n", 485 | " local_anchor_b=(-0.5, -0.5),\n", 486 | " damping=0.1,\n", 487 | " reference_angle=0,\n", 488 | " stiffness=20,\n", 489 | " )\n", 490 | "b2d.plot.animate_world(world, ppm=20, t=5, bounding_box=((0,-5),(5,5)))" 491 | ] 492 | }, 493 | { 494 | "cell_type": "markdown", 495 | "id": "7373461e-d1fa-4ad9-aeaa-048287839fd9", 496 | "metadata": {}, 497 | "source": [ 498 | "## Tutorial 3.5: Wheel Joint" 499 | ] 500 | }, 501 | { 502 | "cell_type": "code", 503 | "execution_count": null, 504 | "id": "1eb711e0-ae53-43ed-b0c1-c0a2fe42b407", 505 | "metadata": {}, 506 | "outputs": [], 507 | "source": [ 508 | "world = b2d.world(gravity=(0, -10))\n", 509 | "edge = world.create_static_body(\n", 510 | " position=(0, 0), fixtures=b2d.fixture_def(shape=b2d.edge_shape([(-20, 0), (5, 0)]))\n", 511 | ")\n", 512 | "\n", 513 | "# random slope\n", 514 | "x = np.linspace(5, 50, 10)\n", 515 | "y = np.random.rand(10) * 4 - 2\n", 516 | "y[0] = 0\n", 517 | "xy = np.stack([x, y]).T\n", 518 | "xy = np.flip(xy, axis=0)\n", 519 | "edge = world.create_static_body(\n", 520 | " position=(0, 0),\n", 521 | " fixtures=b2d.fixture_def(shape=b2d.chain_shape(xy, prev_vertex=(10, 0))),\n", 522 | ")\n", 523 | "# create car\n", 524 | "left_wheel = world.create_dynamic_body(\n", 525 | " position=(-3, 2),\n", 526 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=2), density=1),\n", 527 | ")\n", 528 | "right_wheel = world.create_dynamic_body(\n", 529 | " position=(3, 2),\n", 530 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=2), density=1),\n", 531 | ")\n", 532 | "\n", 533 | "chasis = world.create_dynamic_body(\n", 534 | " position=(0, 2),\n", 535 | " fixtures=b2d.fixture_def(shape=b2d.polygon_shape(box=[3, 0.5]), density=1),\n", 536 | ")\n", 537 | "\n", 538 | "wheel_joint_def = dict(\n", 539 | " stiffness=10,\n", 540 | " enable_motor=True,\n", 541 | " motor_speed=-100,\n", 542 | " max_motor_torque=100,\n", 543 | " collide_connected=False,\n", 544 | " enable_limit=True,\n", 545 | " lower_translation=-0.4,\n", 546 | " upper_translation=0.4,\n", 547 | " local_axis_a=(0, 1),\n", 548 | ")\n", 549 | "world.create_wheel_joint(chasis, left_wheel, local_anchor_a=(-3, 0), **wheel_joint_def)\n", 550 | "world.create_wheel_joint(chasis, right_wheel, local_anchor_a=(3, 0), **wheel_joint_def)\n", 551 | "\n", 552 | "\n", 553 | "b2d.plot.animate_world(world, ppm=20, t=15, bounding_box=((-10,-5),(20,5)))" 554 | ] 555 | }, 556 | { 557 | "cell_type": "markdown", 558 | "id": "d9a0fffc-ae40-47ec-b185-2a6fe0dde496", 559 | "metadata": {}, 560 | "source": [ 561 | "## Tutorial 3.6: Distance Joint" 562 | ] 563 | }, 564 | { 565 | "cell_type": "code", 566 | "execution_count": null, 567 | "id": "3c121398-f08f-4ea0-a875-de141ba53508", 568 | "metadata": {}, 569 | "outputs": [], 570 | "source": [ 571 | "world = b2d.world(gravity=(0, -10))\n", 572 | "\n", 573 | "for i in range(10):\n", 574 | "\n", 575 | " # create static anchor (does not need shape/fixture)\n", 576 | " anchor = world.create_static_body(position=(i, 0))\n", 577 | "\n", 578 | " # 5 below the anchor\n", 579 | " body = world.create_dynamic_body(\n", 580 | " position=(i, -10),\n", 581 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=0.4), density=0.5),\n", 582 | " )\n", 583 | "\n", 584 | " # distance joints of various stiffness-es\n", 585 | " world.create_distance_joint(anchor, body, length=10, stiffness=0.5 * (i + 1))\n", 586 | "\n", 587 | "b2d.plot.animate_world(world, ppm=20, t=10, bounding_box=((-2,-20),(10,0)))" 588 | ] 589 | }, 590 | { 591 | "cell_type": "markdown", 592 | "id": "fb6afaff-8236-4206-85c7-3ba2de466ba9", 593 | "metadata": {}, 594 | "source": [ 595 | "# Tutorial 4: Particles" 596 | ] 597 | }, 598 | { 599 | "cell_type": "code", 600 | "execution_count": null, 601 | "id": "8f5c3b83-51d9-47cb-9b73-d5f8b0e03a76", 602 | "metadata": {}, 603 | "outputs": [], 604 | "source": [ 605 | "world = b2d.world(gravity=(0, -10))\n", 606 | "pdef = b2d.particle_system_def(radius=0.1)\n", 607 | "psystem = world.create_particle_system(pdef)\n", 608 | "\n", 609 | "emitter_pos = (0, 0)\n", 610 | "emitter_def = b2d.RandomizedLinearEmitterDef()\n", 611 | "emitter_def.emite_rate = 400\n", 612 | "emitter_def.lifetime = 5.1\n", 613 | "emitter_def.size = (2, 1)\n", 614 | "emitter_def.velocity = (6, 20)\n", 615 | "emitter = b2d.RandomizedLinearEmitter(psystem, emitter_def)\n", 616 | "b2d.plot.animate_world(world, ppm=20, t=10, bounding_box=((-10,-20),(20,5)), pre_step=emitter.step)" 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "execution_count": null, 622 | "id": "ea9d7882-d3a4-45bb-b59b-cb1c9cd33990", 623 | "metadata": {}, 624 | "outputs": [], 625 | "source": [] 626 | } 627 | ], 628 | "metadata": { 629 | "kernelspec": { 630 | "display_name": "Python 3 (ipykernel)", 631 | "language": "python", 632 | "name": "python3" 633 | }, 634 | "language_info": { 635 | "codemirror_mode": { 636 | "name": "ipython", 637 | "version": 3 638 | }, 639 | "file_extension": ".py", 640 | "mimetype": "text/x-python", 641 | "name": "python", 642 | "nbconvert_exporter": "python", 643 | "pygments_lexer": "ipython3", 644 | "version": "3.10.4" 645 | } 646 | }, 647 | "nbformat": 4, 648 | "nbformat_minor": 5 649 | } 650 | -------------------------------------------------------------------------------- /content/pyodide/pyb2d/games/goo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import sys\n", 10 | "if \"pyodide\" in sys.modules:\n", 11 | " import piplite\n", 12 | " await piplite.install('networkx')\n", 13 | " await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import b2d\n", 23 | "from b2d.testbed import TestbedBase\n", 24 | "import math\n", 25 | "import random\n", 26 | "import numpy\n", 27 | "from functools import partial\n", 28 | "import networkx\n", 29 | "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n", 30 | "\n", 31 | "def best_pairwise_distance(data, f, distance):\n", 32 | " n = len(data)\n", 33 | " best = (None, None, float(\"inf\"))\n", 34 | " for i in range(n - 1):\n", 35 | " da = f(data[i])\n", 36 | " for j in range(i + 1, n):\n", 37 | " db = f(data[j])\n", 38 | "\n", 39 | " d = distance(da, db)\n", 40 | " if d < best[2]:\n", 41 | " best = (i, j, d)\n", 42 | " return best\n", 43 | "\n", 44 | "class Level(object):\n", 45 | " def __init__(self, testbed):\n", 46 | " self.testbed = testbed\n", 47 | " self.world = testbed.world\n", 48 | "\n", 49 | " self.gap_size = 15\n", 50 | " self.kill_sensors_height = 0.5\n", 51 | " self.usable_size = 20\n", 52 | " self.h = 10\n", 53 | " self.end_zone_height = 3\n", 54 | "\n", 55 | " self.outline_verts = [\n", 56 | " (0, self.h),\n", 57 | " (0, 2 * self.h),\n", 58 | " (0, self.h),\n", 59 | " (self.usable_size, self.h),\n", 60 | " (self.usable_size, 0),\n", 61 | " (self.usable_size + self.gap_size, 0),\n", 62 | " (self.usable_size + self.gap_size, self.h),\n", 63 | " (2 * self.usable_size + self.gap_size, self.h),\n", 64 | " (2 * self.usable_size + self.gap_size, 2 * self.h),\n", 65 | " ]\n", 66 | "\n", 67 | " # outline of the level\n", 68 | " shape = b2d.chain_shape(vertices=numpy.flip(self.outline_verts, axis=0))\n", 69 | " self.outline = self.world.create_static_body(position=(0, 0), shape=shape)\n", 70 | "\n", 71 | " # kill sensors\n", 72 | " self.kill_sensor_pos = (\n", 73 | " self.usable_size + self.gap_size / 2,\n", 74 | " self.kill_sensors_height / 2,\n", 75 | " )\n", 76 | "\n", 77 | " shape = b2d.polygon_shape(box=(self.gap_size / 2, self.kill_sensors_height / 2))\n", 78 | " self._kill_sensor = self.world.create_static_body(\n", 79 | " position=self.kill_sensor_pos,\n", 80 | " fixtures=b2d.fixture_def(shape=shape, is_sensor=True),\n", 81 | " )\n", 82 | " self._kill_sensor.user_data = \"destroyer\"\n", 83 | "\n", 84 | " # end sensor\n", 85 | " shape = b2d.polygon_shape(box=(self.usable_size / 2, self.end_zone_height / 2))\n", 86 | " self._end_sensor = self.world.create_static_body(\n", 87 | " position=(\n", 88 | " 1.5 * self.usable_size + self.gap_size,\n", 89 | " self.h + self.end_zone_height / 2,\n", 90 | " ),\n", 91 | " fixtures=b2d.fixture_def(shape=shape, is_sensor=True),\n", 92 | " )\n", 93 | " self._end_sensor.user_data = \"goal\"\n", 94 | "\n", 95 | " goo_radius = 1\n", 96 | " a = self.testbed.insert_goo(\n", 97 | " pos=(self.usable_size / 3, self.h + goo_radius), static=True\n", 98 | " )\n", 99 | " b = self.testbed.insert_goo(\n", 100 | " pos=(self.usable_size * 2 / 3, self.h + goo_radius), static=True\n", 101 | " )\n", 102 | " c = self.testbed.insert_goo(\n", 103 | " pos=(self.usable_size * 1 / 2, self.h + goo_radius + 4), static=False\n", 104 | " )\n", 105 | "\n", 106 | " self.testbed.connect_goos(a, b)\n", 107 | " self.testbed.connect_goos(a, c)\n", 108 | " self.testbed.connect_goos(b, c)\n", 109 | "\n", 110 | " def draw(self, debug_draw):\n", 111 | "\n", 112 | " # draw outline\n", 113 | " for i in range(len(self.outline_verts) - 1):\n", 114 | " debug_draw.draw_segment(\n", 115 | " self.outline_verts[i],\n", 116 | " self.outline_verts[i + 1],\n", 117 | " color=(1, 1, 0),\n", 118 | " line_width=0.3,\n", 119 | " )\n", 120 | "\n", 121 | " left = list(self.kill_sensor_pos)\n", 122 | " left[0] -= self.gap_size / 2\n", 123 | " left[1] += self.kill_sensors_height / 2\n", 124 | "\n", 125 | " right = list(self.kill_sensor_pos)\n", 126 | " right[0] += self.gap_size / 2\n", 127 | " right[1] += self.kill_sensors_height / 2\n", 128 | " debug_draw.draw_segment(left, right, (1, 0, 0), line_width=0.4)\n", 129 | "\n", 130 | "\n", 131 | "class FindGoos(b2d.QueryCallback):\n", 132 | " def __init__(self):\n", 133 | " super(FindGoos, self).__init__()\n", 134 | " self.goos = []\n", 135 | "\n", 136 | " def report_fixture(self, fixture):\n", 137 | " body = fixture.body\n", 138 | " if body.user_data == \"goo\":\n", 139 | " self.goos.append(body)\n", 140 | " return True\n", 141 | "\n", 142 | "\n", 143 | "class Goo(TestbedBase):\n", 144 | "\n", 145 | " name = \"Goo\"\n", 146 | "\n", 147 | " def __init__(self, settings=None):\n", 148 | " super(Goo, self).__init__(settings=settings)\n", 149 | "\n", 150 | " self.goo_graph = networkx.Graph()\n", 151 | " self.level = Level(testbed=self)\n", 152 | "\n", 153 | " # mouse related\n", 154 | " self.last_mouse_pos = None\n", 155 | " self.is_mouse_down = False\n", 156 | " self.could_place_goo_when_mouse_was_down = False\n", 157 | "\n", 158 | " # callback to draw tentative placement\n", 159 | " self.draw_callback = None\n", 160 | "\n", 161 | " # goos marked for destruction\n", 162 | " self.goo_to_destroy = []\n", 163 | "\n", 164 | " # joints marked for destruction\n", 165 | " self.joints_to_destroy = []\n", 166 | " self.gamma = 0.003\n", 167 | " self.break_threshold = 0.5\n", 168 | "\n", 169 | " # time point when goo can be inserted\n", 170 | " self.insert_time_point = 0\n", 171 | " self.insert_delay = 1.0\n", 172 | "\n", 173 | " # handle finishing of level\n", 174 | " self.with_goal_contact = dict()\n", 175 | "\n", 176 | " # amount of seconds one has to be in the finishing zone\n", 177 | " self.win_delay = 3.0\n", 178 | "\n", 179 | " # particle system will be defined an used on win!\n", 180 | " # this is then used for some kind of fireworks\n", 181 | " self.psystem = None\n", 182 | " self.emitter = None\n", 183 | " self.emitter_stop_time = None\n", 184 | " self.emitter_start_time = None\n", 185 | "\n", 186 | " # trigger some fireworks on win\n", 187 | " def on_win(self, win_body):\n", 188 | "\n", 189 | " if self.psystem is None:\n", 190 | " # particle system\n", 191 | " pdef = b2d.particle_system_def(\n", 192 | " viscous_strength=0.9,\n", 193 | " spring_strength=0.0,\n", 194 | " damping_strength=100.5,\n", 195 | " pressure_strength=1.0,\n", 196 | " color_mixing_strength=0.05,\n", 197 | " density=0.1,\n", 198 | " )\n", 199 | "\n", 200 | " self.psystem = self.world.create_particle_system(pdef)\n", 201 | " self.psystem.radius = 0.1\n", 202 | " self.psystem.damping = 0.5\n", 203 | "\n", 204 | " emitter_def = b2d.RandomizedRadialEmitterDef()\n", 205 | " emitter_def.emite_rate = 2000\n", 206 | " emitter_def.lifetime = 0.9\n", 207 | " emitter_def.enabled = True\n", 208 | " emitter_def.inner_radius = 0.0\n", 209 | " emitter_def.outer_radius = 0.1\n", 210 | " emitter_def.velocity_magnitude = 1000.0\n", 211 | " emitter_def.start_angle = 0\n", 212 | " emitter_def.stop_angle = 2 * math.pi\n", 213 | " emitter_def.transform = b2d.Transform(\n", 214 | " win_body.position + b2d.vec2(0, 20), b2d.Rot(0)\n", 215 | " )\n", 216 | " self.emitter = b2d.RandomizedRadialEmitter(self.psystem, emitter_def)\n", 217 | " self.emitter_stop_time = self.elapsed_time + 0.2\n", 218 | "\n", 219 | " def draw_goo(self, pos, angle, body=None):\n", 220 | " self.debug_draw.draw_solid_circle(pos, 1, axis=None, color=(1, 0, 1))\n", 221 | " self.debug_draw.draw_circle(pos, 1.1, (1, 1, 1), line_width=0.1)\n", 222 | "\n", 223 | " if body is not None:\n", 224 | " centers = [\n", 225 | " body.get_world_point((-0.3, 0.2)),\n", 226 | " body.get_world_point((0.3, 0.2)),\n", 227 | " ]\n", 228 | " for center in centers:\n", 229 | " self.debug_draw.draw_solid_circle(\n", 230 | " center, 0.4, axis=None, color=(1, 1, 1)\n", 231 | " )\n", 232 | " self.debug_draw.draw_solid_circle(\n", 233 | " center, 0.2, axis=None, color=(0, 0, 0)\n", 234 | " )\n", 235 | "\n", 236 | " def draw_edge(self, pos_a, pos_b, stress):\n", 237 | " no_stress = numpy.array([1, 1, 1])\n", 238 | " has_stress = numpy.array([1, 0, 0])\n", 239 | " color = (1.0 - stress) * no_stress + stress * has_stress\n", 240 | " color = tuple([float(c) for c in color])\n", 241 | " self.debug_draw.draw_segment(pos_a, pos_b, color=color, line_width=0.4)\n", 242 | "\n", 243 | " def insert_goo(self, pos, static=False):\n", 244 | " if static:\n", 245 | " f = self.world.create_static_body\n", 246 | " else:\n", 247 | " f = self.world.create_dynamic_body\n", 248 | "\n", 249 | " goo = f(\n", 250 | " position=pos,\n", 251 | " fixtures=b2d.fixture_def(shape=b2d.circle_shape(radius=1), density=1),\n", 252 | " user_data=\"goo\",\n", 253 | " )\n", 254 | " self.goo_graph.add_node(goo)\n", 255 | " return goo\n", 256 | "\n", 257 | " def connect_goos(self, goo_a, goo_b):\n", 258 | " length = (goo_a.position - goo_b.position).length\n", 259 | " joint = self.world.create_distance_joint(\n", 260 | " goo_a,\n", 261 | " goo_b,\n", 262 | " stiffness=500,\n", 263 | " damping=0.1,\n", 264 | " length=length,\n", 265 | " user_data=dict(length=length, stress=0),\n", 266 | " )\n", 267 | " self.goo_graph.add_edge(goo_a, goo_b, joint=joint)\n", 268 | "\n", 269 | " def query_placement(self, pos):\n", 270 | "\n", 271 | " radius = 8\n", 272 | "\n", 273 | " # find all goos in around pos\n", 274 | " pos = b2d.vec2(pos)\n", 275 | " box = b2d.aabb(\n", 276 | " lower_bound=pos - b2d.vec2(radius, radius),\n", 277 | " upper_bound=pos + b2d.vec2(radius, radius),\n", 278 | " )\n", 279 | " query = FindGoos()\n", 280 | " self.world.query_aabb(query, box)\n", 281 | " goos = query.goos\n", 282 | " n_goos = len(goos)\n", 283 | "\n", 284 | " if n_goos >= 2:\n", 285 | "\n", 286 | " # try to insert to goo as edge between\n", 287 | " # 2 existing goos\n", 288 | " def distance(a, b, p):\n", 289 | " if self.goo_graph.has_edge(a[0], b[0]):\n", 290 | " return float(\"inf\")\n", 291 | " return numpy.linalg.norm((a[1] + b[1]) / 2 - p)\n", 292 | "\n", 293 | " i, j, best_dist = best_pairwise_distance(\n", 294 | " goos,\n", 295 | " f=lambda goo: (goo, numpy.array(goo.position)),\n", 296 | " distance=partial(distance, p=pos),\n", 297 | " )\n", 298 | "\n", 299 | " if best_dist < 0.8:\n", 300 | "\n", 301 | " def draw_callback():\n", 302 | " self.draw_edge(goos[i].position, goos[j].position, stress=0)\n", 303 | "\n", 304 | " def insert_callack():\n", 305 | " self.connect_goos(goos[i], goos[j])\n", 306 | "\n", 307 | " return True, draw_callback, insert_callack\n", 308 | "\n", 309 | " # try to insert the goo as brand new\n", 310 | " # goo and connect it with 2 existing goos\n", 311 | " f = lambda goo: (goo, (goo.position - b2d.vec2(pos)).length)\n", 312 | "\n", 313 | " def distance(a, b):\n", 314 | " if not self.goo_graph.has_edge(a[0], b[0]):\n", 315 | " return float(\"inf\")\n", 316 | " return a[1] + b[1]\n", 317 | "\n", 318 | " i, j, best_dist = best_pairwise_distance(goos, f=f, distance=distance)\n", 319 | " if best_dist < float(\"inf\"):\n", 320 | "\n", 321 | " def draw_callback():\n", 322 | "\n", 323 | " self.draw_edge(pos, goos[i].position, stress=0)\n", 324 | " self.draw_edge(pos, goos[j].position, stress=0)\n", 325 | " self.draw_goo(pos, angle=None)\n", 326 | "\n", 327 | " def insert_callack():\n", 328 | " goo = self.insert_goo(pos=pos)\n", 329 | " self.connect_goos(goo, goos[i])\n", 330 | " self.connect_goos(goo, goos[j])\n", 331 | "\n", 332 | " return True, draw_callback, insert_callack\n", 333 | "\n", 334 | " return False, None, None\n", 335 | "\n", 336 | " def on_mouse_down(self, pos):\n", 337 | " self.last_mouse_pos = pos\n", 338 | " self.is_mouse_down = True\n", 339 | " can_be_placed, draw_callback, insert_callback = self.query_placement(pos)\n", 340 | " self.could_place_goo_when_mouse_was_down = can_be_placed\n", 341 | " if can_be_placed:\n", 342 | " if self.elapsed_time < self.insert_time_point:\n", 343 | " return True\n", 344 | " self.draw_callback = draw_callback\n", 345 | " return True\n", 346 | " return False\n", 347 | "\n", 348 | " def on_mouse_move(self, pos):\n", 349 | " self.last_mouse_pos = pos\n", 350 | " if self.is_mouse_down:\n", 351 | " can_be_placed, draw_callback, insert_callback = self.query_placement(pos)\n", 352 | " if can_be_placed:\n", 353 | " if self.elapsed_time < self.insert_time_point:\n", 354 | " return True\n", 355 | " self.draw_callback = draw_callback\n", 356 | " return True\n", 357 | " else:\n", 358 | " self.draw_callback = None\n", 359 | " return self.could_place_goo_when_mouse_was_down\n", 360 | "\n", 361 | " def on_mouse_up(self, pos):\n", 362 | " self.last_mouse_pos = pos\n", 363 | " self.is_mouse_down = False\n", 364 | " self.draw_callback = None\n", 365 | " can_be_placed, draw_callback, insert_callback = self.query_placement(pos)\n", 366 | " if can_be_placed:\n", 367 | " if self.elapsed_time < self.insert_time_point:\n", 368 | " return True\n", 369 | " # self.draw_callback = draw_callback\n", 370 | " insert_callback()\n", 371 | " self.insert_time_point = self.elapsed_time + self.insert_delay\n", 372 | " return True\n", 373 | " return False\n", 374 | "\n", 375 | " def begin_contact(self, contact):\n", 376 | " body_a = contact.body_a\n", 377 | " body_b = contact.body_b\n", 378 | " if body_b.user_data == \"goo\":\n", 379 | " body_a, body_b = body_b, body_a\n", 380 | "\n", 381 | " user_data_a = body_a.user_data\n", 382 | " user_data_b = body_b.user_data\n", 383 | " if body_a.user_data == \"goo\":\n", 384 | " if user_data_b == \"destroyer\":\n", 385 | " self.goo_to_destroy.append(body_a)\n", 386 | " elif user_data_b == \"goal\":\n", 387 | " self.with_goal_contact[body_a] = self.elapsed_time + self.win_delay\n", 388 | "\n", 389 | " def end_contact(self, contact):\n", 390 | " body_a = contact.body_a\n", 391 | " body_b = contact.body_b\n", 392 | " if body_b.user_data == \"goo\":\n", 393 | " body_a, body_b = body_b, body_a\n", 394 | "\n", 395 | " user_data_a = body_a.user_data\n", 396 | " user_data_b = body_b.user_data\n", 397 | " if body_a.user_data == \"goo\":\n", 398 | " if user_data_b == \"goal\":\n", 399 | " if body_a in self.with_goal_contact:\n", 400 | " del self.with_goal_contact[body_a]\n", 401 | "\n", 402 | " def pre_step(self, dt):\n", 403 | "\n", 404 | " # query if goo can be inserted\n", 405 | " if (\n", 406 | " self.is_mouse_down\n", 407 | " and self.last_mouse_pos is not None\n", 408 | " and self.draw_callback is None\n", 409 | " ):\n", 410 | " can_be_placed, draw_callback, insert_callback = self.query_placement(\n", 411 | " self.last_mouse_pos\n", 412 | " )\n", 413 | " if can_be_placed and self.elapsed_time >= self.insert_time_point:\n", 414 | " self.draw_callback = draw_callback\n", 415 | "\n", 416 | " # compute joint stress\n", 417 | " for goo_a, goo_b, joint in self.goo_graph.edges(data=\"joint\"):\n", 418 | " jd = joint.user_data\n", 419 | "\n", 420 | " # distance based stress\n", 421 | " insert_length = jd[\"length\"]\n", 422 | " length = (goo_a.position - goo_b.position).length\n", 423 | "\n", 424 | " d = length - insert_length\n", 425 | " if d > 0:\n", 426 | "\n", 427 | " # reaction force based stress\n", 428 | " rf = joint.get_reaction_force(30).length\n", 429 | "\n", 430 | " normalized_rf = 1.0 - math.exp(-rf * self.gamma)\n", 431 | "\n", 432 | " jd[\"stress\"] = normalized_rf / self.break_threshold\n", 433 | " if normalized_rf > self.break_threshold:\n", 434 | " self.joints_to_destroy.append((goo_a, goo_b, joint))\n", 435 | "\n", 436 | " else:\n", 437 | " jd[\"stress\"] = 0\n", 438 | "\n", 439 | " for goo_a, goo_b, joint in self.joints_to_destroy:\n", 440 | " self.goo_graph.remove_edge(u=goo_a, v=goo_b)\n", 441 | " self.world.destroy_joint(joint)\n", 442 | " self.joints_to_destroy = []\n", 443 | "\n", 444 | " # destroy goos\n", 445 | " for goo in self.goo_to_destroy:\n", 446 | " self.goo_graph.remove_node(goo)\n", 447 | " self.world.destroy_body(goo)\n", 448 | "\n", 449 | " # destroy all with wrong degree\n", 450 | " while True:\n", 451 | " destroyed_any = False\n", 452 | " to_remove = []\n", 453 | " for goo in self.goo_graph.nodes:\n", 454 | " if self.goo_graph.degree(goo) < 2:\n", 455 | " destroyed_any = True\n", 456 | " to_remove.append(goo)\n", 457 | " if not destroyed_any:\n", 458 | " break\n", 459 | " for goo in to_remove:\n", 460 | " self.goo_graph.remove_node(goo)\n", 461 | " self.world.destroy_body(goo)\n", 462 | " self.goo_to_destroy = []\n", 463 | "\n", 464 | " # check if we are done\n", 465 | " for goo, finish_time in self.with_goal_contact.items():\n", 466 | " if finish_time <= self.elapsed_time:\n", 467 | " self.on_win(goo)\n", 468 | "\n", 469 | " if self.emitter is not None:\n", 470 | " if self.emitter_stop_time is not None:\n", 471 | " if self.elapsed_time > self.emitter_stop_time:\n", 472 | " self.emitter.enabled = False\n", 473 | " self.emitter_start_time = self.elapsed_time + 0.4\n", 474 | " self.emitter_stop_time = None\n", 475 | " p = list(self.emitter.position)\n", 476 | " p[0] += (random.random() - 0.5) * 10.0\n", 477 | " p[1] += (random.random() - 0.5) * 2.0\n", 478 | " self.emitter.position = p\n", 479 | " if self.emitter_start_time is not None:\n", 480 | " if self.elapsed_time > self.emitter_start_time:\n", 481 | " self.emitter.enabled = True\n", 482 | " self.emitter_start_time = None\n", 483 | " self.emitter_stop_time = self.elapsed_time + 0.2\n", 484 | " self.emitter.step(dt)\n", 485 | "\n", 486 | " def post_debug_draw(self):\n", 487 | "\n", 488 | " self.level.draw(self.debug_draw)\n", 489 | "\n", 490 | " # draw mouse when mouse is down\n", 491 | " if (\n", 492 | " self.is_mouse_down\n", 493 | " and self.last_mouse_pos is not None\n", 494 | " and self.draw_callback is None\n", 495 | " ):\n", 496 | " d = (self.insert_time_point - self.elapsed_time) / self.insert_delay\n", 497 | " if d > 0:\n", 498 | " d = d * math.pi * 2\n", 499 | " x = math.sin(d)\n", 500 | " y = math.cos(d)\n", 501 | " p = self.last_mouse_pos[0] + x, self.last_mouse_pos[1] + y\n", 502 | " self.debug_draw.draw_segment(\n", 503 | " p, self.last_mouse_pos, color=(1, 0, 0), line_width=0.2\n", 504 | " )\n", 505 | " self.debug_draw.draw_circle(\n", 506 | " self.last_mouse_pos, 1, (1, 0, 0), line_width=0.2\n", 507 | " )\n", 508 | "\n", 509 | " # draw the tentative placement\n", 510 | " if self.draw_callback is not None:\n", 511 | " self.draw_callback()\n", 512 | "\n", 513 | " for goo_a, goo_b, joint in self.goo_graph.edges(data=\"joint\"):\n", 514 | " self.draw_edge(\n", 515 | " goo_a.position, goo_b.position, stress=joint.user_data[\"stress\"]\n", 516 | " )\n", 517 | "\n", 518 | " for goo in self.goo_graph:\n", 519 | " self.draw_goo(goo.position, goo.angle, body=goo)\n" 520 | ] 521 | }, 522 | { 523 | "cell_type": "markdown", 524 | "metadata": {}, 525 | "source": [ 526 | "# Controlls\n", 527 | "* To play this game, click and drag next to the existing \"goos\"\n", 528 | "* try to bridge the tiny gap\n", 529 | "* Use the mouse-wheel to zoom in/out, a\n", 530 | "* Click and drag in the empty space to translate the view." 531 | ] 532 | }, 533 | { 534 | "cell_type": "code", 535 | "execution_count": null, 536 | "metadata": {}, 537 | "outputs": [], 538 | "source": [ 539 | "\n", 540 | "s = JupyterAsyncGui.Settings()\n", 541 | "s.resolution = [1000,500]\n", 542 | "s.scale = 8\n", 543 | "tb = b2d.testbed.run(Goo, backend=JupyterAsyncGui, gui_settings=s);" 544 | ] 545 | }, 546 | { 547 | "cell_type": "code", 548 | "execution_count": null, 549 | "metadata": {}, 550 | "outputs": [], 551 | "source": [] 552 | } 553 | ], 554 | "metadata": { 555 | "kernelspec": { 556 | "display_name": "Python 3 (ipykernel)", 557 | "language": "python", 558 | "name": "python3" 559 | }, 560 | "language_info": { 561 | "codemirror_mode": { 562 | "name": "ipython", 563 | "version": 3 564 | }, 565 | "file_extension": ".py", 566 | "mimetype": "text/x-python", 567 | "name": "python", 568 | "nbconvert_exporter": "python", 569 | "pygments_lexer": "ipython3", 570 | "version": "3.10.4" 571 | } 572 | }, 573 | "nbformat": 4, 574 | "nbformat_minor": 4 575 | } 576 | --------------------------------------------------------------------------------