From 568aca01245c0bca202c034f665c6754d9ed9a0d Mon Sep 17 00:00:00 2001 From: John Christensen Date: Mon, 5 Feb 2024 14:59:56 -0600 Subject: [PATCH] Cleanup documentation, add processo usage --- README.md | 79 ++++++++++++++++---- examples/{proceso_test.py => full_screen.py} | 0 examples/helper_test.py | 72 +++++++++++++----- examples/p5js.pyi | 8 -- examples/processo_figure_8.py | 25 +++++++ examples/processo_simple_shapes.py | 29 +++++++ examples/string.py | 8 ++ 7 files changed, 180 insertions(+), 41 deletions(-) rename examples/{proceso_test.py => full_screen.py} (100%) delete mode 100644 examples/p5js.pyi create mode 100644 examples/processo_figure_8.py create mode 100644 examples/processo_simple_shapes.py diff --git a/README.md b/README.md index 0fad944..f26c179 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,17 @@ ## Examples -Live Examples: -- https://johnedchristensen.github.io/diver/src/?filename=sketches/impossible_object.py -- https://johnedchristensen.github.io/diver/src/?filename=sketches/rainbow.py -- https://johnedchristensen.github.io/diver/src/?filename=sketches/e_field.py -- https://johnedchristensen.github.io/diver/src/?filename=sketches/squares.py ### Interactive -- https://johnedchristensen.github.io/diver/src/?filename=sketches/string.py + +Examples: +- [Using processo](https://johnedchristensen.github.io/diver/?filename=examples/processo_figure_8.py) +- [Impossible Triangle](https://johnedchristensen.github.io/diver/?filename=examples/impossible_object.py) +- [Rainbow Wave](https://johnedchristensen.github.io/diver/?filename=examples/rainbow.py) +- [Swirling Squares](https://johnedchristensen.github.io/diver/?filename=examples/squares.py) +- [Simulated String](https://johnedchristensen.github.io/diver/src/?filename=sketches/string.py) ## About -A simple python package that makes it easy to share interactive visuals on the web. +A python package that aimes to make it easy to share interactive visuals online. + Uses Pyodide to run python directly in the browser, meaning code can be shared with others without any need to download/install/configure python. It also can be served from a static website server, meaning it is simple to embed visuals on personal websites/blogs. @@ -20,7 +22,9 @@ It also can be served from a static website server, meaning it is simple to embe - [x] Render static images to a canvas element - [x] live reloading development server - [x] Render animated canvas/WebGL -- [ ] pythonic drawing API (with beginner/user friendly documentation/examples) +- [x] pythonic drawing API (with beginner/user friendly documentation/examples) + - [x] [processo](https://github.com/nickmcintyre/proceso) offers a great python binding for p5js. It is now installed by default. + - Examples: https://proceso.cc/examples/creative_coding/simple_shapes - [x] Write code in browser - [-] in browser LSP features like inline docs, autocomplete, linting, type checking - [ ] Human friendly documentation (with live running/editable examples of course) @@ -31,16 +35,63 @@ It also can be served from a static website server, meaning it is simple to embe ## Usage The current state of the library is in very active development. Expect breaking changes. -### Pixel level drawing -[This example (rainbow.py)](https://johnedchristensen.github.io/diver/src/?filename=rainbow.py) shows some basic usage. It specifies each pixel on the canvas, so you can draw anything you want directly this way. It isn't very performant this way, so the resolution needs to be pretty low to run smoothly. -### 2D Canvas API -[This example (squares.py)](https://johnedchristensen.github.io/diver/src/?filename=squares.py) draws to canvas using the JavaScript canvas API. This is much more performant, and can run at higher resolutions/frame rates. To use this mode you'll need to know (or learn) how to use the [canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) +## Basics +Use "Show Code" button to view/edit the code yourself! +After making changes click "Run" or use Ctrl-Enter to run your code. + +[Get started](https://johnedchristensen.github.io/diver/?filename=examples/processo_simple_shapes.py) with a drawing example using p5js (via processo). +### p5.js +> p5.js is a JavaScript library for creative coding, with a focus on making coding accessible and inclusive for artists, designers, educators, beginners, and anyone else! p5.js is free and open-source because we believe software, and the tools to learn it, should be accessible to everyone.vascript library +https://p5js.org/ + +p5.js is a great project, but is limited to using javascipt. Using Pyodide, python code can now run alongisde javascript code in your browser, and use all the functionality that p5.js offers. An additional library needs to handle translations between p5.js and python, and processo does just that! +### processo +The [processo](https://github.com/nickmcintyre/proceso) lets you use [p5js](https://p5js.org/) from python. +#### Example +[Try it out](https://johnedchristensen.github.io/diver/?filename=examples/processo_simple_shapes.py) +```python +from proceso import Sketch + + +p5 = Sketch() +p5.describe("A rectangle, circle, triangle, and flower drawn in pink on a gray background.") + +# Create the canvas +p5.create_canvas(720, 400) +p5.background(200) + +# Set colors +p5.fill(204, 101, 192, 127) +p5.stroke(127, 63, 120) + +# A rectangle +p5.rect(40, 120, 120, 40) +# A circle +p5.circle(240, 240, 80) +# A triangle +p5.triangle(300, 100, 320, 100, 310, 80) + +# A design for a simple flower +p5.translate(580, 200) +p5.no_stroke() +for _ in range(10): + p5.ellipse(0, 30, 20, 80) + p5.rotate(p5.PI / 5) +``` +From processo documentation: +Checkout more processo examples: https://proceso.cc/examples/creative_coding/ + +### Lower level APIs +These methods are a bit more complex, but can be useful if you want to work more directly with your visuals. +#### Pixel level drawing +[This example (rainbow.py)](https://johnedchristensen.github.io/diver/src/?filename=rainbow.py) shows some basic usage. It specifies each pixel on the canvas, so you can draw anything you want directly this way. It isn't very performant this way, so the resolution needs to be pretty low to run smoothly. + -### Best of both worlds (for me) +#### 2D Canvas API -A more user friendly API is planned that will have better documentation integration to make it easier to find what functions are available straight from the editor. +[This example (squares.py)](https://johnedchristensen.github.io/diver/src/?filename=squares.py) draws to canvas using the JavaScript canvas API. This is much more performant than drawing pixel by pixel, and can run at higher resolutions/frame rates. To use this mode you'll need to know (or learn) how to use the [canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) ## Limitations diff --git a/examples/proceso_test.py b/examples/full_screen.py similarity index 100% rename from examples/proceso_test.py rename to examples/full_screen.py diff --git a/examples/helper_test.py b/examples/helper_test.py index 016fc18..72bfdc7 100644 --- a/examples/helper_test.py +++ b/examples/helper_test.py @@ -22,12 +22,6 @@ class SketchConfig(TypedDict, total=False): @dataclass class Sketch(pSketch): - # processo seems to not refresh self.window_width and height well - def debug_screen(self, desc): - print(f"{desc:20},js :{window.innerWidth:4},{window.innerHeight:4}") - print(f"{desc:20},p5 window:{self.window_width:4},{self.window_height:4}") - print(f"{desc:20},p5 canvas:{self.width:4},{self.height:4}") - def __init__(self, **options: Unpack[SketchConfig]): super().__init__() self.margin = options.get("margin", 50) @@ -36,6 +30,12 @@ def __init__(self, **options: Unpack[SketchConfig]): self._update_system_variables() + # processo seems to not refresh self.window_width and height well + def debug_screen(self, desc): + print(f"{desc:20},js :{window.innerWidth:4},{window.innerHeight:4}") + print(f"{desc:20},p5 window:{self.window_width:4},{self.window_height:4}") + print(f"{desc:20},p5 canvas:{self.width:4},{self.height:4}") + def square_draw_area(self): """set the width and height as large as possible while still square""" figure_width = window.innerWidth - 2 * self.margin @@ -68,6 +68,26 @@ def setup_screen(self): self.translate(self.width // 2, self.height // 2) self.scale(1, -1) + # + def text( + self, + txt: str, + x: float, + y: float, + x2: float | None = None, + y2: float | None = None, + ) -> None: + """Override p5js text to handle flipping y axis for text""" + self.push() + if self.origin == Origin.CENTER: + self.scale(1, -1) + y = -y + if y2 is not None: + y2 = -y2 + pSketch.text(self, txt, x, y, x2, y2) + self.pop() + return + def diver_setup(self): self.setup_screen() self.user_setup() @@ -93,48 +113,62 @@ def setup(): def draw(): p5.clear() + p5.push() # grid lines num_ticks = 20 - # don't let tick's get too small + tick_size = p5.figure_width // num_ticks major_tick_freq = 5 + # start at 0, to make sure the grid goes through zero - for i, x in enumerate(range(0, p5.screen_right, tick_size)): + for i, y in enumerate(range(0, p5.screen_right, tick_size)): if i % major_tick_freq == 0: p5.stroke("lightgrey") else: p5.stroke("grey") - p5.line(x, p5.screen_top, x, p5.screen_bottom) - p5.line(-x, p5.screen_top, -x, p5.screen_bottom) + p5.line(y, p5.screen_top, y, p5.screen_bottom) + p5.line(-y, p5.screen_top, -y, p5.screen_bottom) - for i, x in enumerate(range(0, p5.screen_right, tick_size)): + for i, y in enumerate(range(0, p5.screen_top, tick_size)): if i % major_tick_freq == 0: p5.stroke("lightgrey") else: p5.stroke("grey") - p5.line(p5.screen_left, x, p5.screen_right, x) - p5.line(p5.screen_left, -x, p5.screen_right, -x) + p5.line(p5.screen_left, y, p5.screen_right, y) + p5.line(p5.screen_left, -y, p5.screen_right, -y) # Axis p5.stroke("white") p5.line(0, p5.screen_top, 0, p5.screen_bottom) p5.line(p5.screen_left, 0, p5.screen_right, 0) + # Big circle + p5.stroke_weight(2) t = p5.millis() / 2000 r = sin(t) * p5.figure_width // 2 p5.fill("blue") p5.circle(0, 0, 2 * r) - p5.scale(1, -1) p5.fill("white") - p5.text_size(tick_size) - p5.text(f"{(r / tick_size):.1f}", r, 0) - p5.text(f"{(r / tick_size):.1f}", 0, r) + # moving labels + + p5.text_size(tick_size * 2) + p5.text_descent + + p5.stroke("black") p5.fill("red") - p5.circle(0, r, tick_size / 4) - p5.fill("green") + p5.text(f"{(r / tick_size):.1f}", r, 0) + p5.no_stroke() p5.circle(r, 0, tick_size / 4) + p5.stroke("black") + p5.fill("green") + p5.text(f"{(r / tick_size):.1f}", 0, r) + p5.no_stroke() + p5.circle(0, r, tick_size / 4) + + p5.pop() + p5.start(setup, draw) diff --git a/examples/p5js.pyi b/examples/p5js.pyi deleted file mode 100644 index 4ed410c..0000000 --- a/examples/p5js.pyi +++ /dev/null @@ -1,8 +0,0 @@ -class Color: - r: int - g: int - b: int - a: int - -class p5: - def stroke(self, c: Color): ... diff --git a/examples/processo_figure_8.py b/examples/processo_figure_8.py new file mode 100644 index 0000000..3dfaa15 --- /dev/null +++ b/examples/processo_figure_8.py @@ -0,0 +1,25 @@ +# from https://proceso.cc/examples/creative_coding/ +from proceso import Sketch + + +p5 = Sketch() +p5.describe("A purple circle moving in a figure eight on a light blue background.") + + +def setup(): + p5.create_canvas(400, 400) + p5.background("dodgerblue") + + +def draw(): + p5.translate(p5.width * 0.5, p5.height * 0.5) + x = 80 * p5.cos(0.1 * p5.frame_count) + y = 40 * p5.sin(0.2 * p5.frame_count) + p5.stroke("white") + p5.fill("orchid") + p5.circle(x, y, 20) + if p5.is_mouse_pressed: + p5.background("dodgerblue") + + +p5.run_sketch(setup=setup, draw=draw) diff --git a/examples/processo_simple_shapes.py b/examples/processo_simple_shapes.py new file mode 100644 index 0000000..0581aa4 --- /dev/null +++ b/examples/processo_simple_shapes.py @@ -0,0 +1,29 @@ +# from https://proceso.cc/examples/creative_coding/simple_shapes +# originally adapted from https://p5js.org/examples/hello-p5-simple-shapes.html +from proceso import Sketch + + +p5 = Sketch() +p5.describe("A rectangle, circle, triangle, and flower drawn in pink on a gray background.") + +# Create the canvas +p5.create_canvas(720, 400) +p5.background(200) + +# Set colors +p5.fill(204, 101, 192, 127) +p5.stroke(127, 63, 120) + +# A rectangle +p5.rect(40, 120, 120, 40) +# A circle +p5.circle(240, 240, 80) +# A triangle +p5.triangle(300, 100, 320, 100, 310, 80) + +# A design for a simple flower +p5.translate(580, 200) +p5.no_stroke() +for _ in range(10): + p5.ellipse(0, 30, 20, 80) + p5.rotate(p5.PI / 5) diff --git a/examples/string.py b/examples/string.py index 66ccc56..8a7f624 100644 --- a/examples/string.py +++ b/examples/string.py @@ -148,4 +148,12 @@ def onMouseUp(_: MouseEvent): onMouseMove_proxy = create_proxy(onMouseMove) onMouseUp_proxy = create_proxy(onMouseUp) +text = document.getElementById("simulation-description") +if text is None: + text = document.createElement("div") + text.id = "simulation-description" + text.textContent = "Click and drag string up/down" + document.body.appendChild(text) # type: ignore + + cm.canvas.addEventListener("mousedown", create_proxy(onMouseDown))