From 22565e0ccd1f49e792096cc5fa27907f781f797c Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Tue, 10 Nov 2020 10:00:03 -0300 Subject: [PATCH 01/17] Adding PVector wrapper/helper class --- docs/pyodide/index.html | 318 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) diff --git a/docs/pyodide/index.html b/docs/pyodide/index.html index c1ccb841..385f6368 100644 --- a/docs/pyodide/index.html +++ b/docs/pyodide/index.html @@ -1006,6 +1006,324 @@ pushMatrix = push pushStyle = push +# PVector is a wrapper/helper class for p5.Vector objets +# providing names similar to Processing Python or Java modes +# but mostly keeping p5js functionality + +from numbers import Number + +class PVector: + + def __init__(self, x=0, y=0, z=0): + self.__vector = createVector(x, y, z) + self.add = self.__instance_add__ + self.sub = self.__instance_sub__ + self.mult = self.__instance_mult__ + self.div = self.__instance_div__ + self.cross = self.__instance_cross__ + self.dist = self.__instance_dist__ + self.dot = self.__instance_dot__ + self.lerp = self.__instance_lerp__ + + @property + def x(self): + return self.__vector.x + + @x.setter + def x(self, x): + self.__vector.x = x + + @property + def y(self): + return self.__vector.y + + @y.setter + def y(self, y): + self.__vector.y = y + + @property + def z(self): + return self.__vector.z + + @z.setter + def z(self, z): + self.__vector.z = z + + def mag(self): + return self.__vector.mag() + + def magSq(self): + return self.__vector.magSq() + + def setMag(self, mag): + self.__vector.setMag(mag) + + def normalize(self): + self.__vector.normalize() + + def limit(self, max): + self.__vector.limit(max) + + def heading(self): + return self.__vector.heading() + + def rotate(self, angle): + self.__vector.rotate(angle) + return self + + def __instance_add__(self, *args): + if len(args) == 1: + return PVector.add(self, args[0], self) + else: + return PVector.add(self, PVector(*args), self) + + def __instance_sub__(self, *args): + if len(args) == 1: + return PVector.sub(self, args[0], self) + else: + return PVector.sub(self, PVector(*args), self) + + def __instance_mult__(self, o): + return PVector.mult(self, o, self) + + def __instance_div__(self, f): + return PVector.div(self, f, self) + + def __instance_cross__(self, o): + return PVector.cross(self, o, self) + + def __instance_dist__(self, o): + return PVector.dist(self, o) + + def __instance_dot__(self, *args): + if len(args) == 1: + v = args[0] + else: + v = args + return self.x * v[0] + self.y * v[1] + self.z * v[2] + + def __instance_lerp__(self, *args): + if len(args) == 2: + return PVector.lerp(self, args[0], args[1], self) + else: + vx, vy, vz, f = args + return PVector.lerp(self, PVector(vx, vy, vz), f, self) + + def get(self): + return PVector(self.x, self.y, self.z) + + def copy(self): + return PVector(self.x, self.y, self.z) + + def __getitem__(self, k): + return getattr(self, ('x', 'y', 'z')[k]) + + def __setitem__(self, k, v): + setattr(self, ('x', 'y', 'z')[k], v) + + def __copy__(self): + return PVector(self.x, self.y, self.z) + + def __deepcopy__(self, memo): + return PVector(self.x, self.y, self.z) + + def __repr__(self): # PROVISÓRIO + return f'PVector({self.x}, {self.y}, {self.z})' + + def set(self, *args): + """ + Sets the x, y, and z component of the vector using two or three separate + variables, the data from a p5.Vector, or the values from a float array. + """ + self.__vector.set(*args) + + @classmethod + def add(cls, a, b, dest=None): + if dest is None: + return PVector(a.x + b[0], a.y + b[1], a.z + b[2]) + dest.__vector.set(a.x + b[0], a.y + b[1], a.z + b[2]) + return dest + + @classmethod + def sub(cls, a, b, dest=None): + if dest is None: + return PVector(a.x - b[0], a.y - b[1], a.z - b[2]) + dest.__vector.set(a.x - b[0], a.y - b[1], a.z - b[2]) + return dest + + @classmethod + def mult(cls, a, b, dest=None): + if dest is None: + return PVector(a.x * b, a.y * b, a.z * b) + dest.__vector.set(a.x * b, a.y * b, a.z * b) + return dest + + @classmethod + def div(cls, a, b, dest=None): + if dest is None: + return PVector(a.x / b, a.y / b, a.z / b) + dest.__vector.set(a.x / b, a.y / b, a.z / b) + return dest + + @classmethod + def dist(cls, a, b): + return a.__vector.dist(b.__vector) + + @classmethod + def dot(cls, a, b): + return a.__vector.dot(b.__vector) + + def __add__(a, b): + return PVector.add(a, b, None) + + def __sub__(a, b): + return PVector.sub(a, b, None) + + def __isub__(a, b): + a.sub(b) + return a + + def __iadd__(a, b): + a.add(b) + return a + + def __mul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector.mult(a, float(b), None) + + def __rmul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector.mult(a, float(b), None) + + def __imul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The *= operator can only be used to multiply a PVector by a number") + a.__vector.mult(float(b)) + return a + + def __truediv__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector(a.x / float(b), a.y / float(b), a.z / float(b)) + + def __itruediv__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The /= operator can only be used to multiply a PVector by a number") + a.__vector.set(a.x / float(b), a.y / float(b), a.z / float(b)) + return a + + def __eq__(a, b): + return a.x == b[0] and a.y == b[1] and a.z == b[2] + + def __lt__(a, b): + return a.magSq() < b.magSq() + + def __le__(a, b): + return a.magSq() <= b.magSq() + + def __gt__(a, b): + return a.magSq() > b.magSq() + + def __ge__(a, b): + return a.magSq() >= b.magSq() + + # Problematic class methods, we would rather use p5.Vector when possible... + + @classmethod + def lerp(cls, a, b, f, dest=None): + v = createVector(a.x, a.y, a.z) + v.lerp(b.__vector, f) + if dest is None: + return PVector(v.x, v.y, v.z) + dest.set(v.x, v.y, v.z) + return dest + + @classmethod + def cross(cls, a, b, dest=None): + x = a.y * b[2] - b[1] * a.z + y = a.z * b[0] - b[2] * a.x + z = a.x * b[1] - b[0] * a.y + if dest is None: + return PVector(x, y, z) + dest.set(x, y, z) + return dest + + @classmethod + def fromAngle(cls, angle, length=1): + # https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js + return PVector(length * cos(angle), length * sin(angle), 0) + + @classmethod + def fromAngles(theta, phi, length=1): + # https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js + cosPhi = cos(phi) + sinPhi = sin(phi) + cosTheta = cos(theta) + sinTheta = sin(theta) + return PVector(length * sinTheta * sinPhi, + -length * cosTheta, + length * sinTheta * cosPhi) + + @classmethod + def random2D(cls): + return PVector.fromAngle(random(TWO_PI)) + + @classmethod + def random3D(cls, dest=None): + angle = random(TWO_PI) + vz = random(2) - 1 + mult = sqrt(1 - vz * vz) + vx = mult * cos(angle) + vy = mult * sin(angle) + if dest is None: + return PVector(vx, vy, vz) + dest.set(vx, vy, vz) + return dest + + @classmethod + def angleBetween(cls, a, b): + return acos(a.dot(b) / sqrt(a.magSq() * b.magSq())) + + # Other harmless p5js methods + + def equals(self, v): + return self == v + + def heading2D(self): + return self.__vector.heading() + + def reflect(self, *args): + # Reflect the incoming vector about a normal to a line in 2D, or about + # a normal to a plane in 3D This method acts on the vector directly + r = self.__vector.reflect(*args) + return r + + def array(self): + # Return a representation of this vector as a float array. This is only + # for temporary use. If used in any w fashion, the contents should be + # copied by using the p5.Vector.copy() method to copy into your own + # array. + return self.__vector.array() + + def toString(self): + # Returns a string representation of a vector v by calling String(v) or v.toString(). + # return self.__vector.toString() would be something like "p5.vector + # Object […, …, …]" + return str(self) + + def rem(self, *args): + # Gives remainder of a vector when it is divided by anw vector. See + # examples for more context. + self.__vector.rem(*args) + return self + def pre_draw(p5_instance, draw_func): """ We need to run this before the actual draw to insert and update p5 env variables From a2990eef906f4086dbec55b556f3951aff3349d8 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Tue, 10 Nov 2020 10:12:11 -0300 Subject: [PATCH 02/17] adding `return self` to some methods to allow chaining, I guess --- docs/pyodide/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/pyodide/index.html b/docs/pyodide/index.html index 385f6368..5bd29ec5 100644 --- a/docs/pyodide/index.html +++ b/docs/pyodide/index.html @@ -1057,12 +1057,15 @@ def setMag(self, mag): self.__vector.setMag(mag) + return self def normalize(self): self.__vector.normalize() + return self def limit(self, max): self.__vector.limit(max) + return self def heading(self): return self.__vector.heading() From 72e508cf0e5d1204b9cf96e78d716b30ec94b710 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Wed, 11 Nov 2020 13:42:12 -0300 Subject: [PATCH 03/17] adding PVector alias to createVector() and p5.Vector class method aliases --- pyp5js/pyp5js.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyp5js/pyp5js.py b/pyp5js/pyp5js.py index b673910b..1cc90009 100644 --- a/pyp5js/pyp5js.py +++ b/pyp5js/pyp5js.py @@ -947,6 +947,26 @@ def pop(*args): pushMatrix = push pushStyle = push +# # PVector is a helper/alias to create p5.Vector objects +def PVector(x=0, y=0, z=0): + return _P5_INSTANCE.createVector(x, y, z) +# # aliases for p5.Vector class methods +PVector.dist = p5.Vector.dist +PVector.dist = p5.Vector.dist +PVector.add = p5.Vector.add +PVector.sub = p5.Vector.sub +PVector.mult = p5.Vector.mult +PVector.div = p5.Vector.div +PVector.dot = p5.Vector.dot +PVector.cross = p5.Vector.cross +PVector.lerp = p5.Vector.lerp +PVector.random2D = p5.Vector.random2D +PVector.random3D = p5.Vector.random3D +PVector.angleBetween = p5.Vector.angleBetween +PVector.fromAngle = p5.Vector.fromAngle +PVector.fromAngles = p5.Vector.fromAngles +PVector.equals = p5.Vector.equals + def pre_draw(p5_instance, draw_func): """ We need to run this before the actual draw to insert and update p5 env variables From b92e4091511f9bd49c812e18031fe533d030eb40 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Wed, 11 Nov 2020 15:11:41 -0300 Subject: [PATCH 04/17] using the etattr(PVector, 'name', p5.Vector.name) form --- pyp5js/pyp5js.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/pyp5js/pyp5js.py b/pyp5js/pyp5js.py index 1cc90009..6b3d940c 100644 --- a/pyp5js/pyp5js.py +++ b/pyp5js/pyp5js.py @@ -947,25 +947,24 @@ def pop(*args): pushMatrix = push pushStyle = push -# # PVector is a helper/alias to create p5.Vector objects +# PVector is a helper/alias to create p5.Vector objects def PVector(x=0, y=0, z=0): return _P5_INSTANCE.createVector(x, y, z) -# # aliases for p5.Vector class methods -PVector.dist = p5.Vector.dist -PVector.dist = p5.Vector.dist -PVector.add = p5.Vector.add -PVector.sub = p5.Vector.sub -PVector.mult = p5.Vector.mult -PVector.div = p5.Vector.div -PVector.dot = p5.Vector.dot -PVector.cross = p5.Vector.cross -PVector.lerp = p5.Vector.lerp -PVector.random2D = p5.Vector.random2D -PVector.random3D = p5.Vector.random3D -PVector.angleBetween = p5.Vector.angleBetween -PVector.fromAngle = p5.Vector.fromAngle -PVector.fromAngles = p5.Vector.fromAngles -PVector.equals = p5.Vector.equals +# aliases for p5.Vector class methods +setattr(PVector, 'dist', p5.Vector.dist) +setattr(PVector, 'add', p5.Vector.add) +setattr(PVector, 'sub', p5.Vector.sub) +setattr(PVector, 'mult', p5.Vector.mult) +setattr(PVector, 'div', p5.Vector.div) +setattr(PVector, 'dot', p5.Vector.dot) +setattr(PVector, 'cross', p5.Vector.cross) +setattr(PVector, 'lerp', p5.Vector.lerp) +setattr(PVector, 'random2D', p5.Vector.random2D) +setattr(PVector, 'random3D', p5.Vector.random3D) +setattr(PVector, 'angleBetween', p5.Vector.angleBetween) +setattr(PVector, 'fromAngle', p5.Vector.fromAngle) +setattr(PVector, 'fromAngles', p5.Vector.fromAngles) +setattr(PVector, 'equals', p5.Vector.equals) def pre_draw(p5_instance, draw_func): """ From 0c5dda583d05485e78c03327556c532c8ee7de5e Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Thu, 4 Mar 2021 08:41:25 -0300 Subject: [PATCH 05/17] =?UTF-8?q?trazendo=20os=20avan=C3=A7os=20do=20Berna?= =?UTF-8?q?rdo=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unit test refactoring Signed-off-by: Bernardo Fontes * Move config to its own module * Use indirection to get sketch index html file * Rename test directory * Write/read sketch config as json file * Create index html exclusive for pyodide * Get index html from sketch config file * Sketch files with sketch config * Add new flag --interpreter to command line * Fixes SketchConfig tests for Windows (Hopefully) On Windows, a NamedTemporaryFile [cannot be opened][1] again if it's already open. Now the fixture creates the file, closes it, and removes the file after the tests run. [1]: https://docs.python.org/2/library/tempfile.html#tempfile.NamedTemporaryFile * Move p5.js static files to assets dir * Move LibFiles to config module and define a singleton PYP5JS_FILES * Move LibFiles tests to config tests * Rename SketchFiles class to Sketch * Rename files * Update the docs * Fix imports * Domain exceptions should use domain objects * Document import limitations Fixes #26 * Fix error with f-string * Use __pragma__ to prevent transcrypt from trying get a dict key * Update changelog * Fix tests * Add gitpod yaml config * Use virtuaenv with gitpod * Allow system site package access from virtual env * Add gitpod commands * Inline command in init * add link to return to home on sketchbook * fixing link to sketchbook * Refactor sketch URL and add unit test * Ignore .theia * Add badges * Update changelog * Open in browser * Open in browser * Update dir * Update gitpod branch to main * Version bump * refactors the way of getting target_js * Return template as Path object + docstring * Ignore venv dictory (created by gitpod.io) * Fix breaking tests * Organize compiler specific templates in subdirs * Improve logs * Promote compiler to an interface and refactor existing one to be a transcrypt specific * Move transcryptn target sketch to its own dir * Add template sketch to enable pyodide * Run JS content from target.js * Ignore dev suffix * Refactor tests to centralize fixtures * Populate sketch js code with pyodide + user code * Load pyodide js files * Improve new sketch command docs * Deprecate transcrypt command * Select compiler * Ensure web sketch is always pyodide one * Expose a way to external callers to update the sketch code * Move sketch to target dir * Web app special frontend to work with pyodide * Specific base template per tool * Update the docs * Pyodide mode is now the default one * Properties json file should be occult * Reduce noise in the logs * Add buttons and shortcuts to run/clear/save sketch * Shortcuts working for mac users * Fixes after final PR review * Fix urls * Version bump * Remove duplicated link * 0.5.1 * Fix winndow's ci * Prioritize windows * Change readme title * Improve demo buttons and shortcuts * Propose initial sketch Co-authored-by: Bernardo Fontes Co-authored-by: Flavio Amieiro --- .github/workflows/ci.yml | 2 +- .gitignore | 3 + .gitpod.yml | 17 + CHANGELOG.md | 13 + MANIFEST.in | 1 - README.md | 12 +- VERSION | 2 +- docs/index.md | 37 +- docs/pyodide/index.html | 44 +- .../{ => assets}/static/p5/addons/p5.sound.js | 0 .../static/p5/addons/p5.sound.min.js | 0 pyp5js/{ => assets}/static/p5/p5.js | 0 pyp5js/{ => assets}/static/p5/p5.min.js | 0 pyp5js/cli.py | 39 +- pyp5js/commands.py | 47 +- pyp5js/compiler.py | 67 +- pyp5js/{config.py => config/__init__.py} | 4 + pyp5js/config/fs.py | 69 + pyp5js/config/sketch.py | 51 + pyp5js/exceptions.py | 9 +- pyp5js/http/templates/view_sketch.html | 58 +- pyp5js/http/web_app.py | 36 +- pyp5js/monitor.py | 12 +- pyp5js/pyp5js.py | 33 +- pyp5js/{fs.py => sketch.py} | 79 +- .../templates/pyodide/base_sketch.py.template | 10 + pyp5js/templates/pyodide/index.html | 22 + .../pyodide/target_sketch.js.template | 1217 +++++++++++++++++ .../{ => transcrypt}/base_sketch.py.template | 0 pyp5js/templates/{ => transcrypt}/index.html | 0 .../target_sketch.py.template | 0 pyp5js/templates_renderers.py | 38 +- pyp5js/tests/fixtures.py | 58 + pyp5js/tests/test_commands.py | 62 +- pyp5js/tests/test_compiler.py | 66 +- pyp5js/tests/test_config/__init__.py | 0 pyp5js/tests/test_config/test_fs.py | 54 + pyp5js/tests/test_config/test_sketch.py | 51 + pyp5js/tests/test_fs.py | 114 -- .../{http => test_http}/assets/alien.png | Bin .../tests/{http => test_http}/test_web_app.py | 54 +- pyp5js/tests/test_monitor.py | 8 +- pyp5js/tests/test_pyp5js.py | 16 +- pyp5js/tests/test_sketch.py | 89 ++ pyp5js/tests/test_templates_renderers.py | 30 +- setup.py | 2 +- 46 files changed, 2107 insertions(+), 419 deletions(-) create mode 100644 .gitpod.yml rename pyp5js/{ => assets}/static/p5/addons/p5.sound.js (100%) rename pyp5js/{ => assets}/static/p5/addons/p5.sound.min.js (100%) rename pyp5js/{ => assets}/static/p5/p5.js (100%) rename pyp5js/{ => assets}/static/p5/p5.min.js (100%) rename pyp5js/{config.py => config/__init__.py} (58%) create mode 100644 pyp5js/config/fs.py create mode 100644 pyp5js/config/sketch.py rename pyp5js/{fs.py => sketch.py} (60%) create mode 100644 pyp5js/templates/pyodide/base_sketch.py.template create mode 100644 pyp5js/templates/pyodide/index.html create mode 100644 pyp5js/templates/pyodide/target_sketch.js.template rename pyp5js/templates/{ => transcrypt}/base_sketch.py.template (100%) rename pyp5js/templates/{ => transcrypt}/index.html (100%) rename pyp5js/templates/{ => transcrypt}/target_sketch.py.template (100%) create mode 100644 pyp5js/tests/fixtures.py create mode 100644 pyp5js/tests/test_config/__init__.py create mode 100644 pyp5js/tests/test_config/test_fs.py create mode 100644 pyp5js/tests/test_config/test_sketch.py delete mode 100644 pyp5js/tests/test_fs.py rename pyp5js/tests/{http => test_http}/assets/alien.png (100%) rename pyp5js/tests/{http => test_http}/test_web_app.py (83%) create mode 100644 pyp5js/tests/test_sketch.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c5b2a96..9d66f020 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: max-parallel: 4 matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [windows-latest, ubuntu-latest, macos-latest] python-version: [3.6, 3.7, 3.8] steps: diff --git a/.gitignore b/.gitignore index 8fafc36b..eb58c8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ dist *.egg-info demo_sketch tests-sketchbook +venv +docs/examples/dev* +.theia diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000..f3b04db2 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,17 @@ +image: gitpod/workspace-full + + +ports: + - port: 5000 + onOpen: open-browser + + +tasks: + - init: + export SKETCHBOOK_DIR=sketchbook && + python -m venv venv --system-site-packages && + venv/bin/pip install -r dev-requirements.txt && + venv/bin/python setup.py develop && + source venv/bin/activate && + pytest + command: pyp5js serve diff --git a/CHANGELOG.md b/CHANGELOG.md index 5397a112..35415aaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ Development ----------- +0.5.1 +----- +- Minor fix in view sketch HTML + +0.5.0 +----- +- Support to Pyodide as the Python interpreter + +0.4.5 +----- +- Support to get/set pixels with Transcrypt interpreter +- `pyp5js` can run on top of Gitpod.io + 0.4.4 ----- - Fix to allow directories name with spaces - PR #127 diff --git a/MANIFEST.in b/MANIFEST.in index 0b8cad76..f3a10725 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ recursive-include pyp5js/static * -recursive-include pyp5js/assets * recursive-include pyp5js/templates * recursive-include pyp5js/http/templates * recursive-include pyp5js/http/static * diff --git a/README.md b/README.md index e809b14d..7aab6925 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ -## pyp5js: Python to P5.js Transcriptor +## pyp5js: drawing with Python 3 -[![PyPI version](https://badge.fury.io/py/pyp5js.svg)](https://badge.fury.io/py/pyp5js) ![Continuous Integration](https://github.com/berinhard/pyp5js/workflows/Continuous%20Integration/badge.svg?branch=develop&event=push) +[![PyPI version](https://badge.fury.io/py/pyp5js.svg)](https://badge.fury.io/py/pyp5js) +![Continuous Integration](https://github.com/berinhard/pyp5js/workflows/Continuous%20Integration/badge.svg?branch=develop&event=push) +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/berinhard/pyp5js/tree/main) -> [Processing](https://processing.org) ideas and Python 3 together with [P5.js](https://p5js.org) in the browser, using [Transcrypt](https://transcrypt.org/). +> [Processing](https://processing.org) ideas and Python 3 together with [P5.js](https://p5js.org) in the browser. + +Python 3 drawing in the web! Try it [here](https://berinhard.github.io/pyp5js/pyodide/)! Here's an example of a valid Python code using P5.js API: ```python -from pyp5js import * - def setup(): createCanvas(200, 200) background(160) diff --git a/VERSION b/VERSION index 6f2743d6..4b9fcbec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.4 +0.5.1 diff --git a/docs/index.md b/docs/index.md index 5cbd6a1a..d17f9ded 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,17 @@ -# pyp5js: Python to P5.js Transcriptor +## pyp5js: drawing with Python 3 -[![PyPI version](https://badge.fury.io/py/pyp5js.svg)](https://badge.fury.io/py/pyp5js) ![Continuous Integration](https://github.com/berinhard/pyp5js/workflows/Continuous%20Integration/badge.svg?branch=develop&event=push) +[![PyPI version](https://badge.fury.io/py/pyp5js.svg)](https://badge.fury.io/py/pyp5js) +![Continuous Integration](https://github.com/berinhard/pyp5js/workflows/Continuous%20Integration/badge.svg?branch=develop&event=push) +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/berinhard/pyp5js/tree/main) -> [Processing](https://processing.org) ideas and Python 3 together with [P5.js](https://p5js.org) in the browser, using [Transcrypt](https://transcrypt.org/). -This project started from a proof of concept based in [Axel Tanner's "Transcrypt & p5js" blogpost](https://4nomore.net/2018/transcrypt_p5js/). The source code in [this Github repo](https://github.com/berinhard/pyp5js). +> [Processing](https://processing.org) ideas and Python 3 together with [P5.js](https://p5js.org) in the browser, using [Transcrypt](https://transcrypt.org/). -The project's main goal was to use Tanner's approach combined with decorator and global variables control to enable P5.js API from being called "directly" from the Python code as clean as possible. +Python 3 drawing in the web 🐍 🐍 🐍 Try it [here](https://berinhard.github.io/pyp5js/pyodide/)! `pyp5js` covers **all** the methods, variables and event handlers listed in [the p5.js API documentation](https://p5js.org/reference/). Here's an example of a valid Python code using p5.js API: ```python -from pyp5js import * - - def setup(): createCanvas(200, 200) background(160) @@ -110,16 +108,16 @@ $ pyp5js serve ``` If you just want to compile your code (without running the Web server) there's -the `transcrypt` command: +the `compile` command: ``` -$ pyp5js transcrypt my_sketch +$ pyp5js compile my_sketch ``` If you're lazy as me, you can use the `monitor` command instead of the previous one. The command will monitor your sketch directory and keep track of any changes on any `.py` files. When it notices a new change, it automatically runs -the transcrypt process for you: +the compile process for you: ``` $ pyp5js monitor my_sketch @@ -139,7 +137,7 @@ $ pyp5js --help ``` -### Known [issues](https://github.com/berinhard/pyp5js/issues) and differences to the Processing.Py and P5.js ways of doing things +### Known [issues](https://github.com/berinhard/pyp5js/issues), differences to the Processing.Py and P5.js ways of doing things and limitations - Remember to use **P5.js** method names & conventions for most things. @@ -147,9 +145,11 @@ $ pyp5js --help - There are no `PVector` objects, with their nice syntatic operator overloaded sugar - use `p5.Vector` with `createVector()` and P5.js conventions ... for now... +- For the `mouseWheel()` event funtion, use `def mouseWheel()` with NO parameters, then, inside the function, the magic `event.delta` will have a value equivalent to the one returned by Java&Python Mode's `event.getCount()`. + - At this point, it is a known limitation that you have to "declare" global variables before `setup()` and `draw()`, maybe using `name = None`, as they can't be created inside methods. -- For the `mouseWheel()` event funtion, use `def mouseWheel()` with NO parameters, then, inside the function, the magic `event.delta` will have a value equivalent to the one returned by Java&Python Mode's `event.getCount()`. +- Not all Python libs are available when using Transcrypt because they required a JS-version to be enabled. In [this link](https://github.com/QQuick/Transcrypt/tree/master/transcrypt/modules) you can take a look in all modules Transcrypt enables. ## How can I contribute? @@ -176,14 +176,21 @@ $ make test After that, you should have the `pyp5js` command enabled and it will respect all the changes you introduce to the code. Now, a brief explanation about the code under `pyp5js` directory: -- `cli.py`: the entrypoint for `pyp5js` commands such as `new` or `transcrypt` +- `config` module: centralize pieces of code used to configure how `pyp5js` runs +- `cli.py`: the entrypoint for `pyp5js` commands such as `new` or `compile` - `commands.py`: just functions responsible for the real implementations for `pyp5js` commands - `compiler.py`: where all the magic happens! -- `fs.py`: classes to abstract the files and directories manipulations from the commands - `exception.py`: custom exceptions used by `pyp5js` - `monitor.py`: module with the objects used by the `monitor` command - `pyp5js.py`: module which is imported by the sketches and integrates with P5.js API +- `sketch.py`: class to abstract Sketches' files, directories and configuration - `template_renderers.py`: simple module with the renderization logic for the code templates like `target_sketch.py` - `http/web_app.py`: Flask application for the web interface. Now go [fetch yourself an issue](https://github.com/berinhard/pyp5js/issues) and happy hacking! + +### References + +This project started from a proof of concept based in [Axel Tanner's "Transcrypt & p5js" blogpost](https://4nomore.net/2018/transcrypt_p5js/). The source code in [this Github repo](https://github.com/berinhard/pyp5js). + +The [Pyodide](https://github.com/iodide-project/pyodide) interpreter was implemented based on [Luca Damasco's experiment](https://github.com/Luxapodular/Py5.js) supported by [COSA (The Clinic for Open Source Arts)](https://www.du.edu/ahss/opensourcearts/) at the University of Denver College of Arts and Sciences. diff --git a/docs/pyodide/index.html b/docs/pyodide/index.html index c1ccb841..e45578ad 100644 --- a/docs/pyodide/index.html +++ b/docs/pyodide/index.html @@ -56,8 +56,8 @@
- - + +
@@ -66,7 +66,7 @@

If you execute the code but nothing is being rendered in the broswer, please open your browser's console to read the error traceback (usually you can do this by pressing F12 and clicking in the Console tab).

-

pyp5js running on top of pyodide, based on: Luca Damasco's experiment supported by COSA (The Clinic for Open Source Arts) at the University of Denver College of Arts and Sciences.

+

pyp5js running on top of pyodide

diff --git a/pyp5js/static/p5/addons/p5.sound.js b/pyp5js/assets/static/p5/addons/p5.sound.js similarity index 100% rename from pyp5js/static/p5/addons/p5.sound.js rename to pyp5js/assets/static/p5/addons/p5.sound.js diff --git a/pyp5js/static/p5/addons/p5.sound.min.js b/pyp5js/assets/static/p5/addons/p5.sound.min.js similarity index 100% rename from pyp5js/static/p5/addons/p5.sound.min.js rename to pyp5js/assets/static/p5/addons/p5.sound.min.js diff --git a/pyp5js/static/p5/p5.js b/pyp5js/assets/static/p5/p5.js similarity index 100% rename from pyp5js/static/p5/p5.js rename to pyp5js/assets/static/p5/p5.js diff --git a/pyp5js/static/p5/p5.min.js b/pyp5js/assets/static/p5/p5.min.js similarity index 100% rename from pyp5js/static/p5/p5.min.js rename to pyp5js/assets/static/p5/p5.min.js diff --git a/pyp5js/cli.py b/pyp5js/cli.py index 7b5866f1..832bde5a 100755 --- a/pyp5js/cli.py +++ b/pyp5js/cli.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 +import warnings from pathlib import Path from cprint import cprint import click from pyp5js import commands -from pyp5js.config import SKETCHBOOK_DIR +from pyp5js.config import SKETCHBOOK_DIR, AVAILABLE_INTERPRETERS, PYODIDE_INTERPRETER @click.group() @@ -18,36 +19,28 @@ def command_line_entrypoint(): @command_line_entrypoint.command('new') @click.argument('sketch_name') -@click.option('--monitor', '-m', is_flag=True) -def configure_new_sketch(sketch_name, monitor): +@click.option('--monitor', '-m', is_flag=True, help='Starts the monitor command too') +@click.option('--interpreter', '-i', type=click.Choice(AVAILABLE_INTERPRETERS), default=PYODIDE_INTERPRETER, help='Which python tool to use to run the sketch. (defaults to pyodide)') +def configure_new_sketch(sketch_name, monitor, interpreter): """ - Create dir and configure boilerplate - - Params: - - sketch_name: name of the sketch (will create a {sketch_name}.py) - - Opitionals: - - monitor: start the monitor command as well - - Example: - $ pyp5js new my_sketch + Create dir and configure boilerplate - Example:\n + $ pyp5js new my_sketch -i pyodide """ - files = commands.new_sketch(sketch_name) + files = commands.new_sketch(sketch_name, interpreter) cprint.ok(f"Your sketch was created!") if not monitor: cprint.ok(f"Please, open and edit the file {files.sketch_py} to draw. When you're ready to see your results, just run:") - cmd = f"\t pyp5js transcrypt {sketch_name}" + cmd = f"\t pyp5js compile {sketch_name}" cprint.ok(cmd) cprint.ok(f"And open file://{files.index_html.absolute()} on your browser to see yor results!") else: - cprint.ok(f"Please, open and edit the file {sketch_py} to draw.") + cprint.ok(f"Please, open and edit the file {files.sketch_py} to draw.") cprint.ok(f"And open file://{files.index_html.absolute()} on your browser to see yor results!") commands.monitor_sketch(sketch_name) - @command_line_entrypoint.command("transcrypt") @click.argument("sketch_name") def transcrypt_sketch(sketch_name): @@ -55,12 +48,20 @@ def transcrypt_sketch(sketch_name): Command to generate the P5.js code for a python sketch Params: + - sketch_name: name of the sketch Example: $ pyp5js transcrypt my_sketch """ - files = commands.transcrypt_sketch(sketch_name) + msg = f"transcript command is deprecated. Instead, please run: \n\n\tpyp5js compile {sketch_name}\n" + warnings.warn(msg, UserWarning) + + +@command_line_entrypoint.command("compile") +@click.argument("sketch_name") +def compile_sketch(sketch_name): + files = commands.compile_sketch(sketch_name) cprint.ok(f"Your sketch is ready and available at file://{files.index_html.absolute()}") @@ -72,9 +73,11 @@ def monitor_sketch(sketch_name): it'll automatically generate the JS files as in pyp5js transcrypt command Params: + - sketch_name: name of the sketch Example: + $ pyp5js monitor my_sketch """ commands.monitor_sketch(sketch_name) diff --git a/pyp5js/commands.py b/pyp5js/commands.py index 6d01b888..ab9e2f51 100644 --- a/pyp5js/commands.py +++ b/pyp5js/commands.py @@ -4,41 +4,44 @@ from cprint import cprint from jinja2 import Environment, FileSystemLoader +from pyp5js.config.fs import PYP5JS_FILES from pyp5js.compiler import compile_sketch_js from pyp5js.exceptions import PythonSketchDoesNotExist -from pyp5js.fs import SketchFiles +from pyp5js.sketch import Sketch from pyp5js.http import pyp5js_web_app from pyp5js.monitor import monitor_sketch as monitor_sketch_service from pyp5js.templates_renderers import get_sketch_index_content +from pyp5js.config import PYODIDE_INTERPRETER -def new_sketch(sketch_name): +def new_sketch(sketch_name, interpreter=PYODIDE_INTERPRETER): """ Creates a new sketch with the required assets and a index.html file, based on pyp5js's templates :param sketch_name: name for new sketch + :param interpreter: interpreter to use (transcrypt or pyodide) :type sketch_name: string :return: file names :rtype: list of strings """ - sketch_files = SketchFiles(sketch_name) - sketch_files.create_sketch_dir() + sketch = Sketch(sketch_name, interpreter=interpreter) + sketch.create_sketch_dir() templates_files = [ - (sketch_files.from_lib.base_sketch, sketch_files.sketch_py), - (sketch_files.from_lib.p5js, sketch_files.p5js), + (sketch.config.get_base_sketch_template(), sketch.sketch_py), + (PYP5JS_FILES.p5js, sketch.p5js), ] for src, dest in templates_files: shutil.copyfile(src, dest) - index_contet = get_sketch_index_content(sketch_files) - with open(sketch_files.index_html, "w") as fd: + index_contet = get_sketch_index_content(sketch) + with open(sketch.index_html, "w") as fd: fd.write(index_contet) - return sketch_files + return sketch -def transcrypt_sketch(sketch_name): +def compile_sketch(sketch_name): """ Transcrypt the sketch python code to javascript. @@ -48,14 +51,14 @@ def transcrypt_sketch(sketch_name): :rtype: list of strings """ - sketch_files = SketchFiles(sketch_name) - sketch_files.validate_name() + sketch = Sketch(sketch_name) + sketch.validate_name() - if not sketch_files.sketch_exists: - raise PythonSketchDoesNotExist(sketch_files.sketch_py.resolve()) + if not sketch.sketch_exists: + raise PythonSketchDoesNotExist(sketch) - compile_sketch_js(sketch_files) - return sketch_files + compile_sketch_js(sketch) + return sketch def monitor_sketch(sketch_name): @@ -69,16 +72,16 @@ def monitor_sketch(sketch_name): :rtype: list of strings """ - sketch_files = SketchFiles(sketch_name) - sketch_files.validate_name() + sketch = Sketch(sketch_name) + sketch.validate_name() - if not sketch_files.sketch_exists: - raise PythonSketchDoesNotExist(sketch_files.sketch_py.resolve()) + if not sketch.sketch_exists: + raise PythonSketchDoesNotExist(sketch) - cprint(f"Monitoring for changes in {sketch_files.sketch_dir.resolve()}...") + cprint(f"Monitoring for changes in {sketch.sketch_dir.resolve()}...") try: - monitor_sketch_service(sketch_files) + monitor_sketch_service(sketch) except KeyboardInterrupt: cprint.info("Exiting monitor...") diff --git a/pyp5js/compiler.py b/pyp5js/compiler.py index d02f7d08..6f68bfe6 100644 --- a/pyp5js/compiler.py +++ b/pyp5js/compiler.py @@ -2,33 +2,54 @@ import subprocess from cprint import cprint +from pyp5js.config.fs import PYP5JS_FILES from pyp5js.templates_renderers import get_target_sketch_content -class Pyp5jsCompiler: +class BasePyp5jsCompiler: - def __init__(self, sketch_files): - self.sketch_files = sketch_files + def __init__(self, sketch): + self.sketch = sketch + + @property + def target_dir(self): + """ + Path to directory with the js and assets files + """ + return self.sketch.sketch_dir.joinpath('__target__') def compile_sketch_js(self): self.prepare() self.run_compiler() self.clean_up() - @property - def target_dir(self): + def run_compiler(self): + pass + + def clean_up(self): + pass + + def prepare(self): """ - Path to directory with the js and assets files + Creates target_sketch.py to import the sketch's functions """ - return self.sketch_files.sketch_dir.joinpath('__target__') + content = get_target_sketch_content(self.sketch) + + with self.sketch.target_sketch.open('w') as fd: + fd.write(content) + + cprint.info(f"{self.sketch.target_sketch.resolve()} updated with sketch code") + + +class TranscryptCompiler(BasePyp5jsCompiler): @property def command_line(self): """ Builds transcrypt command line with the required parameters and flags """ - pyp5_dir = self.sketch_files.from_lib.install - target = self.sketch_files.target_sketch + pyp5_dir = PYP5JS_FILES.install + target = self.sketch.target_sketch return ' '.join([str(c) for c in [ 'transcrypt', '-xp', f'"{pyp5_dir}"', '-k', '-ks', '-b', '-m', '-n', f'"{target}"' ]]) @@ -49,22 +70,22 @@ def clean_up(self): This is required because github pages can't deal with assets under a __target__ directory """ - if self.sketch_files.target_dir.exists(): - shutil.rmtree(self.sketch_files.target_dir) - shutil.move(self.target_dir, self.sketch_files.target_dir) + if self.sketch.target_dir.exists(): + shutil.rmtree(self.sketch.target_dir) + # mv __target__ target + shutil.move(self.target_dir, self.sketch.target_dir) - if self.sketch_files.target_sketch.exists(): - self.sketch_files.target_sketch.unlink() + if self.sketch.target_sketch.exists(): + self.sketch.target_sketch.unlink() - def prepare(self): - """ - Creates target_sketch.py to import the sketch's functions - """ - with self.sketch_files.target_sketch.open('w') as fd: - content = get_target_sketch_content(self.sketch_files) - fd.write(content) + +class PyodideCompiler(BasePyp5jsCompiler): + pass -def compile_sketch_js(sketch_files): - compiler = Pyp5jsCompiler(sketch_files) +def compile_sketch_js(sketch): + if sketch.config.is_transcrypt: + compiler = TranscryptCompiler(sketch) + else: + compiler = PyodideCompiler(sketch) compiler.compile_sketch_js() diff --git a/pyp5js/config.py b/pyp5js/config/__init__.py similarity index 58% rename from pyp5js/config.py rename to pyp5js/config/__init__.py index 5d094262..c942fe6a 100644 --- a/pyp5js/config.py +++ b/pyp5js/config/__init__.py @@ -1,3 +1,5 @@ +from . import sketch +from .sketch import TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER from decouple import config from pathlib import Path @@ -5,3 +7,5 @@ if not SKETCHBOOK_DIR.exists(): SKETCHBOOK_DIR.mkdir() + +AVAILABLE_INTERPRETERS = [TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER] diff --git a/pyp5js/config/fs.py b/pyp5js/config/fs.py new file mode 100644 index 00000000..40a6fb70 --- /dev/null +++ b/pyp5js/config/fs.py @@ -0,0 +1,69 @@ +from pathlib import Path + + +class LibFiles(): + """ + This class abstracts pyp5js lib files path from the filesystem. + It expose properties for the directories and files. + Every property returns a pathlib.Path object + """ + + def __init__(self): + self.install = Path(__file__).parents[1] + + ##### GENERAL PURPOSE + + @property + def templates_dir(self): + return self.install.joinpath('templates') + + @property + def assets_dir(self): + return self.install.joinpath('assets') + + @property + def static_dir(self): + return self.assets_dir.joinpath('static') + + @property + def pytop5js(self): + return self.install.joinpath('pyp5js.py') + + @property + def p5js(self): + return self.static_dir.joinpath('p5', 'p5.min.js') + + @property + def p5_yml(self): + return self.assets_dir.joinpath('p5_reference.yml') + + ##### TRANSCRYPT SPECIFICS + + @property + def transcrypt_index_html(self): + return self.templates_dir.joinpath('transcrypt', 'index.html') + + @property + def transcrypt_target_sketch_template(self): + return self.templates_dir.joinpath('transcrypt', 'target_sketch.py.template') + + @property + def transcrypt_base_sketch_template(self): + return self.templates_dir.joinpath('transcrypt', 'base_sketch.py.template') + + ##### PYODIDE SPECIFICS + + @property + def pyodide_target_sketch_template(self): + return self.templates_dir.joinpath('pyodide', 'target_sketch.js.template') + + @property + def pyodide_index_html(self): + return self.templates_dir.joinpath('pyodide', 'index.html') + + @property + def pyodide_base_sketch_template(self): + return self.templates_dir.joinpath('pyodide', 'base_sketch.py.template') + + +PYP5JS_FILES = LibFiles() diff --git a/pyp5js/config/sketch.py b/pyp5js/config/sketch.py new file mode 100644 index 00000000..d4de5c2d --- /dev/null +++ b/pyp5js/config/sketch.py @@ -0,0 +1,51 @@ +import json + +from pyp5js.config.fs import PYP5JS_FILES + +TRANSCRYPT_INTERPRETER = 'transcrypt' +PYODIDE_INTERPRETER = 'pyodide' + +class SketchConfig: + + @classmethod + def from_json(cls, json_file_path): + with open(json_file_path) as fd: + config_data = json.load(fd) + return cls(**config_data) + + def __init__(self, interpreter): + self.interpreter = interpreter + + def write(self, fname): + with open(fname, "w") as fd: + data = {"interpreter": self.interpreter} + json.dump(data, fd) + + @property + def is_transcrypt(self): + return self.interpreter == TRANSCRYPT_INTERPRETER + + @property + def is_pyodide(self): + return self.interpreter == PYODIDE_INTERPRETER + + def get_index_template(self): + index_map = { + TRANSCRYPT_INTERPRETER: PYP5JS_FILES.transcrypt_index_html, + PYODIDE_INTERPRETER: PYP5JS_FILES.pyodide_index_html, + } + return index_map[self.interpreter] + + def get_target_js_template(self): + target_map = { + TRANSCRYPT_INTERPRETER: PYP5JS_FILES.transcrypt_target_sketch_template, + PYODIDE_INTERPRETER: PYP5JS_FILES.pyodide_target_sketch_template, + } + return target_map[self.interpreter] + + def get_base_sketch_template(self): + base_map = { + TRANSCRYPT_INTERPRETER: PYP5JS_FILES.transcrypt_base_sketch_template, + PYODIDE_INTERPRETER: PYP5JS_FILES.pyodide_base_sketch_template, + } + return base_map[self.interpreter] diff --git a/pyp5js/exceptions.py b/pyp5js/exceptions.py index b552585b..ab13360a 100644 --- a/pyp5js/exceptions.py +++ b/pyp5js/exceptions.py @@ -1,19 +1,22 @@ class PythonSketchDoesNotExist(Exception): - def __init__(self, sketch_py): + def __init__(self, sketch): + sketch_py = sketch.sketch_py.resolve() message = f"Sketch file {sketch_py} does not exist" super().__init__(message) class SketchDirAlreadyExistException(Exception): - def __init__(self, sketch_dir): + def __init__(self, sketch): + sketch_dir = sketch.sketch_dir.resolve() message = f'The directory {sketch_dir} already exists.' super().__init__(message) class InvalidName(Exception): - def __init__(self, sketch_name): + def __init__(self, sketch): + sketch_name = sketch.sketch_name message = f'The name {sketch_name} must start with a letter or an underscore and ' + \ 'contain alphanumeric and underscore characters only.' super().__init__(message) diff --git a/pyp5js/http/templates/view_sketch.html b/pyp5js/http/templates/view_sketch.html index d9b66409..6ae03e84 100644 --- a/pyp5js/http/templates/view_sketch.html +++ b/pyp5js/http/templates/view_sketch.html @@ -24,6 +24,10 @@ float: left; margin: 1.5em; } + + #sketch-buttons { + margin: 0 1.5em; + } {% endblock %} @@ -31,13 +35,18 @@
+ Back to sketchbook
{{ py_code }}
-
- +
+ {% if live_run %} + + + {% endif %} +
@@ -49,6 +58,7 @@ {% endblock content %} {% block custom_js %} + + + - + + + + {% endblock %} diff --git a/pyp5js/http/web_app.py b/pyp5js/http/web_app.py index a7b81539..0f7745ad 100644 --- a/pyp5js/http/web_app.py +++ b/pyp5js/http/web_app.py @@ -4,9 +4,9 @@ from slugify import slugify from pyp5js import commands -from pyp5js.config import SKETCHBOOK_DIR +from pyp5js.config import SKETCHBOOK_DIR, PYODIDE_INTERPRETER from pyp5js.exceptions import PythonSketchDoesNotExist, SketchDirAlreadyExistException -from pyp5js.fs import SketchFiles +from pyp5js.sketch import Sketch app = Flask(__name__) @@ -18,11 +18,11 @@ def sketches_list_view(): sketches = [] for sketch_dir in (p for p in SKETCHBOOK_DIR.iterdir() if p.is_dir()): name = sketch_dir.name - sketch_files = SketchFiles(name) - if sketch_files.has_all_files: + sketch = Sketch(name) + if sketch.has_all_files: sketches.append({ 'name': name, - 'url': f'/sketch/{name}' + 'url': f'/sketch/{name}/' }) sketches = sorted(sketches, key=lambda s: s['name']) @@ -41,7 +41,9 @@ def add_new_sketch_view(): context['error'] = "You have to input a sketch name to proceed." else: try: - files = commands.new_sketch(sketch_name) + # web client only supports pyodide mode for now + # TODO: improve post payload to accept a select + files = commands.new_sketch(sketch_name, interpreter=PYODIDE_INTERPRETER) template = 'new_sketch_success.html' context.update({ 'files': files, @@ -57,11 +59,11 @@ def add_new_sketch_view(): @app.route('/sketch//', defaults={'static_path': ''}, methods=['GET', 'POST']) @app.route('/sketch//') def render_sketch_view(sketch_name, static_path): - sketch_files = SketchFiles(sketch_name) + sketch = Sketch(sketch_name) error = '' if static_path: - return _serve_static(sketch_files.sketch_dir, static_path) + return _serve_static(sketch.sketch_dir, static_path) elif request.method == 'POST': py_code = request.form.get('py_code', '') @@ -73,23 +75,25 @@ def render_sketch_view(sketch_name, static_path): error = 'You have to define a draw function.' else: try: - ast.parse(py_code, sketch_files.sketch_py.name) - sketch_files.sketch_py.write_text(py_code) + ast.parse(py_code, sketch.sketch_py.name) + sketch.sketch_py.write_text(py_code) except SyntaxError as exc: error = f'SyntaxError: {exc}' if not error: try: - commands.transcrypt_sketch(sketch_name) + commands.compile_sketch(sketch_name) except PythonSketchDoesNotExist: - return f"There's no sketch in {sketch_files.sketch_dir.resolve()}", 404 + return f"There's no sketch in {sketch.sketch_dir.resolve()}", 404 context = { - 'p5_js_url': sketch_files.urls.p5_js_url, - 'sketch_js_url': sketch_files.urls.sketch_js_url, - 'sketch_name': sketch_files.sketch_name, - 'py_code': sketch_files.sketch_py.read_text(), + 'p5_js_url': sketch.urls.p5_js_url, + 'sketch_js_url': sketch.urls.sketch_js_url, + 'sketch_name': sketch.sketch_name, + 'py_code': sketch.sketch_py.read_text(), 'error': error, + 'js_as_module': sketch.config.is_transcrypt, + 'live_run': sketch.config.is_pyodide, } return render_template('view_sketch.html', **context) diff --git a/pyp5js/monitor.py b/pyp5js/monitor.py index d7df8e65..c4eacc76 100644 --- a/pyp5js/monitor.py +++ b/pyp5js/monitor.py @@ -6,12 +6,12 @@ from pyp5js.compiler import compile_sketch_js -def monitor_sketch(sketch_files): +def monitor_sketch(sketch): observer = Observer() - event_handler = TranscryptSketchEventHandler(sketch_files=sketch_files, observer=observer) + event_handler = TranscryptSketchEventHandler(sketch=sketch, observer=observer) - observer.schedule(event_handler, str(sketch_files.sketch_dir.resolve())) + observer.schedule(event_handler, str(sketch.sketch_dir.resolve())) observer.start() try: while True: @@ -26,7 +26,7 @@ class TranscryptSketchEventHandler(PatternMatchingEventHandler): patterns = ["*.py"] def __init__(self, *args, **kwargs): - self.sketch_files = kwargs.pop('sketch_files') + self.sketch = kwargs.pop('sketch') self.observer = kwargs.pop('observer') self._last_event = None super().__init__(*args, **kwargs) @@ -38,11 +38,11 @@ def on_modified(self, event): handlers_config = self.observer._handlers.copy() handlers_copy = {} - compile_sketch_js(self.sketch_files) + compile_sketch_js(self.sketch) queue = self.observer.event_queue while queue.qsize(): queue.get() - index_file = self.sketch_files.index_html + index_file = self.sketch.index_html cprint.ok(f"Your sketch is ready and available at file://{index_file.absolute()}") diff --git a/pyp5js/pyp5js.py b/pyp5js/pyp5js.py index b673910b..1fc78ab4 100644 --- a/pyp5js/pyp5js.py +++ b/pyp5js/pyp5js.py @@ -548,8 +548,32 @@ def saveCanvas(*args): def saveFrames(*args): return _P5_INSTANCE.saveFrames(*args) + +def image_proxy(img): + """ + Proxy to turn of transcypt when calling img.get/set methods + """ + + def _set(*args): + __pragma__('noalias', 'set') + value = img.set(*args) + __pragma__('alias', 'set', 'py_set') + return value + + def _get(*args): + __pragma__('noalias', 'get') + value = img.get(*args) + __pragma__('alias', 'get', 'py_get') + return value + + img.set = _set + img.get = _get + return img + + def loadImage(*args): - return _P5_INSTANCE.loadImage(*args) + imageObj = _P5_INSTANCE.loadImage(*args) + return image_proxy(imageObj) def image(*args): return _P5_INSTANCE.image(*args) @@ -575,9 +599,11 @@ def filter(*args): else: return _P5_INSTANCE.filter(*args) - def get(*args): - return _P5_INSTANCE.get(*args) + __pragma__('noalias', 'get') + p5_get = _P5_INSTANCE.get(*args) + __pragma__('alias', 'get', 'py_get') + return p5_get def loadPixels(*args): return _P5_INSTANCE.loadPixels(*args) @@ -588,7 +614,6 @@ def set(*args): else: return _P5_INSTANCE.set(*args) - def updatePixels(*args): return _P5_INSTANCE.updatePixels(*args) diff --git a/pyp5js/fs.py b/pyp5js/sketch.py similarity index 60% rename from pyp5js/fs.py rename to pyp5js/sketch.py index de4e2754..d1cc62af 100644 --- a/pyp5js/fs.py +++ b/pyp5js/sketch.py @@ -1,41 +1,47 @@ import os import re -import shutil -from pathlib import Path -from cprint import cprint from collections import namedtuple from pyp5js import config +from pyp5js.config.sketch import SketchConfig from pyp5js.exceptions import SketchDirAlreadyExistException, InvalidName SketchUrls = namedtuple('SketchUrls', ['p5_js_url', 'sketch_js_url']) -class SketchFiles(): +class Sketch: + """ + This class abstracts the sketch filesystem and configuration. + Every path propery return pathlib.Path objects. + """ TARGET_NAME = 'target' STATIC_NAME = 'static' - def __init__(self, sketch_name): + def __init__(self, sketch_name, interpreter=config.TRANSCRYPT_INTERPRETER, **cfg): self.sketch_name = sketch_name - self.from_lib = LibFiles() + if self.config_file.exists(): + self.config = SketchConfig.from_json(self.config_file) + else: + self.config = SketchConfig(interpreter=interpreter, **cfg) def validate_name(self): does_not_start_with_letter_or_underscore = r'^[^a-zA-Z_]' contains_non_alphanumerics_except_underscore = r'[^a-zA-Z0-9_]' if re.match(does_not_start_with_letter_or_underscore, self.sketch_name) or \ re.search(contains_non_alphanumerics_except_underscore, self.sketch_name): - raise InvalidName(self.sketch_name) + raise InvalidName(self) def create_sketch_dir(self): self.validate_name() if self.sketch_dir.exists(): - raise SketchDirAlreadyExistException(self.sketch_dir.resolve()) + raise SketchDirAlreadyExistException(self) os.makedirs(self.sketch_dir) self.static_dir.mkdir() self.target_dir.mkdir() + self.config.write(self.config_file) @property def sketch_exists(self): @@ -66,12 +72,25 @@ def p5js(self): @property def target_sketch(self): - return self.sketch_dir.joinpath("target_sketch.py") + # TODO: There is a potential major refactoring here that's to create + # base sketch classes and specific implementations. One for a TranscryptSketch + # and another one for a PyodideSketch. I think the config + # attribute strategy can escalate complexity quickly and it + # was a bad idea, but have been working so far... + # bonus: opens path to a BrythonSketch ;] + if self.config.is_transcrypt: + return self.sketch_dir.joinpath("target_sketch.py") + else: + return self.target_dir.joinpath("target_sketch.js") @property def sketch_py(self): return self.sketch_dir.joinpath(f'{self.sketch_name}.py') + @property + def config_file(self): + return self.sketch_dir.joinpath('.properties.json') + @property def target_dir(self): return self.sketch_dir.joinpath(self.TARGET_NAME) @@ -85,45 +104,3 @@ def urls(self): p5_js_url=f"{self.STATIC_NAME}/p5.js", sketch_js_url=f"{self.TARGET_NAME}/target_sketch.js", ) - - -class LibFiles(): - - def __init__(self): - self.install = Path(__file__).parent - - @property - def templates_dir(self): - return self.install.joinpath('templates') - - @property - def assets_dir(self): - return self.install.joinpath('assets') - - @property - def static_dir(self): - return self.install.joinpath('static') - - @property - def pytop5js(self): - return self.install.joinpath('pyp5js.py') - - @property - def base_sketch(self): - return self.templates_dir.joinpath('base_sketch.py.template') - - @property - def target_sketch_template(self): - return self.templates_dir.joinpath('target_sketch.py.template') - - @property - def index_html(self): - return self.templates_dir.joinpath('index.html') - - @property - def p5js(self): - return self.static_dir.joinpath('p5', 'p5.min.js') - - @property - def p5_yml(self): - return self.assets_dir.joinpath('p5_reference.yml') diff --git a/pyp5js/templates/pyodide/base_sketch.py.template b/pyp5js/templates/pyodide/base_sketch.py.template new file mode 100644 index 00000000..1ba3782e --- /dev/null +++ b/pyp5js/templates/pyodide/base_sketch.py.template @@ -0,0 +1,10 @@ +def setup(): + createCanvas(200, 200) + background(160) + + +def draw(): + fill("blue") + background(200) + radius = sin(frameCount / 60) * 50 + 50 + ellipse(100, 100, radius, radius) diff --git a/pyp5js/templates/pyodide/index.html b/pyp5js/templates/pyodide/index.html new file mode 100644 index 00000000..3696e8f2 --- /dev/null +++ b/pyp5js/templates/pyodide/index.html @@ -0,0 +1,22 @@ + + + + + + + + + {{ sketch_name }} - pyp5js (using pyodide) + + + + + + + + +
+ +
+ + diff --git a/pyp5js/templates/pyodide/target_sketch.js.template b/pyp5js/templates/pyodide/target_sketch.js.template new file mode 100644 index 00000000..a1d273f6 --- /dev/null +++ b/pyp5js/templates/pyodide/target_sketch.js.template @@ -0,0 +1,1217 @@ +let wrapper_content = ` +class PythonFunctions: pass + +setattr(PythonFunctions, 'map', map) +setattr(PythonFunctions, 'filter', filter) +setattr(PythonFunctions, 'set', set) + + +_P5_INSTANCE = None + +_CTX_MIDDLE = None +_DEFAULT_FILL = None +_DEFAULT_LEADMULT = None +_DEFAULT_STROKE = None +_DEFAULT_TEXT_FILL = None + +ADD = None +ALT = None +ARROW = None +AUDIO = None +AUTO = None +AXES = None +BACKSPACE = None +BASELINE = None +BEVEL = None +BEZIER = None +BLEND = None +BLUR = None +BOLD = None +BOLDITALIC = None +BOTTOM = None +BURN = None +CENTER = None +CHORD = None +CLAMP = None +CLOSE = None +CONTROL = None +CORNER = None +CORNERS = None +CROSS = None +CURVE = None +DARKEST = None +DEG_TO_RAD = None +DEGREES = None +DELETE = None +DIFFERENCE = None +DILATE = None +DODGE = None +DOWN_ARROW = None +ENTER = None +ERODE = None +ESCAPE = None +EXCLUSION = None +FILL = None +GRAY = None +GRID = None +HALF_PI = None +HAND = None +HARD_LIGHT = None +HSB = None +HSL = None +IMAGE = None +IMMEDIATE = None +INVERT = None +ITALIC = None +LANDSCAPE = None +LEFT = None +LEFT_ARROW = None +LIGHTEST = None +LINE_LOOP = None +LINE_STRIP = None +LINEAR = None +LINES = None +MIRROR = None +MITER = None +MOVE = None +MULTIPLY = None +NEAREST = None +NORMAL = None +OPAQUE = None +OPEN = None +OPTION = None +OVERLAY = None +PI = None +PIE = None +POINTS = None +PORTRAIT = None +POSTERIZE = None +PROJECT = None +QUAD_STRIP = None +QUADRATIC = None +QUADS = None +QUARTER_PI = None +RAD_TO_DEG = None +RADIANS = None +RADIUS = None +REPEAT = None +REPLACE = None +RETURN = None +RGB = None +RIGHT = None +RIGHT_ARROW = None +ROUND = None +SCREEN = None +SHIFT = None +SOFT_LIGHT = None +SQUARE = None +STROKE = None +SUBTRACT = None +TAB = None +TAU = None +TEXT = None +TEXTURE = None +THRESHOLD = None +TOP = None +TRIANGLE_FAN = None +TRIANGLE_STRIP = None +TRIANGLES = None +TWO_PI = None +UP_ARROW = None +VIDEO = None +WAIT = None +WEBGL = None +P2D = None +PI = None + +frameCount = None +focused = None +displayWidth = None +displayHeight = None +windowWidth = None +windowHeight = None +width = None +height = None +deviceOrientation = None +accelerationX = None +accelerationY = None +accelerationZ = None +pAccelerationX = None +pAccelerationY = None +pAccelerationZ = None +rotationX = None +rotationY = None +rotationZ = None +pRotationX = None +pRotationY = None +pRotationZ = None +turnAxis = None +keyIsPressed = None +key = None +keyCode = None +mouseX = None +mouseY = None +pmouseX = None +pmouseY = None +winMouseX = None +winMouseY = None +pwinMouseX = None +pwinMouseY = None +mouseButton = None +mouseIsPressed = None +touches = None +pixels = None + + +def alpha(*args): + return _P5_INSTANCE.alpha(*args) + +def blue(*args): + return _P5_INSTANCE.blue(*args) + +def brightness(*args): + return _P5_INSTANCE.brightness(*args) + +def color(*args): + return _P5_INSTANCE.color(*args) + +def green(*args): + return _P5_INSTANCE.green(*args) + +def hue(*args): + return _P5_INSTANCE.hue(*args) + +def lerpColor(*args): + return _P5_INSTANCE.lerpColor(*args) + +def lightness(*args): + return _P5_INSTANCE.lightness(*args) + +def red(*args): + return _P5_INSTANCE.red(*args) + +def saturation(*args): + return _P5_INSTANCE.saturation(*args) + +def background(*args): + return _P5_INSTANCE.background(*args) + +def clear(*args): + p5_clear = _P5_INSTANCE.clear(*args) + return p5_clear + +def erase(*args): + return _P5_INSTANCE.erase(*args) + +def noErase(*args): + return _P5_INSTANCE.noErase(*args) + +def colorMode(*args): + return _P5_INSTANCE.colorMode(*args) + +def fill(*args): + return _P5_INSTANCE.fill(*args) + +def noFill(*args): + return _P5_INSTANCE.noFill(*args) + +def noStroke(*args): + return _P5_INSTANCE.noStroke(*args) + +def stroke(*args): + return _P5_INSTANCE.stroke(*args) + +def arc(*args): + return _P5_INSTANCE.arc(*args) + +def ellipse(*args): + return _P5_INSTANCE.ellipse(*args) + +def circle(*args): + return _P5_INSTANCE.circle(*args) + +def line(*args): + return _P5_INSTANCE.line(*args) + +def point(*args): + return _P5_INSTANCE.point(*args) + +def quad(*args): + return _P5_INSTANCE.quad(*args) + +def rect(*args): + return _P5_INSTANCE.rect(*args) + +def square(*args): + return _P5_INSTANCE.square(*args) + +def triangle(*args): + return _P5_INSTANCE.triangle(*args) + +def plane(*args): + return _P5_INSTANCE.plane(*args) + +def box(*args): + return _P5_INSTANCE.box(*args) + +def sphere(*args): + return _P5_INSTANCE.sphere(*args) + +def cylinder(*args): + return _P5_INSTANCE.cylinder(*args) + +def cone(*args): + return _P5_INSTANCE.cone(*args) + +def ellipsoid(*args): + return _P5_INSTANCE.ellipsoid(*args) + +def torus(*args): + return _P5_INSTANCE.torus(*args) + +def loadModel(*args): + return _P5_INSTANCE.loadModel(*args) + +def model(*args): + return _P5_INSTANCE.model(*args) + +def ellipseMode(*args): + return _P5_INSTANCE.ellipseMode(*args) + +def noSmooth(*args): + return _P5_INSTANCE.noSmooth(*args) + +def rectMode(*args): + return _P5_INSTANCE.rectMode(*args) + +def smooth(*args): + return _P5_INSTANCE.smooth(*args) + +def strokeCap(*args): + return _P5_INSTANCE.strokeCap(*args) + +def strokeJoin(*args): + return _P5_INSTANCE.strokeJoin(*args) + +def strokeWeight(*args): + return _P5_INSTANCE.strokeWeight(*args) + +def bezier(*args): + return _P5_INSTANCE.bezier(*args) + +def bezierDetail(*args): + return _P5_INSTANCE.bezierDetail(*args) + +def bezierPoint(*args): + return _P5_INSTANCE.bezierPoint(*args) + +def bezierTangent(*args): + return _P5_INSTANCE.bezierTangent(*args) + +def curve(*args): + return _P5_INSTANCE.curve(*args) + +def curveDetail(*args): + return _P5_INSTANCE.curveDetail(*args) + +def curveTightness(*args): + return _P5_INSTANCE.curveTightness(*args) + +def curvePoint(*args): + return _P5_INSTANCE.curvePoint(*args) + +def curveTangent(*args): + return _P5_INSTANCE.curveTangent(*args) + +def beginContour(*args): + return _P5_INSTANCE.beginContour(*args) + +def beginShape(*args): + return _P5_INSTANCE.beginShape(*args) + +def bezierVertex(*args): + return _P5_INSTANCE.bezierVertex(*args) + +def curveVertex(*args): + return _P5_INSTANCE.curveVertex(*args) + +def endContour(*args): + return _P5_INSTANCE.endContour(*args) + +def endShape(*args): + return _P5_INSTANCE.endShape(*args) + +def quadraticVertex(*args): + return _P5_INSTANCE.quadraticVertex(*args) + +def vertex(*args): + return _P5_INSTANCE.vertex(*args) + +def cursor(*args): + return _P5_INSTANCE.cursor(*args) + +def frameRate(*args): + return _P5_INSTANCE.frameRate(*args) + +def noCursor(*args): + return _P5_INSTANCE.noCursor(*args) + +def fullscreen(*args): + return _P5_INSTANCE.fullscreen(*args) + +def pixelDensity(*args): + return _P5_INSTANCE.pixelDensity(*args) + +def displayDensity(*args): + return _P5_INSTANCE.displayDensity(*args) + +def getURL(*args): + return _P5_INSTANCE.getURL(*args) + +def getURLPath(*args): + return _P5_INSTANCE.getURLPath(*args) + +def getURLParams(*args): + return _P5_INSTANCE.getURLParams(*args) + +def preload(*args): + return _P5_INSTANCE.preload(*args) + +def remove(*args): + return _P5_INSTANCE.remove(*args) + +def noLoop(*args): + return _P5_INSTANCE.noLoop(*args) + +def loop(*args): + return _P5_INSTANCE.loop(*args) + +def push(*args): + return _P5_INSTANCE.push(*args) + +def redraw(*args): + return _P5_INSTANCE.redraw(*args) + +def resizeCanvas(*args): + return _P5_INSTANCE.resizeCanvas(*args) + +def noCanvas(*args): + return _P5_INSTANCE.noCanvas(*args) + +def createGraphics(*args): + return _P5_INSTANCE.createGraphics(*args) + +def blendMode(*args): + return _P5_INSTANCE.blendMode(*args) + +def setAttributes(*args): + return _P5_INSTANCE.setAttributes(*args) + +def applyMatrix(*args): + return _P5_INSTANCE.applyMatrix(*args) + +def resetMatrix(*args): + return _P5_INSTANCE.resetMatrix(*args) + +def rotate(*args): + return _P5_INSTANCE.rotate(*args) + +def rotateX(*args): + return _P5_INSTANCE.rotateX(*args) + +def rotateY(*args): + return _P5_INSTANCE.rotateY(*args) + +def rotateZ(*args): + return _P5_INSTANCE.rotateZ(*args) + +def scale(*args): + return _P5_INSTANCE.scale(*args) + +def shearX(*args): + return _P5_INSTANCE.shearX(*args) + +def shearY(*args): + return _P5_INSTANCE.shearY(*args) + +def translate(*args): + return _P5_INSTANCE.translate(*args) + +def createStringDict(*args): + return _P5_INSTANCE.createStringDict(*args) + +def createNumberDict(*args): + return _P5_INSTANCE.createNumberDict(*args) + +def append(*args): + return _P5_INSTANCE.append(*args) + +def arrayCopy(*args): + return _P5_INSTANCE.arrayCopy(*args) + +def concat(*args): + return _P5_INSTANCE.concat(*args) + +def reverse(*args): + return _P5_INSTANCE.reverse(*args) + +def shorten(*args): + return _P5_INSTANCE.shorten(*args) + +def shuffle(*args): + return _P5_INSTANCE.shuffle(*args) + +def sort(*args): + return _P5_INSTANCE.sort(*args) + +def splice(*args): + return _P5_INSTANCE.splice(*args) + +def subset(*args): + return _P5_INSTANCE.subset(*args) + +def float(*args): + return _P5_INSTANCE.float(*args) + +def int(*args): + return _P5_INSTANCE.int(*args) + +def str(*args): + return _P5_INSTANCE.str(*args) + +def boolean(*args): + return _P5_INSTANCE.boolean(*args) + +def byte(*args): + return _P5_INSTANCE.byte(*args) + +def char(*args): + return _P5_INSTANCE.char(*args) + +def unchar(*args): + return _P5_INSTANCE.unchar(*args) + +def hex(*args): + return _P5_INSTANCE.hex(*args) + +def unhex(*args): + return _P5_INSTANCE.unhex(*args) + +def join(*args): + return _P5_INSTANCE.join(*args) + +def match(*args): + return _P5_INSTANCE.match(*args) + +def matchAll(*args): + return _P5_INSTANCE.matchAll(*args) + +def nf(*args): + return _P5_INSTANCE.nf(*args) + +def nfc(*args): + return _P5_INSTANCE.nfc(*args) + +def nfp(*args): + return _P5_INSTANCE.nfp(*args) + +def nfs(*args): + return _P5_INSTANCE.nfs(*args) + +def split(*args): + return _P5_INSTANCE.split(*args) + +def splitTokens(*args): + return _P5_INSTANCE.splitTokens(*args) + +def trim(*args): + return _P5_INSTANCE.trim(*args) + +def setMoveThreshold(*args): + return _P5_INSTANCE.setMoveThreshold(*args) + +def setShakeThreshold(*args): + return _P5_INSTANCE.setShakeThreshold(*args) + +def keyIsDown(*args): + return _P5_INSTANCE.keyIsDown(*args) + +def createImage(*args): + return _P5_INSTANCE.createImage(*args) + +def saveCanvas(*args): + return _P5_INSTANCE.saveCanvas(*args) + +def saveFrames(*args): + return _P5_INSTANCE.saveFrames(*args) + +def loadImage(*args): + return _P5_INSTANCE.loadImage(*args) + +def image(*args): + return _P5_INSTANCE.image(*args) + +def tint(*args): + return _P5_INSTANCE.tint(*args) + +def noTint(*args): + return _P5_INSTANCE.noTint(*args) + +def imageMode(*args): + return _P5_INSTANCE.imageMode(*args) + +def blend(*args): + return _P5_INSTANCE.blend(*args) + +def copy(*args): + return _P5_INSTANCE.copy(*args) + +def filter(*args): + if len(args) > 1 and (args[0] is None or callable(args[0])): + return PythonFunctions.filter(*args) + else: + return _P5_INSTANCE.filter(*args) + +def get(*args): + return _P5_INSTANCE.get(*args) + +def loadPixels(*args): + return _P5_INSTANCE.loadPixels(*args) + +def set(*args): + if len(args) <= 1: + return PythonFunctions.set(*args) + else: + return _P5_INSTANCE.set(*args) + +def updatePixels(*args): + return _P5_INSTANCE.updatePixels(*args) + +def loadJSON(*args): + return _P5_INSTANCE.loadJSON(*args) + +def loadStrings(*args): + return _P5_INSTANCE.loadStrings(*args) + +def loadTable(*args): + return _P5_INSTANCE.loadTable(*args) + +def loadXML(*args): + return _P5_INSTANCE.loadXML(*args) + +def loadBytes(*args): + return _P5_INSTANCE.loadBytes(*args) + +def httpGet(*args): + return _P5_INSTANCE.httpGet(*args) + +def httpPost(*args): + return _P5_INSTANCE.httpPost(*args) + +def httpDo(*args): + return _P5_INSTANCE.httpDo(*args) + +def createWriter(*args): + return _P5_INSTANCE.createWriter(*args) + +def save(*args): + return _P5_INSTANCE.save(*args) + +def saveJSON(*args): + return _P5_INSTANCE.saveJSON(*args) + +def saveStrings(*args): + return _P5_INSTANCE.saveStrings(*args) + +def saveTable(*args): + return _P5_INSTANCE.saveTable(*args) + +def day(*args): + return _P5_INSTANCE.day(*args) + +def hour(*args): + return _P5_INSTANCE.hour(*args) + +def minute(*args): + return _P5_INSTANCE.minute(*args) + +def millis(*args): + return _P5_INSTANCE.millis(*args) + +def month(*args): + return _P5_INSTANCE.month(*args) + +def second(*args): + return _P5_INSTANCE.second(*args) + +def year(*args): + return _P5_INSTANCE.year(*args) + +def createVector(*args): + return _P5_INSTANCE.createVector(*args) + +def abs(*args): + return _P5_INSTANCE.abs(*args) + +def ceil(*args): + return _P5_INSTANCE.ceil(*args) + +def constrain(*args): + return _P5_INSTANCE.constrain(*args) + +def dist(*args): + return _P5_INSTANCE.dist(*args) + +def exp(*args): + return _P5_INSTANCE.exp(*args) + +def floor(*args): + return _P5_INSTANCE.floor(*args) + +def lerp(*args): + return _P5_INSTANCE.lerp(*args) + +def log(*args): + return _P5_INSTANCE.log(*args) + +def mag(*args): + return _P5_INSTANCE.mag(*args) + +def map(*args): + if len(args) > 1 and callable(args[0]): + return PythonFunctions.map(*args) + else: + return _P5_INSTANCE.map(*args) + +def max(*args): + return _P5_INSTANCE.max(*args) + +def min(*args): + return _P5_INSTANCE.min(*args) + +def norm(*args): + return _P5_INSTANCE.norm(*args) + +def pow(*args): + return _P5_INSTANCE.pow(*args) + +def round(*args): + return _P5_INSTANCE.round(*args) + +def sq(*args): + return _P5_INSTANCE.sq(*args) + +def sqrt(*args): + return _P5_INSTANCE.sqrt(*args) + +def noise(*args): + return _P5_INSTANCE.noise(*args) + +def noiseDetail(*args): + return _P5_INSTANCE.noiseDetail(*args) + +def noiseSeed(*args): + return _P5_INSTANCE.noiseSeed(*args) + +def randomSeed(*args): + return _P5_INSTANCE.randomSeed(*args) + +def random(*args): + return _P5_INSTANCE.random(*args) + +def randomGaussian(*args): + return _P5_INSTANCE.randomGaussian(*args) + +def acos(*args): + return _P5_INSTANCE.acos(*args) + +def asin(*args): + return _P5_INSTANCE.asin(*args) + +def atan(*args): + return _P5_INSTANCE.atan(*args) + +def atan2(*args): + return _P5_INSTANCE.atan2(*args) + +def cos(*args): + return _P5_INSTANCE.cos(*args) + +def sin(*args): + return _P5_INSTANCE.sin(*args) + +def tan(*args): + return _P5_INSTANCE.tan(*args) + +def degrees(*args): + return _P5_INSTANCE.degrees(*args) + +def radians(*args): + return _P5_INSTANCE.radians(*args) + +def angleMode(*args): + return _P5_INSTANCE.angleMode(*args) + +def textAlign(*args): + return _P5_INSTANCE.textAlign(*args) + +def textLeading(*args): + return _P5_INSTANCE.textLeading(*args) + +def textSize(*args): + return _P5_INSTANCE.textSize(*args) + +def textStyle(*args): + return _P5_INSTANCE.textStyle(*args) + +def textWidth(*args): + return _P5_INSTANCE.textWidth(*args) + +def textAscent(*args): + return _P5_INSTANCE.textAscent(*args) + +def textDescent(*args): + return _P5_INSTANCE.textDescent(*args) + +def loadFont(*args): + return _P5_INSTANCE.loadFont(*args) + +def text(*args): + return _P5_INSTANCE.text(*args) + +def textFont(*args): + return _P5_INSTANCE.textFont(*args) + +def orbitControl(*args): + return _P5_INSTANCE.orbitControl(*args) + +def debugMode(*args): + return _P5_INSTANCE.debugMode(*args) + +def noDebugMode(*args): + return _P5_INSTANCE.noDebugMode(*args) + +def ambientLight(*args): + return _P5_INSTANCE.ambientLight(*args) + +def directionalLight(*args): + return _P5_INSTANCE.directionalLight(*args) + +def pointLight(*args): + return _P5_INSTANCE.pointLight(*args) + +def lights(*args): + return _P5_INSTANCE.lights(*args) + +def loadShader(*args): + return _P5_INSTANCE.loadShader(*args) + +def createShader(*args): + return _P5_INSTANCE.createShader(*args) + +def shader(*args): + return _P5_INSTANCE.shader(*args) + +def resetShader(*args): + return _P5_INSTANCE.resetShader(*args) + +def normalMaterial(*args): + return _P5_INSTANCE.normalMaterial(*args) + +def texture(*args): + return _P5_INSTANCE.texture(*args) + +def textureMode(*args): + return _P5_INSTANCE.textureMode(*args) + +def textureWrap(*args): + return _P5_INSTANCE.textureWrap(*args) + +def ambientMaterial(*args): + return _P5_INSTANCE.ambientMaterial(*args) + +def specularMaterial(*args): + return _P5_INSTANCE.specularMaterial(*args) + +def shininess(*args): + return _P5_INSTANCE.shininess(*args) + +def camera(*args): + return _P5_INSTANCE.camera(*args) + +def perspective(*args): + return _P5_INSTANCE.perspective(*args) + +def ortho(*args): + return _P5_INSTANCE.ortho(*args) + +def createCamera(*args): + return _P5_INSTANCE.createCamera(*args) + +def setCamera(*args): + return _P5_INSTANCE.setCamera(*args) + +def select(*args): + return _P5_INSTANCE.select(*args) + +def selectAll(*args): + return _P5_INSTANCE.selectAll(*args) + +def removeElements(*args): + return _P5_INSTANCE.removeElements(*args) + +def changed(*args): + return _P5_INSTANCE.changed(*args) + +def input(*args): + return _P5_INSTANCE.input(*args) + +def createDiv(*args): + return _P5_INSTANCE.createDiv(*args) + +def createP(*args): + return _P5_INSTANCE.createP(*args) + +def createSpan(*args): + return _P5_INSTANCE.createSpan(*args) + +def createImg(*args): + return _P5_INSTANCE.createImg(*args) + +def createA(*args): + return _P5_INSTANCE.createA(*args) + +def createSlider(*args): + return _P5_INSTANCE.createSlider(*args) + +def createButton(*args): + return _P5_INSTANCE.createButton(*args) + +def createCheckbox(*args): + return _P5_INSTANCE.createCheckbox(*args) + +def createSelect(*args): + return _P5_INSTANCE.createSelect(*args) + +def createRadio(*args): + return _P5_INSTANCE.createRadio(*args) + +def createColorPicker(*args): + return _P5_INSTANCE.createColorPicker(*args) + +def createInput(*args): + return _P5_INSTANCE.createInput(*args) + +def createFileInput(*args): + return _P5_INSTANCE.createFileInput(*args) + +def createVideo(*args): + return _P5_INSTANCE.createVideo(*args) + +def createAudio(*args): + return _P5_INSTANCE.createAudio(*args) + +def createCapture(*args): + return _P5_INSTANCE.createCapture(*args) + +def createElement(*args): + return _P5_INSTANCE.createElement(*args) + +def createCanvas(*args): + canvas = _P5_INSTANCE.createCanvas(*args) + + global width, height + width = _P5_INSTANCE.width + height = _P5_INSTANCE.height + + return canvas + + +def pop(*args): + p5_pop = _P5_INSTANCE.pop(*args) + return p5_pop + + +# Processing Python or Java mode compatibility aliases +size = createCanvas +popMatrix = pop +popStyle = pop +pushMatrix = push +pushStyle = push + +def pre_draw(p5_instance, draw_func): + """ + We need to run this before the actual draw to insert and update p5 env variables + """ + global _CTX_MIDDLE, _DEFAULT_FILL, _DEFAULT_LEADMULT, _DEFAULT_STROKE, _DEFAULT_TEXT_FILL + + global ADD, ALT, ARROW, AUTO, AUDIO, AXES, BACKSPACE, BASELINE, BEVEL, BEZIER, BLEND, BLUR, BOLD, BOLDITALIC + global BOTTOM, BURN, CENTER, CHORD, CLAMP, CLOSE, CONTROL, CORNER, CORNERS, CROSS, CURVE, DARKEST + global DEG_TO_RAD, DEGREES, DELETE, DIFFERENCE, DILATE, DODGE, DOWN_ARROW, ENTER, ERODE, ESCAPE, EXCLUSION + global FILL, GRAY, GRID, HALF_PI, HAND, HARD_LIGHT, HSB, HSL, IMAGE, IMMEDIATE, INVERT, ITALIC, LANDSCAPE + global LEFT, LEFT_ARROW, LIGHTEST, LINE_LOOP, LINE_STRIP, LINEAR, LINES, MIRROR, MITER, MOVE, MULTIPLY, NEAREST + global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP, QUADRATIC + global QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW + global ROUND, SCREEN, SHIFT, SOFT_LIGHT, SQUARE, STROKE, SUBTRACT, TAB, TAU, TEXT, TEXTURE, THRESHOLD, TOP + global TRIANGLE_FAN, TRIANGLE_STRIP, TRIANGLES, TWO_PI, UP_ARROW, VIDEO, WAIT, WEBGL + + global frameCount, focused, displayWidth, displayHeight, windowWidth, windowHeight, width, height + global deviceOrientation, accelerationX, accelerationY, accelerationZ + global pAccelerationX, pAccelerationY, pAccelerationZ, rotationX, rotationY, rotationZ + global pRotationX, pRotationY, pRotationZ, turnAxis, keyIsPressed, key, keyCode, mouseX, mouseY, pmouseX, pmouseY + global winMouseX, winMouseY, pwinMouseX, pwinMouseY, mouseButton, mouseIsPressed, touches, pixels + + _CTX_MIDDLE = p5_instance._CTX_MIDDLE + _DEFAULT_FILL = p5_instance._DEFAULT_FILL + _DEFAULT_LEADMULT = p5_instance._DEFAULT_LEADMULT + _DEFAULT_STROKE = p5_instance._DEFAULT_STROKE + _DEFAULT_TEXT_FILL = p5_instance._DEFAULT_TEXT_FILL + + ADD = p5_instance.ADD + ALT = p5_instance.ALT + ARROW = p5_instance.ARROW + AUDIO = p5_instance.AUDIO + AUTO = p5_instance.AUTO + AXES = p5_instance.AXES + BACKSPACE = p5_instance.BACKSPACE + BASELINE = p5_instance.BASELINE + BEVEL = p5_instance.BEVEL + BEZIER = p5_instance.BEZIER + BLEND = p5_instance.BLEND + BLUR = p5_instance.BLUR + BOLD = p5_instance.BOLD + BOLDITALIC = p5_instance.BOLDITALIC + BOTTOM = p5_instance.BOTTOM + BURN = p5_instance.BURN + CENTER = p5_instance.CENTER + CHORD = p5_instance.CHORD + CLAMP = p5_instance.CLAMP + CLOSE = p5_instance.CLOSE + CONTROL = p5_instance.CONTROL + CORNER = p5_instance.CORNER + CORNERS = p5_instance.CORNERS + CROSS = p5_instance.CROSS + CURVE = p5_instance.CURVE + DARKEST = p5_instance.DARKEST + DEG_TO_RAD = p5_instance.DEG_TO_RAD + DEGREES = p5_instance.DEGREES + DELETE = p5_instance.DELETE + DIFFERENCE = p5_instance.DIFFERENCE + DILATE = p5_instance.DILATE + DODGE = p5_instance.DODGE + DOWN_ARROW = p5_instance.DOWN_ARROW + ENTER = p5_instance.ENTER + ERODE = p5_instance.ERODE + ESCAPE = p5_instance.ESCAPE + EXCLUSION = p5_instance.EXCLUSION + FILL = p5_instance.FILL + GRAY = p5_instance.GRAY + GRID = p5_instance.GRID + HALF_PI = p5_instance.HALF_PI + HAND = p5_instance.HAND + HARD_LIGHT = p5_instance.HARD_LIGHT + HSB = p5_instance.HSB + HSL = p5_instance.HSL + IMAGE = p5_instance.IMAGE + IMMEDIATE = p5_instance.IMMEDIATE + INVERT = p5_instance.INVERT + ITALIC = p5_instance.ITALIC + LANDSCAPE = p5_instance.LANDSCAPE + LEFT = p5_instance.LEFT + LEFT_ARROW = p5_instance.LEFT_ARROW + LIGHTEST = p5_instance.LIGHTEST + LINE_LOOP = p5_instance.LINE_LOOP + LINE_STRIP = p5_instance.LINE_STRIP + LINEAR = p5_instance.LINEAR + LINES = p5_instance.LINES + MIRROR = p5_instance.MIRROR + MITER = p5_instance.MITER + MOVE = p5_instance.MOVE + MULTIPLY = p5_instance.MULTIPLY + NEAREST = p5_instance.NEAREST + NORMAL = p5_instance.NORMAL + OPAQUE = p5_instance.OPAQUE + OPEN = p5_instance.OPEN + OPTION = p5_instance.OPTION + OVERLAY = p5_instance.OVERLAY + P2D = p5_instance.P2D + P3D = p5_instance.WEBGL + PI = p5_instance.PI + PIE = p5_instance.PIE + POINTS = p5_instance.POINTS + PORTRAIT = p5_instance.PORTRAIT + POSTERIZE = p5_instance.POSTERIZE + PROJECT = p5_instance.PROJECT + QUAD_STRIP = p5_instance.QUAD_STRIP + QUADRATIC = p5_instance.QUADRATIC + QUADS = p5_instance.QUADS + QUARTER_PI = p5_instance.QUARTER_PI + RAD_TO_DEG = p5_instance.RAD_TO_DEG + RADIANS = p5_instance.RADIANS + RADIUS = p5_instance.RADIUS + REPEAT = p5_instance.REPEAT + REPLACE = p5_instance.REPLACE + RETURN = p5_instance.RETURN + RGB = p5_instance.RGB + RIGHT = p5_instance.RIGHT + RIGHT_ARROW = p5_instance.RIGHT_ARROW + ROUND = p5_instance.ROUND + SCREEN = p5_instance.SCREEN + SHIFT = p5_instance.SHIFT + SOFT_LIGHT = p5_instance.SOFT_LIGHT + SQUARE = p5_instance.SQUARE + STROKE = p5_instance.STROKE + SUBTRACT = p5_instance.SUBTRACT + TAB = p5_instance.TAB + TAU = p5_instance.TAU + TEXT = p5_instance.TEXT + TEXTURE = p5_instance.TEXTURE + THRESHOLD = p5_instance.THRESHOLD + TOP = p5_instance.TOP + TRIANGLE_FAN = p5_instance.TRIANGLE_FAN + TRIANGLE_STRIP = p5_instance.TRIANGLE_STRIP + TRIANGLES = p5_instance.TRIANGLES + TWO_PI = p5_instance.TWO_PI + UP_ARROW = p5_instance.UP_ARROW + VIDEO = p5_instance.VIDEO + WAIT = p5_instance.WAIT + WEBGL = p5_instance.WEBGL + + frameCount = p5_instance.frameCount + focused = p5_instance.focused + displayWidth = p5_instance.displayWidth + displayHeight = p5_instance.displayHeight + windowWidth = p5_instance.windowWidth + windowHeight = p5_instance.windowHeight + width = p5_instance.width + height = p5_instance.height + deviceOrientation = p5_instance.deviceOrientation + accelerationX = p5_instance.accelerationX + accelerationY = p5_instance.accelerationY + accelerationZ = p5_instance.accelerationZ + pAccelerationX = p5_instance.pAccelerationX + pAccelerationY = p5_instance.pAccelerationY + pAccelerationZ = p5_instance.pAccelerationZ + rotationX = p5_instance.rotationX + rotationY = p5_instance.rotationY + rotationZ = p5_instance.rotationZ + pRotationX = p5_instance.pRotationX + pRotationY = p5_instance.pRotationY + pRotationZ = p5_instance.pRotationZ + turnAxis = p5_instance.turnAxis + keyIsPressed = p5_instance.keyIsPressed + key = p5_instance.key + keyCode = p5_instance.keyCode + mouseX = p5_instance.mouseX + mouseY = p5_instance.mouseY + pmouseX = p5_instance.pmouseX + pmouseY = p5_instance.pmouseY + winMouseX = p5_instance.winMouseX + winMouseY = p5_instance.winMouseY + pwinMouseX = p5_instance.pwinMouseX + pwinMouseY = p5_instance.pwinMouseY + mouseButton = p5_instance.mouseButton + mouseIsPressed = p5_instance.mouseIsPressed + touches = p5_instance.touches + pixels = p5_instance.pixels + + return draw_func() + + +def global_p5_injection(p5_sketch): + """ + Injects the p5js's skecth instance as a global variable to setup and draw functions + """ + + def decorator(f): + def wrapper(): + global _P5_INSTANCE + _P5_INSTANCE = p5_sketch + return pre_draw(_P5_INSTANCE, f) + + + return wrapper + + + return decorator + + +def start_p5(setup_func, draw_func, event_functions): + """ + This is the entrypoint function. It accepts 2 parameters: + + - setup_func: a Python setup callable + - draw_func: a Python draw callable + - event_functions: a config dict for the event functions in the format: + {"eventFunctionName": python_event_function} + + This method gets the p5js's sketch instance and injects them + """ + + def sketch_setup(p5_sketch): + """ + Callback function called to configure new p5 instance + """ + p5_sketch.setup = global_p5_injection(p5_sketch)(setup_func) + p5_sketch.draw = global_p5_injection(p5_sketch)(draw_func) + + + window.instance = p5.new(sketch_setup, 'sketch-holder') + + # inject event functions into p5 + event_function_names = ( + "deviceMoved", "deviceTurned", "deviceShaken", "windowResized", + "keyPressed", "keyReleased", "keyTyped", + "mousePressed", "mouseReleased", "mouseClicked", "doubleClicked", + "mouseMoved", "mouseDragged", "mouseWheel", + "touchStarted", "touchMoved", "touchEnded" + ) + + for f_name in [f for f in event_function_names if event_functions.get(f, None)]: + func = event_functions[f_name] + event_func = global_p5_injection(instance)(func) + setattr(instance, f_name, event_func) +`; + +let placeholder = ` +def setup(): + pass + +def draw(): + pass +`; + +let userCode = ` +{{ sketch_content }} +`; + +function runCode() { + let code = [ + placeholder, + userCode, + wrapper_content, + 'start_p5(setup, draw, {});', + ].join('\n'); + + if (window.instance) { + window.instance.canvas.remove(); + } + + console.log("Python execution output:"); + pyodide.runPython(code); +} + +languagePluginLoader.then(() => { + pyodide.runPython(` + import io, code, sys + from js import pyodide, p5, window, document + print(sys.version) + `) + + window.runSketchCode = (code) => { + userCode = code; + runCode(); + } + + runCode(); +}); diff --git a/pyp5js/templates/base_sketch.py.template b/pyp5js/templates/transcrypt/base_sketch.py.template similarity index 100% rename from pyp5js/templates/base_sketch.py.template rename to pyp5js/templates/transcrypt/base_sketch.py.template diff --git a/pyp5js/templates/index.html b/pyp5js/templates/transcrypt/index.html similarity index 100% rename from pyp5js/templates/index.html rename to pyp5js/templates/transcrypt/index.html diff --git a/pyp5js/templates/target_sketch.py.template b/pyp5js/templates/transcrypt/target_sketch.py.template similarity index 100% rename from pyp5js/templates/target_sketch.py.template rename to pyp5js/templates/transcrypt/target_sketch.py.template diff --git a/pyp5js/templates_renderers.py b/pyp5js/templates_renderers.py index 1964122e..755d2c5e 100644 --- a/pyp5js/templates_renderers.py +++ b/pyp5js/templates_renderers.py @@ -1,28 +1,40 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape -from pyp5js.fs import LibFiles, SketchFiles +from pyp5js.config.fs import PYP5JS_FILES +from pyp5js.sketch import Sketch -pyp5js_files = LibFiles() -templates = Environment(loader=FileSystemLoader(str(pyp5js_files.templates_dir))) +templates = Environment(loader=FileSystemLoader(str(PYP5JS_FILES.templates_dir))) -def get_sketch_index_content(sketch_files): +def _template_from_file(filename, context): + templates = Environment(loader=FileSystemLoader(str(filename.parent.resolve()))) + template = templates.get_template(filename.name) + return template.render(context) + + +def get_sketch_index_content(sketch): """ Renders SKETCH_NAME/index.html to display the sketch visualization """ context = { - "sketch_name": sketch_files.sketch_name, - "p5_js_url": sketch_files.urls.p5_js_url, - "sketch_js_url": sketch_files.urls.sketch_js_url, + "sketch_name": sketch.sketch_name, + "p5_js_url": sketch.urls.p5_js_url, + "sketch_js_url": sketch.urls.sketch_js_url, } - index_template = templates.get_template(pyp5js_files.index_html.name) - return index_template.render(context) + template_file = sketch.config.get_index_template() + return _template_from_file(template_file, context) -def get_target_sketch_content(sketch_files): +def get_target_sketch_content(sketch): """ Renders the content to be written in the temporary SKETCH_NAME/target_sketch.py file """ - context = {"sketch_name": sketch_files.sketch_name} - index_template = templates.get_template(pyp5js_files.target_sketch_template.name) - return index_template.render(context) + with sketch.sketch_py.open() as fd: + content = fd.read() + + context = { + "sketch_name": sketch.sketch_name, + "sketch_content": content, + } + target_js_file = sketch.config.get_target_js_template() + return _template_from_file(target_js_file, context) diff --git a/pyp5js/tests/fixtures.py b/pyp5js/tests/fixtures.py new file mode 100644 index 00000000..9b6dd50b --- /dev/null +++ b/pyp5js/tests/fixtures.py @@ -0,0 +1,58 @@ +import json +import os +import shutil +from pytest import fixture +from tempfile import NamedTemporaryFile + +from pyp5js.config import TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER +from pyp5js.config.sketch import SketchConfig +from pyp5js.config.fs import PYP5JS_FILES +from pyp5js.config import SKETCHBOOK_DIR +from pyp5js.sketch import Sketch + + +@fixture +def lib_files(): + return PYP5JS_FILES + + +@fixture() +def sketch(): + files = Sketch('foo') + files.create_sketch_dir() + yield files + shutil.rmtree(SKETCHBOOK_DIR) + +@fixture +def transcrypt_json_file(): + try: + fd = NamedTemporaryFile(mode='w', delete=False) + data = {"interpreter": "transcrypt"} + json.dump(data, fd) + filename = fd.name + fd.seek(0) + fd.close() + yield filename + finally: + os.remove(filename) + +@fixture +def pyodide_json_file(): + try: + fd = NamedTemporaryFile(mode='w', delete=False) + data = {"interpreter": "pyodide"} + json.dump(data, fd) + filename = fd.name + fd.seek(0) + fd.close() + yield filename + finally: + os.remove(filename) + +@fixture +def transcrypt_config(): + return SketchConfig(interpreter=TRANSCRYPT_INTERPRETER) + +@fixture +def pyodide_config(): + return SketchConfig(interpreter=PYODIDE_INTERPRETER) diff --git a/pyp5js/tests/test_commands.py b/pyp5js/tests/test_commands.py index d278b561..8023d9b3 100644 --- a/pyp5js/tests/test_commands.py +++ b/pyp5js/tests/test_commands.py @@ -4,58 +4,52 @@ from unittest.mock import Mock, patch from pyp5js import commands -from pyp5js.config import SKETCHBOOK_DIR +from pyp5js.config import SKETCHBOOK_DIR, TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER from pyp5js.exceptions import PythonSketchDoesNotExist, SketchDirAlreadyExistException, InvalidName -from pyp5js.fs import SketchFiles +from pyp5js.sketch import Sketch +from .fixtures import sketch -@pytest.fixture() -def files(): - files = SketchFiles('foo') - files.create_sketch_dir() - yield files - shutil.rmtree(SKETCHBOOK_DIR) - -def test_transcrypt_sketch(files): - files.sketch_py.touch() +def test_compile_sketch(sketch): + sketch.sketch_py.touch() with patch('pyp5js.commands.compile_sketch_js') as compiler: - output = commands.transcrypt_sketch('foo') + output = commands.compile_sketch('foo') - assert output == files - compiler.assert_called_once_with(files) + assert output == sketch + compiler.assert_called_once_with(sketch) -def test_transcrypt_sketch_error_if_sketch_does_not_exist(files): +def test_compile_sketch_error_if_sketch_does_not_exist(sketch): with patch('pyp5js.commands.compile_sketch_js') as compiler: with pytest.raises(PythonSketchDoesNotExist): - commands.transcrypt_sketch('foo') + commands.compile_sketch('foo') assert not compiler.called -def test_transcrypt_sketch_error_if_invalid_sketch(files): +def test_compile_sketch_error_if_invalid_sketch(sketch): with patch('pyp5js.commands.compile_sketch_js') as compiler: with pytest.raises(InvalidName): - commands.transcrypt_sketch('123foo') + commands.compile_sketch('123foo') assert not compiler.called -def test_monitor_sketch(files): - files.sketch_py.touch() +def test_monitor_sketch(sketch): + sketch.sketch_py.touch() with patch('pyp5js.commands.monitor_sketch_service') as monitor: commands.monitor_sketch('foo') - monitor.assert_called_once_with(files) + monitor.assert_called_once_with(sketch) -def test_monitor_sketch_error_if_sketch_does_not_exist(files): +def test_monitor_sketch_error_if_sketch_does_not_exist(sketch): with patch('pyp5js.commands.monitor_sketch_service') as monitor: with pytest.raises(PythonSketchDoesNotExist): commands.monitor_sketch('foo') assert not monitor.called -def test_monitor_sketch_error_if_invalid_name(files): +def test_monitor_sketch_error_if_invalid_name(sketch): with patch('pyp5js.commands.monitor_sketch_service') as monitor: with pytest.raises(InvalidName): commands.monitor_sketch('1234foo') @@ -66,7 +60,7 @@ class TestNewSketchCommand(TestCase): def setUp(self): self.sketch_name = 'foo' - self.sketch_files = SketchFiles(self.sketch_name) + self.sketch = Sketch(self.sketch_name) def tearDown(self): if SKETCHBOOK_DIR.exists(): @@ -75,12 +69,24 @@ def tearDown(self): def test_create_new_sketch_with_all_required_files(self): commands.new_sketch(self.sketch_name) - assert self.sketch_files.index_html.exists() - assert self.sketch_files.sketch_py.exists() - assert self.sketch_files.p5js.exists() + assert self.sketch.index_html.exists() + assert self.sketch.sketch_py.exists() + assert self.sketch.p5js.exists() + assert self.sketch.config_file.exists() + assert self.sketch.config.interpreter == TRANSCRYPT_INTERPRETER + + def test_create_pyodide_sketch(self): + commands.new_sketch(self.sketch_name, interpreter=PYODIDE_INTERPRETER) + self.sketch = Sketch(self.sketch_name) # read config after init + + assert self.sketch.index_html.exists() + assert self.sketch.sketch_py.exists() + assert self.sketch.p5js.exists() + assert self.sketch.config_file.exists() + assert self.sketch.config.interpreter == PYODIDE_INTERPRETER def test_raise_exception_if_dir_already_exist(self): - self.sketch_files.create_sketch_dir() + self.sketch.create_sketch_dir() with pytest.raises(SketchDirAlreadyExistException): commands.new_sketch(self.sketch_name) diff --git a/pyp5js/tests/test_compiler.py b/pyp5js/tests/test_compiler.py index 6e378532..f67610e9 100644 --- a/pyp5js/tests/test_compiler.py +++ b/pyp5js/tests/test_compiler.py @@ -4,52 +4,46 @@ from unittest import TestCase from unittest.mock import Mock, patch -from pyp5js.compiler import Pyp5jsCompiler, compile_sketch_js +from pyp5js.compiler import TranscryptCompiler, compile_sketch_js from pyp5js import config -from pyp5js.fs import SketchFiles, LibFiles +from pyp5js.config.fs import PYP5JS_FILES +from pyp5js.sketch import Sketch from pyp5js.templates_renderers import get_target_sketch_content +from .fixtures import sketch -@pytest.fixture() -def files(): - files = SketchFiles('foo') - files.create_sketch_dir() - yield files - shutil.rmtree(config.SKETCHBOOK_DIR) - -@patch('pyp5js.compiler.Pyp5jsCompiler') -def test_compile_sketch_js_service(MockedCompiler, files): - compiler = Mock(spec=Pyp5jsCompiler) +@patch('pyp5js.compiler.TranscryptCompiler') +def test_compile_sketch_js_service(MockedCompiler, sketch): + compiler = Mock(spec=TranscryptCompiler) MockedCompiler.return_value = compiler - compile_sketch_js(files) + compile_sketch_js(sketch) - MockedCompiler.assert_called_once_with(files) + MockedCompiler.assert_called_once_with(sketch) compiler.compile_sketch_js.assert_called_once_with() -class Pyp5jsCompilerTests(TestCase): +class TranscryptCompilerTests(TestCase): def setUp(self): - self.pyp5js_files = LibFiles() - self.files = SketchFiles('foo') - self.compiler = Pyp5jsCompiler(self.files) + self.sketch = Sketch('foo') + self.compiler = TranscryptCompiler(self.sketch) - self.files.create_sketch_dir() - self.files.sketch_py.touch() + self.sketch.create_sketch_dir() + self.sketch.sketch_py.touch() def tearDown(self): if config.SKETCHBOOK_DIR.exists(): shutil.rmtree(config.SKETCHBOOK_DIR) def test_transcrypt_target_dir_path(self): - assert self.files.sketch_dir.joinpath( + assert self.sketch.sketch_dir.joinpath( '__target__') == self.compiler.target_dir def test_command_line_string(self): - pyp5_dir = self.pyp5js_files.install - target = self.files.target_sketch + pyp5_dir = PYP5JS_FILES.install + target = self.sketch.target_sketch expected = ' '.join([str(c) for c in [ 'transcrypt', '-xp', f'"{pyp5_dir}"', '-k', '-ks', '-b', '-m', '-n', f'"{target}"' @@ -62,7 +56,7 @@ def test_run_compiler_as_expected(self): self.compiler.run_compiler() assert self.compiler.target_dir.exists() - assert self.files.target_sketch.exists() + assert self.sketch.target_sketch.exists() def test_run_compiler_as_expected_if_dir_name_with_space(self): previous_dir = config.SKETCHBOOK_DIR @@ -71,17 +65,17 @@ def test_run_compiler_as_expected_if_dir_name_with_space(self): dir_with_space.mkdir() config.__dict__["SKETCHBOOK_DIR"] = dir_with_space - self.files = SketchFiles('foo') - self.compiler = Pyp5jsCompiler(self.files) - self.files.create_sketch_dir() - self.files.sketch_py.touch() + self.sketch = Sketch('foo') + self.compiler = TranscryptCompiler(self.sketch) + self.sketch.create_sketch_dir() + self.sketch.sketch_py.touch() try: self.compiler.prepare() self.compiler.run_compiler() assert self.compiler.target_dir.exists() - assert self.files.target_sketch.exists() + assert self.sketch.target_sketch.exists() finally: if dir_with_space.exists(): shutil.rmtree(dir_with_space) @@ -89,21 +83,21 @@ def test_run_compiler_as_expected_if_dir_name_with_space(self): def test_clean_up(self): self.compiler.target_dir.mkdir() - self.files.target_sketch.touch() + self.sketch.target_sketch.touch() self.compiler.clean_up() assert not self.compiler.target_dir.exists() - assert self.files.target_dir.exists() - assert not self.files.target_sketch.exists() + assert self.sketch.target_dir.exists() + assert not self.sketch.target_sketch.exists() def test_prepare_sketch(self): - expected_content = get_target_sketch_content(self.files) - assert not self.files.target_sketch.exists() + expected_content = get_target_sketch_content(self.sketch) + assert not self.sketch.target_sketch.exists() self.compiler.prepare() - assert self.files.target_sketch.exists() - with self.files.target_sketch.open('r') as fd: + assert self.sketch.target_sketch.exists() + with self.sketch.target_sketch.open('r') as fd: content = fd.read() assert expected_content == content diff --git a/pyp5js/tests/test_config/__init__.py b/pyp5js/tests/test_config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyp5js/tests/test_config/test_fs.py b/pyp5js/tests/test_config/test_fs.py new file mode 100644 index 00000000..110dbc34 --- /dev/null +++ b/pyp5js/tests/test_config/test_fs.py @@ -0,0 +1,54 @@ +import pytest +from pathlib import Path + +from pyp5js.config.fs import PYP5JS_FILES + +pyp5_dir = Path(__file__).parents[3].joinpath('pyp5js') + +from ..fixtures import lib_files + +def test_dir_properties(lib_files): + assert pyp5_dir.exists() + + assert lib_files.templates_dir == pyp5_dir.joinpath('templates') + assert lib_files.templates_dir.exists() + assert lib_files.assets_dir == pyp5_dir.joinpath('assets') + assert lib_files.assets_dir.exists() + assert lib_files.static_dir == pyp5_dir.joinpath('assets', 'static') + assert lib_files.static_dir.exists() + + +def test_files_properties(lib_files): + assert pyp5_dir.exists() + + ##### GENERAL PURPOSE + assert lib_files.pytop5js == pyp5_dir.joinpath('pyp5js.py') + assert lib_files.pytop5js.exists() + + assert lib_files.p5js == pyp5_dir.joinpath('assets', 'static', 'p5', 'p5.min.js') + assert lib_files.p5js.exists() + + assert lib_files.p5_yml == pyp5_dir.joinpath('assets', 'p5_reference.yml') + assert lib_files.p5_yml.exists() + + +def test_transcrypt_specifict_properties(lib_files): + assert lib_files.transcrypt_target_sketch_template == pyp5_dir.joinpath('templates', 'transcrypt', 'target_sketch.py.template') + assert lib_files.transcrypt_target_sketch_template.exists() + + assert lib_files.transcrypt_index_html == pyp5_dir.joinpath('templates', 'transcrypt', 'index.html') + assert lib_files.transcrypt_index_html.exists() + + assert lib_files.transcrypt_base_sketch_template == pyp5_dir.joinpath('templates', 'transcrypt', 'base_sketch.py.template') + assert lib_files.transcrypt_base_sketch_template.exists() + + +def test_pyodide_specifict_properties(lib_files): + assert lib_files.pyodide_target_sketch_template == pyp5_dir.joinpath('templates', 'pyodide', 'target_sketch.js.template') + assert lib_files.pyodide_target_sketch_template.exists() + + assert lib_files.pyodide_index_html == pyp5_dir.joinpath('templates', 'pyodide', 'index.html') + assert lib_files.pyodide_index_html.exists() + + assert lib_files.pyodide_base_sketch_template == pyp5_dir.joinpath('templates', 'pyodide', 'base_sketch.py.template') + assert lib_files.pyodide_base_sketch_template.exists() diff --git a/pyp5js/tests/test_config/test_sketch.py b/pyp5js/tests/test_config/test_sketch.py new file mode 100644 index 00000000..76e79ebe --- /dev/null +++ b/pyp5js/tests/test_config/test_sketch.py @@ -0,0 +1,51 @@ +import json +import os +from tempfile import NamedTemporaryFile + +from pyp5js.config import TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER +from pyp5js.config.sketch import SketchConfig +from pyp5js.config.fs import PYP5JS_FILES + +from ..fixtures import transcrypt_json_file, pyodide_json_file, transcrypt_config, pyodide_config + + +def test_init_transcrypt_sketch_config_from_json(transcrypt_json_file): + config = SketchConfig.from_json(transcrypt_json_file) + assert config.interpreter == TRANSCRYPT_INTERPRETER + + +def test_init_pyodide_sketch_config_from_json(pyodide_json_file): + config = SketchConfig.from_json(pyodide_json_file) + assert config.interpreter == PYODIDE_INTERPRETER + + +def test_write_sketch_interpreter_config(transcrypt_config): + config = transcrypt_config + fd = NamedTemporaryFile(mode="w", delete=False) + config.write(fd.name) + fd.close() + with open(fd.name) as fd: + data = json.load(fd) + + assert data["interpreter"] == TRANSCRYPT_INTERPRETER + os.remove(fd.name) + +def test_get_transcrypt_index_template(transcrypt_config): + template = transcrypt_config.get_index_template() + assert PYP5JS_FILES.transcrypt_index_html == template + assert template.exists() + +def test_get_pyodide_index_template(pyodide_config): + template = pyodide_config.get_index_template() + assert PYP5JS_FILES.pyodide_index_html == template + assert template.exists() + +def test_get_transcrypt_target_js_template(transcrypt_config): + template = transcrypt_config.get_target_js_template() + assert PYP5JS_FILES.transcrypt_target_sketch_template == template + assert template.exists() + +def test_get_pyodide_target_js_template(pyodide_config): + template = pyodide_config.get_target_js_template() + assert PYP5JS_FILES.pyodide_target_sketch_template == template + assert template.exists() diff --git a/pyp5js/tests/test_fs.py b/pyp5js/tests/test_fs.py deleted file mode 100644 index be2806fe..00000000 --- a/pyp5js/tests/test_fs.py +++ /dev/null @@ -1,114 +0,0 @@ -import pytest -import shutil -from pathlib import Path -from unittest import TestCase - -from pyp5js.config import SKETCHBOOK_DIR -from pyp5js.exceptions import SketchDirAlreadyExistException -from pyp5js.fs import LibFiles, SketchFiles -from pyp5js.exceptions import InvalidName - -pyp5_dir = Path(__file__).parents[2].joinpath('pyp5js') - -@pytest.fixture -def lib_files(): - return LibFiles() - -def test_dir_properties(lib_files): - assert pyp5_dir.exists() - - assert lib_files.templates_dir == pyp5_dir.joinpath('templates') - assert lib_files.templates_dir.exists() - assert lib_files.assets_dir == pyp5_dir.joinpath('assets') - assert lib_files.assets_dir.exists() - assert lib_files.static_dir == pyp5_dir.joinpath('static') - assert lib_files.static_dir.exists() - - -def test_files_properties(lib_files): - assert pyp5_dir.exists() - - assert lib_files.pytop5js == pyp5_dir.joinpath('pyp5js.py') - assert lib_files.pytop5js.exists() - - assert lib_files.base_sketch == pyp5_dir.joinpath('templates', 'base_sketch.py.template') - assert lib_files.base_sketch.exists() - - assert lib_files.target_sketch_template == pyp5_dir.joinpath('templates', 'target_sketch.py.template') - assert lib_files.target_sketch_template.exists() - - assert lib_files.index_html == pyp5_dir.joinpath('templates', 'index.html') - assert lib_files.index_html.exists() - - assert lib_files.p5js == pyp5_dir.joinpath('static', 'p5', 'p5.min.js') - assert lib_files.p5js.exists() - - assert lib_files.p5_yml == pyp5_dir.joinpath('assets', 'p5_reference.yml') - assert lib_files.p5_yml.exists() - - -class SketchFilesTests(TestCase): - - def setUp(self): - self.base_dir = SKETCHBOOK_DIR - self.sketch_name = 'foo' - self.files = SketchFiles(self.sketch_name) - - def tearDown(self): - if self.base_dir.exists(): - shutil.rmtree(self.base_dir) - - def test_sketch_dirs(self): - assert self.base_dir.joinpath(self.sketch_name) == self.files.sketch_dir - assert self.base_dir.joinpath(self.sketch_name, 'static') == self.files.static_dir - assert self.base_dir.joinpath(self.sketch_name, 'target') == self.files.target_dir - assert self.files.TARGET_NAME == 'target' - - def test_sketch_files(self): - self.files.check_sketch_dir = False - assert self.base_dir.joinpath(self.sketch_name, 'index.html') == self.files.index_html - assert self.base_dir.joinpath(self.sketch_name, 'static', 'p5.js') == self.files.p5js - assert self.base_dir.joinpath(self.sketch_name, 'foo.py') == self.files.sketch_py - assert self.base_dir.joinpath(self.sketch_name, 'target_sketch.py') == self.files.target_sketch - - def test_sketch_files_holds_reference_to_lib_files(self): - lib_files = LibFiles() - assert isinstance(self.files.from_lib, LibFiles) - assert self.files.from_lib.install == lib_files.install - - def test_create_dirs(self): - assert self.files.sketch_dir.exists() is False - assert self.files.static_dir.exists() is False - assert self.files.target_dir.exists() is False - - self.files.create_sketch_dir() - - assert self.files.sketch_dir.exists() is True - assert self.files.static_dir.exists() is True - assert self.files.target_dir.exists() is True - - with pytest.raises(SketchDirAlreadyExistException): - self.files.create_sketch_dir() - - def test_raise_exception_when_name_starts_with_numbers(self): - files = SketchFiles('123name') - with pytest.raises(InvalidName): - files.validate_name() - - def test_raise_exception_when_name_contains_non_alphanumeric_chars(self): - files = SketchFiles('name&') - with pytest.raises(InvalidName): - files.validate_name() - - def test_raise_exception_when_name_creating_dir_with_invalid_name(self): - files = SketchFiles('name&') - with pytest.raises(InvalidName): - files.create_sketch_dir() - - def test_name_should_accept_underscore_in_the_beginning(self): - file = SketchFiles('__name__') - assert file.sketch_name == '__name__' - - def test_name_should_accept_underscore_in_the_middle(self): - file = SketchFiles('na_me') - assert file.sketch_name == 'na_me' diff --git a/pyp5js/tests/http/assets/alien.png b/pyp5js/tests/test_http/assets/alien.png similarity index 100% rename from pyp5js/tests/http/assets/alien.png rename to pyp5js/tests/test_http/assets/alien.png diff --git a/pyp5js/tests/http/test_web_app.py b/pyp5js/tests/test_http/test_web_app.py similarity index 83% rename from pyp5js/tests/http/test_web_app.py rename to pyp5js/tests/test_http/test_web_app.py index 7c5e59ea..72572c59 100644 --- a/pyp5js/tests/http/test_web_app.py +++ b/pyp5js/tests/test_http/test_web_app.py @@ -3,6 +3,7 @@ from pathlib import Path from pyp5js import commands +from pyp5js.sketch import Sketch from pyp5js.config import SKETCHBOOK_DIR from pyp5js.http.web_app import app as web_app from flask_testing import TestCase @@ -28,9 +29,9 @@ def create_sketch(self, name): return commands.new_sketch(name) def create_sketch_with_static_files(self, name): - sketch_files = commands.new_sketch(name) - commands.transcrypt_sketch(name) - return sketch_files.sketch_dir + sketch = commands.new_sketch(name) + commands.compile_sketch(name) + return sketch.sketch_dir def create_file(self, file_name, content=''): mode = 'w' @@ -54,8 +55,8 @@ def test_get_home_renders_index_template_and_context_all_files(self): self.client.get(self.route) self.assert_template_used('index.html') assert len(self.get_context_variable('sketches')) == 2 - assert dict(name='first_sketch', url='/sketch/first_sketch') in self.get_context_variable('sketches') - assert dict(name='second_sketch', url='/sketch/second_sketch') in self.get_context_variable('sketches') + assert dict(name='first_sketch', url='/sketch/first_sketch/') in self.get_context_variable('sketches') + assert dict(name='second_sketch', url='/sketch/second_sketch/') in self.get_context_variable('sketches') class NewSketchViewTests(Pyp5jsWebTestCase): @@ -80,6 +81,7 @@ def test_post_with_existing_sketch_name_should_render_form_with_error(self): def test_post_with_sketch_name_should_render_success_form(self): self.client.post(self.route, data={'sketch_name': 'a_name'}) self.assert_template_used('new_sketch_success.html') + assert Sketch('a_name').config.is_pyodide class SketchViewTests(Pyp5jsWebTestCase): @@ -90,15 +92,15 @@ def test_get_sketch_does_not_exist(self): self.assert_404(response) def test_get_sketch_exists(self): - sketch_files = self.create_sketch('sketch_exists') - py_code = sketch_files.sketch_py.read_text() + sketch = self.create_sketch('sketch_exists') + py_code = sketch.sketch_py.read_text() response = self.client.get(self.route + 'sketch_exists/') self.assert_200(response) self.assert_template_used('view_sketch.html') - self.assert_context('p5_js_url', sketch_files.urls.p5_js_url) - self.assert_context('sketch_js_url', sketch_files.urls.sketch_js_url) - self.assert_context('sketch_name', sketch_files.sketch_name) + self.assert_context('p5_js_url', sketch.urls.p5_js_url) + self.assert_context('sketch_js_url', sketch.urls.sketch_js_url) + self.assert_context('sketch_name', sketch.sketch_name) self.assert_context('py_code', py_code) def test_get_static_file_does_not_exist(self): @@ -149,8 +151,8 @@ def test_regression_test_with_real_png_image(self): alien_img = Path(__file__).resolve().parent / 'assets' / img_name assert alien_img.exists() - sketch_files = self.create_sketch('my_sketch_file') - shutil.copyfile(alien_img, sketch_files.sketch_dir / img_name) + sketch = self.create_sketch('my_sketch_file') + shutil.copyfile(alien_img, sketch.sketch_dir / img_name) response = self.client.get(self.route + f'my_sketch_file/{img_name}') @@ -179,35 +181,35 @@ def draw(): """.strip() def test_update_sketch_on_post(self): - sketch_files = self.create_sketch('sketch_exists') + sketch = self.create_sketch('sketch_exists') url = self.route + 'sketch_exists/' response = self.client.post(url, data={'py_code': self.test_code}) - assert self.test_code == sketch_files.sketch_py.read_text() + assert self.test_code == sketch.sketch_py.read_text() def test_python_code_is_required(self): - sketch_files = self.create_sketch('sketch_exists') - old_content = sketch_files.sketch_py.read_text() + sketch = self.create_sketch('sketch_exists') + old_content = sketch.sketch_py.read_text() url = self.route + 'sketch_exists/' response = self.client.post(url, data={'py_code': ''}) self.assert_template_used('view_sketch.html') self.assert_context('error', 'You have to input the Python code.') - assert old_content == sketch_files.sketch_py.read_text() + assert old_content == sketch.sketch_py.read_text() def test_check_python_syntax_before_updating(self): test_code = self.test_code.replace('300)', '300') - sketch_files = self.create_sketch('sketch_exists') - old_content = sketch_files.sketch_py.read_text() + sketch = self.create_sketch('sketch_exists') + old_content = sketch.sketch_py.read_text() url = self.route + 'sketch_exists/' response = self.client.post(url, data={'py_code': test_code}) self.assert_template_used('view_sketch.html') self.assert_context('error', 'SyntaxError: invalid syntax (sketch_exists.py, line 6)') - assert old_content == sketch_files.sketch_py.read_text() + assert old_content == sketch.sketch_py.read_text() def test_check_for_setup_function_before_updating(self): test_code = """ @@ -216,15 +218,15 @@ def test_check_for_setup_function_before_updating(self): def draw(): rect(10, 10, 200, 100) """.strip() - sketch_files = self.create_sketch('sketch_exists') - old_content = sketch_files.sketch_py.read_text() + sketch = self.create_sketch('sketch_exists') + old_content = sketch.sketch_py.read_text() url = self.route + 'sketch_exists/' response = self.client.post(url, data={'py_code': test_code}) self.assert_template_used('view_sketch.html') self.assert_context('error', 'You have to define a setup function.') - assert old_content == sketch_files.sketch_py.read_text() + assert old_content == sketch.sketch_py.read_text() def test_check_for_draw_function_before_updating(self): test_code = """ @@ -233,12 +235,12 @@ def test_check_for_draw_function_before_updating(self): def setup(): createCanvas(300, 300) """.strip() - sketch_files = self.create_sketch('sketch_exists') - old_content = sketch_files.sketch_py.read_text() + sketch = self.create_sketch('sketch_exists') + old_content = sketch.sketch_py.read_text() url = self.route + 'sketch_exists/' response = self.client.post(url, data={'py_code': test_code}) self.assert_template_used('view_sketch.html') self.assert_context('error', 'You have to define a draw function.') - assert old_content == sketch_files.sketch_py.read_text() + assert old_content == sketch.sketch_py.read_text() diff --git a/pyp5js/tests/test_monitor.py b/pyp5js/tests/test_monitor.py index e2aa9416..c0906b13 100644 --- a/pyp5js/tests/test_monitor.py +++ b/pyp5js/tests/test_monitor.py @@ -3,19 +3,19 @@ from unittest.mock import Mock, patch from pyp5js.monitor import TranscryptSketchEventHandler -from pyp5js.fs import SketchFiles +from pyp5js.sketch import Sketch class TranscryptSketchEventHandlerTests(TestCase): def setUp(self): - self.files = Mock(spec=SketchFiles) + self.files = Mock(spec=Sketch) self.queue = Mock(spec=Queue) self.observer = Mock(event_queue=self.queue) - self.handler = TranscryptSketchEventHandler(sketch_files=self.files, observer=self.observer) + self.handler = TranscryptSketchEventHandler(sketch=self.files, observer=self.observer) def test_handler_config(self): - assert self.files == self.handler.sketch_files + assert self.files == self.handler.sketch assert ['*.py'] == self.handler.patterns assert self.observer == self.handler.observer diff --git a/pyp5js/tests/test_pyp5js.py b/pyp5js/tests/test_pyp5js.py index cdc5ed00..e40a7c93 100644 --- a/pyp5js/tests/test_pyp5js.py +++ b/pyp5js/tests/test_pyp5js.py @@ -4,31 +4,35 @@ from pyaml import yaml import pytest -from pyp5js.fs import LibFiles - -LIB_FILES = LibFiles() +from pyp5js.config.fs import PYP5JS_FILES @pytest.fixture def pyp5js(): - with LIB_FILES.pytop5js.open('r') as fd: + with PYP5JS_FILES.pytop5js.open('r') as fd: return fd.read() @pytest.fixture def p5_reference(): - with LIB_FILES.p5_yml.open('r') as fd: + with PYP5JS_FILES.p5_yml.open('r') as fd: return yaml.load(fd) -special_methods = ['pop', 'clear'] +special_methods = ['pop', 'clear', 'get'] def test_all_regular_p5_methods_are_defined(pyp5js, p5_reference): for method in [m for m in p5_reference['p5']['methods'] if m not in special_methods]: + if method == 'loadImage': + continue assert f"def {method}(*args):" in pyp5js assert f" return _P5_INSTANCE.{method}(*args)" in pyp5js + assert f"def loadImage(*args):" in pyp5js + assert f" imageObj = _P5_INSTANCE.loadImage(*args)" in pyp5js + assert f" return image_proxy(imageObj)" in pyp5js + for method in [m for m in p5_reference['dom']['methods'] if m not in special_methods]: assert f"def {method}(*args):" in pyp5js assert f" return _P5_INSTANCE.{method}(*args)" in pyp5js diff --git a/pyp5js/tests/test_sketch.py b/pyp5js/tests/test_sketch.py new file mode 100644 index 00000000..ac85faa9 --- /dev/null +++ b/pyp5js/tests/test_sketch.py @@ -0,0 +1,89 @@ +import pytest +import shutil +from unittest import TestCase + +from pyp5js.config import SKETCHBOOK_DIR, PYODIDE_INTERPRETER +from pyp5js.exceptions import SketchDirAlreadyExistException +from pyp5js.sketch import Sketch +from pyp5js.exceptions import InvalidName + + +class SketchTests(TestCase): + + def setUp(self): + self.base_dir = SKETCHBOOK_DIR + self.sketch_name = 'foo' + self.files = Sketch(self.sketch_name) + + def tearDown(self): + if self.base_dir.exists(): + shutil.rmtree(self.base_dir) + + def get_expected_path(self, *args): + return self.base_dir.joinpath(self.sketch_name, *args) + + def test_sketch_dirs(self): + assert self.get_expected_path() == self.files.sketch_dir + assert self.get_expected_path('static') == self.files.static_dir + assert self.get_expected_path('target') == self.files.target_dir + assert self.files.TARGET_NAME == 'target' + + def test_sketch(self): + self.files.check_sketch_dir = False + assert self.get_expected_path('index.html') == self.files.index_html + assert self.get_expected_path('static', 'p5.js') == self.files.p5js + assert self.get_expected_path('foo.py') == self.files.sketch_py + assert self.get_expected_path('.properties.json') == self.files.config_file + + def test_target_sketch_variations(self): + assert self.get_expected_path('target_sketch.py') == self.files.target_sketch + self.files.config.interpreter = PYODIDE_INTERPRETER + assert self.get_expected_path('target', 'target_sketch.js') == self.files.target_sketch + + def test_create_dirs(self): + assert self.files.sketch_dir.exists() is False + assert self.files.static_dir.exists() is False + assert self.files.target_dir.exists() is False + assert self.files.config_file.exists() is False + + self.files.create_sketch_dir() + + assert self.files.sketch_dir.exists() is True + assert self.files.static_dir.exists() is True + assert self.files.target_dir.exists() is True + assert self.files.config_file.exists() is True + + with pytest.raises(SketchDirAlreadyExistException): + self.files.create_sketch_dir() + + def test_raise_exception_when_name_starts_with_numbers(self): + files = Sketch('123name') + with pytest.raises(InvalidName): + files.validate_name() + + def test_raise_exception_when_name_contains_non_alphanumeric_chars(self): + files = Sketch('name&') + with pytest.raises(InvalidName): + files.validate_name() + + def test_raise_exception_when_name_creating_dir_with_invalid_name(self): + files = Sketch('name&') + with pytest.raises(InvalidName): + files.create_sketch_dir() + + def test_name_should_accept_underscore_in_the_beginning(self): + file = Sketch('__name__') + assert file.sketch_name == '__name__' + + def test_name_should_accept_underscore_in_the_middle(self): + file = Sketch('na_me') + assert file.sketch_name == 'na_me' + + def test_loads_config_from_config_file(self): + files = Sketch('bar', interpreter=PYODIDE_INTERPRETER) + files.create_sketch_dir() # writes config file json + + same_files = Sketch('bar') + + assert same_files.config_file == files.config_file + assert same_files.config.interpreter == PYODIDE_INTERPRETER diff --git a/pyp5js/tests/test_templates_renderers.py b/pyp5js/tests/test_templates_renderers.py index 5dccdc26..26fc3c29 100644 --- a/pyp5js/tests/test_templates_renderers.py +++ b/pyp5js/tests/test_templates_renderers.py @@ -1,28 +1,30 @@ from pyp5js import templates_renderers as renderers -from pyp5js.fs import SketchFiles +from pyp5js.sketch import Sketch +from pyp5js.config.fs import PYP5JS_FILES +from .fixtures import sketch -def test_get_sketch_index_content(): - sketch_files = SketchFiles('foo') - - expected_template = renderers.templates.get_template(sketch_files.from_lib.index_html.name) +def test_get_sketch_index_content(sketch): + expected_template = renderers.templates.get_template('transcrypt/index.html') expected_content = expected_template.render({ - 'sketch_name': sketch_files.sketch_name, - "p5_js_url": sketch_files.STATIC_NAME + "/p5.js", - "sketch_js_url": sketch_files.TARGET_NAME + "/target_sketch.js", + 'sketch_name': sketch.sketch_name, + "p5_js_url": sketch.STATIC_NAME + "/p5.js", + "sketch_js_url": sketch.TARGET_NAME + "/target_sketch.js", }) - assert expected_content == renderers.get_sketch_index_content(sketch_files) + assert expected_content == renderers.get_sketch_index_content(sketch) -def test_get_target_sketch_content(): - sketch_files = SketchFiles('foo') +def test_get_target_sketch_content(sketch): + with open(sketch.sketch_py, 'w') as fd: + fd.write('content') - expected_template = renderers.templates.get_template(sketch_files.from_lib.target_sketch_template.name) + expected_template = renderers.templates.get_template('transcrypt/target_sketch.py.template') expected_content = expected_template.render({ - 'sketch_name': sketch_files.sketch_name, + 'sketch_name': sketch.sketch_name, + 'sketch_content': 'content' }) - content = renderers.get_target_sketch_content(sketch_files) + content = renderers.get_target_sketch_content(sketch) assert expected_content == content assert "import foo as source_sketch" in content diff --git a/setup.py b/setup.py index 2a200f4c..7990a896 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ package_data={ 'pyp5js': [ 'assets/*', - 'static/**/*', + 'assets/static/**/*', 'templates/*', 'http/templates/*', 'http/static/*', From b19de29086bb93dbf6e1ee7b62169c2d9894c1a8 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Sun, 7 Mar 2021 12:53:20 -0300 Subject: [PATCH 06/17] adding P3D to the global statement note on line 1105: ` P3D = p5_instance.WEBGL` --- docs/pyodide/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pyodide/index.html b/docs/pyodide/index.html index e45578ad..b111999d 100644 --- a/docs/pyodide/index.html +++ b/docs/pyodide/index.html @@ -1017,8 +1017,8 @@ global DEG_TO_RAD, DEGREES, DELETE, DIFFERENCE, DILATE, DODGE, DOWN_ARROW, ENTER, ERODE, ESCAPE, EXCLUSION global FILL, GRAY, GRID, HALF_PI, HAND, HARD_LIGHT, HSB, HSL, IMAGE, IMMEDIATE, INVERT, ITALIC, LANDSCAPE global LEFT, LEFT_ARROW, LIGHTEST, LINE_LOOP, LINE_STRIP, LINEAR, LINES, MIRROR, MITER, MOVE, MULTIPLY, NEAREST - global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP, QUADRATIC - global QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW + global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP, + global QUADRATIC, QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW global ROUND, SCREEN, SHIFT, SOFT_LIGHT, SQUARE, STROKE, SUBTRACT, TAB, TAU, TEXT, TEXTURE, THRESHOLD, TOP global TRIANGLE_FAN, TRIANGLE_STRIP, TRIANGLES, TWO_PI, UP_ARROW, VIDEO, WAIT, WEBGL From 9a1ee2756ed09b07f7decaaa7ffabb19a0da8f87 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Mon, 8 Mar 2021 09:06:23 -0300 Subject: [PATCH 07/17] adding P3D global statement to the main template --- pyp5js/templates/pyodide/target_sketch.js.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyp5js/templates/pyodide/target_sketch.js.template b/pyp5js/templates/pyodide/target_sketch.js.template index a1d273f6..813576cf 100644 --- a/pyp5js/templates/pyodide/target_sketch.js.template +++ b/pyp5js/templates/pyodide/target_sketch.js.template @@ -950,8 +950,8 @@ def pre_draw(p5_instance, draw_func): global DEG_TO_RAD, DEGREES, DELETE, DIFFERENCE, DILATE, DODGE, DOWN_ARROW, ENTER, ERODE, ESCAPE, EXCLUSION global FILL, GRAY, GRID, HALF_PI, HAND, HARD_LIGHT, HSB, HSL, IMAGE, IMMEDIATE, INVERT, ITALIC, LANDSCAPE global LEFT, LEFT_ARROW, LIGHTEST, LINE_LOOP, LINE_STRIP, LINEAR, LINES, MIRROR, MITER, MOVE, MULTIPLY, NEAREST - global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP, QUADRATIC - global QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW + global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP, + global QUADRATIC, QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW global ROUND, SCREEN, SHIFT, SOFT_LIGHT, SQUARE, STROKE, SUBTRACT, TAB, TAU, TEXT, TEXTURE, THRESHOLD, TOP global TRIANGLE_FAN, TRIANGLE_STRIP, TRIANGLES, TWO_PI, UP_ARROW, VIDEO, WAIT, WEBGL From 89bb2b899b467b53491f713ce908a99f3732b5e8 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Mon, 8 Mar 2021 09:39:00 -0300 Subject: [PATCH 08/17] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35415aaf..ff2f7a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Development ----------- +- Pyodide mode bugfix for missing `P3D` global definition 0.5.1 ----- From 07783363419b9aea14b8a84dc51f80fe34768af4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Mar 2021 00:57:33 +0000 Subject: [PATCH 09/17] Bump jinja2 from 2.11.2 to 2.11.3 Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.2 to 2.11.3. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/master/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/2.11.2...2.11.3) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afd7b3a8..7f669f58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Click==7.1.1 -Jinja2==2.11.2 +Jinja2==2.11.3 Transcrypt==3.7.16 cprint==1.1 gunicorn==19.9.0 From 945e1278611f1d5be73a2f5f4acc73e8d8cae744 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Sun, 21 Mar 2021 11:35:11 -0300 Subject: [PATCH 10/17] Remove trailing coma that breaks it! --- docs/pyodide/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pyodide/index.html b/docs/pyodide/index.html index b111999d..de2163ca 100644 --- a/docs/pyodide/index.html +++ b/docs/pyodide/index.html @@ -1017,7 +1017,7 @@ global DEG_TO_RAD, DEGREES, DELETE, DIFFERENCE, DILATE, DODGE, DOWN_ARROW, ENTER, ERODE, ESCAPE, EXCLUSION global FILL, GRAY, GRID, HALF_PI, HAND, HARD_LIGHT, HSB, HSL, IMAGE, IMMEDIATE, INVERT, ITALIC, LANDSCAPE global LEFT, LEFT_ARROW, LIGHTEST, LINE_LOOP, LINE_STRIP, LINEAR, LINES, MIRROR, MITER, MOVE, MULTIPLY, NEAREST - global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP, + global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP global QUADRATIC, QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW global ROUND, SCREEN, SHIFT, SOFT_LIGHT, SQUARE, STROKE, SUBTRACT, TAB, TAU, TEXT, TEXTURE, THRESHOLD, TOP global TRIANGLE_FAN, TRIANGLE_STRIP, TRIANGLES, TWO_PI, UP_ARROW, VIDEO, WAIT, WEBGL From bf9659346c43eaa8b30e34afde74b516e8fae3a8 Mon Sep 17 00:00:00 2001 From: Alexandre B A Villares <3694604+villares@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:42:38 -0300 Subject: [PATCH 11/17] Remove trailing comma that breaks it! --- pyp5js/templates/pyodide/target_sketch.js.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyp5js/templates/pyodide/target_sketch.js.template b/pyp5js/templates/pyodide/target_sketch.js.template index 813576cf..64f036bd 100644 --- a/pyp5js/templates/pyodide/target_sketch.js.template +++ b/pyp5js/templates/pyodide/target_sketch.js.template @@ -950,7 +950,7 @@ def pre_draw(p5_instance, draw_func): global DEG_TO_RAD, DEGREES, DELETE, DIFFERENCE, DILATE, DODGE, DOWN_ARROW, ENTER, ERODE, ESCAPE, EXCLUSION global FILL, GRAY, GRID, HALF_PI, HAND, HARD_LIGHT, HSB, HSL, IMAGE, IMMEDIATE, INVERT, ITALIC, LANDSCAPE global LEFT, LEFT_ARROW, LIGHTEST, LINE_LOOP, LINE_STRIP, LINEAR, LINES, MIRROR, MITER, MOVE, MULTIPLY, NEAREST - global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP, + global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP global QUADRATIC, QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW global ROUND, SCREEN, SHIFT, SOFT_LIGHT, SQUARE, STROKE, SUBTRACT, TAB, TAU, TEXT, TEXTURE, THRESHOLD, TOP global TRIANGLE_FAN, TRIANGLE_STRIP, TRIANGLES, TWO_PI, UP_ARROW, VIDEO, WAIT, WEBGL From a1fe34c839a7f0d4ce7f0b45f096fc0b445c87b6 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Sat, 17 Apr 2021 08:08:24 -0300 Subject: [PATCH 12/17] Ignore envrc file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eb58c8d7..6c3fb90b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ tests-sketchbook venv docs/examples/dev* .theia +.envrc From 53f9484b090f8a83cb6e7a33324dae509e730b19 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Sat, 17 Apr 2021 08:31:53 -0300 Subject: [PATCH 13/17] Ad PVector to base pyodide template --- .../pyodide/target_sketch.js.template | 324 +++++++++++++++++- 1 file changed, 323 insertions(+), 1 deletion(-) diff --git a/pyp5js/templates/pyodide/target_sketch.js.template b/pyp5js/templates/pyodide/target_sketch.js.template index 64f036bd..80938582 100644 --- a/pyp5js/templates/pyodide/target_sketch.js.template +++ b/pyp5js/templates/pyodide/target_sketch.js.template @@ -939,6 +939,328 @@ popStyle = pop pushMatrix = push pushStyle = push +# PVector is a wrapper/helper class for p5.Vector objets +# providing names similar to Processing Python or Java modes +# but mostly keeping p5js functionality + +from numbers import Number + +class PVector: + + def __init__(self, x=0, y=0, z=0): + self.__vector = createVector(x, y, z) + self.add = self.__instance_add__ + self.sub = self.__instance_sub__ + self.mult = self.__instance_mult__ + self.div = self.__instance_div__ + self.cross = self.__instance_cross__ + self.dist = self.__instance_dist__ + self.dot = self.__instance_dot__ + self.lerp = self.__instance_lerp__ + + @property + def x(self): + return self.__vector.x + + @x.setter + def x(self, x): + self.__vector.x = x + + @property + def y(self): + return self.__vector.y + + @y.setter + def y(self, y): + self.__vector.y = y + + @property + def z(self): + return self.__vector.z + + @z.setter + def z(self, z): + self.__vector.z = z + + def mag(self): + return self.__vector.mag() + + def magSq(self): + return self.__vector.magSq() + + def setMag(self, mag): + self.__vector.setMag(mag) + return self + + def normalize(self): + self.__vector.normalize() + return self + + def limit(self, max): + self.__vector.limit(max) + return self + + def heading(self): + return self.__vector.heading() + + def rotate(self, angle): + self.__vector.rotate(angle) + return self + + def __instance_add__(self, *args): + if len(args) == 1: + return PVector.add(self, args[0], self) + else: + return PVector.add(self, PVector(*args), self) + + def __instance_sub__(self, *args): + if len(args) == 1: + return PVector.sub(self, args[0], self) + else: + return PVector.sub(self, PVector(*args), self) + + def __instance_mult__(self, o): + return PVector.mult(self, o, self) + + def __instance_div__(self, f): + return PVector.div(self, f, self) + + def __instance_cross__(self, o): + return PVector.cross(self, o, self) + + def __instance_dist__(self, o): + return PVector.dist(self, o) + + def __instance_dot__(self, *args): + if len(args) == 1: + v = args[0] + else: + v = args + return self.x * v[0] + self.y * v[1] + self.z * v[2] + + def __instance_lerp__(self, *args): + if len(args) == 2: + return PVector.lerp(self, args[0], args[1], self) + else: + vx, vy, vz, f = args + return PVector.lerp(self, PVector(vx, vy, vz), f, self) + + def get(self): + return PVector(self.x, self.y, self.z) + + def copy(self): + return PVector(self.x, self.y, self.z) + + def __getitem__(self, k): + return getattr(self, ('x', 'y', 'z')[k]) + + def __setitem__(self, k, v): + setattr(self, ('x', 'y', 'z')[k], v) + + def __copy__(self): + return PVector(self.x, self.y, self.z) + + def __deepcopy__(self, memo): + return PVector(self.x, self.y, self.z) + + def __repr__(self): # PROVISÓRIO + return f'PVector({self.x}, {self.y}, {self.z})' + + def set(self, *args): + """ + Sets the x, y, and z component of the vector using two or three separate + variables, the data from a p5.Vector, or the values from a float array. + """ + self.__vector.set(*args) + + @classmethod + def add(cls, a, b, dest=None): + if dest is None: + return PVector(a.x + b[0], a.y + b[1], a.z + b[2]) + dest.__vector.set(a.x + b[0], a.y + b[1], a.z + b[2]) + return dest + + @classmethod + def sub(cls, a, b, dest=None): + if dest is None: + return PVector(a.x - b[0], a.y - b[1], a.z - b[2]) + dest.__vector.set(a.x - b[0], a.y - b[1], a.z - b[2]) + return dest + + @classmethod + def mult(cls, a, b, dest=None): + if dest is None: + return PVector(a.x * b, a.y * b, a.z * b) + dest.__vector.set(a.x * b, a.y * b, a.z * b) + return dest + + @classmethod + def div(cls, a, b, dest=None): + if dest is None: + return PVector(a.x / b, a.y / b, a.z / b) + dest.__vector.set(a.x / b, a.y / b, a.z / b) + return dest + + @classmethod + def dist(cls, a, b): + return a.__vector.dist(b.__vector) + + @classmethod + def dot(cls, a, b): + return a.__vector.dot(b.__vector) + + def __add__(a, b): + return PVector.add(a, b, None) + + def __sub__(a, b): + return PVector.sub(a, b, None) + + def __isub__(a, b): + a.sub(b) + return a + + def __iadd__(a, b): + a.add(b) + return a + + def __mul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector.mult(a, float(b), None) + + def __rmul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector.mult(a, float(b), None) + + def __imul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The *= operator can only be used to multiply a PVector by a number") + a.__vector.mult(float(b)) + return a + + def __truediv__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector(a.x / float(b), a.y / float(b), a.z / float(b)) + + def __itruediv__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The /= operator can only be used to multiply a PVector by a number") + a.__vector.set(a.x / float(b), a.y / float(b), a.z / float(b)) + return a + + def __eq__(a, b): + return a.x == b[0] and a.y == b[1] and a.z == b[2] + + def __lt__(a, b): + return a.magSq() < b.magSq() + + def __le__(a, b): + return a.magSq() <= b.magSq() + + def __gt__(a, b): + return a.magSq() > b.magSq() + + def __ge__(a, b): + return a.magSq() >= b.magSq() + + # Problematic class methods, we would rather use p5.Vector when possible... + + @classmethod + def lerp(cls, a, b, f, dest=None): + v = createVector(a.x, a.y, a.z) + v.lerp(b.__vector, f) + if dest is None: + return PVector(v.x, v.y, v.z) + dest.set(v.x, v.y, v.z) + return dest + + @classmethod + def cross(cls, a, b, dest=None): + x = a.y * b[2] - b[1] * a.z + y = a.z * b[0] - b[2] * a.x + z = a.x * b[1] - b[0] * a.y + if dest is None: + return PVector(x, y, z) + dest.set(x, y, z) + return dest + + @classmethod + def fromAngle(cls, angle, length=1): + # https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js + return PVector(length * cos(angle), length * sin(angle), 0) + + @classmethod + def fromAngles(theta, phi, length=1): + # https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js + cosPhi = cos(phi) + sinPhi = sin(phi) + cosTheta = cos(theta) + sinTheta = sin(theta) + return PVector(length * sinTheta * sinPhi, + -length * cosTheta, + length * sinTheta * cosPhi) + + @classmethod + def random2D(cls): + return PVector.fromAngle(random(TWO_PI)) + + @classmethod + def random3D(cls, dest=None): + angle = random(TWO_PI) + vz = random(2) - 1 + mult = sqrt(1 - vz * vz) + vx = mult * cos(angle) + vy = mult * sin(angle) + if dest is None: + return PVector(vx, vy, vz) + dest.set(vx, vy, vz) + return dest + + @classmethod + def angleBetween(cls, a, b): + return acos(a.dot(b) / sqrt(a.magSq() * b.magSq())) + + # Other harmless p5js methods + + def equals(self, v): + return self == v + + def heading2D(self): + return self.__vector.heading() + + def reflect(self, *args): + # Reflect the incoming vector about a normal to a line in 2D, or about + # a normal to a plane in 3D This method acts on the vector directly + r = self.__vector.reflect(*args) + return r + + def array(self): + # Return a representation of this vector as a float array. This is only + # for temporary use. If used in any w fashion, the contents should be + # copied by using the p5.Vector.copy() method to copy into your own + # array. + return self.__vector.array() + + def toString(self): + # Returns a string representation of a vector v by calling String(v) or v.toString(). + # return self.__vector.toString() would be something like "p5.vector + # Object […, …, …]" + return str(self) + + def rem(self, *args): + # Gives remainder of a vector when it is divided by anw vector. See + # examples for more context. + self.__vector.rem(*args) + return self + + def pre_draw(p5_instance, draw_func): """ We need to run this before the actual draw to insert and update p5 env variables @@ -950,7 +1272,7 @@ def pre_draw(p5_instance, draw_func): global DEG_TO_RAD, DEGREES, DELETE, DIFFERENCE, DILATE, DODGE, DOWN_ARROW, ENTER, ERODE, ESCAPE, EXCLUSION global FILL, GRAY, GRID, HALF_PI, HAND, HARD_LIGHT, HSB, HSL, IMAGE, IMMEDIATE, INVERT, ITALIC, LANDSCAPE global LEFT, LEFT_ARROW, LIGHTEST, LINE_LOOP, LINE_STRIP, LINEAR, LINES, MIRROR, MITER, MOVE, MULTIPLY, NEAREST - global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP + global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP global QUADRATIC, QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW global ROUND, SCREEN, SHIFT, SOFT_LIGHT, SQUARE, STROKE, SUBTRACT, TAB, TAU, TEXT, TEXTURE, THRESHOLD, TOP global TRIANGLE_FAN, TRIANGLE_STRIP, TRIANGLES, TWO_PI, UP_ARROW, VIDEO, WAIT, WEBGL From 3b19bb44189be87faf294afbc420e58cdcf09ab4 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Sat, 17 Apr 2021 08:32:06 -0300 Subject: [PATCH 14/17] Example to cover PVector api usage --- docs/examples/sketch_011/.properties.json | 1 + docs/examples/sketch_011/index.html | 240 +++ docs/examples/sketch_011/sketch_011.py | 183 ++ docs/examples/sketch_011/static/p5.js | 3 + .../sketch_011/target/target_sketch.js | 1549 +++++++++++++++++ 5 files changed, 1976 insertions(+) create mode 100644 docs/examples/sketch_011/.properties.json create mode 100644 docs/examples/sketch_011/index.html create mode 100644 docs/examples/sketch_011/sketch_011.py create mode 100644 docs/examples/sketch_011/static/p5.js create mode 100644 docs/examples/sketch_011/target/target_sketch.js diff --git a/docs/examples/sketch_011/.properties.json b/docs/examples/sketch_011/.properties.json new file mode 100644 index 00000000..a84d75db --- /dev/null +++ b/docs/examples/sketch_011/.properties.json @@ -0,0 +1 @@ +{"interpreter": "pyodide"} \ No newline at end of file diff --git a/docs/examples/sketch_011/index.html b/docs/examples/sketch_011/index.html new file mode 100644 index 00000000..44c92707 --- /dev/null +++ b/docs/examples/sketch_011/index.html @@ -0,0 +1,240 @@ + + + + + + + + + sketch_011 - pyp5js + + + + + + + + + + + + + +

+ Python code here. +

+ + +
+
+ +
+ +
+           
+# Made for  https://berinhard.github.io/pyp5js/pyodide/
+# Tests to ensure a Processing-like PVector API under Pyodide
+
+def setup():
+    size(500, 500)
+    test()
+
+def test():
+    """
+    Mostly from JDF py.processing tests
+    """
+    a = PVector()
+    assert a.x == 0
+    assert a.y == 0
+    assert a.z == 0
+
+    a = PVector(5, 7, 11)
+    b = PVector(13, 17, 23)
+    assert a - b == PVector(-8.0, -10.0, -12.0)
+    assert b - a == PVector(8, 10, 12)
+    c = PVector(18, 24, 34)
+    assert b + a == c
+    assert a + b == c
+    assert PVector.add(a, b) == c
+    assert PVector.add(a, b) == c
+    a.add(b)
+    assert a == c
+    a.add(b)
+    assert a == PVector(31.0, 41.0, 57.0)
+
+    c = PVector(310.0, 410.0, 570.0)
+    assert a * 10 == c
+    assert a * 10 == c
+    assert PVector.mult(a, 10) == c
+
+    assert PVector.mult(a, 10) == c
+    a.mult(10)
+    assert a == c
+
+    assert int(1000 * PVector.dist(a, b)) == 736116
+    assert PVector.cross(a, b) == PVector(-260.0, 280.0, -60.0)
+    assert a.cross(b) == PVector(-260.0, 280.0, -60.0)
+    assert PVector.dot(a, b) == 0
+
+    d = a.get()
+    d += b
+    assert d == a + b
+    d = a.get()
+    d -= c
+    assert d == a - c
+    d = a.get()
+    d *= 5.0
+    assert d == a * 5.0
+    d = a.get()
+
+    d /= 5.0
+    assert d == a / 5.0
+
+    assert b * 5 == b * 5.0
+    assert b / 5 == b / 5.0
+    d = b.get()
+    d *= 391
+    assert d == b * 391.0
+    d = b.get()
+    d /= 10203
+    assert d == b / 10203.0
+
+    d = a.get()
+    d += a + a
+    assert d == a + a + a
+
+    assert a * 57.0 == 57.0 * a
+
+    assert (a / 5.0) == (1.0 / 5.0) * a
+
+    m, n = b, c
+    a += b * 5 - c / 2 + PVector(0, 1, 2)
+    assert (m, n) == (b, c)
+
+    import copy
+    x = [a, b]
+    y = copy.deepcopy(x)
+
+    assert x == y
+    x[0].sub(PVector(100, 100, 100))
+    assert x != y
+
+    a = PVector(1, 1)
+    b = PVector(-2, -2)
+    assert a < b
+    assert a <= b
+    assert b > a
+    assert b >= a
+    a = PVector(1, 2, 3)
+    b = PVector(3, 2, 1)
+    assert a != b
+    assert a >= b
+    assert b >= a
+    assert a.magSq() == b.magSq()
+
+    v1 = PVector(10, 20)
+    v2 = PVector(60, 80)
+    a = PVector.angleBetween(v1, v2)
+    # Java implementation gives slightly different value:
+    # assert a == 0.17985349893569946  # more or less
+    assert int(a * 1e8) == 17985349  # more or less
+
+    # Regression test for https://github.com/jdf/Processing.py-Bugs/issues/67
+    assert isinstance(PVector(1, 2), PVector)
+
+    # Regression test for https://github.com/jdf/Processing.py-Bugs/issues/101
+    v = PVector(10, 20, 0)
+    d = v.dot(60, 80, 0)
+    assert d == 2200.0
+    v2 = PVector(60, 80, 0)
+    d = v.dot(v2)
+    assert d == 2200.0
+
+    # PVector.add w/multiple arguments
+    v = PVector(40, 20, 0)
+    v.add(25, 50, 0)
+    assert (v.x, v.y, v.z) == (65, 70, 0)
+
+    # PVector.sub w/multiple arguments
+    v = PVector(40, 20, 0)
+    v.sub(25, 50, 0)
+    assert (v.x, v.y, v.z) == (15, -30, 0)
+
+    # Regression test for https://github.com/jdf/Processing.py-Bugs/issues/102
+    start = PVector(0.0, 0.0)
+    end = PVector(100.0, 100.0)
+    middle = PVector.lerp(start, end, 0.5)
+    assert middle == PVector(50.0, 50.0)
+    assert start == PVector(0, 0)
+    start.lerp(end, .75)
+    assert start == PVector(75, 75)
+    assert end == PVector(100.0, 100.0)
+    end.lerp(200, 200, 0, .5)
+    assert end == PVector(150.0, 150.0)
+
+    # test that instance op returns self
+    a = PVector(3, 5, 7)
+    b = a * 10
+    assert a.mult(10) == b
+
+    # test that a vector can do arithmetic with a tuple
+    assert PVector(1, 2, 3) == (1, 2, 3)
+    assert (PVector(1, 2, 3) + (3, 3, 3)) == (4, 5, 6)
+    assert (PVector(5, 5, 5) - (1, 2, 3)) == (4, 3, 2)
+
+    # Regression test for https://github.com/jdf/processing.py/issues/317
+    r = PVector.random2D() * 10
+    assert -10 <= r.x <= 10
+    assert -10 <= r.y <= 10
+    assert r.z == 0
+
+    PVector.random3D(r)
+    r += (1, 1, 1)
+    assert 0 <= r.x <= 2
+    assert 0 <= r.y <= 2
+    assert 0 <= r.z <= 2
+
+    # Regression test for https://github.com/jdf/processing.py/issues/334
+    r = PVector.fromAngle(0) * 10
+    assert r.x == 10
+    assert r.y == 0
+    assert r.z == 0
+
+    # Other p5js methods
+    text(r.toString(), 120, 120)
+    r.setMag(100)
+    assert r.mag() == 100
+    r.normalize()
+    assert r.mag() == 1
+    r.limit(10)
+    assert r.mag() == 1
+    r.limit(0.1)
+    assert r.mag() == 0.1
+
+    assert r.heading() == 0
+    r.rotate(PI)
+    assert r.heading() == PI
+
+    text('OK - ALL PASSED!', 100, 200)
+           
+        
+ +
+ + + diff --git a/docs/examples/sketch_011/sketch_011.py b/docs/examples/sketch_011/sketch_011.py new file mode 100644 index 00000000..76288c61 --- /dev/null +++ b/docs/examples/sketch_011/sketch_011.py @@ -0,0 +1,183 @@ +# Made for https://berinhard.github.io/pyp5js/pyodide/ + +def setup(): + size(500, 500) + test() + +def test(): + """ + Mostly from JDF py.processing tests + """ + a = PVector() + assert a.x == 0 + assert a.y == 0 + assert a.z == 0 + + a = PVector(5, 7, 11) + b = PVector(13, 17, 23) + assert a - b == PVector(-8.0, -10.0, -12.0) + assert b - a == PVector(8, 10, 12) + c = PVector(18, 24, 34) + assert b + a == c + assert a + b == c + assert PVector.add(a, b) == c + assert PVector.add(a, b) == c + a.add(b) + assert a == c + a.add(b) + assert a == PVector(31.0, 41.0, 57.0) + + c = PVector(310.0, 410.0, 570.0) + assert a * 10 == c + assert a * 10 == c + assert PVector.mult(a, 10) == c + + assert PVector.mult(a, 10) == c + a.mult(10) + assert a == c + + assert int(1000 * PVector.dist(a, b)) == 736116 + assert PVector.cross(a, b) == PVector(-260.0, 280.0, -60.0) + assert a.cross(b) == PVector(-260.0, 280.0, -60.0) + assert PVector.dot(a, b) == 0 + + d = a.get() + d += b + assert d == a + b + d = a.get() + d -= c + assert d == a - c + d = a.get() + d *= 5.0 + assert d == a * 5.0 + d = a.get() + + d /= 5.0 + assert d == a / 5.0 + + assert b * 5 == b * 5.0 + assert b / 5 == b / 5.0 + d = b.get() + d *= 391 + assert d == b * 391.0 + d = b.get() + d /= 10203 + assert d == b / 10203.0 + + d = a.get() + d += a + a + assert d == a + a + a + + assert a * 57.0 == 57.0 * a + + assert (a / 5.0) == (1.0 / 5.0) * a + + m, n = b, c + a += b * 5 - c / 2 + PVector(0, 1, 2) + assert (m, n) == (b, c) + + import copy + x = [a, b] + y = copy.deepcopy(x) + + assert x == y + x[0].sub(PVector(100, 100, 100)) + assert x != y + + a = PVector(1, 1) + b = PVector(-2, -2) + assert a < b + assert a <= b + assert b > a + assert b >= a + a = PVector(1, 2, 3) + b = PVector(3, 2, 1) + assert a != b + assert a >= b + assert b >= a + assert a.magSq() == b.magSq() + + v1 = PVector(10, 20) + v2 = PVector(60, 80) + a = PVector.angleBetween(v1, v2) + # Java implementation gives slightly different value: + # assert a == 0.17985349893569946 # more or less + assert int(a * 1e8) == 17985349 # more or less + + # Regression test for https://github.com/jdf/Processing.py-Bugs/issues/67 + assert isinstance(PVector(1, 2), PVector) + + # Regression test for https://github.com/jdf/Processing.py-Bugs/issues/101 + v = PVector(10, 20, 0) + d = v.dot(60, 80, 0) + assert d == 2200.0 + v2 = PVector(60, 80, 0) + d = v.dot(v2) + assert d == 2200.0 + + # PVector.add w/multiple arguments + v = PVector(40, 20, 0) + v.add(25, 50, 0) + assert (v.x, v.y, v.z) == (65, 70, 0) + + # PVector.sub w/multiple arguments + v = PVector(40, 20, 0) + v.sub(25, 50, 0) + assert (v.x, v.y, v.z) == (15, -30, 0) + + # Regression test for https://github.com/jdf/Processing.py-Bugs/issues/102 + start = PVector(0.0, 0.0) + end = PVector(100.0, 100.0) + middle = PVector.lerp(start, end, 0.5) + assert middle == PVector(50.0, 50.0) + assert start == PVector(0, 0) + start.lerp(end, .75) + assert start == PVector(75, 75) + assert end == PVector(100.0, 100.0) + end.lerp(200, 200, 0, .5) + assert end == PVector(150.0, 150.0) + + # test that instance op returns self + a = PVector(3, 5, 7) + b = a * 10 + assert a.mult(10) == b + + # test that a vector can do arithmetic with a tuple + assert PVector(1, 2, 3) == (1, 2, 3) + assert (PVector(1, 2, 3) + (3, 3, 3)) == (4, 5, 6) + assert (PVector(5, 5, 5) - (1, 2, 3)) == (4, 3, 2) + + # Regression test for https://github.com/jdf/processing.py/issues/317 + r = PVector.random2D() * 10 + assert -10 <= r.x <= 10 + assert -10 <= r.y <= 10 + assert r.z == 0 + + PVector.random3D(r) + r += (1, 1, 1) + assert 0 <= r.x <= 2 + assert 0 <= r.y <= 2 + assert 0 <= r.z <= 2 + + # Regression test for https://github.com/jdf/processing.py/issues/334 + r = PVector.fromAngle(0) * 10 + assert r.x == 10 + assert r.y == 0 + assert r.z == 0 + + # Other p5js methods + text(r.toString(), 120, 120) + r.setMag(100) + assert r.mag() == 100 + r.normalize() + assert r.mag() == 1 + r.limit(10) + assert r.mag() == 1 + r.limit(0.1) + assert r.mag() == 0.1 + + assert r.heading() == 0 + r.rotate(PI) + assert r.heading() == PI + + text('OK - ALL PASSED!', 100, 200) diff --git a/docs/examples/sketch_011/static/p5.js b/docs/examples/sketch_011/static/p5.js new file mode 100644 index 00000000..d5d9c0c6 --- /dev/null +++ b/docs/examples/sketch_011/static/p5.js @@ -0,0 +1,3 @@ +/*! p5.js v1.0.0 February 29, 2020 */ + +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).p5=e()}}(function(){return function o(a,s,l){function u(t,e){if(!s[t]){if(!a[t]){var r="function"==typeof require&&require;if(!e&&r)return r(t,!0);if(h)return h(t,!0);var i=new Error("Cannot find module '"+t+"'");throw i.code="MODULE_NOT_FOUND",i}var n=s[t]={exports:{}};a[t][0].call(n.exports,function(e){return u(a[t][1][e]||e)},n,n.exports,o,a,s,l)}return s[t].exports}for(var h="function"==typeof require&&require,e=0;e>16&255,a[s++]=t>>8&255,a[s++]=255&t;2===o&&(t=u[e.charCodeAt(r)]<<2|u[e.charCodeAt(r+1)]>>4,a[s++]=255&t);1===o&&(t=u[e.charCodeAt(r)]<<10|u[e.charCodeAt(r+1)]<<4|u[e.charCodeAt(r+2)]>>2,a[s++]=t>>8&255,a[s++]=255&t);return a},r.fromByteArray=function(e){for(var t,r=e.length,i=r%3,n=[],o=0,a=r-i;o>2]+s[t<<4&63]+"==")):2==i&&(t=(e[r-2]<<8)+e[r-1],n.push(s[t>>10]+s[t>>4&63]+s[t<<2&63]+"="));return n.join("")};for(var s=[],u=[],h="undefined"!=typeof Uint8Array?Uint8Array:Array,i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=0,o=i.length;n>18&63]+s[n>>12&63]+s[n>>6&63]+s[63&n]);return o.join("")}u["-".charCodeAt(0)]=62,u["_".charCodeAt(0)]=63},{}],20:[function(e,t,r){},{}],21:[function(N,e,F){(function(c){"use strict";var i=N("base64-js"),o=N("ieee754"),e="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;F.Buffer=c,F.SlowBuffer=function(e){+e!=e&&(e=0);return c.alloc(+e)},F.INSPECT_MAX_BYTES=50;var r=2147483647;function a(e){if(r>>1;case"base64":return O(e).length;default:if(n)return i?-1:R(e).length;t=(""+t).toLowerCase(),n=!0}}function d(e,t,r){var i=e[t];e[t]=e[r],e[r]=i}function p(e,t,r,i,n){if(0===e.length)return-1;if("string"==typeof r?(i=r,r=0):2147483647=e.length){if(n)return-1;r=e.length-1}else if(r<0){if(!n)return-1;r=0}if("string"==typeof t&&(t=c.from(t,i)),c.isBuffer(t))return 0===t.length?-1:m(e,t,r,i,n);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?n?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):m(e,[t],r,i,n);throw new TypeError("val must be string, number or Buffer")}function m(e,t,r,i,n){var o,a=1,s=e.length,l=t.length;if(void 0!==i&&("ucs2"===(i=String(i).toLowerCase())||"ucs-2"===i||"utf16le"===i||"utf-16le"===i)){if(e.length<2||t.length<2)return-1;s/=a=2,l/=2,r/=2}function u(e,t){return 1===a?e[t]:e.readUInt16BE(t*a)}if(n){var h=-1;for(o=r;o>8,n=r%256,o.push(n),o.push(i);return o}(t,e.length-r),e,r,i)}function b(e,t,r){return 0===t&&r===e.length?i.fromByteArray(e):i.fromByteArray(e.slice(t,r))}function _(e,t,r){r=Math.min(e.length,r);for(var i=[],n=t;n>>10&1023|55296),h=56320|1023&h),i.push(h),n+=c}return function(e){var t=e.length;if(t<=x)return String.fromCharCode.apply(String,e);var r="",i=0;for(;ithis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e=e||"utf8";;)switch(e){case"hex":return M(this,t,r);case"utf8":case"utf-8":return _(this,t,r);case"ascii":return w(this,t,r);case"latin1":case"binary":return S(this,t,r);case"base64":return b(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,t,r);default:if(i)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),i=!0}}.apply(this,arguments)},c.prototype.equals=function(e){if(!c.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===c.compare(this,e)},c.prototype.inspect=function(){var e="",t=F.INSPECT_MAX_BYTES;return e=this.toString("hex",0,t).replace(/(.{2})/g,"$1 ").trim(),this.length>t&&(e+=" ... "),""},e&&(c.prototype[e]=c.prototype.inspect),c.prototype.compare=function(e,t,r,i,n){if(A(e,Uint8Array)&&(e=c.from(e,e.offset,e.byteLength)),!c.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===i&&(i=0),void 0===n&&(n=this.length),t<0||r>e.length||i<0||n>this.length)throw new RangeError("out of range index");if(n<=i&&r<=t)return 0;if(n<=i)return-1;if(r<=t)return 1;if(this===e)return 0;for(var o=(n>>>=0)-(i>>>=0),a=(r>>>=0)-(t>>>=0),s=Math.min(o,a),l=this.slice(i,n),u=e.slice(t,r),h=0;h>>=0,isFinite(r)?(r>>>=0,void 0===i&&(i="utf8")):(i=r,r=void 0)}var n=this.length-t;if((void 0===r||nthis.length)throw new RangeError("Attempt to write outside buffer bounds");i=i||"utf8";for(var o,a,s,l,u,h,c=!1;;)switch(i){case"hex":return g(this,e,t,r);case"utf8":case"utf-8":return u=t,h=r,D(R(e,(l=this).length-u),l,u,h);case"ascii":return v(this,e,t,r);case"latin1":case"binary":return v(this,e,t,r);case"base64":return o=this,a=t,s=r,D(O(e),o,a,s);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return y(this,e,t,r);default:if(c)throw new TypeError("Unknown encoding: "+i);i=(""+i).toLowerCase(),c=!0}},c.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var x=4096;function w(e,t,r){var i="";r=Math.min(e.length,r);for(var n=t;ne.length)throw new RangeError("Index out of range")}function P(e,t,r,i){if(r+i>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function L(e,t,r,i,n){return t=+t,r>>>=0,n||P(e,0,r,4),o.write(e,t,r,i,23,4),r+4}function k(e,t,r,i,n){return t=+t,r>>>=0,n||P(e,0,r,8),o.write(e,t,r,i,52,8),r+8}c.prototype.slice=function(e,t){var r=this.length;(e=~~e)<0?(e+=r)<0&&(e=0):r>>=0,t>>>=0,r||T(e,t,this.length);for(var i=this[e],n=1,o=0;++o>>=0,t>>>=0,r||T(e,t,this.length);for(var i=this[e+--t],n=1;0>>=0,t||T(e,1,this.length),this[e]},c.prototype.readUInt16LE=function(e,t){return e>>>=0,t||T(e,2,this.length),this[e]|this[e+1]<<8},c.prototype.readUInt16BE=function(e,t){return e>>>=0,t||T(e,2,this.length),this[e]<<8|this[e+1]},c.prototype.readUInt32LE=function(e,t){return e>>>=0,t||T(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},c.prototype.readUInt32BE=function(e,t){return e>>>=0,t||T(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},c.prototype.readIntLE=function(e,t,r){e>>>=0,t>>>=0,r||T(e,t,this.length);for(var i=this[e],n=1,o=0;++o>>=0,t>>>=0,r||T(e,t,this.length);for(var i=t,n=1,o=this[e+--i];0>>=0,t||T(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},c.prototype.readInt16LE=function(e,t){e>>>=0,t||T(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},c.prototype.readInt16BE=function(e,t){e>>>=0,t||T(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},c.prototype.readInt32LE=function(e,t){return e>>>=0,t||T(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},c.prototype.readInt32BE=function(e,t){return e>>>=0,t||T(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},c.prototype.readFloatLE=function(e,t){return e>>>=0,t||T(e,4,this.length),o.read(this,e,!0,23,4)},c.prototype.readFloatBE=function(e,t){return e>>>=0,t||T(e,4,this.length),o.read(this,e,!1,23,4)},c.prototype.readDoubleLE=function(e,t){return e>>>=0,t||T(e,8,this.length),o.read(this,e,!0,52,8)},c.prototype.readDoubleBE=function(e,t){return e>>>=0,t||T(e,8,this.length),o.read(this,e,!1,52,8)},c.prototype.writeUIntLE=function(e,t,r,i){e=+e,t>>>=0,r>>>=0,i||C(this,e,t,r,Math.pow(2,8*r)-1,0);var n=1,o=0;for(this[t]=255&e;++o>>=0,r>>>=0,i||C(this,e,t,r,Math.pow(2,8*r)-1,0);var n=r-1,o=1;for(this[t+n]=255&e;0<=--n&&(o*=256);)this[t+n]=e/o&255;return t+r},c.prototype.writeUInt8=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,1,255,0),this[t]=255&e,t+1},c.prototype.writeUInt16LE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},c.prototype.writeUInt16BE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},c.prototype.writeUInt32LE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},c.prototype.writeUInt32BE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},c.prototype.writeIntLE=function(e,t,r,i){if(e=+e,t>>>=0,!i){var n=Math.pow(2,8*r-1);C(this,e,t,r,n-1,-n)}var o=0,a=1,s=0;for(this[t]=255&e;++o>0)-s&255;return t+r},c.prototype.writeIntBE=function(e,t,r,i){if(e=+e,t>>>=0,!i){var n=Math.pow(2,8*r-1);C(this,e,t,r,n-1,-n)}var o=r-1,a=1,s=0;for(this[t+o]=255&e;0<=--o&&(a*=256);)e<0&&0===s&&0!==this[t+o+1]&&(s=1),this[t+o]=(e/a>>0)-s&255;return t+r},c.prototype.writeInt8=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},c.prototype.writeInt16LE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},c.prototype.writeInt16BE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},c.prototype.writeInt32LE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},c.prototype.writeInt32BE=function(e,t,r){return e=+e,t>>>=0,r||C(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},c.prototype.writeFloatLE=function(e,t,r){return L(this,e,t,!0,r)},c.prototype.writeFloatBE=function(e,t,r){return L(this,e,t,!1,r)},c.prototype.writeDoubleLE=function(e,t,r){return k(this,e,t,!0,r)},c.prototype.writeDoubleBE=function(e,t,r){return k(this,e,t,!1,r)},c.prototype.copy=function(e,t,r,i){if(!c.isBuffer(e))throw new TypeError("argument should be a Buffer");if(r=r||0,i||0===i||(i=this.length),t>=e.length&&(t=e.length),t=t||0,0=this.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("sourceEnd out of bounds");i>this.length&&(i=this.length),e.length-t>>=0,r=void 0===r?this.length:r>>>0,"number"==typeof(e=e||0))for(o=t;o>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function O(e){return i.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(t,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function D(e,t,r,i){for(var n=0;n=t.length||n>=e.length);++n)t[n+r]=e[n];return n}function A(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function I(e){return e!=e}var U=function(){for(var e="0123456789abcdef",t=new Array(256),r=0;r<16;++r)for(var i=16*r,n=0;n<16;++n)t[i+n]=e[r]+e[n];return t}()}).call(this,N("buffer").Buffer)},{"base64-js":19,buffer:21,ieee754:30}],22:[function(e,t,r){"use strict";t.exports=e("./").polyfill()},{"./":23}],23:[function(z,r,i){(function(j,V){var e,t;e=this,t=function(){"use strict";function l(e){return"function"==typeof e}var r=Array.isArray?Array.isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)},i=0,t=void 0,n=void 0,a=function(e,t){f[i]=e,f[i+1]=t,2===(i+=2)&&(n?n(d):y())};var e="undefined"!=typeof window?window:void 0,o=e||{},s=o.MutationObserver||o.WebKitMutationObserver,u="undefined"==typeof self&&void 0!==j&&"[object process]"==={}.toString.call(j),h="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel;function c(){var e=setTimeout;return function(){return e(d,1)}}var f=new Array(1e3);function d(){for(var e=0;e":">",'"':""","'":"'","/":"/"};function S(e){return"string"==typeof e?e.replace(/[&<>"'\/]/g,function(e){return w[e]}):e}var M=function(){function i(e){var t,r=1=this.maxReplaces)break}for(a=0;r=this.regexp.exec(e);){if(void 0===(i=h(r[1].trim())))if("function"==typeof c){var d=c(e,r,t);i="string"==typeof d?d:""}else this.logger.warn("missed to pass in variable ".concat(r[1]," for interpolating ").concat(e)),i="";else"string"==typeof i||this.useRawValueToEscape||(i=v(i));if(i=this.escapeValue?u(this.escape(i)):u(i),e=e.replace(r[0],i),this.regexp.lastIndex=0,++a>=this.maxReplaces)break}return e}},{key:"nest",value:function(e,t,r){var i,n,o=D({},2>1,h=-7,c=r?n-1:0,f=r?-1:1,d=e[t+c];for(c+=f,o=d&(1<<-h)-1,d>>=-h,h+=s;0>=-h,h+=i;0>1,f=23===n?Math.pow(2,-24)-Math.pow(2,-77):0,d=i?0:o-1,p=i?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=h):(a=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-a))<1&&(a--,l*=2),2<=(t+=1<=a+c?f/l:f*Math.pow(2,1-c))*l&&(a++,l/=2),h<=a+c?(s=0,a=h):1<=a+c?(s=(t*l-1)*Math.pow(2,n),a+=c):(s=t*Math.pow(2,c-1)*Math.pow(2,n),a=0));8<=n;e[r+d]=255&s,d+=p,s/=256,n-=8);for(a=a<Math.abs(e[0])&&(t=1),Math.abs(e[2])>Math.abs(e[t])&&(t=2),t}var C=4e150;function P(e,t){e.f+=t.f,e.b.f+=t.b.f}function u(e,t,r){return e=e.a,t=t.a,r=r.a,t.b.a===e?r.b.a===e?v(t.a,r.a)?b(r.b.a,t.a,r.a)<=0:0<=b(t.b.a,r.a,t.a):b(r.b.a,e,r.a)<=0:r.b.a===e?0<=b(t.b.a,e,t.a):(t=y(t.b.a,e,t.a),(e=y(r.b.a,e,r.a))<=t)}function L(e){e.a.i=null;var t=e.e;t.a.c=t.c,t.c.a=t.a,e.e=null}function h(e,t){c(e.a),e.c=!1,(e.a=t).i=e}function k(e){for(var t=e.a.a;(e=fe(e)).a.a===t;);return e.c&&(h(e,t=f(ce(e).a.b,e.a.e)),e=fe(e)),e}function R(e,t,r){var i=new he;return i.a=r,i.e=W(e.f,t.e,i),r.i=i}function O(e,t){switch(e.s){case 100130:return 0!=(1&t);case 100131:return 0!==t;case 100132:return 0>1]],s[a[u]])?le(r,u):ue(r,u)),s[o]=null,l[o]=r.b,r.b=o}else for(r.c[-(o+1)]=null;0Math.max(a.a,l.a))return!1;if(v(o,a)){if(0i.f&&(i.f*=2,i.c=oe(i.c,i.f+1)),0===i.b?r=n:(r=i.b,i.b=i.c[i.b]),i.e[r]=t,i.c[r]=n,i.d[n]=r,i.h&&ue(i,n),r}return i=e.a++,e.c[i]=t,-(i+1)}function ie(e){if(0===e.a)return se(e.b);var t=e.c[e.d[e.a-1]];if(0!==e.b.a&&v(ae(e.b),t))return se(e.b);for(;--e.a,0e.a||v(i[a],i[l])){n[r[o]=a]=o;break}n[r[o]=l]=o,o=s}}function ue(e,t){for(var r=e.d,i=e.e,n=e.c,o=t,a=r[o];;){var s=o>>1,l=r[s];if(0==s||v(i[l],i[a])){n[r[o]=a]=o;break}n[r[o]=l]=o,o=s}}function he(){this.e=this.a=null,this.f=0,this.c=this.b=this.h=this.d=!1}function ce(e){return e.e.c.b}function fe(e){return e.e.a.b}(i=q.prototype).x=function(){Y(this,0)},i.B=function(e,t){switch(e){case 100142:return;case 100140:switch(t){case 100130:case 100131:case 100132:case 100133:case 100134:return void(this.s=t)}break;case 100141:return void(this.m=!!t);default:return void Z(this,100900)}Z(this,100901)},i.y=function(e){switch(e){case 100142:return 0;case 100140:return this.s;case 100141:return this.m;default:Z(this,100900)}return!1},i.A=function(e,t,r){this.j[0]=e,this.j[1]=t,this.j[2]=r},i.z=function(e,t){var r=t||null;switch(e){case 100100:case 100106:this.h=r;break;case 100104:case 100110:this.l=r;break;case 100101:case 100107:this.k=r;break;case 100102:case 100108:this.i=r;break;case 100103:case 100109:this.p=r;break;case 100105:case 100111:this.o=r;break;case 100112:this.r=r;break;default:Z(this,100900)}},i.C=function(e,t){var r=!1,i=[0,0,0];Y(this,2);for(var n=0;n<3;++n){var o=e[n];o<-1e150&&(o=-1e150,r=!0),1e150n[u]&&(n[u]=h,a[u]=l)}if(l=0,n[1]-o[1]>n[0]-o[0]&&(l=1),n[2]-o[2]>n[l]-o[l]&&(l=2),o[l]>=n[l])i[0]=0,i[1]=0,i[2]=1;else{for(n=0,o=s[l],a=a[l],s=[0,0,0],o=[o.g[0]-a.g[0],o.g[1]-a.g[1],o.g[2]-a.g[2]],u=[0,0,0],l=r.e;l!==r;l=l.e)u[0]=l.g[0]-a.g[0],u[1]=l.g[1]-a.g[1],u[2]=l.g[2]-a.g[2],s[0]=o[1]*u[2]-o[2]*u[1],s[1]=o[2]*u[0]-o[0]*u[2],s[2]=o[0]*u[1]-o[1]*u[0],n<(h=s[0]*s[0]+s[1]*s[1]+s[2]*s[2])&&(n=h,i[0]=s[0],i[1]=s[1],i[2]=s[2]);n<=0&&(i[0]=i[1]=i[2]=0,i[T(o)]=1)}r=!0}for(s=T(i),l=this.b.c,n=(s+1)%3,a=(s+2)%3,s=0>=l,h-=l,g!=o){if(g==a)break;for(var v=g>8,++y;var _=b;if(i>=8;null!==m&&s<4096&&(p[s++]=m<<8|_,u+1<=s&&l<12&&(++l,u=u<<1|1)),m=g}else s=1+a,u=(1<<(l=n+1))-1,m=null}return f!==i&&console.log("Warning, gif stream shorter than expected."),r}try{r.GifWriter=function(v,e,t,r){var y=0,i=void 0===(r=void 0===r?{}:r).loop?null:r.loop,b=void 0===r.palette?null:r.palette;if(e<=0||t<=0||65535>=1;)++n;if(a=1<>8&255,v[y++]=255&t,v[y++]=t>>8&255,v[y++]=(null!==b?128:0)|n,v[y++]=o,v[y++]=0,null!==b)for(var s=0,l=b.length;s>16&255,v[y++]=u>>8&255,v[y++]=255&u}if(null!==i){if(i<0||65535>8&255,v[y++]=0}var x=!1;this.addFrame=function(e,t,r,i,n,o){if(!0===x&&(--y,x=!1),o=void 0===o?{}:o,e<0||t<0||65535>=1;)++u;l=1<>8&255,v[y++]=d,v[y++]=0),v[y++]=44,v[y++]=255&e,v[y++]=e>>8&255,v[y++]=255&t,v[y++]=t>>8&255,v[y++]=255&r,v[y++]=r>>8&255,v[y++]=255&i,v[y++]=i>>8&255,v[y++]=!0===a?128|u-1:0,!0===a)for(var p=0,m=s.length;p>16&255,v[y++]=g>>8&255,v[y++]=255&g}return y=function(t,r,e,i){t[r++]=e;var n=r++,o=1<>=8,h-=8,r===n+256&&(t[n]=255,n=r++)}function d(e){c|=e<>=8,h-=8,r===n+256&&(t[n]=255,n=r++);4096===l?(d(o),l=1+s,u=e+1,m={}):(1<>7,n=1<<1+(7&r);x[e++],x[e++];var o=null,a=null;i&&(o=e,e+=3*(a=n));var s=!0,l=[],u=0,h=null,c=0,f=null;for(this.width=w,this.height=t;s&&e>2&7,e++;break;case 254:for(;;){if(!(0<=(C=x[e++])))throw Error("Invalid block size");if(0===C)break;e+=C}break;default:throw new Error("Unknown graphic control label: 0x"+x[e-1].toString(16))}break;case 44:var p=x[e++]|x[e++]<<8,m=x[e++]|x[e++]<<8,g=x[e++]|x[e++]<<8,v=x[e++]|x[e++]<<8,y=x[e++],b=y>>6&1,_=1<<1+(7&y),S=o,M=a,E=!1;if(y>>7){E=!0;S=e,e+=3*(M=_)}var T=e;for(e++;;){var C;if(!(0<=(C=x[e++])))throw Error("Invalid block size");if(0===C)break;e+=C}l.push({x:p,y:m,width:g,height:v,has_local_palette:E,palette_offset:S,palette_size:M,data_offset:T,data_length:e-T,transparent_index:h,interlaced:!!b,delay:u,disposal:c});break;case 59:s=!1;break;default:throw new Error("Unknown gif block: 0x"+x[e-1].toString(16))}this.numFrames=function(){return l.length},this.loopCount=function(){return f},this.frameInfo=function(e){if(e<0||e>=l.length)throw new Error("Frame index out of range.");return l[e]},this.decodeAndBlitFrameBGRA=function(e,t){var r=this.frameInfo(e),i=r.width*r.height,n=new Uint8Array(i);P(x,r.data_offset,n,i);var o=r.palette_offset,a=r.transparent_index;null===a&&(a=256);var s=r.width,l=w-s,u=s,h=4*(r.y*w+r.x),c=4*((r.y+r.height)*w+r.x),f=h,d=4*l;!0===r.interlaced&&(d+=4*w*7);for(var p=8,m=0,g=n.length;m>=1)),v===a)f+=4;else{var y=x[o+3*v],b=x[o+3*v+1],_=x[o+3*v+2];t[f++]=_,t[f++]=b,t[f++]=y,t[f++]=255}--u}},this.decodeAndBlitFrameRGBA=function(e,t){var r=this.frameInfo(e),i=r.width*r.height,n=new Uint8Array(i);P(x,r.data_offset,n,i);var o=r.palette_offset,a=r.transparent_index;null===a&&(a=256);var s=r.width,l=w-s,u=s,h=4*(r.y*w+r.x),c=4*((r.y+r.height)*w+r.x),f=h,d=4*l;!0===r.interlaced&&(d+=4*w*7);for(var p=8,m=0,g=n.length;m>=1)),v===a)f+=4;else{var y=x[o+3*v],b=x[o+3*v+1],_=x[o+3*v+2];t[f++]=y,t[f++]=b,t[f++]=_,t[f++]=255}--u}}}}catch(e){}},{}],33:[function(jr,t,r){(function(Gr){var e;e=this,function(E){"use strict";function e(e){if(null==this)throw TypeError();var t=String(this),r=t.length,i=e?Number(e):0;if(i!=i&&(i=0),!(i<0||r<=i)){var n,o=t.charCodeAt(i);return 55296<=o&&o<=56319&&i+1>>=1,t}function _(e,t,r){if(!t)return r;for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>16-t;return e.tag>>>=t,e.bitcount-=t,i+r}function x(e,t){for(;e.bitcount<24;)e.tag|=e.source[e.sourceIndex++]<>>=1,++n,r+=t.table[n],0<=(i-=t.table[n]););return e.tag=o,e.bitcount-=n,t.trans[r+i]}function w(e,t,r){var i,n,o,a,s,l;for(i=_(e,5,257),n=_(e,5,1),o=_(e,4,4),a=0;a<19;++a)g[a]=0;for(a=0;athis.x2&&(this.x2=e)),"number"==typeof t&&((isNaN(this.y1)||isNaN(this.y2))&&(this.y1=t,this.y2=t),tthis.y2&&(this.y2=t))},C.prototype.addX=function(e){this.addPoint(e,null)},C.prototype.addY=function(e){this.addPoint(null,e)},C.prototype.addBezier=function(e,t,r,i,n,o,a,s){var l=this,u=[e,t],h=[r,i],c=[n,o],f=[a,s];this.addPoint(e,t),this.addPoint(a,s);for(var d=0;d<=1;d++){var p=6*u[d]-12*h[d]+6*c[d],m=-3*u[d]+9*h[d]-9*c[d]+3*f[d],g=3*h[d]-3*u[d];if(0!=m){var v=Math.pow(p,2)-4*g*m;if(!(v<0)){var y=(-p+Math.sqrt(v))/(2*m);0>8&255,255&e]},A.USHORT=U(2),D.SHORT=function(e){return 32768<=e&&(e=-(65536-e)),[e>>8&255,255&e]},A.SHORT=U(2),D.UINT24=function(e){return[e>>16&255,e>>8&255,255&e]},A.UINT24=U(3),D.ULONG=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]},A.ULONG=U(4),D.LONG=function(e){return R<=e&&(e=-(2*R-e)),[e>>24&255,e>>16&255,e>>8&255,255&e]},A.LONG=U(4),D.FIXED=D.ULONG,A.FIXED=A.ULONG,D.FWORD=D.SHORT,A.FWORD=A.SHORT,D.UFWORD=D.USHORT,A.UFWORD=A.USHORT,D.LONGDATETIME=function(e){return[0,0,0,0,e>>24&255,e>>16&255,e>>8&255,255&e]},A.LONGDATETIME=U(8),D.TAG=function(e){return k.argument(4===e.length,"Tag should be exactly 4 ASCII characters."),[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]},A.TAG=U(4),D.Card8=D.BYTE,A.Card8=A.BYTE,D.Card16=D.USHORT,A.Card16=A.USHORT,D.OffSize=D.BYTE,A.OffSize=A.BYTE,D.SID=D.USHORT,A.SID=A.USHORT,D.NUMBER=function(e){return-107<=e&&e<=107?[e+139]:108<=e&&e<=1131?[247+((e-=108)>>8),255&e]:-1131<=e&&e<=-108?[251+((e=-e-108)>>8),255&e]:-32768<=e&&e<=32767?D.NUMBER16(e):D.NUMBER32(e)},A.NUMBER=function(e){return D.NUMBER(e).length},D.NUMBER16=function(e){return[28,e>>8&255,255&e]},A.NUMBER16=U(3),D.NUMBER32=function(e){return[29,e>>24&255,e>>16&255,e>>8&255,255&e]},A.NUMBER32=U(5),D.REAL=function(e){var t=e.toString(),r=/\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(t);if(r){var i=parseFloat("1e"+((r[2]?+r[2]:0)+r[1].length));t=(Math.round(e*i)/i).toString()}for(var n="",o=0,a=t.length;o>8&255,t[t.length]=255&i}return t},A.UTF16=function(e){return 2*e.length};var N={"x-mac-croatian":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ","x-mac-cyrillic":"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњјЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю","x-mac-gaelic":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæøṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ","x-mac-greek":"Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩάΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ­","x-mac-icelandic":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-inuit":"ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł","x-mac-ce":"ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ",macintosh:"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-romanian":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ","x-mac-turkish":"ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ"};O.MACSTRING=function(e,t,r,i){var n=N[i];if(void 0!==n){for(var o="",a=0;a>8&255,l+256&255)}return o}D.MACSTRING=function(e,t){var r=function(e){if(!F)for(var t in F={},N)F[t]=new String(t);var r=F[e];if(void 0!==r){if(B){var i=B.get(r);if(void 0!==i)return i}var n=N[e];if(void 0!==n){for(var o={},a=0;a>8,t[c+1]=255&f,t=t.concat(i[h])}return t},A.TABLE=function(e){for(var t=0,r=e.fields.length,i=0;i>1,t.skip("uShort",3),e.glyphIndexMap={};for(var a=new se.Parser(r,i+n+14),s=new se.Parser(r,i+n+16+2*o),l=new se.Parser(r,i+n+16+4*o),u=new se.Parser(r,i+n+16+6*o),h=i+n+16+8*o,c=0;c>4,o=15&i;if(15==n)break;if(t+=r[n],15==o)break;t+=r[o]}return parseFloat(t)}(e);if(32<=t&&t<=246)return t-139;if(247<=t&&t<=250)return 256*(t-247)+e.parseByte()+108;if(251<=t&&t<=254)return 256*-(t-251)-e.parseByte()-108;throw new Error("Invalid b0 "+t)}function Te(e,t,r){t=void 0!==t?t:0;var i=new se.Parser(e,t),n=[],o=[];for(r=void 0!==r?r:e.length;i.relativeOffset>1,T.length=0,P=!0}return function e(t){for(var r,i,n,o,a,s,l,u,h,c,f,d,p=0;pMath.abs(d-R)?k=f+T.shift():R=d+T.shift(),E.curveTo(y,b,_,x,l,u),E.curveTo(h,c,f,d,k,R);break;default:console.log("Glyph "+v.index+": unknown operator 1200"+m),T.length=0}break;case 14:0>3;break;case 21:2>16),p+=2;break;case 29:a=T.pop()+g.gsubrsBias,(s=g.gsubrs[a])&&e(s);break;case 30:for(;0=r.begin&&e=fe.length){var a=i.parseChar();r.names.push(i.parseString(a))}break;case 2.5:r.numberOfGlyphs=i.parseUShort(),r.offset=new Array(r.numberOfGlyphs);for(var s=0;st.value.tag?1:-1}),t.fields=t.fields.concat(i),t.fields=t.fields.concat(n),t}function gt(e,t,r){for(var i=0;i 123 are reserved for internal usage");d|=1<>>1,o=e[n].tag;if(o===t)return n;o>>1,o=e[n];if(o===t)return n;o>>1,a=(r=e[o]).start;if(a===t)return r;a(r=e[i-1]).end?0:r}function xt(e,t){this.font=e,this.tableName=t}function wt(e){xt.call(this,e,"gpos")}function St(e){xt.call(this,e,"gsub")}function Mt(e,t){var r=e.length;if(r!==t.length)return!1;for(var i=0;it.points.length-1||i.matchedPoints[1]>n.points.length-1)throw Error("Matched points out of range in "+t.name);var a=t.points[i.matchedPoints[0]],s=n.points[i.matchedPoints[1]],l={xScale:i.xScale,scale01:i.scale01,scale10:i.scale10,yScale:i.yScale,dx:0,dy:0};s=kt([s],l)[0],l.dx=a.x-s.x,l.dy=a.y-s.y,o=kt(n.points,l)}t.points=t.points.concat(o)}}return Rt(t.points)}(wt.prototype=xt.prototype={searchTag:yt,binSearch:bt,getTable:function(e){var t=this.font.tables[this.tableName];return!t&&e&&(t=this.font.tables[this.tableName]=this.createDefaultTable()),t},getScriptNames:function(){var e=this.getTable();return e?e.scripts.map(function(e){return e.tag}):[]},getDefaultScriptName:function(){var e=this.getTable();if(e){for(var t=!1,r=0;r=s[u-1].tag,"Features must be added in alphabetical order."),o={tag:r,feature:{params:0,lookupListIndexes:[]}},s.push(o),a.push(u),o.feature}}},getLookupTables:function(e,t,r,i,n){var o=this.getFeatureTable(e,t,r,n),a=[];if(o){for(var s,l=o.lookupListIndexes,u=this.font.tables[this.tableName].lookups,h=0;h",s),t.stack.push(Math.round(64*s))}function vr(e,t){var r=t.stack,i=r.pop(),n=t.fv,o=t.pv,a=t.ppem,s=t.deltaBase+16*(e-1),l=t.deltaShift,u=t.z0;E.DEBUG&&console.log(t.step,"DELTAP["+e+"]",i,r);for(var h=0;h>4)===a){var d=(15&f)-8;0<=d&&d++,E.DEBUG&&console.log(t.step,"DELTAPFIX",c,"by",d*l);var p=u[c];n.setRelative(p,p,d*l,o)}}}function yr(e,t){var r=t.stack,i=r.pop();E.DEBUG&&console.log(t.step,"ROUND[]"),r.push(64*t.round(i/64))}function br(e,t){var r=t.stack,i=r.pop(),n=t.ppem,o=t.deltaBase+16*(e-1),a=t.deltaShift;E.DEBUG&&console.log(t.step,"DELTAC["+e+"]",i,r);for(var s=0;s>4)===n){var h=(15&u)-8;0<=h&&h++;var c=h*a;E.DEBUG&&console.log(t.step,"DELTACFIX",l,"by",c),t.cvt[l]+=c}}}function _r(e,t){var r,i,n=t.stack,o=n.pop(),a=n.pop(),s=t.z2[o],l=t.z1[a];E.DEBUG&&console.log(t.step,"SDPVTL["+e+"]",o,a),i=e?(r=s.y-l.y,l.x-s.x):(r=l.x-s.x,l.y-s.y),t.dpv=Zt(r,i)}function xr(e,t){var r=t.stack,i=t.prog,n=t.ip;E.DEBUG&&console.log(t.step,"PUSHB["+e+"]");for(var o=0;o":"_")+(i?"R":"_")+(0===n?"Gr":1===n?"Bl":2===n?"Wh":"")+"]",e?c+"("+o.cvt[c]+","+u+")":"",f,"(d =",a,"->",l*s,")"),o.rp1=o.rp0,o.rp2=f,t&&(o.rp0=f)}Ft.prototype.exec=function(e,t){if("number"!=typeof t)throw new Error("Point size is not a number!");if(!(2",i),s.interpolate(c,o,a,l),s.touch(c)}e.loop=1},dr.bind(void 0,0),dr.bind(void 0,1),function(e){for(var t=e.stack,r=e.rp0,i=e.z0[r],n=e.loop,o=e.fv,a=e.pv,s=e.z1;n--;){var l=t.pop(),u=s[l];E.DEBUG&&console.log(e.step,(1=a.width||t>=a.height?[0,0,0,0]:this._getPixel(e,t);var s=new l.default.Image(r,i);return s.canvas.getContext("2d").drawImage(a,e,t,r*o,i*o,0,0,r,i),s},l.default.Renderer.prototype.textLeading=function(e){return"number"==typeof e?(this._setProperty("_textLeading",e),this._pInst):this._textLeading},l.default.Renderer.prototype.textSize=function(e){return"number"==typeof e?(this._setProperty("_textSize",e),this._setProperty("_textLeading",e*y._DEFAULT_LEADMULT),this._applyTextProperties()):this._textSize},l.default.Renderer.prototype.textStyle=function(e){return e?(e!==y.NORMAL&&e!==y.ITALIC&&e!==y.BOLD&&e!==y.BOLDITALIC||this._setProperty("_textStyle",e),this._applyTextProperties()):this._textStyle},l.default.Renderer.prototype.textAscent=function(){return null===this._textAscent&&this._updateTextMetrics(),this._textAscent},l.default.Renderer.prototype.textDescent=function(){return null===this._textDescent&&this._updateTextMetrics(),this._textDescent},l.default.Renderer.prototype.textAlign=function(e,t){return void 0!==e?(this._setProperty("_textAlign",e),void 0!==t&&this._setProperty("_textBaseline",t),this._applyTextProperties()):{horizontal:this._textAlign,vertical:this._textBaseline}},l.default.Renderer.prototype.text=function(e,t,r,i,n){var o,a,s,l,u,h,c,f,d=this._pInst,p=Number.MAX_VALUE;if((this._doFill||this._doStroke)&&void 0!==e){if("string"!=typeof e&&(e=e.toString()),o=(e=e.replace(/(\t)/g," ")).split("\n"),void 0!==i){for(s=f=0;ss.HALF_PI&&e<=3*s.HALF_PI?Math.atan(r/i*Math.tan(e))+s.PI:Math.atan(r/i*Math.tan(e))+s.TWO_PI,t=t<=s.HALF_PI?Math.atan(r/i*Math.tan(t)):t>s.HALF_PI&&t<=3*s.HALF_PI?Math.atan(r/i*Math.tan(t))+s.PI:Math.atan(r/i*Math.tan(t))+s.TWO_PI),tv||Math.abs(this.accelerationY-this.pAccelerationY)>v||Math.abs(this.accelerationZ-this.pAccelerationZ)>v)&&e();var t=this.deviceTurned||window.deviceTurned;if("function"==typeof t){var r=this.rotationX+180,i=this.pRotationX+180,n=c+180;0>>24],i+=x[(16711680&C)>>16],n+=x[(65280&C)>>8],o+=x[255&C],r+=k[_],s++}w[l=T+y]=a/r,S[l]=i/r,M[l]=n/r,E[l]=o/r}T+=d}for(h=(u=-P)*d,b=T=0;b>>16,e[r+1]=(65280&t[i])>>>8,e[r+2]=255&t[i],e[r+3]=(4278190080&t[i])>>>24},O._toImageData=function(e){return e instanceof ImageData?e:e.getContext("2d").getImageData(0,0,e.width,e.height)},O._createImageData=function(e,t){return O._tmpCanvas=document.createElement("canvas"),O._tmpCtx=O._tmpCanvas.getContext("2d"),this._tmpCtx.createImageData(e,t)},O.apply=function(e,t,r){var i=e.getContext("2d"),n=i.getImageData(0,0,e.width,e.height),o=t(n,r);o instanceof ImageData?i.putImageData(o,0,0,0,0,e.width,e.height):i.putImageData(n,0,0,0,0,e.width,e.height)},O.threshold=function(e,t){var r=O._toPixels(e);void 0===t&&(t=.5);for(var i=Math.floor(255*t),n=0;n>8)/i,r[n+1]=255*(a*t>>8)/i,r[n+2]=255*(s*t>>8)/i}},O.dilate=function(e){for(var t,r,i,n,o,a,s,l,u,h,c,f,d,p,m,g,v,y=O._toPixels(e),b=0,_=y.length?y.length/4:0,x=new Int32Array(_);b<_;)for(r=(t=b)+e.width;b>16&255)+151*(i>>8&255)+28*(255&i))<(m=77*(c>>16&255)+151*(c>>8&255)+28*(255&c))&&(n=c,o=m),o<(p=77*((h=O._getARGB(y,a))>>16&255)+151*(h>>8&255)+28*(255&h))&&(n=h,o=p),o<(g=77*(f>>16&255)+151*(f>>8&255)+28*(255&f))&&(n=f,o=g),o<(v=77*(d>>16&255)+151*(d>>8&255)+28*(255&d))&&(n=d,o=v),x[b++]=n;O._setPixels(y,x)},O.erode=function(e){for(var t,r,i,n,o,a,s,l,u,h,c,f,d,p,m,g,v,y=O._toPixels(e),b=0,_=y.length?y.length/4:0,x=new Int32Array(_);b<_;)for(r=(t=b)+e.width;b>16&255)+151*(c>>8&255)+28*(255&c))<(o=77*(i>>16&255)+151*(i>>8&255)+28*(255&i))&&(n=c,o=m),(p=77*((h=O._getARGB(y,a))>>16&255)+151*(h>>8&255)+28*(255&h))>16&255)+151*(f>>8&255)+28*(255&f))>16&255)+151*(d>>8&255)+28*(255&d))=r){var i=Math.floor(t.timeDisplayed/r);if(t.timeDisplayed=0,t.displayIndex+=i,t.loopCount=Math.floor(t.displayIndex/t.numFrames),null!==t.loopLimit&&t.loopCount>=t.loopLimit)t.playing=!1;else{var n=t.displayIndex%t.numFrames;this.drawingContext.putImageData(t.frames[n].image,0,0),t.displayIndex=n,this.setModified(!0)}}}},n.default.Image.prototype._setProperty=function(e,t){this[e]=t,this.setModified(!0)},n.default.Image.prototype.loadPixels=function(){n.default.Renderer2D.prototype.loadPixels.call(this),this.setModified(!0)},n.default.Image.prototype.updatePixels=function(e,t,r,i){n.default.Renderer2D.prototype.updatePixels.call(this,e,t,r,i),this.setModified(!0)},n.default.Image.prototype.get=function(e,t,r,i){return n.default._validateParameters("p5.Image.get",arguments),n.default.Renderer2D.prototype.get.apply(this,arguments)},n.default.Image.prototype._getPixel=n.default.Renderer2D.prototype._getPixel,n.default.Image.prototype.set=function(e,t,r){n.default.Renderer2D.prototype.set.call(this,e,t,r),this.setModified(!0)},n.default.Image.prototype.resize=function(e,t){0===e&&0===t?(e=this.canvas.width,t=this.canvas.height):0===e?e=this.canvas.width*t/this.canvas.height:0===t&&(t=this.canvas.height*e/this.canvas.width),e=Math.floor(e),t=Math.floor(t);var r=document.createElement("canvas");if(r.width=e,r.height=t,this.gifProperties)for(var i=this.gifProperties,n=function(e,t){for(var r=0,i=0;i/g,">").replace(/"/g,""").replace(/'/g,"'")}function l(e,t){t&&!0!==t&&"true"!==t||(t="");var r="";return(e=e||"untitled")&&e.includes(".")&&(r=e.split(".").pop()),t&&r!==t&&(r=t,e="".concat(e,".").concat(r)),[e,r]}e("../core/error_helpers"),v.default.prototype.loadJSON=function(){for(var e=arguments.length,t=new Array(e),r=0;r"),n.print("");if(n.print(' '),n.print(""),n.print(""),n.print(" "),"0"!==o[0]){n.print(" ");for(var h=0;h".concat(c)),n.print(" ")}n.print(" ")}for(var f=0;f");for(var d=0;d".concat(p)),n.print(" ")}n.print(" ")}n.print("
"),n.print(""),n.print("")}n.close(),n.clear()},v.default.prototype.writeFile=function(e,t,r){var i="application/octet-stream";v.default.prototype._isSafari()&&(i="text/plain");var n=new Blob(e,{type:i});v.default.prototype.downloadFile(n,t,r)},v.default.prototype.downloadFile=function(e,t,r){var i=l(t,r),n=i[0];if(e instanceof Blob)s.default.saveAs(e,n);else{var o=document.createElement("a");if(o.href=e,o.download=n,o.onclick=function(e){var t;t=e,document.body.removeChild(t.target),e.stopPropagation()},o.style.display="none",document.body.appendChild(o),v.default.prototype._isSafari()){var a="Hello, Safari user! To download this file...\n";a+="1. Go to File --\x3e Save As.\n",a+='2. Choose "Page Source" as the Format.\n',a+='3. Name it with this extension: ."'.concat(i[1],'"'),alert(a)}o.click()}},v.default.prototype._checkFileExtension=l,v.default.prototype._isSafari=function(){return 0>>0},getSeed:function(){return t},rand:function(){return(r=(1664525*r+1013904223)%i)/i}});n.setSeed(e),_=new Array(4096);for(var o=0;o<4096;o++)_[o]=n.rand()};var o=n.default;r.default=o},{"../core/main":49}],82:[function(e,t,r){"use strict";function a(e){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var i,l=(i=e("../core/main"))&&i.__esModule?i:{default:i},o=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!==a(e)&&"function"!=typeof e)return{default:e};var t=s();if(t&&t.has(e))return t.get(e);var r={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var o=i?Object.getOwnPropertyDescriptor(e,n):null;o&&(o.get||o.set)?Object.defineProperty(r,n,o):r[n]=e[n]}r.default=e,t&&t.set(e,r);return r}(e("../core/constants"));function s(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return s=function(){return e},e}l.default.Vector=function(e,t,r){var i,n,o;o=e instanceof l.default?(this.p5=e,i=t[0]||0,n=t[1]||0,t[2]||0):(i=e||0,n=t||0,r||0),this.x=i,this.y=n,this.z=o},l.default.Vector.prototype.toString=function(){return"p5.Vector Object : [".concat(this.x,", ").concat(this.y,", ").concat(this.z,"]")},l.default.Vector.prototype.set=function(e,t,r){return e instanceof l.default.Vector?(this.x=e.x||0,this.y=e.y||0,this.z=e.z||0):e instanceof Array?(this.x=e[0]||0,this.y=e[1]||0,this.z=e[2]||0):(this.x=e||0,this.y=t||0,this.z=r||0),this},l.default.Vector.prototype.copy=function(){return this.p5?new l.default.Vector(this.p5,[this.x,this.y,this.z]):new l.default.Vector(this.x,this.y,this.z)},l.default.Vector.prototype.add=function(e,t,r){return e instanceof l.default.Vector?(this.x+=e.x||0,this.y+=e.y||0,this.z+=e.z||0):e instanceof Array?(this.x+=e[0]||0,this.y+=e[1]||0,this.z+=e[2]||0):(this.x+=e||0,this.y+=t||0,this.z+=r||0),this};function u(e,t){return 0!==e&&(this.x=this.x%e),0!==t&&(this.y=this.y%t),this}function h(e,t,r){return 0!==e&&(this.x=this.x%e),0!==t&&(this.y=this.y%t),0!==r&&(this.z=this.z%r),this}l.default.Vector.prototype.rem=function(e,t,r){if(e instanceof l.default.Vector){if(Number.isFinite(e.x)&&Number.isFinite(e.y)&&Number.isFinite(e.z)){var i=parseFloat(e.x),n=parseFloat(e.y),o=parseFloat(e.z);h.call(this,i,n,o)}}else if(e instanceof Array)e.every(function(e){return Number.isFinite(e)})&&(2===e.length&&u.call(this,e[0],e[1]),3===e.length&&h.call(this,e[0],e[1],e[2]));else if(1===arguments.length){if(Number.isFinite(e)&&0!==e)return this.x=this.x%e,this.y=this.y%e,this.z=this.z%e,this}else if(2===arguments.length){var a=Array.prototype.slice.call(arguments);a.every(function(e){return Number.isFinite(e)})&&2===a.length&&u.call(this,a[0],a[1])}else if(3===arguments.length){var s=Array.prototype.slice.call(arguments);s.every(function(e){return Number.isFinite(e)})&&3===s.length&&h.call(this,s[0],s[1],s[2])}},l.default.Vector.prototype.sub=function(e,t,r){return e instanceof l.default.Vector?(this.x-=e.x||0,this.y-=e.y||0,this.z-=e.z||0):e instanceof Array?(this.x-=e[0]||0,this.y-=e[1]||0,this.z-=e[2]||0):(this.x-=e||0,this.y-=t||0,this.z-=r||0),this},l.default.Vector.prototype.mult=function(e){return"number"==typeof e&&isFinite(e)?(this.x*=e,this.y*=e,this.z*=e):console.warn("p5.Vector.prototype.mult:","n is undefined or not a finite number"),this},l.default.Vector.prototype.div=function(e){return"number"==typeof e&&isFinite(e)?0===e?console.warn("p5.Vector.prototype.div:","divide by 0"):(this.x/=e,this.y/=e,this.z/=e):console.warn("p5.Vector.prototype.div:","n is undefined or not a finite number"),this},l.default.Vector.prototype.mag=function(){return Math.sqrt(this.magSq())},l.default.Vector.prototype.magSq=function(){var e=this.x,t=this.y,r=this.z;return e*e+t*t+r*r},l.default.Vector.prototype.dot=function(e,t,r){return e instanceof l.default.Vector?this.dot(e.x,e.y,e.z):this.x*(e||0)+this.y*(t||0)+this.z*(r||0)},l.default.Vector.prototype.cross=function(e){var t=this.y*e.z-this.z*e.y,r=this.z*e.x-this.x*e.z,i=this.x*e.y-this.y*e.x;return this.p5?new l.default.Vector(this.p5,[t,r,i]):new l.default.Vector(t,r,i)},l.default.Vector.prototype.dist=function(e){return e.copy().sub(this).mag()},l.default.Vector.prototype.normalize=function(){var e=this.mag();return 0!==e&&this.mult(1/e),this},l.default.Vector.prototype.limit=function(e){var t=this.magSq();return e*e>>0},n.default.prototype.randomSeed=function(e){this._lcgSetSeed(o,e),this._gaussian_previous=!1},n.default.prototype.random=function(e,t){var r;if(n.default._validateParameters("random",arguments),r=null!=this[o]?this._lcg(o):Math.random(),void 0===e)return r;if(void 0===t)return e instanceof Array?e[Math.floor(r*e.length)]:r*e;if(tf){var P=p,L=l,k=u;p=d+f*(s&&d=t&&(r=r.substring(r.length-t,r.length)),r}},n.default.prototype.unhex=function(e){return e instanceof Array?e.map(n.default.prototype.unhex):parseInt("0x".concat(e),16)};var o=n.default;r.default=o},{"../core/main":49}],90:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var i,a=(i=e("../core/main"))&&i.__esModule?i:{default:i};function n(e,t,r){var i=e<0,n=i?e.toString().substring(1):e.toString(),o=n.indexOf("."),a=-1!==o?n.substring(0,o):n,s=-1!==o?n.substring(o+1):"",l=i?"-":"";if(void 0!==r){var u="";(-1!==o||0r&&(s=s.substring(0,r));for(var h=0;hi.length)for(var o=t-(i+=-1===r?".":"").length+1,a=0;a=d.TWO_PI?"".concat("ellipse","|").concat(u,"|"):"".concat("arc","|").concat(a,"|").concat(s,"|").concat(l,"|").concat(u,"|"),!this.geometryInHash(t)){var h=new T.default.Geometry(u,1,function(){if(this.strokeIndices=[],a.toFixed(10)!==s.toFixed(10)){l!==d.PIE&&void 0!==l||(this.vertices.push(new T.default.Vector(.5,.5,0)),this.uvs.push([.5,.5]));for(var e=0;e<=u;e++){var t=(s-a)*(e/u)+a,r=.5+Math.cos(t)/2,i=.5+Math.sin(t)/2;this.vertices.push(new T.default.Vector(r,i,0)),this.uvs.push([r,i]),e>5&31)/31,(y>>10&31)/31):(r=a,i=s,l)}for(var b=1;b<=3;b++){var _=p+12*b,x=new S.default.Vector(u.getFloat32(_,!0),u.getFloat32(8+_,!0),u.getFloat32(4+_,!0));e.vertices.push(x),c&&o.push(r,i,n)}var w=new S.default.Vector(m,g,v);e.vertexNormals.push(w,w,w),e.faces.push([3*d,3*d+1,3*d+2]),e.uvs.push([0,0],[0,0],[0,0])}}(e,t);else{var r=new DataView(t);if(!("TextDecoder"in window))return console.warn("Sorry, ASCII STL loading only works in browsers that support TextDecoder (https://caniuse.com/#feat=textencoder)");var i=new TextDecoder("utf-8").decode(r).split("\n");!function(e,t){for(var r,i,n="",o=[],a=0;aMath.PI?l=Math.PI:l<=0&&(l=.001);var u=Math.sin(l)*a*Math.sin(s),h=Math.cos(l)*a,c=Math.sin(l)*a*Math.cos(s);this.camera(u+this.centerX,h+this.centerY,c+this.centerZ,this.centerX,this.centerY,this.centerZ,0,1,0)},m.default.Camera.prototype._isActive=function(){return this===this._renderer._curCamera},m.default.prototype.setCamera=function(e){this._renderer._curCamera=e,this._renderer.uPMatrix.set(e.projMatrix.mat4[0],e.projMatrix.mat4[1],e.projMatrix.mat4[2],e.projMatrix.mat4[3],e.projMatrix.mat4[4],e.projMatrix.mat4[5],e.projMatrix.mat4[6],e.projMatrix.mat4[7],e.projMatrix.mat4[8],e.projMatrix.mat4[9],e.projMatrix.mat4[10],e.projMatrix.mat4[11],e.projMatrix.mat4[12],e.projMatrix.mat4[13],e.projMatrix.mat4[14],e.projMatrix.mat4[15])};var n=m.default.Camera;r.default=n},{"../core/main":49}],98:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var i,h=(i=e("../core/main"))&&i.__esModule?i:{default:i};h.default.Geometry=function(e,t,r){return this.vertices=[],this.lineVertices=[],this.lineNormals=[],this.vertexNormals=[],this.faces=[],this.uvs=[],this.edges=[],this.vertexColors=[],this.detailX=void 0!==e?e:1,this.detailY=void 0!==t?t:1,this.dirtyFlags={},r instanceof Function&&r.call(this),this},h.default.Geometry.prototype.reset=function(){this.lineVertices.length=0,this.lineNormals.length=0,this.vertices.length=0,this.edges.length=0,this.vertexColors.length=0,this.vertexNormals.length=0,this.uvs.length=0,this.dirtyFlags={}},h.default.Geometry.prototype.computeFaces=function(){this.faces.length=0;for(var e,t,r,i,n=this.detailX+1,o=0;othis.vertices.length-1-this.detailX;i--)e.add(this.vertexNormals[i]);e=h.default.Vector.div(e,this.detailX);for(var n=this.vertices.length-1;n>this.vertices.length-1-this.detailX;n--)this.vertexNormals[n]=e;return this},h.default.Geometry.prototype._makeTriangleEdges=function(){if(this.edges.length=0,Array.isArray(this.strokeIndices))for(var e=0,t=this.strokeIndices.length;e vTexCoord.y;\n bool y1 = p1.y > vTexCoord.y;\n bool y2 = p2.y > vTexCoord.y;\n\n // could web be under the curve (after t1)?\n if (y1 ? !y2 : y0) {\n // add the coverage for t1\n coverage.x += saturate(C1.x + 0.5);\n // calculate the anti-aliasing for t1\n weight.x = min(weight.x, abs(C1.x));\n }\n\n // are we outside the curve (after t2)?\n if (y1 ? !y0 : y2) {\n // subtract the coverage for t2\n coverage.x -= saturate(C2.x + 0.5);\n // calculate the anti-aliasing for t2\n weight.x = min(weight.x, abs(C2.x));\n }\n}\n\n// this is essentially the same as coverageX, but with the axes swapped\nvoid coverageY(vec2 p0, vec2 p1, vec2 p2) {\n\n vec2 C1, C2;\n calulateCrossings(p0, p1, p2, C1, C2);\n\n bool x0 = p0.x > vTexCoord.x;\n bool x1 = p1.x > vTexCoord.x;\n bool x2 = p2.x > vTexCoord.x;\n\n if (x1 ? !x2 : x0) {\n coverage.y -= saturate(C1.y + 0.5);\n weight.y = min(weight.y, abs(C1.y));\n }\n\n if (x1 ? !x0 : x2) {\n coverage.y += saturate(C2.y + 0.5);\n weight.y = min(weight.y, abs(C2.y));\n }\n}\n\nvoid main() {\n\n // calculate the pixel scale based on screen-coordinates\n pixelScale = hardness / fwidth(vTexCoord);\n\n // which grid cell is this pixel in?\n ivec2 gridCoord = ifloor(vTexCoord * vec2(uGridSize));\n\n // intersect curves in this row\n {\n // the index into the row info bitmap\n int rowIndex = gridCoord.y + uGridOffset.y;\n // fetch the info texel\n vec4 rowInfo = getTexel(uSamplerRows, rowIndex, uGridImageSize);\n // unpack the rowInfo\n int rowStrokeIndex = getInt16(rowInfo.xy);\n int rowStrokeCount = getInt16(rowInfo.zw);\n\n for (int iRowStroke = INT(0); iRowStroke < N; iRowStroke++) {\n if (iRowStroke >= rowStrokeCount)\n break;\n\n // each stroke is made up of 3 points: the start and control point\n // and the start of the next curve.\n // fetch the indices of this pair of strokes:\n vec4 strokeIndices = getTexel(uSamplerRowStrokes, rowStrokeIndex++, uCellsImageSize);\n\n // unpack the stroke index\n int strokePos = getInt16(strokeIndices.xy);\n\n // fetch the two strokes\n vec4 stroke0 = getTexel(uSamplerStrokes, strokePos + INT(0), uStrokeImageSize);\n vec4 stroke1 = getTexel(uSamplerStrokes, strokePos + INT(1), uStrokeImageSize);\n\n // calculate the coverage\n coverageX(stroke0.xy, stroke0.zw, stroke1.xy);\n }\n }\n\n // intersect curves in this column\n {\n int colIndex = gridCoord.x + uGridOffset.x;\n vec4 colInfo = getTexel(uSamplerCols, colIndex, uGridImageSize);\n int colStrokeIndex = getInt16(colInfo.xy);\n int colStrokeCount = getInt16(colInfo.zw);\n \n for (int iColStroke = INT(0); iColStroke < N; iColStroke++) {\n if (iColStroke >= colStrokeCount)\n break;\n\n vec4 strokeIndices = getTexel(uSamplerColStrokes, colStrokeIndex++, uCellsImageSize);\n\n int strokePos = getInt16(strokeIndices.xy);\n vec4 stroke0 = getTexel(uSamplerStrokes, strokePos + INT(0), uStrokeImageSize);\n vec4 stroke1 = getTexel(uSamplerStrokes, strokePos + INT(1), uStrokeImageSize);\n coverageY(stroke0.xy, stroke0.zw, stroke1.xy);\n }\n }\n\n weight = saturate(1.0 - weight * 2.0);\n float distance = max(weight.x + weight.y, minDistance); // manhattan approx.\n float antialias = abs(dot(coverage, weight) / distance);\n float cover = min(abs(coverage.x), abs(coverage.y));\n gl_FragColor = uMaterialColor;\n gl_FragColor.a *= saturate(max(antialias, cover));\n}",lineVert:"/*\n Part of the Processing project - http://processing.org\n Copyright (c) 2012-15 The Processing Foundation\n Copyright (c) 2004-12 Ben Fry and Casey Reas\n Copyright (c) 2001-04 Massachusetts Institute of Technology\n This library is free software; you can redistribute it and/or\n modify it under the terms of the GNU Lesser General Public\n License as published by the Free Software Foundation, version 2.1.\n This library is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n Lesser General Public License for more details.\n You should have received a copy of the GNU Lesser General\n Public License along with this library; if not, write to the\n Free Software Foundation, Inc., 59 Temple Place, Suite 330,\n Boston, MA 02111-1307 USA\n*/\n\n#define PROCESSING_LINE_SHADER\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform float uStrokeWeight;\n\nuniform vec4 uViewport;\nuniform int uPerspective;\n\nattribute vec4 aPosition;\nattribute vec4 aDirection;\n \nvoid main() {\n // using a scale <1 moves the lines towards the camera\n // in order to prevent popping effects due to half of\n // the line disappearing behind the geometry faces.\n vec3 scale = vec3(0.9995);\n\n vec4 posp = uModelViewMatrix * aPosition;\n vec4 posq = uModelViewMatrix * (aPosition + vec4(aDirection.xyz, 0));\n\n // Moving vertices slightly toward the camera\n // to avoid depth-fighting with the fill triangles.\n // Discussed here:\n // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=252848 \n posp.xyz = posp.xyz * scale;\n posq.xyz = posq.xyz * scale;\n\n vec4 p = uProjectionMatrix * posp;\n vec4 q = uProjectionMatrix * posq;\n\n // formula to convert from clip space (range -1..1) to screen space (range 0..[width or height])\n // screen_p = (p.xy/p.w + <1,1>) * 0.5 * uViewport.zw\n\n // prevent division by W by transforming the tangent formula (div by 0 causes\n // the line to disappear, see https://github.com/processing/processing/issues/5183)\n // t = screen_q - screen_p\n //\n // tangent is normalized and we don't care which aDirection it points to (+-)\n // t = +- normalize( screen_q - screen_p )\n // t = +- normalize( (q.xy/q.w+<1,1>)*0.5*uViewport.zw - (p.xy/p.w+<1,1>)*0.5*uViewport.zw )\n //\n // extract common factor, <1,1> - <1,1> cancels out\n // t = +- normalize( (q.xy/q.w - p.xy/p.w) * 0.5 * uViewport.zw )\n //\n // convert to common divisor\n // t = +- normalize( ((q.xy*p.w - p.xy*q.w) / (p.w*q.w)) * 0.5 * uViewport.zw )\n //\n // remove the common scalar divisor/factor, not needed due to normalize and +-\n // (keep uViewport - can't remove because it has different components for x and y\n // and corrects for aspect ratio, see https://github.com/processing/processing/issues/5181)\n // t = +- normalize( (q.xy*p.w - p.xy*q.w) * uViewport.zw )\n\n vec2 tangent = normalize((q.xy*p.w - p.xy*q.w) * uViewport.zw);\n\n // flip tangent to normal (it's already normalized)\n vec2 normal = vec2(-tangent.y, tangent.x);\n\n float thickness = aDirection.w * uStrokeWeight;\n vec2 offset = normal * thickness / 2.0;\n\n vec2 curPerspScale;\n\n if(uPerspective == 1) {\n // Perspective ---\n // convert from world to clip by multiplying with projection scaling factor\n // to get the right thickness (see https://github.com/processing/processing/issues/5182)\n // invert Y, projections in Processing invert Y\n curPerspScale = (uProjectionMatrix * vec4(1, -1, 0, 0)).xy;\n } else {\n // No Perspective ---\n // multiply by W (to cancel out division by W later in the pipeline) and\n // convert from screen to clip (derived from clip to screen above)\n curPerspScale = p.w / (0.5 * uViewport.zw);\n }\n\n gl_Position.xy = p.xy + offset.xy * curPerspScale;\n gl_Position.zw = p.zw;\n}\n",lineFrag:"precision mediump float;\nprecision mediump int;\n\nuniform vec4 uMaterialColor;\n\nvoid main() {\n gl_FragColor = uMaterialColor;\n}",pointVert:"attribute vec3 aPosition;\nuniform float uPointSize;\nvarying float vStrokeWeight;\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nvoid main() {\n\tvec4 positionVec4 = vec4(aPosition, 1.0);\n\tgl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n\tgl_PointSize = uPointSize;\n\tvStrokeWeight = uPointSize;\n}",pointFrag:"precision mediump float;\nprecision mediump int;\nuniform vec4 uMaterialColor;\nvarying float vStrokeWeight;\n\nvoid main(){\n\tfloat mask = 0.0;\n\n\t// make a circular mask using the gl_PointCoord (goes from 0 - 1 on a point)\n // might be able to get a nicer edge on big strokeweights with smoothstep but slightly less performant\n\n\tmask = step(0.98, length(gl_PointCoord * 2.0 - 1.0));\n\n\t// if strokeWeight is 1 or less lets just draw a square\n\t// this prevents weird artifacting from carving circles when our points are really small\n\t// if strokeWeight is larger than 1, we just use it as is\n\n\tmask = mix(0.0, mask, clamp(floor(vStrokeWeight - 0.5),0.0,1.0));\n\n\t// throw away the borders of the mask\n // otherwise we get weird alpha blending issues\n\n\tif(mask > 0.98){\n discard;\n \t}\n\n \tgl_FragColor = vec4(uMaterialColor.rgb * (1.0 - mask), uMaterialColor.a) ;\n}"};u.default.RendererGL=function(e,t,r,i){return u.default.Renderer.call(this,e,t,r),this._setAttributeDefaults(t),this._initContext(),this.isP3D=!0,this.GL=this.drawingContext,this._isErasing=!1,this._enableLighting=!1,this.ambientLightColors=[],this.specularColors=[1,1,1],this.directionalLightDirections=[],this.directionalLightDiffuseColors=[],this.directionalLightSpecularColors=[],this.pointLightPositions=[],this.pointLightDiffuseColors=[],this.pointLightSpecularColors=[],this.spotLightPositions=[],this.spotLightDirections=[],this.spotLightDiffuseColors=[],this.spotLightSpecularColors=[],this.spotLightAngle=[],this.spotLightConc=[],this.drawMode=o.FILL,this.curFillColor=this._cachedFillStyle=[1,1,1,1],this.curStrokeColor=this._cachedStrokeStyle=[0,0,0,1],this.curBlendMode=o.BLEND,this._cachedBlendMode=void 0,this.blendExt=this.GL.getExtension("EXT_blend_minmax"),this._isBlending=!1,this._useSpecularMaterial=!1,this._useEmissiveMaterial=!1,this._useNormalMaterial=!1,this._useShininess=1,this._tint=[255,255,255,255],this.constantAttenuation=1,this.linearAttenuation=0,this.quadraticAttenuation=0,this.uMVMatrix=new u.default.Matrix,this.uPMatrix=new u.default.Matrix,this.uNMatrix=new u.default.Matrix("mat3"),this._curCamera=new u.default.Camera(this),this._curCamera._computeCameraDefaultSettings(),this._curCamera._setDefaultCamera(),this._defaultLightShader=void 0,this._defaultImmediateModeShader=void 0,this._defaultNormalShader=void 0,this._defaultColorShader=void 0,this._defaultPointShader=void 0,this.userFillShader=void 0,this.userStrokeShader=void 0,this.userPointShader=void 0,this.retainedMode={geometry:{},buffers:{stroke:[new u.default.RenderBuffer(3,"lineVertices","lineVertexBuffer","aPosition",this,this._flatten),new u.default.RenderBuffer(4,"lineNormals","lineNormalBuffer","aDirection",this,this._flatten)],fill:[new u.default.RenderBuffer(3,"vertices","vertexBuffer","aPosition",this,this._vToNArray),new u.default.RenderBuffer(3,"vertexNormals","normalBuffer","aNormal",this,this._vToNArray),new u.default.RenderBuffer(4,"vertexColors","colorBuffer","aMaterialColor",this),new u.default.RenderBuffer(3,"vertexAmbients","ambientBuffer","aAmbientColor",this),new u.default.RenderBuffer(2,"uvs","uvBuffer","aTexCoord",this,this._flatten)],text:[new u.default.RenderBuffer(3,"vertices","vertexBuffer","aPosition",this,this._vToNArray),new u.default.RenderBuffer(2,"uvs","uvBuffer","aTexCoord",this,this._flatten)]}},this.immediateMode={geometry:new u.default.Geometry,shapeMode:o.TRIANGLE_FAN,_bezierVertex:[],_quadraticVertex:[],_curveVertex:[],buffers:{fill:[new u.default.RenderBuffer(3,"vertices","vertexBuffer","aPosition",this,this._vToNArray),new u.default.RenderBuffer(3,"vertexNormals","normalBuffer","aNormal",this,this._vToNArray),new u.default.RenderBuffer(4,"vertexColors","colorBuffer","aVertexColor",this),new u.default.RenderBuffer(3,"vertexAmbients","ambientBuffer","aAmbientColor",this),new u.default.RenderBuffer(2,"uvs","uvBuffer","aTexCoord",this,this._flatten)],stroke:[new u.default.RenderBuffer(3,"lineVertices","lineVertexBuffer","aPosition",this,this._flatten),new u.default.RenderBuffer(4,"lineNormals","lineNormalBuffer","aDirection",this,this._flatten)],point:this.GL.createBuffer()}},this.pointSize=5,this.curStrokeWeight=1,this.textures=[],this.textureMode=o.IMAGE,this.textureWrapX=o.CLAMP,this.textureWrapY=o.CLAMP,this._tex=null,this._curveTightness=6,this._lookUpTableBezier=[],this._lookUpTableQuadratic=[],this._lutBezierDetail=0,this._lutQuadraticDetail=0,this._tessy=this._initTessy(),this.fontInfos={},this._curShader=void 0,this},u.default.RendererGL.prototype=Object.create(u.default.Renderer.prototype),u.default.RendererGL.prototype._setAttributeDefaults=function(e){var t={alpha:!0,depth:!0,stencil:!0,antialias:navigator.userAgent.toLowerCase().includes("safari"),premultipliedAlpha:!1,preserveDrawingBuffer:!0,perPixelLighting:!0};null===e._glAttributes?e._glAttributes=t:e._glAttributes=Object.assign(t,e._glAttributes)},u.default.RendererGL.prototype._initContext=function(){try{if(this.drawingContext=this.canvas.getContext("webgl",this._pInst._glAttributes)||this.canvas.getContext("experimental-webgl",this._pInst._glAttributes),null===this.drawingContext)throw new Error("Error creating webgl context");var e=this.drawingContext;e.enable(e.DEPTH_TEST),e.depthFunc(e.LEQUAL),e.viewport(0,0,e.drawingBufferWidth,e.drawingBufferHeight),this._viewport=this.drawingContext.getParameter(this.drawingContext.VIEWPORT)}catch(e){throw e}},u.default.RendererGL.prototype._resetContext=function(e,t){var r=this.width,i=this.height,n=this.canvas.id,o=this._pInst instanceof u.default.Graphics;if(o){var a=this._pInst;a.canvas.parentNode.removeChild(a.canvas),a.canvas=document.createElement("canvas"),(a._pInst._userNode||document.body).appendChild(a.canvas),u.default.Element.call(a,a.canvas,a._pInst),a.width=r,a.height=i}else{var s=this.canvas;s&&s.parentNode.removeChild(s),(s=document.createElement("canvas")).id=n,this._pInst._userNode?this._pInst._userNode.appendChild(s):document.body.appendChild(s),this._pInst.canvas=s}var l=new u.default.RendererGL(this._pInst.canvas,this._pInst,!o);this._pInst._setProperty("_renderer",l),l.resize(r,i),l._applyDefaults(),o||this._pInst._elements.push(l),"function"==typeof t&&setTimeout(function(){t.apply(window._renderer,e)},0)},u.default.prototype.setAttributes=function(e,t){if(void 0!==this._glAttributes){var r=!0;if(void 0!==t?(null===this._glAttributes&&(this._glAttributes={}),this._glAttributes[e]!==t&&(this._glAttributes[e]=t,r=!1)):e instanceof Object&&this._glAttributes!==e&&(this._glAttributes=e,r=!1),this._renderer.isP3D&&!r){if(!this._setupDone)for(var i in this._renderer.retainedMode.geometry)if(this._renderer.retainedMode.geometry.hasOwnProperty(i))return void console.error("Sorry, Could not set the attributes, you need to call setAttributes() before calling the other drawing methods in setup()");this.push(),this._renderer._resetContext(),this.pop(),this._renderer._curCamera&&(this._renderer._curCamera._renderer=this._renderer)}}else console.log("You are trying to use setAttributes on a p5.Graphics object that does not use a WEBGL renderer.")},u.default.RendererGL.prototype._update=function(){this.uMVMatrix.set(this._curCamera.cameraMatrix.mat4[0],this._curCamera.cameraMatrix.mat4[1],this._curCamera.cameraMatrix.mat4[2],this._curCamera.cameraMatrix.mat4[3],this._curCamera.cameraMatrix.mat4[4],this._curCamera.cameraMatrix.mat4[5],this._curCamera.cameraMatrix.mat4[6],this._curCamera.cameraMatrix.mat4[7],this._curCamera.cameraMatrix.mat4[8],this._curCamera.cameraMatrix.mat4[9],this._curCamera.cameraMatrix.mat4[10],this._curCamera.cameraMatrix.mat4[11],this._curCamera.cameraMatrix.mat4[12],this._curCamera.cameraMatrix.mat4[13],this._curCamera.cameraMatrix.mat4[14],this._curCamera.cameraMatrix.mat4[15]),this.ambientLightColors.length=0,this.specularColors=[1,1,1],this.directionalLightDirections.length=0,this.directionalLightDiffuseColors.length=0,this.directionalLightSpecularColors.length=0,this.pointLightPositions.length=0,this.pointLightDiffuseColors.length=0,this.pointLightSpecularColors.length=0,this.spotLightPositions.length=0,this.spotLightDirections.length=0,this.spotLightDiffuseColors.length=0,this.spotLightSpecularColors.length=0,this.spotLightAngle.length=0,this.spotLightConc.length=0,this._enableLighting=!1,this._tint=[255,255,255,255],this.GL.clear(this.GL.DEPTH_BUFFER_BIT)},u.default.RendererGL.prototype.background=function(){var e,t=(e=this._pInst).color.apply(e,arguments),r=t.levels[0]/255,i=t.levels[1]/255,n=t.levels[2]/255,o=t.levels[3]/255;this.GL.clearColor(r,i,n,o),this.GL.clear(this.GL.COLOR_BUFFER_BIT)},u.default.RendererGL.prototype.fill=function(e,t,r,i){var n=u.default.prototype.color.apply(this._pInst,arguments);this.curFillColor=n._array,this.drawMode=o.FILL,this._useNormalMaterial=!1,this._tex=null},u.default.RendererGL.prototype.stroke=function(e,t,r,i){arguments[3]=255;var n=u.default.prototype.color.apply(this._pInst,arguments);this.curStrokeColor=n._array},u.default.RendererGL.prototype.strokeCap=function(e){console.error("Sorry, strokeCap() is not yet implemented in WEBGL mode")},u.default.RendererGL.prototype.strokeJoin=function(e){console.error("Sorry, strokeJoin() is not yet implemented in WEBGL mode")},u.default.RendererGL.prototype.filter=function(e){console.error("filter() does not work in WEBGL mode")},u.default.RendererGL.prototype.blendMode=function(e){e===o.DARKEST||e===o.LIGHTEST||e===o.ADD||e===o.BLEND||e===o.SUBTRACT||e===o.SCREEN||e===o.EXCLUSION||e===o.REPLACE||e===o.MULTIPLY||e===o.REMOVE?this.curBlendMode=e:e!==o.BURN&&e!==o.OVERLAY&&e!==o.HARD_LIGHT&&e!==o.SOFT_LIGHT&&e!==o.DODGE||console.warn("BURN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, and DODGE only work for blendMode in 2D mode.")},u.default.RendererGL.prototype.erase=function(e,t){this._isErasing||(this._cachedBlendMode=this.curBlendMode,this.blendMode(o.REMOVE),this._cachedFillStyle=this.curFillColor.slice(),this.curFillColor=[1,1,1,e/255],this._cachedStrokeStyle=this.curStrokeColor.slice(),this.curStrokeColor=[1,1,1,t/255],this._isErasing=!0)},u.default.RendererGL.prototype.noErase=function(){this._isErasing&&(this.curFillColor=this._cachedFillStyle.slice(),this.curStrokeColor=this._cachedStrokeStyle.slice(),this.blendMode(this._cachedBlendMode),this._isErasing=!1)},u.default.RendererGL.prototype.strokeWeight=function(e){this.curStrokeWeight!==e&&(this.pointSize=e,this.curStrokeWeight=e)},u.default.RendererGL.prototype._getPixel=function(e,t){var r;return r=new Uint8Array(4),this.drawingContext.readPixels(e,t,1,1,this.drawingContext.RGBA,this.drawingContext.UNSIGNED_BYTE,r),[r[0],r[1],r[2],r[3]]},u.default.RendererGL.prototype.loadPixels=function(){var e=this._pixelsState;if(!0===this._pInst._glAttributes.preserveDrawingBuffer){var t=e.pixels,r=this.GL.drawingBufferWidth*this.GL.drawingBufferHeight*4;t instanceof Uint8Array&&t.length===r||(t=new Uint8Array(r),this._pixelsState._setProperty("pixels",t));var i=this._pInst._pixelDensity;this.GL.readPixels(0,0,this.width*i,this.height*i,this.GL.RGBA,this.GL.UNSIGNED_BYTE,t)}else console.log("loadPixels only works in WebGL when preserveDrawingBuffer is true.")},u.default.RendererGL.prototype.geometryInHash=function(e){return void 0!==this.retainedMode.geometry[e]},u.default.RendererGL.prototype.resize=function(e,t){u.default.Renderer.prototype.resize.call(this,e,t),this.GL.viewport(0,0,this.GL.drawingBufferWidth,this.GL.drawingBufferHeight),this._viewport=this.GL.getParameter(this.GL.VIEWPORT),this._curCamera._resize();var r=this._pixelsState;void 0!==r.pixels&&r._setProperty("pixels",new Uint8Array(this.GL.drawingBufferWidth*this.GL.drawingBufferHeight*4))},u.default.RendererGL.prototype.clear=function(){var e=(arguments.length<=0?void 0:arguments[0])||0,t=(arguments.length<=1?void 0:arguments[1])||0,r=(arguments.length<=2?void 0:arguments[2])||0,i=(arguments.length<=3?void 0:arguments[3])||0;this.GL.clearColor(e,t,r,i),this.GL.clear(this.GL.COLOR_BUFFER_BIT|this.GL.DEPTH_BUFFER_BIT)},u.default.RendererGL.prototype.applyMatrix=function(e,t,r,i,n,o){16===arguments.length?u.default.Matrix.prototype.apply.apply(this.uMVMatrix,arguments):this.uMVMatrix.apply([e,t,0,0,r,i,0,0,0,0,1,0,n,o,0,1])},u.default.RendererGL.prototype.translate=function(e,t,r){return e instanceof u.default.Vector&&(r=e.z,t=e.y,e=e.x),this.uMVMatrix.translate([e,t,r]),this},u.default.RendererGL.prototype.scale=function(e,t,r){return this.uMVMatrix.scale(e,t,r),this},u.default.RendererGL.prototype.rotate=function(e,t){return void 0===t?this.rotateZ(e):(u.default.Matrix.prototype.rotate.apply(this.uMVMatrix,arguments),this)},u.default.RendererGL.prototype.rotateX=function(e){return this.rotate(e,1,0,0),this},u.default.RendererGL.prototype.rotateY=function(e){return this.rotate(e,0,1,0),this},u.default.RendererGL.prototype.rotateZ=function(e){return this.rotate(e,0,0,1),this},u.default.RendererGL.prototype.push=function(){var e=u.default.Renderer.prototype.push.apply(this),t=e.properties;return t.uMVMatrix=this.uMVMatrix.copy(),t.uPMatrix=this.uPMatrix.copy(),t._curCamera=this._curCamera,this._curCamera=this._curCamera.copy(),t.ambientLightColors=this.ambientLightColors.slice(),t.specularColors=this.specularColors.slice(),t.directionalLightDirections=this.directionalLightDirections.slice(),t.directionalLightDiffuseColors=this.directionalLightDiffuseColors.slice(),t.directionalLightSpecularColors=this.directionalLightSpecularColors.slice(),t.pointLightPositions=this.pointLightPositions.slice(),t.pointLightDiffuseColors=this.pointLightDiffuseColors.slice(),t.pointLightSpecularColors=this.pointLightSpecularColors.slice(),t.spotLightPositions=this.spotLightPositions.slice(),t.spotLightDirections=this.spotLightDirections.slice(),t.spotLightDiffuseColors=this.spotLightDiffuseColors.slice(),t.spotLightSpecularColors=this.spotLightSpecularColors.slice(),t.spotLightAngle=this.spotLightAngle.slice(),t.spotLightConc=this.spotLightConc.slice(),t.userFillShader=this.userFillShader,t.userStrokeShader=this.userStrokeShader,t.userPointShader=this.userPointShader,t.pointSize=this.pointSize,t.curStrokeWeight=this.curStrokeWeight,t.curStrokeColor=this.curStrokeColor,t.curFillColor=this.curFillColor,t._useSpecularMaterial=this._useSpecularMaterial,t._useEmissiveMaterial=this._useEmissiveMaterial,t._useShininess=this._useShininess,t.constantAttenuation=this.constantAttenuation,t.linearAttenuation=this.linearAttenuation,t.quadraticAttenuation=this.quadraticAttenuation,t._enableLighting=this._enableLighting,t._useNormalMaterial=this._useNormalMaterial,t._tex=this._tex,t.drawMode=this.drawMode,e},u.default.RendererGL.prototype.resetMatrix=function(){return this.uMVMatrix=u.default.Matrix.identity(this._pInst),this},u.default.RendererGL.prototype._getImmediateStrokeShader=function(){var e=this.userStrokeShader;return e&&e.isStrokeShader()?e:this._getLineShader()},u.default.RendererGL.prototype._getRetainedStrokeShader=u.default.RendererGL.prototype._getImmediateStrokeShader,u.default.RendererGL.prototype._getImmediateFillShader=function(){var e=this.userFillShader;if(this._useNormalMaterial&&(!e||!e.isNormalShader()))return this._getNormalShader();if(this._enableLighting){if(!e||!e.isLightShader())return this._getLightShader()}else if(this._tex){if(!e||!e.isTextureShader())return this._getLightShader()}else if(!e)return this._getImmediateModeShader();return e},u.default.RendererGL.prototype._getRetainedFillShader=function(){if(this._useNormalMaterial)return this._getNormalShader();var e=this.userFillShader;if(this._enableLighting){if(!e||!e.isLightShader())return this._getLightShader()}else if(this._tex){if(!e||!e.isTextureShader())return this._getLightShader()}else if(!e)return this._getColorShader();return e},u.default.RendererGL.prototype._getImmediatePointShader=function(){var e=this.userPointShader;return e&&e.isPointShader()?e:this._getPointShader()},u.default.RendererGL.prototype._getRetainedLineShader=u.default.RendererGL.prototype._getImmediateLineShader,u.default.RendererGL.prototype._getLightShader=function(){return this._defaultLightShader||(this._pInst._glAttributes.perPixelLighting?this._defaultLightShader=new u.default.Shader(this,c.phongVert,c.phongFrag):this._defaultLightShader=new u.default.Shader(this,c.lightVert,c.lightTextureFrag)),this._defaultLightShader},u.default.RendererGL.prototype._getImmediateModeShader=function(){return this._defaultImmediateModeShader||(this._defaultImmediateModeShader=new u.default.Shader(this,c.immediateVert,c.vertexColorFrag)),this._defaultImmediateModeShader},u.default.RendererGL.prototype._getNormalShader=function(){return this._defaultNormalShader||(this._defaultNormalShader=new u.default.Shader(this,c.normalVert,c.normalFrag)),this._defaultNormalShader},u.default.RendererGL.prototype._getColorShader=function(){return this._defaultColorShader||(this._defaultColorShader=new u.default.Shader(this,c.normalVert,c.basicFrag)),this._defaultColorShader},u.default.RendererGL.prototype._getPointShader=function(){return this._defaultPointShader||(this._defaultPointShader=new u.default.Shader(this,c.pointVert,c.pointFrag)),this._defaultPointShader},u.default.RendererGL.prototype._getLineShader=function(){return this._defaultLineShader||(this._defaultLineShader=new u.default.Shader(this,c.lineVert,c.lineFrag)),this._defaultLineShader},u.default.RendererGL.prototype._getFontShader=function(){return this._defaultFontShader||(this.GL.getExtension("OES_standard_derivatives"),this._defaultFontShader=new u.default.Shader(this,c.fontVert,c.fontFrag)),this._defaultFontShader},u.default.RendererGL.prototype._getEmptyTexture=function(){if(!this._emptyTexture){var e=new u.default.Image(1,1);e.set(0,0,255),this._emptyTexture=new u.default.Texture(this,e)}return this._emptyTexture},u.default.RendererGL.prototype.getTexture=function(e){var t=this.textures,r=!0,i=!1,n=void 0;try{for(var o,a=t[Symbol.iterator]();!(r=(o=a.next()).done);r=!0){var s=o.value;if(s.src===e)return s}}catch(e){i=!0,n=e}finally{try{r||null==a.return||a.return()}finally{if(i)throw n}}var l=new u.default.Texture(this,e);return t.push(l),l},u.default.RendererGL.prototype._setStrokeUniforms=function(e){e.bindShader(),e.setUniform("uMaterialColor",this.curStrokeColor),e.setUniform("uStrokeWeight",this.curStrokeWeight)},u.default.RendererGL.prototype._setFillUniforms=function(e){e.bindShader(),e.setUniform("uMaterialColor",this.curFillColor),e.setUniform("isTexture",!!this._tex),this._tex&&e.setUniform("uSampler",this._tex),e.setUniform("uTint",this._tint),e.setUniform("uSpecular",this._useSpecularMaterial),e.setUniform("uEmissive",this._useEmissiveMaterial),e.setUniform("uShininess",this._useShininess),e.setUniform("uUseLighting",this._enableLighting);var t=this.pointLightDiffuseColors.length/3;e.setUniform("uPointLightCount",t),e.setUniform("uPointLightLocation",this.pointLightPositions),e.setUniform("uPointLightDiffuseColors",this.pointLightDiffuseColors),e.setUniform("uPointLightSpecularColors",this.pointLightSpecularColors);var r=this.directionalLightDiffuseColors.length/3;e.setUniform("uDirectionalLightCount",r),e.setUniform("uLightingDirection",this.directionalLightDirections),e.setUniform("uDirectionalDiffuseColors",this.directionalLightDiffuseColors),e.setUniform("uDirectionalSpecularColors",this.directionalLightSpecularColors);var i=this.ambientLightColors.length/3;e.setUniform("uAmbientLightCount",i),e.setUniform("uAmbientColor",this.ambientLightColors);var n=this.spotLightDiffuseColors.length/3;e.setUniform("uSpotLightCount",n),e.setUniform("uSpotLightAngle",this.spotLightAngle),e.setUniform("uSpotLightConc",this.spotLightConc),e.setUniform("uSpotLightDiffuseColors",this.spotLightDiffuseColors),e.setUniform("uSpotLightSpecularColors",this.spotLightSpecularColors),e.setUniform("uSpotLightLocation",this.spotLightPositions),e.setUniform("uSpotLightDirection",this.spotLightDirections),e.setUniform("uConstantAttenuation",this.constantAttenuation),e.setUniform("uLinearAttenuation",this.linearAttenuation),e.setUniform("uQuadraticAttenuation",this.quadraticAttenuation),e.bindTextures()},u.default.RendererGL.prototype._setPointUniforms=function(e){e.bindShader(),e.setUniform("uMaterialColor",this.curStrokeColor),e.setUniform("uPointSize",this.pointSize)},u.default.RendererGL.prototype._bindBuffer=function(e,t,r,i,n){if(t=t||this.GL.ARRAY_BUFFER,this.GL.bindBuffer(t,e),void 0!==r){var o=new(i||Float32Array)(r);this.GL.bufferData(t,o,n||this.GL.STATIC_DRAW)}},u.default.RendererGL.prototype._arraysEqual=function(e,t){var r=e.length;if(r!==t.length)return!1;for(var i=0;i>7,127&f,c>>7,127&c);for(var d=0;d>7,127&p,0,0)}}return{cellImageInfo:l,dimOffset:o,dimImageInfo:n}}return(t=this.glyphInfos[e.index]={glyph:e,uGlyphRect:[i.x1,-i.y1,i.x2,-i.y2],strokeImageInfo:U,strokes:d,colInfo:G(m,this.colDimImageInfos,this.colCellImageInfos),rowInfo:G(p,this.rowDimImageInfos,this.rowCellImageInfos)}).uGridOffset=[t.colInfo.dimOffset,t.rowInfo.dimOffset],t}}var z=Math.sqrt(3);j.default.RendererGL.prototype._renderText=function(e,t,r,i,n){if(this._textFont&&"string"!=typeof this._textFont){if(!(n<=i)&&this._doFill){if(!this._isOpenType())return console.log("WEBGL: only Opentype (.otf) and Truetype (.ttf) fonts are supported"),e;e.push();var o=this._doStroke,a=this.drawMode;this._doStroke=!1,this.drawMode=D.TEXTURE;var s=this._textFont.font,l=this._textFont._fontInfo;l=l||(this._textFont._fontInfo=new A(s));var u=this._textFont._handleAlignment(this,t,r,i),h=this._textSize/s.unitsPerEm;this.translate(u.x,u.y,0),this.scale(h,h,1);var c=this.GL,f=!this._defaultFontShader,d=this._getFontShader();d.init(),d.bindShader(),f&&(d.setUniform("uGridImageSize",[64,64]),d.setUniform("uCellsImageSize",[64,64]),d.setUniform("uStrokeImageSize",[64,64]),d.setUniform("uGridSize",[9,9])),this._applyColorBlend(this.curFillColor);var p=this.retainedMode.geometry.glyph;if(!p){var m=this._textGeom=new j.default.Geometry(1,1,function(){for(var e=0;e<=1;e++)for(var t=0;t<=1;t++)this.vertices.push(new j.default.Vector(t,e,0)),this.uvs.push(t,e)});m.computeFaces().computeNormals(),p=this.createBuffers("glyph",m)}var g=!0,v=!1,y=void 0;try{for(var b,_=this.retainedMode.buffers.text[Symbol.iterator]();!(g=(b=_.next()).done);g=!0){b.value._prepareBuffer(p,d)}}catch(e){v=!0,y=e}finally{try{g||null==_.return||_.return()}finally{if(v)throw y}}this._bindBuffer(p.indexBuffer,c.ELEMENT_ARRAY_BUFFER),d.setUniform("uMaterialColor",this.curFillColor);try{var x=0,w=null,S=s.stringToGlyphs(t),M=!0,E=!1,T=void 0;try{for(var C,P=S[Symbol.iterator]();!(M=(C=P.next()).done);M=!0){var L=C.value;w&&(x+=s.getKerningValue(w,L));var k=l.getGlyphInfo(L);if(k.uGlyphRect){var R=k.rowInfo,O=k.colInfo;d.setUniform("uSamplerStrokes",k.strokeImageInfo.imageData),d.setUniform("uSamplerRowStrokes",R.cellImageInfo.imageData),d.setUniform("uSamplerRows",R.dimImageInfo.imageData),d.setUniform("uSamplerColStrokes",O.cellImageInfo.imageData),d.setUniform("uSamplerCols",O.dimImageInfo.imageData),d.setUniform("uGridOffset",k.uGridOffset),d.setUniform("uGlyphRect",k.uGlyphRect),d.setUniform("uGlyphOffset",x),d.bindTextures(),c.drawElements(c.TRIANGLES,6,this.GL.UNSIGNED_SHORT,0)}x+=L.advanceWidth,w=L}}catch(e){E=!0,T=e}finally{try{M||null==P.return||P.return()}finally{if(E)throw T}}}finally{d.unbindShader(),this._doStroke=o,this.drawMode=a,e.pop()}return e}}else console.log("WEBGL: you must load and set a font before drawing text. See `loadFont` and `textFont` for more details.")}},{"../core/constants":42,"../core/main":49,"./p5.RendererGL.Retained":102,"./p5.Shader":104}],107:[function(e,t,r){t.exports={fes:{autoplay:"The media that tried to play (with '{{src}}') wasn't allowed to by this browser, most likely due to the browser's autoplay policy. Check out {{link}} for more information about why.",fileLoadError:{bytes:"It looks like there was a problem loading your file. {{suggestion}}",font:"It looks like there was a problem loading your font. {{suggestion}}",gif:"There was some trouble loading your GIF. Make sure that your GIF is using 87a or 89a encoding.",image:"It looks like there was a problem loading your image. {{suggestion}}",json:"It looks like there was a problem loading your JSON file. {{suggestion}}",large:"If your large file isn't fetched successfully, we recommend splitting the file into smaller segments and fetching those.",strings:"It looks like there was a problem loading your text file. {{suggestion}}",suggestion:"Try checking if the file path ({{filePath}}) is correct, hosting the file online, or running a local server. (More info at {{link}})",table:"It looks like there was a problem loading your table file. {{suggestion}}",xml:"It looks like there was a problem loading your XML file. {{suggestion}}"},misusedTopLevel:"Did you just try to use p5.js's {{symbolName}} {{symbolType}}? If so, you may want to move it into your sketch's setup() function.\n\nFor more details, see: {{link}}",pre:"🌸 p5.js says: {{message}}",welcome:"Welcome! This is your friendly debugger. To turn me off, switch to using p5.min.js."}}},{}],108:[function(e,t,r){t.exports={fes:{autoplay:"Su browser impidío un medio tocar (de '{{src}}'), posiblemente porque las reglas de autoplay. Para aprender más, visite {{link}}.",fileLoadError:{bytes:"",font:"",gif:"",image:"",json:"",large:"",strings:"",suggestion:"",table:"",xml:""},misusedTopLevel:"",pre:"🌸 p5.js dice: {{message}}",welcome:""}}},{}],109:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var i=o(e("./en/translation")),n=o(e("./es/translation"));function o(e){return e&&e.__esModule?e:{default:e}}var a={en:{translation:i.default},es:{translation:n.default}};r.default=a},{"./en/translation":107,"./es/translation":108}]},{},[37])(37)}); \ No newline at end of file diff --git a/docs/examples/sketch_011/target/target_sketch.js b/docs/examples/sketch_011/target/target_sketch.js new file mode 100644 index 00000000..0e54026f --- /dev/null +++ b/docs/examples/sketch_011/target/target_sketch.js @@ -0,0 +1,1549 @@ +let wrapper_content = ` +class PythonFunctions: pass + +setattr(PythonFunctions, 'map', map) +setattr(PythonFunctions, 'filter', filter) +setattr(PythonFunctions, 'set', set) + + +_P5_INSTANCE = None + +_CTX_MIDDLE = None +_DEFAULT_FILL = None +_DEFAULT_LEADMULT = None +_DEFAULT_STROKE = None +_DEFAULT_TEXT_FILL = None + +ADD = None +ALT = None +ARROW = None +AUDIO = None +AUTO = None +AXES = None +BACKSPACE = None +BASELINE = None +BEVEL = None +BEZIER = None +BLEND = None +BLUR = None +BOLD = None +BOLDITALIC = None +BOTTOM = None +BURN = None +CENTER = None +CHORD = None +CLAMP = None +CLOSE = None +CONTROL = None +CORNER = None +CORNERS = None +CROSS = None +CURVE = None +DARKEST = None +DEG_TO_RAD = None +DEGREES = None +DELETE = None +DIFFERENCE = None +DILATE = None +DODGE = None +DOWN_ARROW = None +ENTER = None +ERODE = None +ESCAPE = None +EXCLUSION = None +FILL = None +GRAY = None +GRID = None +HALF_PI = None +HAND = None +HARD_LIGHT = None +HSB = None +HSL = None +IMAGE = None +IMMEDIATE = None +INVERT = None +ITALIC = None +LANDSCAPE = None +LEFT = None +LEFT_ARROW = None +LIGHTEST = None +LINE_LOOP = None +LINE_STRIP = None +LINEAR = None +LINES = None +MIRROR = None +MITER = None +MOVE = None +MULTIPLY = None +NEAREST = None +NORMAL = None +OPAQUE = None +OPEN = None +OPTION = None +OVERLAY = None +PI = None +PIE = None +POINTS = None +PORTRAIT = None +POSTERIZE = None +PROJECT = None +QUAD_STRIP = None +QUADRATIC = None +QUADS = None +QUARTER_PI = None +RAD_TO_DEG = None +RADIANS = None +RADIUS = None +REPEAT = None +REPLACE = None +RETURN = None +RGB = None +RIGHT = None +RIGHT_ARROW = None +ROUND = None +SCREEN = None +SHIFT = None +SOFT_LIGHT = None +SQUARE = None +STROKE = None +SUBTRACT = None +TAB = None +TAU = None +TEXT = None +TEXTURE = None +THRESHOLD = None +TOP = None +TRIANGLE_FAN = None +TRIANGLE_STRIP = None +TRIANGLES = None +TWO_PI = None +UP_ARROW = None +VIDEO = None +WAIT = None +WEBGL = None +P2D = None +PI = None + +frameCount = None +focused = None +displayWidth = None +displayHeight = None +windowWidth = None +windowHeight = None +width = None +height = None +deviceOrientation = None +accelerationX = None +accelerationY = None +accelerationZ = None +pAccelerationX = None +pAccelerationY = None +pAccelerationZ = None +rotationX = None +rotationY = None +rotationZ = None +pRotationX = None +pRotationY = None +pRotationZ = None +turnAxis = None +keyIsPressed = None +key = None +keyCode = None +mouseX = None +mouseY = None +pmouseX = None +pmouseY = None +winMouseX = None +winMouseY = None +pwinMouseX = None +pwinMouseY = None +mouseButton = None +mouseIsPressed = None +touches = None +pixels = None + + +def alpha(*args): + return _P5_INSTANCE.alpha(*args) + +def blue(*args): + return _P5_INSTANCE.blue(*args) + +def brightness(*args): + return _P5_INSTANCE.brightness(*args) + +def color(*args): + return _P5_INSTANCE.color(*args) + +def green(*args): + return _P5_INSTANCE.green(*args) + +def hue(*args): + return _P5_INSTANCE.hue(*args) + +def lerpColor(*args): + return _P5_INSTANCE.lerpColor(*args) + +def lightness(*args): + return _P5_INSTANCE.lightness(*args) + +def red(*args): + return _P5_INSTANCE.red(*args) + +def saturation(*args): + return _P5_INSTANCE.saturation(*args) + +def background(*args): + return _P5_INSTANCE.background(*args) + +def clear(*args): + p5_clear = _P5_INSTANCE.clear(*args) + return p5_clear + +def erase(*args): + return _P5_INSTANCE.erase(*args) + +def noErase(*args): + return _P5_INSTANCE.noErase(*args) + +def colorMode(*args): + return _P5_INSTANCE.colorMode(*args) + +def fill(*args): + return _P5_INSTANCE.fill(*args) + +def noFill(*args): + return _P5_INSTANCE.noFill(*args) + +def noStroke(*args): + return _P5_INSTANCE.noStroke(*args) + +def stroke(*args): + return _P5_INSTANCE.stroke(*args) + +def arc(*args): + return _P5_INSTANCE.arc(*args) + +def ellipse(*args): + return _P5_INSTANCE.ellipse(*args) + +def circle(*args): + return _P5_INSTANCE.circle(*args) + +def line(*args): + return _P5_INSTANCE.line(*args) + +def point(*args): + return _P5_INSTANCE.point(*args) + +def quad(*args): + return _P5_INSTANCE.quad(*args) + +def rect(*args): + return _P5_INSTANCE.rect(*args) + +def square(*args): + return _P5_INSTANCE.square(*args) + +def triangle(*args): + return _P5_INSTANCE.triangle(*args) + +def plane(*args): + return _P5_INSTANCE.plane(*args) + +def box(*args): + return _P5_INSTANCE.box(*args) + +def sphere(*args): + return _P5_INSTANCE.sphere(*args) + +def cylinder(*args): + return _P5_INSTANCE.cylinder(*args) + +def cone(*args): + return _P5_INSTANCE.cone(*args) + +def ellipsoid(*args): + return _P5_INSTANCE.ellipsoid(*args) + +def torus(*args): + return _P5_INSTANCE.torus(*args) + +def loadModel(*args): + return _P5_INSTANCE.loadModel(*args) + +def model(*args): + return _P5_INSTANCE.model(*args) + +def ellipseMode(*args): + return _P5_INSTANCE.ellipseMode(*args) + +def noSmooth(*args): + return _P5_INSTANCE.noSmooth(*args) + +def rectMode(*args): + return _P5_INSTANCE.rectMode(*args) + +def smooth(*args): + return _P5_INSTANCE.smooth(*args) + +def strokeCap(*args): + return _P5_INSTANCE.strokeCap(*args) + +def strokeJoin(*args): + return _P5_INSTANCE.strokeJoin(*args) + +def strokeWeight(*args): + return _P5_INSTANCE.strokeWeight(*args) + +def bezier(*args): + return _P5_INSTANCE.bezier(*args) + +def bezierDetail(*args): + return _P5_INSTANCE.bezierDetail(*args) + +def bezierPoint(*args): + return _P5_INSTANCE.bezierPoint(*args) + +def bezierTangent(*args): + return _P5_INSTANCE.bezierTangent(*args) + +def curve(*args): + return _P5_INSTANCE.curve(*args) + +def curveDetail(*args): + return _P5_INSTANCE.curveDetail(*args) + +def curveTightness(*args): + return _P5_INSTANCE.curveTightness(*args) + +def curvePoint(*args): + return _P5_INSTANCE.curvePoint(*args) + +def curveTangent(*args): + return _P5_INSTANCE.curveTangent(*args) + +def beginContour(*args): + return _P5_INSTANCE.beginContour(*args) + +def beginShape(*args): + return _P5_INSTANCE.beginShape(*args) + +def bezierVertex(*args): + return _P5_INSTANCE.bezierVertex(*args) + +def curveVertex(*args): + return _P5_INSTANCE.curveVertex(*args) + +def endContour(*args): + return _P5_INSTANCE.endContour(*args) + +def endShape(*args): + return _P5_INSTANCE.endShape(*args) + +def quadraticVertex(*args): + return _P5_INSTANCE.quadraticVertex(*args) + +def vertex(*args): + return _P5_INSTANCE.vertex(*args) + +def cursor(*args): + return _P5_INSTANCE.cursor(*args) + +def frameRate(*args): + return _P5_INSTANCE.frameRate(*args) + +def noCursor(*args): + return _P5_INSTANCE.noCursor(*args) + +def fullscreen(*args): + return _P5_INSTANCE.fullscreen(*args) + +def pixelDensity(*args): + return _P5_INSTANCE.pixelDensity(*args) + +def displayDensity(*args): + return _P5_INSTANCE.displayDensity(*args) + +def getURL(*args): + return _P5_INSTANCE.getURL(*args) + +def getURLPath(*args): + return _P5_INSTANCE.getURLPath(*args) + +def getURLParams(*args): + return _P5_INSTANCE.getURLParams(*args) + +def preload(*args): + return _P5_INSTANCE.preload(*args) + +def remove(*args): + return _P5_INSTANCE.remove(*args) + +def noLoop(*args): + return _P5_INSTANCE.noLoop(*args) + +def loop(*args): + return _P5_INSTANCE.loop(*args) + +def push(*args): + return _P5_INSTANCE.push(*args) + +def redraw(*args): + return _P5_INSTANCE.redraw(*args) + +def resizeCanvas(*args): + return _P5_INSTANCE.resizeCanvas(*args) + +def noCanvas(*args): + return _P5_INSTANCE.noCanvas(*args) + +def createGraphics(*args): + return _P5_INSTANCE.createGraphics(*args) + +def blendMode(*args): + return _P5_INSTANCE.blendMode(*args) + +def setAttributes(*args): + return _P5_INSTANCE.setAttributes(*args) + +def applyMatrix(*args): + return _P5_INSTANCE.applyMatrix(*args) + +def resetMatrix(*args): + return _P5_INSTANCE.resetMatrix(*args) + +def rotate(*args): + return _P5_INSTANCE.rotate(*args) + +def rotateX(*args): + return _P5_INSTANCE.rotateX(*args) + +def rotateY(*args): + return _P5_INSTANCE.rotateY(*args) + +def rotateZ(*args): + return _P5_INSTANCE.rotateZ(*args) + +def scale(*args): + return _P5_INSTANCE.scale(*args) + +def shearX(*args): + return _P5_INSTANCE.shearX(*args) + +def shearY(*args): + return _P5_INSTANCE.shearY(*args) + +def translate(*args): + return _P5_INSTANCE.translate(*args) + +def createStringDict(*args): + return _P5_INSTANCE.createStringDict(*args) + +def createNumberDict(*args): + return _P5_INSTANCE.createNumberDict(*args) + +def append(*args): + return _P5_INSTANCE.append(*args) + +def arrayCopy(*args): + return _P5_INSTANCE.arrayCopy(*args) + +def concat(*args): + return _P5_INSTANCE.concat(*args) + +def reverse(*args): + return _P5_INSTANCE.reverse(*args) + +def shorten(*args): + return _P5_INSTANCE.shorten(*args) + +def shuffle(*args): + return _P5_INSTANCE.shuffle(*args) + +def sort(*args): + return _P5_INSTANCE.sort(*args) + +def splice(*args): + return _P5_INSTANCE.splice(*args) + +def subset(*args): + return _P5_INSTANCE.subset(*args) + +def float(*args): + return _P5_INSTANCE.float(*args) + +def int(*args): + return _P5_INSTANCE.int(*args) + +def str(*args): + return _P5_INSTANCE.str(*args) + +def boolean(*args): + return _P5_INSTANCE.boolean(*args) + +def byte(*args): + return _P5_INSTANCE.byte(*args) + +def char(*args): + return _P5_INSTANCE.char(*args) + +def unchar(*args): + return _P5_INSTANCE.unchar(*args) + +def hex(*args): + return _P5_INSTANCE.hex(*args) + +def unhex(*args): + return _P5_INSTANCE.unhex(*args) + +def join(*args): + return _P5_INSTANCE.join(*args) + +def match(*args): + return _P5_INSTANCE.match(*args) + +def matchAll(*args): + return _P5_INSTANCE.matchAll(*args) + +def nf(*args): + return _P5_INSTANCE.nf(*args) + +def nfc(*args): + return _P5_INSTANCE.nfc(*args) + +def nfp(*args): + return _P5_INSTANCE.nfp(*args) + +def nfs(*args): + return _P5_INSTANCE.nfs(*args) + +def split(*args): + return _P5_INSTANCE.split(*args) + +def splitTokens(*args): + return _P5_INSTANCE.splitTokens(*args) + +def trim(*args): + return _P5_INSTANCE.trim(*args) + +def setMoveThreshold(*args): + return _P5_INSTANCE.setMoveThreshold(*args) + +def setShakeThreshold(*args): + return _P5_INSTANCE.setShakeThreshold(*args) + +def keyIsDown(*args): + return _P5_INSTANCE.keyIsDown(*args) + +def createImage(*args): + return _P5_INSTANCE.createImage(*args) + +def saveCanvas(*args): + return _P5_INSTANCE.saveCanvas(*args) + +def saveFrames(*args): + return _P5_INSTANCE.saveFrames(*args) + +def loadImage(*args): + return _P5_INSTANCE.loadImage(*args) + +def image(*args): + return _P5_INSTANCE.image(*args) + +def tint(*args): + return _P5_INSTANCE.tint(*args) + +def noTint(*args): + return _P5_INSTANCE.noTint(*args) + +def imageMode(*args): + return _P5_INSTANCE.imageMode(*args) + +def blend(*args): + return _P5_INSTANCE.blend(*args) + +def copy(*args): + return _P5_INSTANCE.copy(*args) + +def filter(*args): + if len(args) > 1 and (args[0] is None or callable(args[0])): + return PythonFunctions.filter(*args) + else: + return _P5_INSTANCE.filter(*args) + +def get(*args): + return _P5_INSTANCE.get(*args) + +def loadPixels(*args): + return _P5_INSTANCE.loadPixels(*args) + +def set(*args): + if len(args) <= 1: + return PythonFunctions.set(*args) + else: + return _P5_INSTANCE.set(*args) + +def updatePixels(*args): + return _P5_INSTANCE.updatePixels(*args) + +def loadJSON(*args): + return _P5_INSTANCE.loadJSON(*args) + +def loadStrings(*args): + return _P5_INSTANCE.loadStrings(*args) + +def loadTable(*args): + return _P5_INSTANCE.loadTable(*args) + +def loadXML(*args): + return _P5_INSTANCE.loadXML(*args) + +def loadBytes(*args): + return _P5_INSTANCE.loadBytes(*args) + +def httpGet(*args): + return _P5_INSTANCE.httpGet(*args) + +def httpPost(*args): + return _P5_INSTANCE.httpPost(*args) + +def httpDo(*args): + return _P5_INSTANCE.httpDo(*args) + +def createWriter(*args): + return _P5_INSTANCE.createWriter(*args) + +def save(*args): + return _P5_INSTANCE.save(*args) + +def saveJSON(*args): + return _P5_INSTANCE.saveJSON(*args) + +def saveStrings(*args): + return _P5_INSTANCE.saveStrings(*args) + +def saveTable(*args): + return _P5_INSTANCE.saveTable(*args) + +def day(*args): + return _P5_INSTANCE.day(*args) + +def hour(*args): + return _P5_INSTANCE.hour(*args) + +def minute(*args): + return _P5_INSTANCE.minute(*args) + +def millis(*args): + return _P5_INSTANCE.millis(*args) + +def month(*args): + return _P5_INSTANCE.month(*args) + +def second(*args): + return _P5_INSTANCE.second(*args) + +def year(*args): + return _P5_INSTANCE.year(*args) + +def createVector(*args): + return _P5_INSTANCE.createVector(*args) + +def abs(*args): + return _P5_INSTANCE.abs(*args) + +def ceil(*args): + return _P5_INSTANCE.ceil(*args) + +def constrain(*args): + return _P5_INSTANCE.constrain(*args) + +def dist(*args): + return _P5_INSTANCE.dist(*args) + +def exp(*args): + return _P5_INSTANCE.exp(*args) + +def floor(*args): + return _P5_INSTANCE.floor(*args) + +def lerp(*args): + return _P5_INSTANCE.lerp(*args) + +def log(*args): + return _P5_INSTANCE.log(*args) + +def mag(*args): + return _P5_INSTANCE.mag(*args) + +def map(*args): + if len(args) > 1 and callable(args[0]): + return PythonFunctions.map(*args) + else: + return _P5_INSTANCE.map(*args) + +def max(*args): + return _P5_INSTANCE.max(*args) + +def min(*args): + return _P5_INSTANCE.min(*args) + +def norm(*args): + return _P5_INSTANCE.norm(*args) + +def pow(*args): + return _P5_INSTANCE.pow(*args) + +def round(*args): + return _P5_INSTANCE.round(*args) + +def sq(*args): + return _P5_INSTANCE.sq(*args) + +def sqrt(*args): + return _P5_INSTANCE.sqrt(*args) + +def noise(*args): + return _P5_INSTANCE.noise(*args) + +def noiseDetail(*args): + return _P5_INSTANCE.noiseDetail(*args) + +def noiseSeed(*args): + return _P5_INSTANCE.noiseSeed(*args) + +def randomSeed(*args): + return _P5_INSTANCE.randomSeed(*args) + +def random(*args): + return _P5_INSTANCE.random(*args) + +def randomGaussian(*args): + return _P5_INSTANCE.randomGaussian(*args) + +def acos(*args): + return _P5_INSTANCE.acos(*args) + +def asin(*args): + return _P5_INSTANCE.asin(*args) + +def atan(*args): + return _P5_INSTANCE.atan(*args) + +def atan2(*args): + return _P5_INSTANCE.atan2(*args) + +def cos(*args): + return _P5_INSTANCE.cos(*args) + +def sin(*args): + return _P5_INSTANCE.sin(*args) + +def tan(*args): + return _P5_INSTANCE.tan(*args) + +def degrees(*args): + return _P5_INSTANCE.degrees(*args) + +def radians(*args): + return _P5_INSTANCE.radians(*args) + +def angleMode(*args): + return _P5_INSTANCE.angleMode(*args) + +def textAlign(*args): + return _P5_INSTANCE.textAlign(*args) + +def textLeading(*args): + return _P5_INSTANCE.textLeading(*args) + +def textSize(*args): + return _P5_INSTANCE.textSize(*args) + +def textStyle(*args): + return _P5_INSTANCE.textStyle(*args) + +def textWidth(*args): + return _P5_INSTANCE.textWidth(*args) + +def textAscent(*args): + return _P5_INSTANCE.textAscent(*args) + +def textDescent(*args): + return _P5_INSTANCE.textDescent(*args) + +def loadFont(*args): + return _P5_INSTANCE.loadFont(*args) + +def text(*args): + return _P5_INSTANCE.text(*args) + +def textFont(*args): + return _P5_INSTANCE.textFont(*args) + +def orbitControl(*args): + return _P5_INSTANCE.orbitControl(*args) + +def debugMode(*args): + return _P5_INSTANCE.debugMode(*args) + +def noDebugMode(*args): + return _P5_INSTANCE.noDebugMode(*args) + +def ambientLight(*args): + return _P5_INSTANCE.ambientLight(*args) + +def directionalLight(*args): + return _P5_INSTANCE.directionalLight(*args) + +def pointLight(*args): + return _P5_INSTANCE.pointLight(*args) + +def lights(*args): + return _P5_INSTANCE.lights(*args) + +def loadShader(*args): + return _P5_INSTANCE.loadShader(*args) + +def createShader(*args): + return _P5_INSTANCE.createShader(*args) + +def shader(*args): + return _P5_INSTANCE.shader(*args) + +def resetShader(*args): + return _P5_INSTANCE.resetShader(*args) + +def normalMaterial(*args): + return _P5_INSTANCE.normalMaterial(*args) + +def texture(*args): + return _P5_INSTANCE.texture(*args) + +def textureMode(*args): + return _P5_INSTANCE.textureMode(*args) + +def textureWrap(*args): + return _P5_INSTANCE.textureWrap(*args) + +def ambientMaterial(*args): + return _P5_INSTANCE.ambientMaterial(*args) + +def specularMaterial(*args): + return _P5_INSTANCE.specularMaterial(*args) + +def shininess(*args): + return _P5_INSTANCE.shininess(*args) + +def camera(*args): + return _P5_INSTANCE.camera(*args) + +def perspective(*args): + return _P5_INSTANCE.perspective(*args) + +def ortho(*args): + return _P5_INSTANCE.ortho(*args) + +def createCamera(*args): + return _P5_INSTANCE.createCamera(*args) + +def setCamera(*args): + return _P5_INSTANCE.setCamera(*args) + +def select(*args): + return _P5_INSTANCE.select(*args) + +def selectAll(*args): + return _P5_INSTANCE.selectAll(*args) + +def removeElements(*args): + return _P5_INSTANCE.removeElements(*args) + +def changed(*args): + return _P5_INSTANCE.changed(*args) + +def input(*args): + return _P5_INSTANCE.input(*args) + +def createDiv(*args): + return _P5_INSTANCE.createDiv(*args) + +def createP(*args): + return _P5_INSTANCE.createP(*args) + +def createSpan(*args): + return _P5_INSTANCE.createSpan(*args) + +def createImg(*args): + return _P5_INSTANCE.createImg(*args) + +def createA(*args): + return _P5_INSTANCE.createA(*args) + +def createSlider(*args): + return _P5_INSTANCE.createSlider(*args) + +def createButton(*args): + return _P5_INSTANCE.createButton(*args) + +def createCheckbox(*args): + return _P5_INSTANCE.createCheckbox(*args) + +def createSelect(*args): + return _P5_INSTANCE.createSelect(*args) + +def createRadio(*args): + return _P5_INSTANCE.createRadio(*args) + +def createColorPicker(*args): + return _P5_INSTANCE.createColorPicker(*args) + +def createInput(*args): + return _P5_INSTANCE.createInput(*args) + +def createFileInput(*args): + return _P5_INSTANCE.createFileInput(*args) + +def createVideo(*args): + return _P5_INSTANCE.createVideo(*args) + +def createAudio(*args): + return _P5_INSTANCE.createAudio(*args) + +def createCapture(*args): + return _P5_INSTANCE.createCapture(*args) + +def createElement(*args): + return _P5_INSTANCE.createElement(*args) + +def createCanvas(*args): + canvas = _P5_INSTANCE.createCanvas(*args) + + global width, height + width = _P5_INSTANCE.width + height = _P5_INSTANCE.height + + return canvas + + +def pop(*args): + p5_pop = _P5_INSTANCE.pop(*args) + return p5_pop + + +# Processing Python or Java mode compatibility aliases +size = createCanvas +popMatrix = pop +popStyle = pop +pushMatrix = push +pushStyle = push + +# PVector is a wrapper/helper class for p5.Vector objets +# providing names similar to Processing Python or Java modes +# but mostly keeping p5js functionality + +from numbers import Number + +class PVector: + + def __init__(self, x=0, y=0, z=0): + self.__vector = createVector(x, y, z) + self.add = self.__instance_add__ + self.sub = self.__instance_sub__ + self.mult = self.__instance_mult__ + self.div = self.__instance_div__ + self.cross = self.__instance_cross__ + self.dist = self.__instance_dist__ + self.dot = self.__instance_dot__ + self.lerp = self.__instance_lerp__ + + @property + def x(self): + return self.__vector.x + + @x.setter + def x(self, x): + self.__vector.x = x + + @property + def y(self): + return self.__vector.y + + @y.setter + def y(self, y): + self.__vector.y = y + + @property + def z(self): + return self.__vector.z + + @z.setter + def z(self, z): + self.__vector.z = z + + def mag(self): + return self.__vector.mag() + + def magSq(self): + return self.__vector.magSq() + + def setMag(self, mag): + self.__vector.setMag(mag) + return self + + def normalize(self): + self.__vector.normalize() + return self + + def limit(self, max): + self.__vector.limit(max) + return self + + def heading(self): + return self.__vector.heading() + + def rotate(self, angle): + self.__vector.rotate(angle) + return self + + def __instance_add__(self, *args): + if len(args) == 1: + return PVector.add(self, args[0], self) + else: + return PVector.add(self, PVector(*args), self) + + def __instance_sub__(self, *args): + if len(args) == 1: + return PVector.sub(self, args[0], self) + else: + return PVector.sub(self, PVector(*args), self) + + def __instance_mult__(self, o): + return PVector.mult(self, o, self) + + def __instance_div__(self, f): + return PVector.div(self, f, self) + + def __instance_cross__(self, o): + return PVector.cross(self, o, self) + + def __instance_dist__(self, o): + return PVector.dist(self, o) + + def __instance_dot__(self, *args): + if len(args) == 1: + v = args[0] + else: + v = args + return self.x * v[0] + self.y * v[1] + self.z * v[2] + + def __instance_lerp__(self, *args): + if len(args) == 2: + return PVector.lerp(self, args[0], args[1], self) + else: + vx, vy, vz, f = args + return PVector.lerp(self, PVector(vx, vy, vz), f, self) + + def get(self): + return PVector(self.x, self.y, self.z) + + def copy(self): + return PVector(self.x, self.y, self.z) + + def __getitem__(self, k): + return getattr(self, ('x', 'y', 'z')[k]) + + def __setitem__(self, k, v): + setattr(self, ('x', 'y', 'z')[k], v) + + def __copy__(self): + return PVector(self.x, self.y, self.z) + + def __deepcopy__(self, memo): + return PVector(self.x, self.y, self.z) + + def __repr__(self): # PROVISÓRIO + return f'PVector({self.x}, {self.y}, {self.z})' + + def set(self, *args): + """ + Sets the x, y, and z component of the vector using two or three separate + variables, the data from a p5.Vector, or the values from a float array. + """ + self.__vector.set(*args) + + @classmethod + def add(cls, a, b, dest=None): + if dest is None: + return PVector(a.x + b[0], a.y + b[1], a.z + b[2]) + dest.__vector.set(a.x + b[0], a.y + b[1], a.z + b[2]) + return dest + + @classmethod + def sub(cls, a, b, dest=None): + if dest is None: + return PVector(a.x - b[0], a.y - b[1], a.z - b[2]) + dest.__vector.set(a.x - b[0], a.y - b[1], a.z - b[2]) + return dest + + @classmethod + def mult(cls, a, b, dest=None): + if dest is None: + return PVector(a.x * b, a.y * b, a.z * b) + dest.__vector.set(a.x * b, a.y * b, a.z * b) + return dest + + @classmethod + def div(cls, a, b, dest=None): + if dest is None: + return PVector(a.x / b, a.y / b, a.z / b) + dest.__vector.set(a.x / b, a.y / b, a.z / b) + return dest + + @classmethod + def dist(cls, a, b): + return a.__vector.dist(b.__vector) + + @classmethod + def dot(cls, a, b): + return a.__vector.dot(b.__vector) + + def __add__(a, b): + return PVector.add(a, b, None) + + def __sub__(a, b): + return PVector.sub(a, b, None) + + def __isub__(a, b): + a.sub(b) + return a + + def __iadd__(a, b): + a.add(b) + return a + + def __mul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector.mult(a, float(b), None) + + def __rmul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector.mult(a, float(b), None) + + def __imul__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The *= operator can only be used to multiply a PVector by a number") + a.__vector.mult(float(b)) + return a + + def __truediv__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The * operator can only be used to multiply a PVector by a number") + return PVector(a.x / float(b), a.y / float(b), a.z / float(b)) + + def __itruediv__(a, b): + if not isinstance(b, Number): + raise TypeError( + "The /= operator can only be used to multiply a PVector by a number") + a.__vector.set(a.x / float(b), a.y / float(b), a.z / float(b)) + return a + + def __eq__(a, b): + return a.x == b[0] and a.y == b[1] and a.z == b[2] + + def __lt__(a, b): + return a.magSq() < b.magSq() + + def __le__(a, b): + return a.magSq() <= b.magSq() + + def __gt__(a, b): + return a.magSq() > b.magSq() + + def __ge__(a, b): + return a.magSq() >= b.magSq() + + # Problematic class methods, we would rather use p5.Vector when possible... + + @classmethod + def lerp(cls, a, b, f, dest=None): + v = createVector(a.x, a.y, a.z) + v.lerp(b.__vector, f) + if dest is None: + return PVector(v.x, v.y, v.z) + dest.set(v.x, v.y, v.z) + return dest + + @classmethod + def cross(cls, a, b, dest=None): + x = a.y * b[2] - b[1] * a.z + y = a.z * b[0] - b[2] * a.x + z = a.x * b[1] - b[0] * a.y + if dest is None: + return PVector(x, y, z) + dest.set(x, y, z) + return dest + + @classmethod + def fromAngle(cls, angle, length=1): + # https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js + return PVector(length * cos(angle), length * sin(angle), 0) + + @classmethod + def fromAngles(theta, phi, length=1): + # https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js + cosPhi = cos(phi) + sinPhi = sin(phi) + cosTheta = cos(theta) + sinTheta = sin(theta) + return PVector(length * sinTheta * sinPhi, + -length * cosTheta, + length * sinTheta * cosPhi) + + @classmethod + def random2D(cls): + return PVector.fromAngle(random(TWO_PI)) + + @classmethod + def random3D(cls, dest=None): + angle = random(TWO_PI) + vz = random(2) - 1 + mult = sqrt(1 - vz * vz) + vx = mult * cos(angle) + vy = mult * sin(angle) + if dest is None: + return PVector(vx, vy, vz) + dest.set(vx, vy, vz) + return dest + + @classmethod + def angleBetween(cls, a, b): + return acos(a.dot(b) / sqrt(a.magSq() * b.magSq())) + + # Other harmless p5js methods + + def equals(self, v): + return self == v + + def heading2D(self): + return self.__vector.heading() + + def reflect(self, *args): + # Reflect the incoming vector about a normal to a line in 2D, or about + # a normal to a plane in 3D This method acts on the vector directly + r = self.__vector.reflect(*args) + return r + + def array(self): + # Return a representation of this vector as a float array. This is only + # for temporary use. If used in any w fashion, the contents should be + # copied by using the p5.Vector.copy() method to copy into your own + # array. + return self.__vector.array() + + def toString(self): + # Returns a string representation of a vector v by calling String(v) or v.toString(). + # return self.__vector.toString() would be something like "p5.vector + # Object […, …, …]" + return str(self) + + def rem(self, *args): + # Gives remainder of a vector when it is divided by anw vector. See + # examples for more context. + self.__vector.rem(*args) + return self + + +def pre_draw(p5_instance, draw_func): + """ + We need to run this before the actual draw to insert and update p5 env variables + """ + global _CTX_MIDDLE, _DEFAULT_FILL, _DEFAULT_LEADMULT, _DEFAULT_STROKE, _DEFAULT_TEXT_FILL + + global ADD, ALT, ARROW, AUTO, AUDIO, AXES, BACKSPACE, BASELINE, BEVEL, BEZIER, BLEND, BLUR, BOLD, BOLDITALIC + global BOTTOM, BURN, CENTER, CHORD, CLAMP, CLOSE, CONTROL, CORNER, CORNERS, CROSS, CURVE, DARKEST + global DEG_TO_RAD, DEGREES, DELETE, DIFFERENCE, DILATE, DODGE, DOWN_ARROW, ENTER, ERODE, ESCAPE, EXCLUSION + global FILL, GRAY, GRID, HALF_PI, HAND, HARD_LIGHT, HSB, HSL, IMAGE, IMMEDIATE, INVERT, ITALIC, LANDSCAPE + global LEFT, LEFT_ARROW, LIGHTEST, LINE_LOOP, LINE_STRIP, LINEAR, LINES, MIRROR, MITER, MOVE, MULTIPLY, NEAREST + global NORMAL, OPAQUE, OPEN, OPTION, OVERLAY, P2D, P3D, PI, PIE, POINTS, PORTRAIT, POSTERIZE, PROJECT, QUAD_STRIP + global QUADRATIC, QUADS, QUARTER_PI, RAD_TO_DEG, RADIANS, RADIUS, REPEAT, REPLACE, RETURN, RGB, RIGHT, RIGHT_ARROW + global ROUND, SCREEN, SHIFT, SOFT_LIGHT, SQUARE, STROKE, SUBTRACT, TAB, TAU, TEXT, TEXTURE, THRESHOLD, TOP + global TRIANGLE_FAN, TRIANGLE_STRIP, TRIANGLES, TWO_PI, UP_ARROW, VIDEO, WAIT, WEBGL + + global frameCount, focused, displayWidth, displayHeight, windowWidth, windowHeight, width, height + global deviceOrientation, accelerationX, accelerationY, accelerationZ + global pAccelerationX, pAccelerationY, pAccelerationZ, rotationX, rotationY, rotationZ + global pRotationX, pRotationY, pRotationZ, turnAxis, keyIsPressed, key, keyCode, mouseX, mouseY, pmouseX, pmouseY + global winMouseX, winMouseY, pwinMouseX, pwinMouseY, mouseButton, mouseIsPressed, touches, pixels + + _CTX_MIDDLE = p5_instance._CTX_MIDDLE + _DEFAULT_FILL = p5_instance._DEFAULT_FILL + _DEFAULT_LEADMULT = p5_instance._DEFAULT_LEADMULT + _DEFAULT_STROKE = p5_instance._DEFAULT_STROKE + _DEFAULT_TEXT_FILL = p5_instance._DEFAULT_TEXT_FILL + + ADD = p5_instance.ADD + ALT = p5_instance.ALT + ARROW = p5_instance.ARROW + AUDIO = p5_instance.AUDIO + AUTO = p5_instance.AUTO + AXES = p5_instance.AXES + BACKSPACE = p5_instance.BACKSPACE + BASELINE = p5_instance.BASELINE + BEVEL = p5_instance.BEVEL + BEZIER = p5_instance.BEZIER + BLEND = p5_instance.BLEND + BLUR = p5_instance.BLUR + BOLD = p5_instance.BOLD + BOLDITALIC = p5_instance.BOLDITALIC + BOTTOM = p5_instance.BOTTOM + BURN = p5_instance.BURN + CENTER = p5_instance.CENTER + CHORD = p5_instance.CHORD + CLAMP = p5_instance.CLAMP + CLOSE = p5_instance.CLOSE + CONTROL = p5_instance.CONTROL + CORNER = p5_instance.CORNER + CORNERS = p5_instance.CORNERS + CROSS = p5_instance.CROSS + CURVE = p5_instance.CURVE + DARKEST = p5_instance.DARKEST + DEG_TO_RAD = p5_instance.DEG_TO_RAD + DEGREES = p5_instance.DEGREES + DELETE = p5_instance.DELETE + DIFFERENCE = p5_instance.DIFFERENCE + DILATE = p5_instance.DILATE + DODGE = p5_instance.DODGE + DOWN_ARROW = p5_instance.DOWN_ARROW + ENTER = p5_instance.ENTER + ERODE = p5_instance.ERODE + ESCAPE = p5_instance.ESCAPE + EXCLUSION = p5_instance.EXCLUSION + FILL = p5_instance.FILL + GRAY = p5_instance.GRAY + GRID = p5_instance.GRID + HALF_PI = p5_instance.HALF_PI + HAND = p5_instance.HAND + HARD_LIGHT = p5_instance.HARD_LIGHT + HSB = p5_instance.HSB + HSL = p5_instance.HSL + IMAGE = p5_instance.IMAGE + IMMEDIATE = p5_instance.IMMEDIATE + INVERT = p5_instance.INVERT + ITALIC = p5_instance.ITALIC + LANDSCAPE = p5_instance.LANDSCAPE + LEFT = p5_instance.LEFT + LEFT_ARROW = p5_instance.LEFT_ARROW + LIGHTEST = p5_instance.LIGHTEST + LINE_LOOP = p5_instance.LINE_LOOP + LINE_STRIP = p5_instance.LINE_STRIP + LINEAR = p5_instance.LINEAR + LINES = p5_instance.LINES + MIRROR = p5_instance.MIRROR + MITER = p5_instance.MITER + MOVE = p5_instance.MOVE + MULTIPLY = p5_instance.MULTIPLY + NEAREST = p5_instance.NEAREST + NORMAL = p5_instance.NORMAL + OPAQUE = p5_instance.OPAQUE + OPEN = p5_instance.OPEN + OPTION = p5_instance.OPTION + OVERLAY = p5_instance.OVERLAY + P2D = p5_instance.P2D + P3D = p5_instance.WEBGL + PI = p5_instance.PI + PIE = p5_instance.PIE + POINTS = p5_instance.POINTS + PORTRAIT = p5_instance.PORTRAIT + POSTERIZE = p5_instance.POSTERIZE + PROJECT = p5_instance.PROJECT + QUAD_STRIP = p5_instance.QUAD_STRIP + QUADRATIC = p5_instance.QUADRATIC + QUADS = p5_instance.QUADS + QUARTER_PI = p5_instance.QUARTER_PI + RAD_TO_DEG = p5_instance.RAD_TO_DEG + RADIANS = p5_instance.RADIANS + RADIUS = p5_instance.RADIUS + REPEAT = p5_instance.REPEAT + REPLACE = p5_instance.REPLACE + RETURN = p5_instance.RETURN + RGB = p5_instance.RGB + RIGHT = p5_instance.RIGHT + RIGHT_ARROW = p5_instance.RIGHT_ARROW + ROUND = p5_instance.ROUND + SCREEN = p5_instance.SCREEN + SHIFT = p5_instance.SHIFT + SOFT_LIGHT = p5_instance.SOFT_LIGHT + SQUARE = p5_instance.SQUARE + STROKE = p5_instance.STROKE + SUBTRACT = p5_instance.SUBTRACT + TAB = p5_instance.TAB + TAU = p5_instance.TAU + TEXT = p5_instance.TEXT + TEXTURE = p5_instance.TEXTURE + THRESHOLD = p5_instance.THRESHOLD + TOP = p5_instance.TOP + TRIANGLE_FAN = p5_instance.TRIANGLE_FAN + TRIANGLE_STRIP = p5_instance.TRIANGLE_STRIP + TRIANGLES = p5_instance.TRIANGLES + TWO_PI = p5_instance.TWO_PI + UP_ARROW = p5_instance.UP_ARROW + VIDEO = p5_instance.VIDEO + WAIT = p5_instance.WAIT + WEBGL = p5_instance.WEBGL + + frameCount = p5_instance.frameCount + focused = p5_instance.focused + displayWidth = p5_instance.displayWidth + displayHeight = p5_instance.displayHeight + windowWidth = p5_instance.windowWidth + windowHeight = p5_instance.windowHeight + width = p5_instance.width + height = p5_instance.height + deviceOrientation = p5_instance.deviceOrientation + accelerationX = p5_instance.accelerationX + accelerationY = p5_instance.accelerationY + accelerationZ = p5_instance.accelerationZ + pAccelerationX = p5_instance.pAccelerationX + pAccelerationY = p5_instance.pAccelerationY + pAccelerationZ = p5_instance.pAccelerationZ + rotationX = p5_instance.rotationX + rotationY = p5_instance.rotationY + rotationZ = p5_instance.rotationZ + pRotationX = p5_instance.pRotationX + pRotationY = p5_instance.pRotationY + pRotationZ = p5_instance.pRotationZ + turnAxis = p5_instance.turnAxis + keyIsPressed = p5_instance.keyIsPressed + key = p5_instance.key + keyCode = p5_instance.keyCode + mouseX = p5_instance.mouseX + mouseY = p5_instance.mouseY + pmouseX = p5_instance.pmouseX + pmouseY = p5_instance.pmouseY + winMouseX = p5_instance.winMouseX + winMouseY = p5_instance.winMouseY + pwinMouseX = p5_instance.pwinMouseX + pwinMouseY = p5_instance.pwinMouseY + mouseButton = p5_instance.mouseButton + mouseIsPressed = p5_instance.mouseIsPressed + touches = p5_instance.touches + pixels = p5_instance.pixels + + return draw_func() + + +def global_p5_injection(p5_sketch): + """ + Injects the p5js's skecth instance as a global variable to setup and draw functions + """ + + def decorator(f): + def wrapper(): + global _P5_INSTANCE + _P5_INSTANCE = p5_sketch + return pre_draw(_P5_INSTANCE, f) + + + return wrapper + + + return decorator + + +def start_p5(setup_func, draw_func, event_functions): + """ + This is the entrypoint function. It accepts 2 parameters: + + - setup_func: a Python setup callable + - draw_func: a Python draw callable + - event_functions: a config dict for the event functions in the format: + {"eventFunctionName": python_event_function} + + This method gets the p5js's sketch instance and injects them + """ + + def sketch_setup(p5_sketch): + """ + Callback function called to configure new p5 instance + """ + p5_sketch.setup = global_p5_injection(p5_sketch)(setup_func) + p5_sketch.draw = global_p5_injection(p5_sketch)(draw_func) + + + window.instance = p5.new(sketch_setup, 'sketch-holder') + + # inject event functions into p5 + event_function_names = ( + "deviceMoved", "deviceTurned", "deviceShaken", "windowResized", + "keyPressed", "keyReleased", "keyTyped", + "mousePressed", "mouseReleased", "mouseClicked", "doubleClicked", + "mouseMoved", "mouseDragged", "mouseWheel", + "touchStarted", "touchMoved", "touchEnded" + ) + + for f_name in [f for f in event_function_names if event_functions.get(f, None)]: + func = event_functions[f_name] + event_func = global_p5_injection(instance)(func) + setattr(instance, f_name, event_func) +`; + +let placeholder = ` +def setup(): + pass + +def draw(): + pass +`; + +let userCode = ` +def setup(): + createCanvas(200, 250) + background(160) + + +def draw(): + fill("blue") + background(200) + radius = sin(frameCount / 60) * 50 + 50 + ellipse(100, 100, radius, radius) + +`; + +function runCode() { + let code = [ + placeholder, + userCode, + wrapper_content, + 'start_p5(setup, draw, {});', + ].join('\n'); + + if (window.instance) { + window.instance.canvas.remove(); + } + + console.log("Python execution output:"); + pyodide.runPython(code); +} + +languagePluginLoader.then(() => { + pyodide.runPython(` + import io, code, sys + from js import pyodide, p5, window, document + print(sys.version) + `) + + window.runSketchCode = (code) => { + userCode = code; + runCode(); + } + + runCode(); +}); \ No newline at end of file From b488178f5548a54169ad2db7c82d6cb9fcaf2bb8 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Sat, 17 Apr 2021 08:32:06 -0300 Subject: [PATCH 15/17] Example to cover PVector api usage --- docs/examples/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/examples/index.md b/docs/examples/index.md index 0d07105e..d2462647 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -20,3 +20,5 @@ - [sketch_009](https://github.com/berinhard/pyp5js/tree/develop/docs/examples/sketch_009): [**Working with images**](sketch_009/index.html) - [sketch_010](https://github.com/berinhard/pyp5js/tree/develop/docs/examples/sketch_010): [**Complex shapes**](sketch_010/index.html) by [Pedro Queiroga](https://github.com/pedroqueiroga/pqueiroga.github.io/blob/master/curveVertexExample/main.js) + +- [sketch_011](https://github.com/berinhard/pyp5js/tree/develop/docs/examples/sketch_011): [**PVector**](sketch_011/index.html) processing-like implementation by [Alexandre Villares](sketch_011/index.html) From d09360b7c9bc64b2432ea3d952c70f60a7c26d51 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Sat, 17 Apr 2021 08:35:34 -0300 Subject: [PATCH 16/17] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff2f7a49..29764fa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ Development ----------- - Pyodide mode bugfix for missing `P3D` global definition +- Processing-like PVector class under transcrypt mode +- Processing-like PVector class under pyodide mode 0.5.1 ----- From 117fce1f8ed4159ffcc8695536d9f44ddafffb8d Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Sat, 17 Apr 2021 08:36:25 -0300 Subject: [PATCH 17/17] Version bump --- CHANGELOG.md | 3 +++ VERSION | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29764fa1..2668298d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ Development ----------- + +0.5.2 +----- - Pyodide mode bugfix for missing `P3D` global definition - Processing-like PVector class under transcrypt mode - Processing-like PVector class under pyodide mode diff --git a/VERSION b/VERSION index 4b9fcbec..cb0c939a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.1 +0.5.2