diff --git a/docs/gen_drone_renders.py b/docs/gen_drone_renders.py new file mode 100644 index 00000000..c84e694b --- /dev/null +++ b/docs/gen_drone_renders.py @@ -0,0 +1,108 @@ +"""Render one image of every supported drone for the documentation. + +This script is executed by the mkdocs-gen-files plugin during ``mkdocs build`` or +``mkdocs serve``. The images are written to ``img/drones/.png`` in the virtual docs tree +and are never committed. Rendering is headless via EGL on Linux and CGL on macOS unless +``MUJOCO_GL`` is already set. +""" + +import logging +import os +import sys +from pathlib import Path + +if sys.platform == "linux": + os.environ.setdefault("MUJOCO_GL", "egl") + # libglvnd only searches /usr/share/glvnd, which does not exist on a minimal CI runner. Append + # the Mesa driver of this environment so EGL still finds a (software) device there. + _vendors = Path(sys.prefix, "share/glvnd/egl_vendor.d") + if _vendors.is_dir(): + os.environ.setdefault( + "__EGL_VENDOR_LIBRARY_DIRS", f"/usr/share/glvnd/egl_vendor.d:{_vendors}" + ) +elif sys.platform == "darwin": + os.environ.setdefault("MUJOCO_GL", "cgl") + +try: + import mkdocs_gen_files +except ImportError: + pass # not running in a docs environment — nothing to generate +else: + import imageio.v3 as iio + import mujoco + import numpy as np + + from crazyflow.drones import Drone + + log = logging.getLogger("mkdocs.plugins.gen_drone_renders") + + DRONE_DIR = Path("crazyflow/drones") + WIDTH, HEIGHT = 2000, 1500 + MARGIN = 80 + + def render(name: str) -> np.ndarray: + """Render a drone in front of a transparent background, cropped to its bounding box.""" + spec = mujoco.MjSpec.from_file(str(DRONE_DIR / f"{name}.xml")) + for body in list(spec.worldbody.bodies): + if body.name != "drone": + spec.delete(body) + for material in spec.materials: + if 0 < material.rgba[3] < 1: + material.rgba[3] = 1 + spec.visual.global_.offwidth = WIDTH + spec.visual.global_.offheight = HEIGHT + spec.visual.quality.shadowsize = 8192 + spec.visual.quality.offsamples = 16 + spec.visual.headlight.ambient = [0.35, 0.35, 0.35] + spec.visual.headlight.diffuse = [0.5, 0.5, 0.5] + spec.worldbody.add_light( + pos=[0.4, -0.3, 0.6], dir=[-0.55, 0.4, -0.8], diffuse=[0.6, 0.6, 0.6], castshadow=True + ) + spec.worldbody.add_light( + pos=[-0.4, 0.3, 0.3], dir=[0.6, -0.45, -0.45], diffuse=[0.3, 0.3, 0.3], castshadow=False + ) + model = spec.compile() + data = mujoco.MjData(model) + mujoco.mj_forward(model, data) + + camera = mujoco.MjvCamera() + camera.lookat[:] = model.body("drone").pos + camera.distance, camera.azimuth, camera.elevation = 0.28, 135, -28 + option = mujoco.MjvOption() + option.geomgroup[:] = 0 + option.geomgroup[2] = 1 + + renderer = mujoco.Renderer(model, height=HEIGHT, width=WIDTH) + try: + renderer.update_scene(data, camera, option) + rgb = renderer.render() + renderer.enable_segmentation_rendering() + renderer.update_scene(data, camera, option) + mask = renderer.render()[..., 0] != -1 + finally: + renderer.close() + + rows, cols = np.nonzero(mask) + y0, y1 = max(rows.min() - MARGIN, 0), min(rows.max() + MARGIN, HEIGHT) + x0, x1 = max(cols.min() - MARGIN, 0), min(cols.max() + MARGIN, WIDTH) + alpha = mask.astype(np.uint8) * 255 + return np.dstack([rgb, alpha])[y0:y1, x0:x1] + + def gl_available() -> bool: + """Check for a usable OpenGL context, so an unusable one is reported only once.""" + try: + mujoco.Renderer(mujoco.MjModel.from_xml_string(""), 4, 4).close() + except Exception as e: + log.warning(f"Could not create an OpenGL context, skipping the drone renders: {e!r}") + return False + return True + + if gl_available(): + for drone in Drone: + try: + image = render(drone) + except Exception as e: + log.warning(f"Could not render drone '{drone}': {e!r}") + continue + with mkdocs_gen_files.open(f"img/drones/{drone}.png", "wb") as fd: + fd.write(iio.imwrite("", image, extension=".png")) diff --git a/docs/index.md b/docs/index.md index ccafa25e..e0c373a0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,18 +85,33 @@ Crazyflow is a research simulator for Crazyflie-style quadrotors that runs milli ## Supported drones - All available drones are members of `crazyflow.Drone`. See [Available drone configurations](user-guide/dynamics/parametrize.md#available-drone-configurations) for the full list and how to select one. diff --git a/docs/javascripts/showcase.js b/docs/javascripts/showcase.js index 91cab3f7..740f8e02 100644 --- a/docs/javascripts/showcase.js +++ b/docs/javascripts/showcase.js @@ -1,7 +1,8 @@ -function initializeCarousel() { - const carouselContainer = document.querySelector(".carousel-container"); +const carousels = []; +let focusedCarousel = null; - if (!carouselContainer || carouselContainer.dataset.carouselReady === "true") { +function initializeCarousel(carouselContainer) { + if (carouselContainer.dataset.carouselReady === "true") { return; } @@ -66,17 +67,12 @@ function initializeCarousel() { nextBtn.addEventListener("click", nextSlide); } - document.addEventListener("keydown", (event) => { - if (!document.querySelector(".carousel-container")) { - return; - } - - if (event.key === "ArrowLeft") { - prevSlide(); - } else if (event.key === "ArrowRight") { - nextSlide(); - } - }); + const api = { container: carouselContainer, nextSlide, prevSlide }; + const focus = () => { + focusedCarousel = api; + }; + carouselContainer.addEventListener("pointerenter", focus); + carouselContainer.addEventListener("focusin", focus); let touchStartX = 0; let touchEndX = 0; @@ -84,9 +80,10 @@ function initializeCarousel() { carousel.addEventListener( "touchstart", (event) => { + focus(); touchStartX = event.changedTouches[0].screenX; }, - false + { passive: true } ); carousel.addEventListener( @@ -95,7 +92,7 @@ function initializeCarousel() { touchEndX = event.changedTouches[0].screenX; handleSwipe(); }, - false + { passive: true } ); function handleSwipe() { @@ -113,13 +110,37 @@ function initializeCarousel() { } } + carousels.push(api); goToSlide(0); } +function initializeCarousels() { + for (let i = carousels.length - 1; i >= 0; i--) { + if (!carousels[i].container.isConnected) { + carousels.splice(i, 1); + } + } + document.querySelectorAll(".carousel-container").forEach(initializeCarousel); +} + +document.addEventListener("keydown", (event) => { + if (carousels.length === 0 || (event.key !== "ArrowLeft" && event.key !== "ArrowRight")) { + return; + } + + const target = carousels.includes(focusedCarousel) ? focusedCarousel : carousels[0]; + + if (event.key === "ArrowLeft") { + target.prevSlide(); + } else { + target.nextSlide(); + } +}); + if (typeof document$ !== "undefined") { - document$.subscribe(initializeCarousel); + document$.subscribe(initializeCarousels); } else if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", initializeCarousel); + document.addEventListener("DOMContentLoaded", initializeCarousels); } else { - initializeCarousel(); + initializeCarousels(); } diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 29da1f7f..127f3bca 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -102,6 +102,24 @@ text-align: center; } +.carousel-slide-image { + background: linear-gradient(180deg, #fafafa, #d9d9d9); +} + +.carousel-slide img { + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 1.5rem 1.5rem 3rem; + display: block; + object-fit: contain; +} + +.carousel-slide-image .carousel-caption { + background: none; + color: #1a1a1a; +} + .carousel-btn { position: absolute; top: 50%; @@ -219,16 +237,6 @@ } } -/* Drone grid */ -.drone-grid table { - margin: 0 auto; -} - -.drone-grid td { - vertical-align: middle; - text-align: center; -} - /* Example page media */ .example-media, .example-media-grid { diff --git a/docs/user-guide/adding-drones.md b/docs/user-guide/adding-drones.md index 1689abd2..4f60e965 100644 --- a/docs/user-guide/adding-drones.md +++ b/docs/user-guide/adding-drones.md @@ -34,6 +34,8 @@ Add `[.core]`, `[.state2attitude]`, `[.attitude2force_torque]` Add the platform to the table in [Parametrization](dynamics/parametrize.md#available-drone-configurations). +The docs build renders every `Drone` member from its MJCF file, so a new drone gets an image at `img/drones/.png` without further work. Add a slide for it to the carousel on the [home page](../index.md#supported-drones). + ## 5. Run the tests The test suite parametrizes over `Drone` and over the supported drone-dynamics pairs, so the new drone is tested without any changes to the tests. diff --git a/pixi.lock b/pixi.lock index cf14b19a..fca819bc 100644 --- a/pixi.lock +++ b/pixi.lock @@ -698,6 +698,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-9_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-9_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-hd45a770_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.129-h7cc23a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_2.conda @@ -705,12 +707,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.1.0-ha9f2e26_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.1.0-h69a702a_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.1.0-h79bb938_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.1.0-he0feb66_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h0cb94f2_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-9_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm23-23.1.1-h474f4eb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvmspirv23-23.1.1-h7ef34fa_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.34-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h922cc85_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.1.0-h934c35e_2.conda @@ -718,8 +726,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-hb83e432_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.4-hf3af7cc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.4-h7df9aa5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mesa-lavapipe-26.2.3-h1d927ee_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mesa-libgallium-26.2.3-h1d927ee_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mesa-llvmpipe-26.2.3-hddcb11c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mesalib-26.2.3-he59ec28_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.5.2-py314h2b28147_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda @@ -730,10 +744,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.16.3-hc991c3d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/spirv-tools-2026.3-h7148c6a_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd70dff1_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/watchdog-6.0.0-py314h9e666f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-h7cc23a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxshmfence-1.3.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.7-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hce19668_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda @@ -1687,8 +1708,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-hb83e432_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.2-h51789e4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.4-hf3af7cc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.4-h7df9aa5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.11.1-py314h815e797_2.conda @@ -2844,6 +2865,43 @@ packages: - libllvm22 >=22.1.8,<22.2.0a0 size: 44320272 timestamp: 1781788728739 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm23-23.1.1-h474f4eb_0.conda + sha256: 5c716beeb80cd420c37cb811102a9e76c66072f47ed319de1a931ecdd98c9e6b + md5: d96e790721dd4e9af624acbf46cdd818 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - libxml2 + - libxml2-16 >=2.15.4 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: + weak: + - libllvm23 >=23.1.1,<23.2.0a0 + size: 45065095 + timestamp: 1788900116889 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvmspirv23-23.1.1-h7ef34fa_0.conda + sha256: e6f6bdd822098bb6703601650e29e134b3c3e0ae00a59d206c2afd433f187611 + md5: ae371878b08717b23a0379f03363e493 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libllvm23 >=23.1.1,<23.2.0a0 + - libstdcxx >=15 + constrains: + - llvm-spirv 23.1.1.* *_0 + - llvmdev 23.1.* + license: NCSA + license_family: BSD + purls: + - pkg:pypi/libllvmspirv23 + run_exports: {} + size: 1293391 + timestamp: 1789119629470 - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda sha256: 9787df8c22a59c9a70d3e5a10db9ad663485e75e9ccc3f09bd092cb7b95e0dab md5: 1390b7c5ac0b1d8e447bc5efa6d3c8c2 @@ -3155,6 +3213,24 @@ packages: run_exports: {} size: 559775 timestamp: 1776376739004 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.4-hf3af7cc_0.conda + sha256: 9a1685c8d8b8488cd7882a9cd6e673e687211ca763ebbcd1e91b6f594eeadc45 + md5: b7545c57a73a51d72aa6eee5695cf051 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.4 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 569196 + timestamp: 1788635197456 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_0.conda sha256: 3bc5551720c58591f6ea1146f7d1539c734ed1c40e7b9f5cb8cb7e900c509aba md5: 995d8c8bad2a3cc8db14675a153dec2b @@ -3175,6 +3251,26 @@ packages: - libxml2-16 >=2.15.3 size: 46810 timestamp: 1776376751152 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.4-h7df9aa5_0.conda + sha256: 053d85821ce5e36b7ce03faea5930382f6af695df9e2cd68e5d8d273072d1551 + md5: ea192d1ef556bf1d55de4acb8c0f5b65 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.4 hf3af7cc_0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.4 + size: 47077 + timestamp: 1788635202698 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda sha256: 0694760a3e62bdc659d90a14ae9c6e132b525a7900e59785b18a08bb52a5d7e5 md5: 87e6096ec6d542d1c1f8b33245fe8300 @@ -3270,6 +3366,91 @@ packages: run_exports: {} size: 9068707 timestamp: 1785211110030 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mesa-lavapipe-26.2.3-h1d927ee_0.conda + sha256: b33364c47fb374dd6ddd8aa01ad811bd5b658a6427cefd2a14a5e11474390e18 + md5: ccb182a9d4973ebabce1902e7e7d5139 + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + - xorg-libxrandr >=1.5.5,<2.0a0 + - xorg-libxshmfence >=1.3.3,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - libdrm >=2.4.129,<2.5.0a0 + - libllvmspirv23 >=23.1.1,<24.0a0 + - libxcb >=1.17.0,<2.0a0 + - libllvm23 >=23.1.1,<23.2.0a0 + - spirv-tools >=2026,<2027.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - mesa-lavapipe >=26.2.3,<26.3.0a0 + size: 2972405 + timestamp: 1789876978794 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mesa-libgallium-26.2.3-h1d927ee_0.conda + sha256: fe6e00271bf3a10af188b05bc8e25ba36fbaf0b646540b512ea9d715bdbf45fe + md5: 5d318c5805b5a98efe4268eaa0e48fc6 + depends: + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - libxcb >=1.17.0,<2.0a0 + - libdrm >=2.4.129,<2.5.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libllvmspirv23 >=23.1.1,<24.0a0 + - libegl >=1.7.0,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - spirv-tools >=2026,<2027.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - xorg-libxshmfence >=1.3.3,<2.0a0 + - xorg-libxxf86vm >=1.1.7,<2.0a0 + - libllvm23 >=23.1.1,<23.2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - libglvnd >=1.7.0,<2.0a0 + - xorg-libxrandr >=1.5.5,<2.0a0 + - libglx >=1.7.0,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - mesa-libgallium >=26.2.3,<26.3.0a0 + size: 11115648 + timestamp: 1789876978794 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mesa-llvmpipe-26.2.3-hddcb11c_0.conda + sha256: d8627aa094d5ede83cec45b0e5f0c22bbe97692dc84f3621346751d066ab40f6 + md5: 0550116f2d79e2c193f9ee331e2e2c59 + depends: + - mesa-libgallium ==26.2.3 h1d927ee_0 + - mesa-libgallium >=26.2.3,<26.3.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - mesa-llvmpipe >=26.2.3,<26.3.0a0 + size: 16660 + timestamp: 1789876978794 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mesalib-26.2.3-he59ec28_0.conda + sha256: e94b9a63c490809eb615d0c18d1ae4e7d60a6f7756c7ec7b2a6b6a2032a26b53 + md5: 96d19b715d42957d765c62dd21efc06a + depends: + - mesa-lavapipe ==26.2.3 h1d927ee_0 + - mesa-llvmpipe ==26.2.3 hddcb11c_0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - mesalib >=26.2.3,<26.3.0a0 + size: 12443 + timestamp: 1789876978794 - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda sha256: 5d46557214ed184381dafe835b7c94a474a1c3b307a08a250b1ea4779b44ffb3 md5: ee6c0cd80a60961a1f48aa3e0b91f986 @@ -3445,7 +3626,8 @@ packages: - libopengl >=1.7.0,<2.0a0 license: LGPL-3.0-only purls: - - pkg:pypi/pyside6?source=compressed-mapping + - pkg:pypi/pyside6?source=hash-mapping + - pkg:pypi/shiboken6?source=hash-mapping run_exports: {} size: 13950298 timestamp: 1787219457013 @@ -3620,6 +3802,23 @@ packages: run_exports: {} size: 9549002 timestamp: 1786872517294 +- conda: https://conda.anaconda.org/conda-forge/linux-64/spirv-tools-2026.3-h7148c6a_1.conda + sha256: 1ff7cdc2e65d3980f977a898d07f4e2b1b6f50dbf7e2fed88b3b4221faf34365 + md5: ac9adda1573683c31a8c58850e911273 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + constrains: + - spirv-headers >=1.4.357.0,<1.4.357.1.0a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - spirv-tools >=2026,<2027.0a0 + size: 2380634 + timestamp: 1787525383516 - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd70dff1_3.conda build_number: 103 sha256: 43624eab22f5f29df7d6ffe914cf442f28fd559b55b290906255492826e636e8 @@ -4003,6 +4202,20 @@ packages: - xorg-libxrender >=0.9.12,<0.10.0a0 size: 34645 timestamp: 1787100191192 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxshmfence-1.3.3-hb9d3cd8_0.conda + sha256: c0830fe9fa78d609cd9021f797307e7e0715ef5122be3f784765dad1b4d8a193 + md5: 9a809ce9f65460195777f2f2116bae02 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxshmfence >=1.3.3,<2.0a0 + size: 12302 + timestamp: 1734168591429 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda sha256: 752fdaac5d58ed863bbf685bb6f98092fe1a488ea8ebb7ed7b606ccfce08637a md5: 7bbe9a0cc0df0ac5f5a8ad6d6a11af2f diff --git a/properdocs.yml b/properdocs.yml index d09b19bd..dbb1c083 100644 --- a/properdocs.yml +++ b/properdocs.yml @@ -88,6 +88,7 @@ plugins: - gen-files: scripts: - docs/gen_ref_pages.py + - docs/gen_drone_renders.py - literate-nav: nav_file: SUMMARY.md - section-index diff --git a/pyproject.toml b/pyproject.toml index ffce46c7..120e4c9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -204,6 +204,11 @@ mkdocs-literate-nav = ">=0.6.0" mkdocs-section-index = ">=0.3.0" mkdocs-charts-plugin = ">=0.0.10" +# Headless OpenGL for rendering the drone images during the docs build +[tool.pixi.feature.docs.target.linux-64.dependencies] +libegl = "*" +mesalib = "*" + [tool.pixi.feature.docs.tasks] docs-build = { cmd = "properdocs build --strict", description = "Build docs" } docs-serve = { cmd = "properdocs serve --livereload", description = "Serve docs locally" }