From 3454fc99b64c6641d032f35a34f0511d357099fd Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 9 May 2026 21:49:44 +0200 Subject: [PATCH 01/16] add local examples script --- examples/serve_browser_examples.py | 291 +++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 examples/serve_browser_examples.py diff --git a/examples/serve_browser_examples.py b/examples/serve_browser_examples.py new file mode 100644 index 000000000..0b1f6f7d7 --- /dev/null +++ b/examples/serve_browser_examples.py @@ -0,0 +1,291 @@ +""" +Serve browser examples locally. +=============================== + + +A little script to serve pygfx examples on localhost so you can try them in the browser. +This is an iteration of the same script in rendercanvas and wgpu-py although some changes: + +* swapped in pathlib for os.path (mostly) +* avoided pyscript for now, but likely a good idea to use for webworker recovery etc. + +Files are loaded from disk on each request, so you can leave the server running +and just update examples, update pygfx and build the wheel, etc. +""" + +import os +import sys +import webbrowser +from http.server import BaseHTTPRequestHandler, HTTPServer +from pathlib import Path + +import flit + +import fastplotlib + + +# sphinx_gallery_pygfx_docs = 'hidden' +# sphinx_gallery_pygfx_test = 'off' + +# "hosted" upstream deps, will break in the future +wgpu_wheel = "https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.31.0-py3-none-any.whl" # very hacky way to serve this but it does work... +uharfbuzz_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/uharfbuzz-0.54.1-cp310-abi3-pyodide_2025_0_wasm32.whl" # try to get it from the github release, so we don't need to include it... +pygfx_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.16.0-py3-none-any.whl" + +# the pygfx wheel will be listed after this. it might be possible to still get deps from pyproject.toml +fpl_deps = [wgpu_wheel, uharfbuzz_wheel, pygfx_wheel, "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] + +root = Path(__file__).parent.parent.absolute() + +short_version = ".".join(str(i) for i in fastplotlib.version_info[:3]) +wheel_name = f"fastplotlib-{short_version}-py3-none-any.whl" + +example_files = list((root / "examples").glob("**/*.py")) + +def get_html_index(): + """Create a landing page.""" + + examples_list = [f"
  • {name.relative_to(root / "examples")!s}
  • " for name in example_files] + + html = """ + + + + fastplotlib browser examples + + + + + Rebuild the wheel

    + """ + + html += "List of examples that might run in Pyodide:\n" + html += f"
    \n\n" + + html += "\n\n" + return html + + +html_index = get_html_index() + +# TODO: a pyodide example for the compute examples (so we can capture output?) +# modified from _pyodide_iframe.html from rendercanvas, and then again from pygfx +pyodide_compute_template = """ + + + + + {example_script} via Pyodide + + + + + +

    Loading...

    +
    + + Back to list

    + +

    + {docstring} +

    + +
    +

    Output:

    +
    + + + + +""" + + +imageio_patch = """ +from pyodide.http import pyfetch +from pyodide.ffi import run_sync + +async def _imread_async(filename, **kwargs): + # pyodide working version of iio.imread, uses fetch and waiting on it, also replaced the "imageio:" + filename = str(filename) # maybe breaks some Path stuff? + if filename.startswith("imageio:"): + filename = filename.replace("imageio:", "https://raw.githubusercontent.com/imageio/imageio-binaries/master/images/") + + if "." in filename: + kwargs["extension"] = "." + filename.rsplit(".", 1)[-1] + + response = await pyfetch(filename) + res = iio.imread((await response.bytes()), **kwargs) + return res + +def _imread(filename:str, **kwargs): + print(f"Loading image: {filename!r} with kwargs: {kwargs}") + res = run_sync(_imread_async(filename, **kwargs)) + return res +""" + +def patch_imageio_for_pyodide(python_code:str) -> str: + if "iio.imread(" not in python_code: + return python_code + + return imageio_patch + "\n\n" + python_code.replace("iio.imread(", "_imread(") + + + +def build_wheel(): + toml_filename = (root / "pyproject.toml") + # spews more and more with each build... might need to clear stdout or something? + flit.main(["-f", str(toml_filename.resolve()), "build", "--no-use-vcs", "--format", "wheel"]) + wheel_filename = root / "dist" / wheel_name + assert wheel_filename.is_file(), f"{wheel_name} does not exist" + # also copy the wgpu wheel if it's in a repo nearby... so make it a bit less work to update both. + + +def get_docstring_from_py_file(fname): + filename = root / "examples" / fname + docstate = 0 + doc = "" + with open(filename, "rb") as f: + for line in f: + line = line.decode() + if docstate == 0: + if line.lstrip().startswith('"""'): + docstate = 1 + else: + if docstate == 1 and line.lstrip().startswith(("---", "===")): + docstate = 2 + doc = "" + elif '"""' in line: + doc += line.partition('"""')[0] + break + else: + doc += line + + return doc.replace("\n", "
    ") + + +class MyHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/": + self.respond(200, html_index, "text/html") + elif self.path == "/build": + try: + self.respond(200, "Building wheel...
    ", "text/html") + build_wheel() + except Exception as err: + self.respond(500, str(err), "text/plain") + else: + html = f"
    Wheel build: {wheel_name}

    Back to list" + self.respond(200, html, "text/html") + elif self.path.endswith(".whl"): + requested_path = Path(self.path) + filename = root / "dist" / requested_path.name + if os.path.isfile(filename): + with open(filename, "rb") as f: + data = f.read() + self.respond(200, data, "application/octet-stream") + else: + self.respond(404, "wheel not found") + elif self.path.endswith(".html"): + # name = self.path.strip("/") + pyname = self.path.replace(".html", ".py").lstrip("/") + try: + doc = get_docstring_from_py_file(pyname) + deps = [*fpl_deps, f"/{wheel_name}"] + html = pyodide_compute_template.format( + docstring=doc, + example_script=pyname, + # todo: refactor this to a list and maybe get other deps from pyodide.loadPackagesFromImports + dependencies="\n".join( + [f"await micropip.install({dep!r});" for dep in deps] + ), + ) + self.respond(200, html, "text/html") + except Exception as err: + self.respond(404, f"example not found: {err}") + elif self.path.endswith(".py"): + filename = os.path.join(root, "examples", self.path.strip("/")) + if os.path.isfile(filename): + with open(filename, "rb") as f: + data = f.read() + data = patch_imageio_for_pyodide(data.decode()).encode() + self.respond(200, data, "text/plain") + else: + self.respond(404, "py file not found") + + # TODO: we could try to mount a virtual filesystem and fill it... but I think using fetch and serving the files could work easier. + elif self.path.startswith("/home/data/"): + requested_path = Path(self.path) + # this is for the pyodide examples that need to load data files - we mount the examples/data folder to /home/data in pyodide, so we can serve those files here. + filename = root / "examples" / "data" / requested_path.relative_to("/home/data") + if os.path.isfile(filename): + with open(filename, "rb") as f: + data = f.read() + self.respond(200, data, "application/octet-stream") + else: + self.respond(404, "data file not found") + + else: + self.respond(404, "not found") + + def respond(self, code, body, content_type="text/plain"): + self.send_response(code) + self.send_header("Content-type", content_type) + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "Content-Type") + self.end_headers() + if isinstance(body, str): + body = body.encode() + self.wfile.write(body) + + +if __name__ == "__main__": + port = 8000 + if len(sys.argv) > 1: + try: + port = int(sys.argv[-1]) + except ValueError: + pass + + build_wheel() + print("Opening page in web browser ...") + webbrowser.open(f"http://localhost:{port}/") + HTTPServer(("", port), MyHandler).serve_forever() + + From bdb4bdbd16d4d84584f3b0670fcccc5a845ef790 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 10 May 2026 02:27:23 +0200 Subject: [PATCH 02/16] use imgui_bundle for examples --- examples/serve_browser_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/serve_browser_examples.py b/examples/serve_browser_examples.py index 0b1f6f7d7..a5ba1cfc1 100644 --- a/examples/serve_browser_examples.py +++ b/examples/serve_browser_examples.py @@ -33,7 +33,7 @@ pygfx_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.16.0-py3-none-any.whl" # the pygfx wheel will be listed after this. it might be possible to still get deps from pyproject.toml -fpl_deps = [wgpu_wheel, uharfbuzz_wheel, pygfx_wheel, "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] +fpl_deps = [wgpu_wheel, uharfbuzz_wheel, pygfx_wheel, "imgui-bundle", "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] root = Path(__file__).parent.parent.absolute() From acf6504132b9518a823f7ec6b3de31070f7236ff Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 23 May 2026 03:25:58 +0200 Subject: [PATCH 03/16] hack pyodide into the docs --- .gitignore | 1 + docs/source/_static/_pyodide_iframe.html | 63 ++++++++++++++++ docs/source/_static/style.css | 5 ++ docs/source/conf.py | 96 ++++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 docs/source/_static/_pyodide_iframe.html diff --git a/.gitignore b/.gitignore index 383c36cab..2e610c7c5 100644 --- a/.gitignore +++ b/.gitignore @@ -143,3 +143,4 @@ docs/source/_imgui_images/ # generated by docs/source/generate_api.py on every docs build docs/source/api/ docs/source/user_guide/event_tables.rst +docs/source/_static/*.whl diff --git a/docs/source/_static/_pyodide_iframe.html b/docs/source/_static/_pyodide_iframe.html new file mode 100644 index 000000000..6b60815ab --- /dev/null +++ b/docs/source/_static/_pyodide_iframe.html @@ -0,0 +1,63 @@ + + + + + + Fastplotlib examples in the browser using Pyodide + + + + + +

    click the button to load pyodide

    + +
    + +
    +

    Output:

    +
    + + + + \ No newline at end of file diff --git a/docs/source/_static/style.css b/docs/source/_static/style.css index e69de29bb..8cb58c477 100644 --- a/docs/source/_static/style.css +++ b/docs/source/_static/style.css @@ -0,0 +1,5 @@ +section[id^="interactive-example"] iframe { + width: 100%; + height: 520px; + border: none; +} diff --git a/docs/source/conf.py b/docs/source/conf.py index 4a9cbe8ab..95b8dc180 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -50,6 +50,94 @@ "generate_api", ] +# note this is largely copied from the pygfx PR branch: https://github.com/pygfx/pygfx/pull/1273 +# -- Build wheel so Pyodide examples can use exactly this version of fpl ----------------------------------------------------- +import subprocess +import shutil + +short_version = ".".join(str(i) for i in fastplotlib.version_info[:3]) +wheel_name = f"fastplotlib-{short_version}-py3-none-any.whl" + +# Build the wheel +subprocess.run([sys.executable, "-m", "build", "-nw"], cwd=ROOT_DIR) +wheel_filename = os.path.join(ROOT_DIR, "dist", wheel_name) +assert os.path.isfile(wheel_filename), f"{wheel_name} does not exist" + +# Copy into static +# TODO: you can use --outdir on the build command directly. also use the html_static_path in this namespace +print("Copy wheel to _static dir") +shutil.copy( + wheel_filename, + os.path.join(ROOT_DIR, "docs", "source", "_static", wheel_name), +) + +# -- Sphix Gallery ----------------------------------------------------- + +## pyodide demos, adapted from wgpu, adapted from rendercanvas... might make sense to put this in the scaper? +iframe_placeholder_rst = """ +.. only:: html + + Interactive example + ------------------- + + Try this example in your browser using Pyodide. Might not work with all examples and all devices. Check the output and your browser's console for details. + + .. raw:: html + + +""" +python_files = {} + +# I have a feeling this import might be really sketchy to have on CI... but hey - this is a hack for a hack +from examples.server_browser_examples import patch_imageio_for_pyodide + + +def add_pyodide_to_examples(app): + if app.builder.name != "html": + return + + gallery_dir = ROOT_DIR / "docs" / "source" / "_gallery" + example_files = gallery_dir.glob("**/*.py") + + for py_file in example_files: + fname = py_file.name + with open(py_file, "rb") as f: + py = f.read().decode() + py = patch_imageio_for_pyodide(py) + if fname: + print("Adding Pyodide example to", fname) + fname_rst = py_file.with_suffix(".rst") + # Update rst file + rst = iframe_placeholder_rst.replace("example.py", fname) + # we likely don't want append here? + with open(fname_rst, "ab") as f: + # TODO: skip if it already ends with the placeholder? otherwise the append will keep on appending (we have to hook this into the gen_rst to skip if possible?) + f.write(rst.encode()) + python_files[py_file.relative_to(gallery_dir)] = py + +def add_files_to_run_pyodide_examples(app, exception): + if app.builder.name != "html": + return + + gallery_build_dir = os.path.join(app.outdir, "_gallery") + + # Write html file that can load pyodide examples + with open( + os.path.join(ROOT_DIR, "docs", "source", "_static", "_pyodide_iframe.html"), "rb" + ) as f: + html = f.read().decode() + html = html.replace('"fastplotlib"', f'"../_static/{wheel_name}"') + with open(os.path.join(gallery_build_dir, "pyodide.html"), "wb") as f: + f.write(html.encode()) + + # Write the python files + for fname, py in python_files.items(): + print("Writing", fname) + with open(os.path.join(gallery_build_dir, fname), "wb") as f: + f.write(py.encode()) + + + sphinx_gallery_conf = { "gallery_dirs": "_gallery", "notebook_extensions": {}, # remove the download notebook button @@ -150,3 +238,11 @@ "rendercanvas": ("https://rendercanvas.readthedocs.io/stable/", None), # "fastplotlib": ("https://www.fastplotlib.org/", None), } + +html_css_files = [ + "style.css", +] + +def setup(app): + app.connect("builder-inited", add_pyodide_to_examples) + app.connect("build-finished", add_files_to_run_pyodide_examples) From eb2544cd1881b5091d765e3f2da7a91950c70f8d Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 21 Sep 2026 22:46:52 +0200 Subject: [PATCH 04/16] update links --- docs/source/_static/_pyodide_iframe.html | 8 ++++---- examples/serve_browser_examples.py | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/source/_static/_pyodide_iframe.html b/docs/source/_static/_pyodide_iframe.html index 6b60815ab..74cb95231 100644 --- a/docs/source/_static/_pyodide_iframe.html +++ b/docs/source/_static/_pyodide_iframe.html @@ -4,7 +4,7 @@ Fastplotlib examples in the browser using Pyodide - + @@ -38,11 +38,11 @@

    click the button to load pyodide

    await pyodide.loadPackage("micropip"); const micropip = pyodide.pyimport("micropip"); await micropip.install([ - 'https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.31.0-py3-none-any.whl', + 'https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.32.0-py3-none-any.whl', 'imgui-bundle', - "https://pygfx--1273.org.readthedocs.build/1273/_static/uharfbuzz-0.54.1-cp310-abi3-pyodide_2025_0_wasm32.whl", + "uharfbuzz", 'hsluv', 'pylinalg', 'jinja2', 'httpx', 'trimesh', 'gltflib', 'imageio', - "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.16.0-py3-none-any.whl" + "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.17.0-py3-none-any.whl" ]); await micropip.install("fastplotlib") // this one should be replaced by the wheel during docs build. await pyodide.loadPackagesFromImports(pythonCode); // additional non pypi packages from the examples... like imageio (not patched...) diff --git a/examples/serve_browser_examples.py b/examples/serve_browser_examples.py index a5ba1cfc1..3fdb734a8 100644 --- a/examples/serve_browser_examples.py +++ b/examples/serve_browser_examples.py @@ -28,12 +28,11 @@ # sphinx_gallery_pygfx_test = 'off' # "hosted" upstream deps, will break in the future -wgpu_wheel = "https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.31.0-py3-none-any.whl" # very hacky way to serve this but it does work... -uharfbuzz_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/uharfbuzz-0.54.1-cp310-abi3-pyodide_2025_0_wasm32.whl" # try to get it from the github release, so we don't need to include it... -pygfx_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.16.0-py3-none-any.whl" +wgpu_wheel = "https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.32.0-py3-none-any.whl" # very hacky way to serve this but it does work... +pygfx_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.17.0-py3-none-any.whl" # the pygfx wheel will be listed after this. it might be possible to still get deps from pyproject.toml -fpl_deps = [wgpu_wheel, uharfbuzz_wheel, pygfx_wheel, "imgui-bundle", "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] +fpl_deps = [wgpu_wheel, pygfx_wheel, "imgui-bundle", "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] root = Path(__file__).parent.parent.absolute() @@ -52,7 +51,7 @@ def get_html_index(): fastplotlib browser examples - + @@ -76,7 +75,7 @@ def get_html_index(): {example_script} via Pyodide - + From b58e92630328c8bb9bcc019380a35e671f9252f7 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 7 Oct 2026 22:47:37 +0200 Subject: [PATCH 05/16] fix shader patching in one specific case --- fastplotlib/graphics/shaders/_highlight_shaders.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fastplotlib/graphics/shaders/_highlight_shaders.py b/fastplotlib/graphics/shaders/_highlight_shaders.py index 6b114bf7a..ed96a55f0 100644 --- a/fastplotlib/graphics/shaders/_highlight_shaders.py +++ b/fastplotlib/graphics/shaders/_highlight_shaders.py @@ -248,8 +248,7 @@ def get_bindings(self, wobject, shared, scene): return {0: group0} def get_code(self): - wgsl = super().get_code() - + wgsl = super().get_code().replace("\r\n", "\n") # newlines on windows were \r\n breaking the matching here # Pre-sample: inject visibility LUT remapping. fpl_texcoord is always # declared here so the highlight block below can safely reference it # regardless of whether visibility is active From ad1c2c605a178e95760772b1f215da7ffde8a6e2 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 7 Oct 2026 22:48:15 +0200 Subject: [PATCH 06/16] test with local wheels --- examples/serve_browser_examples.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/serve_browser_examples.py b/examples/serve_browser_examples.py index 3fdb734a8..d427d3785 100644 --- a/examples/serve_browser_examples.py +++ b/examples/serve_browser_examples.py @@ -25,11 +25,15 @@ # sphinx_gallery_pygfx_docs = 'hidden' -# sphinx_gallery_pygfx_test = 'off' +# sphinx_gallery_pygfx_test = 'off'< # "hosted" upstream deps, will break in the future -wgpu_wheel = "https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.32.0-py3-none-any.whl" # very hacky way to serve this but it does work... -pygfx_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.17.0-py3-none-any.whl" +# wgpu_wheel = "https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.32.0-py3-none-any.whl" # very hacky way to serve this but it does work... +# pygfx_wheel = "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.17.0-py3-none-any.whl" + +# local wheels (in dist/) +wgpu_wheel = "wgpu-0.32.0-py3-none-any.whl" +pygfx_wheel = "pygfx-0.17.0-py3-none-any.whl" # the pygfx wheel will be listed after this. it might be possible to still get deps from pyproject.toml fpl_deps = [wgpu_wheel, pygfx_wheel, "imgui-bundle", "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] From d762e8df414284235a2176e8b380bcb1306a7100 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 8 Oct 2026 20:39:33 +0200 Subject: [PATCH 07/16] copy local wgpu and pygfx wheels --- examples/serve_browser_examples.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/examples/serve_browser_examples.py b/examples/serve_browser_examples.py index d427d3785..2b19152d6 100644 --- a/examples/serve_browser_examples.py +++ b/examples/serve_browser_examples.py @@ -177,6 +177,35 @@ def build_wheel(): wheel_filename = root / "dist" / wheel_name assert wheel_filename.is_file(), f"{wheel_name} does not exist" # also copy the wgpu wheel if it's in a repo nearby... so make it a bit less work to update both. + try: + # would also be fun if this actually built the wheel too... but that might be too much atm. + wgpu_wheel_filename = root.parent / "wgpu-py" / "dist" / wgpu_wheel + if wgpu_wheel_filename.is_file(): + # TODO: can use Pathlib copy instead? + target = root / "dist" / wgpu_wheel + with open(wgpu_wheel_filename, "rb") as src, open(target, "wb") as dst: + dst.write(src.read()) + print(f"Copied {wgpu_wheel} to dist folder.") + else: + print(f"{wgpu_wheel} not found in nearby repo, skipping copy. falling 'back' to hosted wheel") + # maye fall back to online wheel? + except Exception as e: + print(f"Error copying {wgpu_wheel}: {e}") + # alsp pygfx + try: + # would also be fun if this actually built the wheel too... but that might be too much atm. + pygfx_wheel_filename = root.parent / "pygfx" / "dist" / pygfx_wheel + if pygfx_wheel_filename.is_file(): + # TODO: can use Pathlib copy instead? + target = root / "dist" / pygfx_wheel + with open(pygfx_wheel_filename, "rb") as src, open(target, "wb") as dst: + dst.write(src.read()) + print(f"Copied {pygfx_wheel} to dist folder.") + else: + print(f"{pygfx_wheel} not found in nearby repo, skipping copy. falling 'back' to hosted wheel") + # maye fall back to online wheel? + except Exception as e: + print(f"Error copying {pygfx_wheel}: {e}") def get_docstring_from_py_file(fname): From 0e1b0e42f80d3b8dec7b63c782c2df372e8e09c8 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 12 Oct 2026 22:45:36 +0200 Subject: [PATCH 08/16] cleanup reproducer --- examples/serve_browser_examples.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/serve_browser_examples.py b/examples/serve_browser_examples.py index 2b19152d6..e0a21ec35 100644 --- a/examples/serve_browser_examples.py +++ b/examples/serve_browser_examples.py @@ -35,8 +35,11 @@ wgpu_wheel = "wgpu-0.32.0-py3-none-any.whl" pygfx_wheel = "pygfx-0.17.0-py3-none-any.whl" +rendercanvas_wheel = "rendercanvas-2.7.2-py3-none-any.whl" # still moved manually ... + + # the pygfx wheel will be listed after this. it might be possible to still get deps from pyproject.toml -fpl_deps = [wgpu_wheel, pygfx_wheel, "imgui-bundle", "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] +fpl_deps = [rendercanvas_wheel, wgpu_wheel, pygfx_wheel, "imgui-bundle", "hsluv", "pylinalg", "jinja2", "httpx", "trimesh", "gltflib", "imageio"] root = Path(__file__).parent.parent.absolute() From db25ded9b283850db3b643d642bd1ba6e4a3cd5e Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 14 Oct 2026 21:56:46 +0200 Subject: [PATCH 09/16] preview rendercanvas fix --- docs/source/_static/_pyodide_iframe.html | 5 +++-- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/_static/_pyodide_iframe.html b/docs/source/_static/_pyodide_iframe.html index 74cb95231..993c6df46 100644 --- a/docs/source/_static/_pyodide_iframe.html +++ b/docs/source/_static/_pyodide_iframe.html @@ -38,11 +38,12 @@

    click the button to load pyodide

    await pyodide.loadPackage("micropip"); const micropip = pyodide.pyimport("micropip"); await micropip.install([ - 'https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.32.0-py3-none-any.whl', + 'https://rendercanvas--258.org.readthedocs.build/258/_static/rendercanvas-2.7.2-py3-none-any.whl', // fixes to run_sync for right click menus + 'https://wgpu-py--753.org.readthedocs.build/en/753/_static/wgpu-0.32.0-py3-none-any.whl', // WIP pyodide.ffi bindings 'imgui-bundle', "uharfbuzz", 'hsluv', 'pylinalg', 'jinja2', 'httpx', 'trimesh', 'gltflib', 'imageio', - "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.17.0-py3-none-any.whl" + "https://pygfx--1273.org.readthedocs.build/1273/_static/pygfx-0.17.0-py3-none-any.whl" // couple of tweaks for fonts ]); await micropip.install("fastplotlib") // this one should be replaced by the wheel during docs build. await pyodide.loadPackagesFromImports(pythonCode); // additional non pypi packages from the examples... like imageio (not patched...) diff --git a/pyproject.toml b/pyproject.toml index be331b5fe..b5905a599 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,7 @@ Repository = "https://github.com/fastplotlib/fastplotlib" # ===== Building [build-system] -requires = ["flit_core >=3.2,<4"] +requires = ["flit_core >=3.2,<5"] build-backend = "flit_core.buildapi" # ===== Tooling From 823935406419b6f704f441607d60384b48fe240d Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 14 Oct 2026 23:24:51 +0200 Subject: [PATCH 10/16] little more space --- docs/source/_static/_pyodide_iframe.html | 2 +- docs/source/_static/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/_static/_pyodide_iframe.html b/docs/source/_static/_pyodide_iframe.html index 993c6df46..7c24ff4ad 100644 --- a/docs/source/_static/_pyodide_iframe.html +++ b/docs/source/_static/_pyodide_iframe.html @@ -13,7 +13,7 @@

    click the button to load pyodide

    -
    +

    Output: