diff --git a/.github/workflows/pip_publish.yml b/.github/workflows/pip_publish.yml index 7104183f..e136e4ca 100644 --- a/.github/workflows/pip_publish.yml +++ b/.github/workflows/pip_publish.yml @@ -1,6 +1,7 @@ -name: Upload Python Package to PyPI when a Release is Created +name: Build Python Wheels on: + pull_request: release: types: [created] @@ -13,16 +14,6 @@ jobs: with: submodules: recursive - - name: Copy stuff - run: | - rm -rf python_bindings/vendored_deps - rm -rf python_bindings/slamd_src - rm -rf python_bindings/README.md - - cp -r vendored_deps python_bindings/vendored_deps - cp -r slamd python_bindings/slamd_src - cp README.md python_bindings/README.md - - name: Set up Python uses: actions/setup-python@v4 with: @@ -49,15 +40,13 @@ jobs: libxkbcommon-devel \ mesa-libEGL-devel \ mesa-libGLES-devel - run: | - cd python_bindings - cibuildwheel --output-dir dist + run: cibuildwheel --output-dir dist - name: Upload Linux artifact uses: actions/upload-artifact@v4 with: name: dist-linux - path: python_bindings/dist + path: dist build-macos: name: Build wheels on macOS @@ -67,16 +56,6 @@ jobs: with: submodules: recursive - - name: Copy stuff - run: | - rm -rf python_bindings/vendored_deps - rm -rf python_bindings/slamd_src - rm -rf python_bindings/README.md - - cp -r vendored_deps python_bindings/vendored_deps - cp -r slamd python_bindings/slamd_src - cp README.md python_bindings/README.md - - name: Set up Python uses: actions/setup-python@v4 with: @@ -88,20 +67,19 @@ jobs: pip install cibuildwheel - name: Build wheels with cibuildwheel - run: | - cd python_bindings - cibuildwheel --output-dir dist + run: cibuildwheel --output-dir dist - name: Upload macOS artifact uses: actions/upload-artifact@v4 with: name: dist-macos - path: python_bindings/dist + path: dist publish: name: Publish to PyPI + if: github.event_name == 'release' runs-on: ubuntu-latest - needs: [build-linux, build-macos] # 🔥 waits for both builds + needs: [build-linux, build-macos] environment: name: pypi permissions: diff --git a/.github/workflows/test_python_build.yml b/.github/workflows/test_python_build.yml deleted file mode 100644 index a75f638f..00000000 --- a/.github/workflows/test_python_build.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Build python wheels - -on: [pull_request] - -jobs: - build-wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Copy stuff - run: | - rm -rf python_bindings/vendored_deps - rm -rf python_bindings/slamd_src - rm -rf python_bindings/README.md - - cp -r vendored_deps python_bindings/vendored_deps - cp -r slamd python_bindings/slamd_src - cp README.md python_bindings/README.md - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install build deps - run: | - python -m pip install --upgrade pip - pip install cibuildwheel - - - name: Build wheels with cibuildwheel - env: - CIBW_ARCHS_LINUX: 'x86_64' - CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 - CIBW_BEFORE_ALL_LINUX: | - yum install -y epel-release && \ - yum install -y \ - glfw-devel \ - libX11-devel \ - libXcursor-devel \ - libXrandr-devel \ - libXinerama-devel \ - libXi-devel \ - wayland-devel \ - mesa-libEGL-devel \ - mesa-libGLES-devel \ - SDL2-devel \ - qt5-qtbase-devel \ - gtk3-devel - run: | - cd python_bindings - cibuildwheel --output-dir dist diff --git a/.gitignore b/.gitignore index 27bec269..59ff82a0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,8 @@ build/ **/*.so **/*__pycache__ .cache/ -.testvenv/ \ No newline at end of file +.testvenv/ +dist/ +*.egg-info/ +_skbuild/ +src/slamd/slamd_window diff --git a/.vscode/settings.json b/.vscode/settings.json index bb965869..d3dd6bf1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,6 @@ { "clangd.arguments": [ - "--compile-commands-dir=python_bindings/build", - // "--compile-commands-dir=examples/build", + "--compile-commands-dir=build/cp312-cp312-linux_x86_64", "--function-arg-placeholders=false", "--tweaks=-Wall", "--tweaks=-Wextra", @@ -119,8 +118,8 @@ "C_Cpp.inlayHints.autoDeclarationTypes.showOnLeft": false, "C_Cpp.inlayHints.parameterNames.enabled": true, "C_Cpp.inlayHints.referenceOperator.enabled": true, - "python.analysis.extraPaths": ["python_bindings/src/"], - "cmake.sourceDirectory": "/home/robert/projects/slam_dunk/examples", + "python.analysis.extraPaths": ["src/"], + "cmake.sourceDirectory": "${workspaceFolder}", "python.analysis.exclude": [ "**/build/", "**/.venv/", diff --git a/python_bindings/CMakeLists.txt b/CMakeLists.txt similarity index 95% rename from python_bindings/CMakeLists.txt rename to CMakeLists.txt index d0cd6038..969a65c8 100644 --- a/python_bindings/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required(VERSION 3.14) -project(myviz_bindings LANGUAGES CXX) +project(slamd LANGUAGES CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(LIB_DIR vendored_deps) +set(LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendored_deps) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(BUILD_SHARED_LIBS OFF) @@ -36,7 +36,7 @@ add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_OFF) # EXCLUDE_FROM_ALL prevents vendored deps from installing their own # files (static archives, examples, tools) into the wheel. -add_subdirectory(slamd_src EXCLUDE_FROM_ALL) +add_subdirectory(slamd EXCLUDE_FROM_ALL) find_package(Python REQUIRED COMPONENTS Interpreter Development.Module REQUIRED) @@ -82,7 +82,7 @@ target_link_libraries(bindings fmt::fmt ) -# slamd_window is excluded from ALL due to EXCLUDE_FROM_ALL on slamd_src, +# slamd_window is excluded from ALL due to EXCLUDE_FROM_ALL on slamd, # so we need to make bindings depend on it to ensure it gets built. add_dependencies(bindings slamd_window) diff --git a/README.md b/README.md index 3e5e56c8..a094ccf2 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,97 @@ -![](./images/logo.png) + --- -SlamDunk is a powerful and user-friendly Python library for making 3D and 2D visualizations for prototyping, data exploration, and algorithm development. + -It is lightweight, built using OpenGL and ImGui. + -# Examples +`slamd` is a 3D visualization library for Python. `pip install`, write a few lines, and you have a GPU-accelerated interactive 3D viewer. No event loops, no boilerplate — just set geometry and it shows up. -## Hello world - -Here is a simple "hello world" program for a SlamDunk visualization. - -```python -import slamd - -if __name__ == "__main__": - vis = slamd.Visualizer("Hello world") - - scene = vis.scene("scene") - - scene.set_object("/origin", slamd.geom.Triad()) - - vis.hang_forever() +```bash +pip install slamd ``` -Running this program results in the following interactive visualization: -![](./images/hello_world.png) - -This example highlights the main components of SlamDunk. - -The `Visualizer` object maintains the state of the visualization, and starts a TCP server that the visualization window connects to. +## Why slamd? -By default, it spawns a window process that reads from it and displays the visualizations. You can opt out of this with the `spawn` argument, and control the port with the `port` argument. -In this case, you can start a window with the `slamd-window` executable: +Most 3D visualization in Python is either painfully slow (matplotlib's 3D mode), or requires you to learn a massive framework. `slamd` is neither. -``` -slamd-window --port [port] --ip [ip] -``` +- **Dead simple** — create a visualizer, set geometry, done. The viewer window spawns automatically in a separate process. No event loop, no main thread hijacking, no callbacks. +- **Real 3D rendering** — GPU-accelerated OpenGL. Handles millions of points, animated meshes, and real geometry at interactive framerates. This is not a plot library pretending to do 3D. +- **Pose tree** — objects live in a transform tree (like ROS TF or a scene graph). Set a parent transform and everything underneath moves. Makes articulated and hierarchical scenes trivial. +- **The right primitives** — point clouds, meshes, camera frustums, triads, arrows, polylines, spheres, planes. The stuff you actually need when working with 3D data, robotics, or SLAM. -This client-server architecture allows launching a visualizer a remote server, and connecting to it on your local machine. +## How is this different from Rerun? -A `Scene` object represents and contains a tree of 3D objects, accessed by paths like `/comp1/comp2/comp3`. 3D poses and `Geometry` objects can be assigned with the `set_transform` and `set_object` methods. +`slamd` is a **stateful viewer**, not a logging database. There's no append-only log, no timeline, no timestamps forced onto your data. You have a tree of geometry — you set objects, move them, delete them, and what you see is what's there right now. -`Geometry` objects represent the objects that are displayed in the scene. +Rerun is powerful, but it's a big tool with a lot of concepts. `slamd` does one thing: show your geometry, right now, with minimal API surface. If you want a data recording platform with time-series scrubbing, use Rerun. If you want to throw some geometry on screen and look at it, use `slamd`. -## Multiple scenes - -SlamDunk uses ImGui to allow multiple sub-windows with floating and docking support inside the SlamDunk viewer. The following example illustrates creating two windows, each showing its own scene. +## Quick Start ```python import slamd -import numpy as np - -if __name__ == "__main__": - vis = slamd.Visualizer("two windows") - - scene1 = vis.scene("scene 1") - scene2 = vis.scene("scene 2") - - scene1.set_object("/box", slamd.geom.Box()) - - scene2.set_object("/origin", slamd.geom.Triad()) - - scene2.set_object("/ball", slamd.geom.Sphere(2.0)) - - sphere_transform = np.identity(4, dtype=np.float32) - sphere_transform[:, 3] = np.array([5.0, 1.0, 2.0, 1.0]) - - scene2.set_transform("/ball", sphere_transform) - - vis.hang_forever() +vis = slamd.Visualizer("Hello world") +scene = vis.scene("scene") +scene.set_object("/origin", slamd.geom.Triad()) ``` -The resulting window looks like this: - -![](./images/two_scenes.png) +![](./images/hello_world.gif) -The windows are fully controllable - you can drag them around, make tabs, use them in floating mode, or dock them to the sides like you see in the screenshot. All of this is supported by [ImGui](https://github.com/ocornut/imgui). +That's it. A window opens with an interactive 3D view. -Here is a slightly more elaborate example of something you can do with SlamDunk: +Objects live in a transform tree — move a parent and children follow: -![](./images/moving_mesh.gif) - -Or this one: +```python +scene.set_object("/robot/camera/frustum", slamd.geom.CameraFrustum(K, w, h, scale=0.2)) +scene.set_object("/robot/lidar/cloud", slamd.geom.PointCloud(pts, colors, point_size)) -![](./images/spiral.gif) +# Move the whole robot — camera and lidar come with it +scene.set_transform("/robot", pose) +``` -## Supported geometry primitives +## Multiple Windows -### 3D +Create multiple scenes — each gets its own sub-window with ImGui docking. Drag, tab, float, or dock them however you like: -- Camera Frustums (with optional image) (`slamd.geom.CameraFrustum`) -- Arrows/Vectors (`slamd.geom.Arrows`) -- Arbitrary meshes (`slamd.geom.Mesh`) -- Planes (`slamd.geom.Plane`) -- Point Clouds (`slamd.geom.PointCloud`) -- Piecewise linear curves (`slamd.geom.PolyLine`) -- Spheres (`slamd.geom.Sphere`) -- Triads/reference frames (`slamd.geom.Triad`) +```python +vis = slamd.Visualizer("multi-view") +scene1 = vis.scene("RGB camera") +scene2 = vis.scene("point cloud") -## 2D +scene1.set_object("/frustum", slamd.geom.CameraFrustum(K, w, h, img, 1.0)) +scene2.set_object("/cloud", slamd.geom.PointCloud(pts, colors, 0.3, 0.5)) +``` -- Images (`slamd.geom2d.Image`) -- Points (`slamd.geom2d.Points`) -- Piecewise linear curves (`slamd.geom2d.PolyLine`) -- Circles (`slamd.geom2d.Circles`) +![](./images/two_windows.gif) -## Further reading +## Geometry -The examples in `python_examples` showcase some more features of SlamDunk. Some examples are canvases for 2D visualizations and lots of additional geometry primitives such as point clouds, meshes, camera frustums, etc. +- Point clouds — `slamd.geom.PointCloud` +- Meshes — `slamd.geom.Mesh` +- Camera frustums (with image) — `slamd.geom.CameraFrustum` +- Arrows — `slamd.geom.Arrows` +- Polylines — `slamd.geom.PolyLine` +- Spheres — `slamd.geom.Sphere` / `slamd.geom.Spheres` +- Triads — `slamd.geom.Triad` +- Planes — `slamd.geom.Plane` +- Boxes — `slamd.geom.Box` -# Installation +## Installation -Wheels are available on [PyPi](https://pypi.org/project/slamd/), so you can simply +Wheels on [PyPI](https://pypi.org/project/slamd/) for Linux and macOS (Python >= 3.11): ```bash pip install slamd ``` -# Contributions +Only runtime dependency is `numpy >= 1.23`. + +## Examples + +See [examples/](./examples) for the full set. + +## License -All contributions and feedback are welcome and appreciated! +Apache 2.0 — see [LICENSE](./LICENSE). diff --git a/examples/attractor_swarm.py b/examples/attractor_swarm.py new file mode 100644 index 00000000..8599e5f7 --- /dev/null +++ b/examples/attractor_swarm.py @@ -0,0 +1,86 @@ +""" +Aizawa attractor — thousands of particles swirling through a strange attractor. + +The Aizawa attractor produces a beautiful mushroom/tornado shape with particles +spiraling around a central axis, periodically collapsing inward and exploding +outward. With a swarm of particles you see the attractor's structure come alive. +""" + +import slamd +import time +import numpy as np + + +def aizawa_deriv(states, a=0.95, b=0.7, c=0.6, d=3.5, e=0.25, f=0.1): + """Vectorized Aizawa attractor derivatives for N particles.""" + x, y, z = states[:, 0], states[:, 1], states[:, 2] + x2 = x * x + y2 = y * y + z2 = z * z + z3 = z2 * z + + dx = (z - b) * x - d * y + dy = d * x + (z - b) * y + dz = c + a * z - z3 / 3.0 - (x2 + y2) * (1.0 + e * z) + f * z * x2 * x + + return np.column_stack([dx, dy, dz]) + + +def rk4_step(states, dt): + k1 = aizawa_deriv(states) + k2 = aizawa_deriv(states + 0.5 * dt * k1) + k3 = aizawa_deriv(states + 0.5 * dt * k2) + k4 = aizawa_deriv(states + dt * k3) + return states + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4) + + +def main(): + vis = slamd.Visualizer("Aizawa Attractor", spawn=True, port=6002) + scene = vis.scene("scene") + + n = 50000 + rng = np.random.default_rng(42) + + # Seed all particles near the attractor with small random offsets + base = np.array([0.1, 0.0, 0.0]) + states = base + rng.normal(0, 0.05, (n, 3)) + + # Warm up — let particles settle onto the attractor + dt = 0.005 + for _ in range(200): + states = rk4_step(states, dt) + + spheres = None + scale = 3.0 + + while True: + for _ in range(3): + states = rk4_step(states, dt) + + positions = (states * scale).astype(np.float32) + + # Color by angle around z-axis + height + angle = np.arctan2(states[:, 1], states[:, 0]) + angle_norm = (angle + np.pi) / (2 * np.pi) + z_norm = np.clip((states[:, 2] + 1.5) / 3.0, 0, 1) + + colors = np.column_stack([ + 0.9 * (0.5 + 0.5 * np.sin(angle_norm * 6.28 + 0.0)), + 0.3 + 0.6 * z_norm, + 0.9 * (0.5 + 0.5 * np.sin(angle_norm * 6.28 + 2.5)), + ]).astype(np.float32) + + radii = np.full(n, 0.025, dtype=np.float32) + + if spheres is None: + spheres = slamd.geom.Spheres(positions, colors, radii, 0.15) + scene.set_object("/swarm", spheres) + else: + spheres.update_positions(positions) + spheres.update_colors(colors) + + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/python_examples/bunch_of_triads.py b/examples/bunch_of_triads.py similarity index 100% rename from python_examples/bunch_of_triads.py rename to examples/bunch_of_triads.py diff --git a/examples/double_pendulum.py b/examples/double_pendulum.py new file mode 100644 index 00000000..5271903e --- /dev/null +++ b/examples/double_pendulum.py @@ -0,0 +1,92 @@ +""" +Double pendulum — chaotic motion with a fading trail. +""" + +import slamd +import numpy as np +import time + +G = 9.81 +L1, L2 = 1.5, 1.5 +M1, M2 = 1.0, 1.0 + + +def derivs(state): + t1, w1, t2, w2 = state + delta = t1 - t2 + sd, cd = np.sin(delta), np.cos(delta) + den = 2 * M1 + M2 - M2 * np.cos(2 * delta) + + a1 = (-G * (2 * M1 + M2) * np.sin(t1) + - M2 * G * np.sin(t1 - 2 * t2) + - 2 * sd * M2 * (w2**2 * L2 + w1**2 * L1 * cd)) / (L1 * den) + + a2 = (2 * sd * (w1**2 * L1 * (M1 + M2) + + G * (M1 + M2) * np.cos(t1) + + w2**2 * L2 * M2 * cd)) / (L2 * den) + + return np.array([w1, a1, w2, a2]) + + +def rk4(state, dt): + k1 = derivs(state) + k2 = derivs(state + 0.5 * dt * k1) + k3 = derivs(state + 0.5 * dt * k2) + k4 = derivs(state + dt * k3) + return state + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4) + + +def angles_to_xy(t1, t2): + x1 = L1 * np.sin(t1) + y1 = -L1 * np.cos(t1) + x2 = x1 + L2 * np.sin(t2) + y2 = y1 - L2 * np.cos(t2) + return (x1, y1), (x2, y2) + + +def main(): + vis = slamd.Visualizer("Double Pendulum", spawn=True, port=6001) + scene = vis.scene("scene") + + state = np.array([2.8, 0.0, 3.0, 0.0]) # high angles -> chaos + dt = 0.002 + trail = [] + max_trail = 500 + + spheres = None + arm_line = None + trail_line = None + + while True: + for _ in range(10): + state = rk4(state, dt) + + p1, p2 = angles_to_xy(state[0], state[2]) + trail.append([p2[0], 0.0, p2[1]]) + if len(trail) > max_trail: + trail = trail[-max_trail:] + + positions = np.array([[p1[0], 0, p1[1]], [p2[0], 0, p2[1]]], dtype=np.float32) + colors = np.array([[1.0, 0.2, 0.1], [0.2, 0.6, 1.0]], dtype=np.float32) + radii = np.array([0.12, 0.12], dtype=np.float32) + + if spheres is None: + spheres = slamd.geom.Spheres(positions, colors, radii, 0.3) + scene.set_object("/masses", spheres) + else: + spheres.update_positions(positions) + + arm_pts = np.array([[0, 0, 0], [p1[0], 0, p1[1]], [p2[0], 0, p2[1]]], dtype=np.float32) + arm = slamd.geom.PolyLine(arm_pts, 0.04, np.array([0.9, 0.9, 0.9], dtype=np.float32), 0.5) + scene.set_object("/arm", arm) + + if len(trail) > 2: + pts = np.array(trail, dtype=np.float32) + trail_obj = slamd.geom.PolyLine(pts, 0.02, np.array([1.0, 0.7, 0.1], dtype=np.float32), 0.3) + scene.set_object("/trail", trail_obj) + + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/python_examples/frustum_with_images.py b/examples/frustum_with_images.py similarity index 96% rename from python_examples/frustum_with_images.py rename to examples/frustum_with_images.py index cda1cbce..2900a513 100644 --- a/python_examples/frustum_with_images.py +++ b/examples/frustum_with_images.py @@ -48,7 +48,7 @@ def get_camera_intrinsics_and_image(): frustum = slamd.geom.CameraFrustum(K, width, height, img, 1.0) - scene.set_object("/rot1/tr/rot2/cam/triad", slamd.geom.Triad(0.5)) + scene.set_object("/rot1/tr/rot2/cam/triad", slamd.geom.Triad(scale=0.5)) scene.set_object("/rot1/tr/rot2/cam/frustum", frustum) rot2 = np.eye(4) @@ -61,10 +61,10 @@ def get_camera_intrinsics_and_image(): scene.set_transform("/rot1/tr", tr) - t = 0.0 + t0 = time.monotonic() while True: + t = time.monotonic() - t0 rot1 = np.eye(4) rot1[:3, :3] = Rotation.from_euler("z", t).as_matrix() scene.set_transform("/rot1", rot1) - t += 0.01 time.sleep(10 / 1000) diff --git a/examples/galaxy.py b/examples/galaxy.py new file mode 100644 index 00000000..bb35b361 --- /dev/null +++ b/examples/galaxy.py @@ -0,0 +1,78 @@ +""" +Spiral galaxy — 150k stars in a rotating galaxy with arms, bulge, and dust. +""" + +import slamd +import numpy as np + + +def make_galaxy(n: int, rng: np.random.Generator) -> tuple[np.ndarray, np.ndarray]: + n_bulge = n // 5 + n_disk = n - n_bulge + + # Spiral disk + r = rng.exponential(3.0, n_disk) + theta = rng.uniform(0, 2 * np.pi, n_disk) + # Add spiral arm structure — stars cluster around 3 arms + n_arms = 3 + arm = rng.integers(0, n_arms, n_disk) + arm_offset = arm * (2 * np.pi / n_arms) + spiral_angle = arm_offset + r * 0.6 + rng.normal(0, 0.3 / (1 + r * 0.1), n_disk) + + x = r * np.cos(spiral_angle) + y = r * np.sin(spiral_angle) + z = rng.normal(0, 0.1 + 0.02 * r, n_disk) + + disk_pos = np.column_stack([x, y, z]) + + # Hot blue-white in arms, redder further out + arm_phase = np.cos(spiral_angle * n_arms - r * 0.6 * n_arms) + brightness = np.clip(0.5 + 0.5 * arm_phase, 0.2, 1.0) + temp = np.clip(1.0 - r / 15.0, 0.0, 1.0) + + disk_colors = np.column_stack([ + 0.9 * brightness + 0.1, + 0.7 * brightness * temp + 0.3 * brightness, + 0.9 * brightness * temp, + ]) + + # Central bulge + br = rng.exponential(0.8, n_bulge) + btheta = rng.uniform(0, 2 * np.pi, n_bulge) + bphi = rng.normal(0, 0.5, n_bulge) + + bx = br * np.cos(btheta) * np.cos(bphi) + by = br * np.sin(btheta) * np.cos(bphi) + bz = br * np.sin(bphi) * 0.4 + + bulge_pos = np.column_stack([bx, by, bz]) + + bulge_brightness = np.clip(1.0 - br / 3.0, 0.3, 1.0) + bulge_colors = np.column_stack([ + bulge_brightness, + bulge_brightness * 0.85, + bulge_brightness * 0.5, + ]) + + positions = np.concatenate([disk_pos, bulge_pos]).astype(np.float32) + colors = np.concatenate([disk_colors, bulge_colors]).astype(np.float32) + + return positions, colors + + +def main(): + vis = slamd.Visualizer("Galaxy") + scene = vis.scene("scene") + + n = 1_000_000 + rng = np.random.default_rng(42) + positions, colors = make_galaxy(n, rng) + + cloud = slamd.geom.PointCloud(positions, colors, 0.08, 0.7) + scene.set_object("/galaxy", cloud) + + vis.hang_forever() + + +if __name__ == "__main__": + main() diff --git a/python_examples/hello_world.py b/examples/hello_world.py similarity index 87% rename from python_examples/hello_world.py rename to examples/hello_world.py index 2df6726a..b16f2b09 100644 --- a/python_examples/hello_world.py +++ b/examples/hello_world.py @@ -6,5 +6,3 @@ scene = vis.scene("scene") scene.set_object("/origin", slamd.geom.Triad()) - - vis.hang_forever() diff --git a/examples/large_scale_stress_test.py b/examples/large_scale_stress_test.py new file mode 100644 index 00000000..f564a865 --- /dev/null +++ b/examples/large_scale_stress_test.py @@ -0,0 +1,39 @@ +import slamd +import numpy as np + + +def main(): + vis = slamd.Visualizer("large scale stress test") + scene = vis.scene("scene") + + # Objects at scales of hundreds of thousands to millions + # Simulates e.g. millimeter-unit coordinate systems + scale = 500_000.0 + triad_size = scale * 0.1 + + # Place triads in a grid pattern at large scale + for i, x in enumerate(np.linspace(-scale, scale, 5)): + for j, y in enumerate(np.linspace(-scale, scale, 5)): + pose = np.eye(4) + pose[0, 3] = x + pose[1, 3] = y + scene.set_object( + f"/triad_{i}_{j}", + slamd.geom.Triad(pose, triad_size), + ) + + # A big point cloud spread across the large space + n_points = 1000 + points = np.random.uniform(-scale, scale, size=(n_points, 3)) + points[:, 2] = np.abs(points[:, 2]) * 0.1 # flatten Z a bit + colors = np.random.uniform(0.2, 1.0, size=(n_points, 3)) + scene.set_object( + "/point_cloud", + slamd.geom.PointCloud(points, colors, triad_size * 0.2), + ) + + vis.hang_forever() + + +if __name__ == "__main__": + main() diff --git a/examples/lorenz_attractor.py b/examples/lorenz_attractor.py new file mode 100644 index 00000000..afeca10d --- /dev/null +++ b/examples/lorenz_attractor.py @@ -0,0 +1,77 @@ +""" +Lorenz attractor — multiple trails tracing the strange attractor with color gradients. + +This is a fun math demo, not representative of typical slamd usage. +See the other examples for more practical use cases. +""" + +import slamd +import numpy as np +import time + + +def lorenz_deriv(state, sigma=10.0, rho=28.0, beta=8 / 3): + x, y, z = state[0], state[1], state[2] + return np.array([sigma * (y - x), x * (rho - z) - y, x * y - beta * z]) + + +def rk4_step(state, dt, sigma=10.0, rho=28.0, beta=8 / 3): + k1 = lorenz_deriv(state, sigma, rho, beta) + k2 = lorenz_deriv(state + 0.5 * dt * k1, sigma, rho, beta) + k3 = lorenz_deriv(state + 0.5 * dt * k2, sigma, rho, beta) + k4 = lorenz_deriv(state + dt * k3, sigma, rho, beta) + return state + (dt / 6) * (k1 + 2 * k2 + 2 * k3 + k4) + + +TRAIL_COLORS = [ + (1.0, 0.3, 0.1), # red-orange + (0.2, 0.7, 1.0), # sky blue + (1.0, 0.85, 0.1), # gold + (0.4, 1.0, 0.5), # mint green + (1.0, 0.4, 0.7), # pink + (0.6, 0.4, 1.0), # purple +] + + +def main(): + vis = slamd.Visualizer("Lorenz Attractor") + scene = vis.scene("scene") + + n_trails = 6 + dt = 0.01 + + # Known point on the attractor — no pre-run needed + base = np.array([-6.2, -8.3, 22.0]) + states = [base + np.array([i * 0.05, 0, 0]) for i in range(n_trails)] + + max_len = 600 + trails: list[list[np.ndarray]] = [[] for _ in range(n_trails)] + + frame = 0 + while True: + for _ in range(3): + for i in range(n_trails): + states[i] = rk4_step(states[i], dt) + trails[i].append(states[i].astype(np.float32)) + if len(trails[i]) > max_len: + trails[i] = trails[i][-max_len:] + + frame += 1 + if frame % 2 != 0: + time.sleep(0.01) + continue + + for i in range(n_trails): + if len(trails[i]) < 10: + continue + points = np.array(trails[i], dtype=np.float32) + r, g, b = TRAIL_COLORS[i % len(TRAIL_COLORS)] + color = np.array([r, g, b], dtype=np.float32) + polyline = slamd.geom.PolyLine(points, 0.2, color, 0.5) + scene.set_object(f"/trail_{i}", polyline) + + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/python_examples/lots_of_paths.py b/examples/lots_of_paths.py similarity index 100% rename from python_examples/lots_of_paths.py rename to examples/lots_of_paths.py diff --git a/python_examples/moving_mesh.py b/examples/moving_mesh.py similarity index 97% rename from python_examples/moving_mesh.py rename to examples/moving_mesh.py index 1f80728b..6db23de2 100644 --- a/python_examples/moving_mesh.py +++ b/examples/moving_mesh.py @@ -53,9 +53,10 @@ def main(): mesh = None - t = 0 + t0 = time.monotonic() while True: time.sleep(0.01) + t = time.monotonic() - t0 z = f(coords, t) points = np.concatenate((coords, z[:, None]), axis=1) @@ -76,7 +77,6 @@ def main(): mesh.update_positions(points) mesh.update_colors(colors) - t += 0.02 if __name__ == "__main__": diff --git a/python_examples/moving_pointcloud.py b/examples/moving_pointcloud.py similarity index 92% rename from python_examples/moving_pointcloud.py rename to examples/moving_pointcloud.py index 79bfbb13..7e48789a 100644 --- a/python_examples/moving_pointcloud.py +++ b/examples/moving_pointcloud.py @@ -29,7 +29,7 @@ def uniform_grid_points(n: int, a: float) -> np.ndarray: def main(): - vis = slamd.Visualizer("hello python", spawn=True, port=6000) + vis = slamd.Visualizer("hello python", spawn=True, port=6001) # coords = uniform_grid_points(100000, 10.0) coords = uniform_grid_points(100000, 30.0) @@ -39,9 +39,10 @@ def main(): point_cloud = None - t = 0 + t0 = time.monotonic() while True: time.sleep(0.01) + t = time.monotonic() - t0 z = f(coords, t) points = np.concatenate((coords, z[:, None]), axis=1) @@ -62,8 +63,6 @@ def main(): point_cloud.update_positions(points) point_cloud.update_colors(colors) - t += 0.02 - if __name__ == "__main__": main() diff --git a/examples/moving_spheres.py b/examples/moving_spheres.py new file mode 100644 index 00000000..4a78641e --- /dev/null +++ b/examples/moving_spheres.py @@ -0,0 +1,56 @@ +import slamd +import time +import numpy as np + + +def uniform_grid_points(n: int, a: float) -> np.ndarray: + side = int(np.sqrt(n)) + x = np.linspace(-a, a, side) + y = np.linspace(-a, a, side) + xv, yv = np.meshgrid(x, y) + grid = np.stack([xv.ravel(), yv.ravel()], axis=1) + return grid[:n] + + +def main(): + vis = slamd.Visualizer("moving spheres", spawn=True, port=6001) + scene = vis.scene("scene") + + coords = uniform_grid_points(100000, 30.0) + dist = np.sqrt(coords[:, 0] ** 2 + coords[:, 1] ** 2) + spheres = None + + t0 = time.monotonic() + while True: + time.sleep(0.01) + t = time.monotonic() - t0 + + # Calm concentric ripple + z = 2.0 * np.sin(dist * 0.4 - t * 1.5) * np.exp(-dist * 0.03) + + points = np.column_stack( + [coords[:, 0], coords[:, 1], z] + ).astype(np.float32) + + # Smooth gradient: deep blue in troughs, warm orange on peaks + h = (z - z.min()) / (z.max() - z.min() + 1e-8) + colors = np.column_stack([ + h * 1.0, + h * 0.4 + 0.1, + (1.0 - h) * 0.8 + 0.2, + ]).astype(np.float32) + + # Radii: gentle swell that follows the ripple outward + radii = (0.15 + 0.08 * np.sin(dist * 0.4 - t * 1.5)).astype(np.float32) + + if spheres is None: + spheres = slamd.geom.Spheres(points, colors, radii, 0.3) + scene.set_object("/spheres", spheres) + else: + spheres.update_positions(points) + spheres.update_colors(colors) + spheres.update_radii(radii) + + +if __name__ == "__main__": + main() diff --git a/examples/noisy_sphere.py b/examples/noisy_sphere.py new file mode 100644 index 00000000..f4a71f29 --- /dev/null +++ b/examples/noisy_sphere.py @@ -0,0 +1,123 @@ +import slamd +import time +import numpy as np + + +def uv_sphere(subdivisions: int = 5) -> tuple[np.ndarray, list[int]]: + n_lat = 2 ** (subdivisions + 1) + n_lon = 2 * n_lat + + vertices = [] + indices = [] + + for i in range(n_lat + 1): + theta = np.pi * i / n_lat + for j in range(n_lon + 1): + phi = 2.0 * np.pi * j / n_lon + x = np.sin(theta) * np.cos(phi) + y = np.sin(theta) * np.sin(phi) + z = np.cos(theta) + vertices.append([x, y, z]) + + for i in range(n_lat): + for j in range(n_lon): + p0 = i * (n_lon + 1) + j + p1 = p0 + 1 + p2 = p0 + (n_lon + 1) + p3 = p2 + 1 + indices.extend([p0, p2, p1]) + indices.extend([p1, p2, p3]) + + return np.array(vertices, dtype=np.float32), indices + + +def snoise(positions: np.ndarray, t: float, freq: float, speed: float, phase: float = 0.0) -> np.ndarray: + x, y, z = positions[:, 0], positions[:, 1], positions[:, 2] + return ( + np.sin(freq * x + speed * t + phase) + * np.sin(freq * y - 0.7 * speed * t + phase * 1.3) + * np.cos(freq * z + 0.3 * speed * t + phase * 0.7) + ) + + +def main(): + vis = slamd.Visualizer("noisy sphere", port=6000) + + scene = slamd.Scene() + vis.add_scene("scene", scene) + + base_verts, indices = uv_sphere(subdivisions=5) + radius = 5.0 + normals = base_verts.copy() + + # Precompute spherical coords for color effects + theta = np.arccos(np.clip(base_verts[:, 2], -1, 1)) + phi = np.arctan2(base_verts[:, 1], base_verts[:, 0]) + + mesh = None + t0 = time.monotonic() + + while True: + time.sleep(0.01) + t = time.monotonic() - t0 + + # Breathing base radius + breath = 1.0 + 0.15 * np.sin(0.5 * t) + + # Displacement: many octaves with drifting phases for chaotic feel + d = ( + 0.50 * snoise(base_verts, t, freq=2.0, speed=0.8) + + 0.30 * snoise(base_verts, t, freq=4.0, speed=1.3, phase=np.sin(0.2 * t)) + + 0.20 * snoise(base_verts, t, freq=7.0, speed=1.9, phase=np.cos(0.3 * t)) + + 0.10 * snoise(base_verts, t, freq=13.0, speed=2.7, phase=t * 0.1) + + 0.05 * snoise(base_verts, t, freq=23.0, speed=3.5) + ) + + # Warp: let displacement feed back into itself for gnarly topology + warp = 0.4 * np.sin(3.0 * d + 1.5 * t) + d_final = d + warp + + points = (radius * breath + d_final[:, None]) * normals + + # --- Psychedelic color --- + nd = d_final / (np.abs(d_final).max() + 1e-8) + + # Rotating hue based on spherical position + time + hue_shift = phi + 0.8 * t + 2.0 * nd + saturation_wave = theta * 2.0 + 0.5 * t + + # HSV-ish via sine waves = trippy rainbow + r = 0.5 + 0.5 * np.sin(hue_shift) + g = 0.5 + 0.5 * np.sin(hue_shift + 2.094) # +120 deg + b = 0.5 + 0.5 * np.sin(hue_shift + 4.189) # +240 deg + + # Iridescent shimmer tied to displacement peaks + shimmer = 0.4 * np.sin(10.0 * nd + 3.0 * t) * np.sin(saturation_wave) + r += shimmer + g += shimmer * 0.6 + b -= shimmer * 0.3 + + # Deep glow in the valleys, hot highlights on peaks + glow = np.exp(-2.0 * (nd + 0.5) ** 2) # gaussian centered on valleys + r += 0.3 * glow * np.sin(t * 1.1) + g += 0.1 * glow + b += 0.5 * glow * np.cos(t * 0.7) + + # Pulsing veins along latitude lines + veins = 0.3 * (np.sin(12.0 * theta + 4.0 * d + 1.5 * t) ** 8) + g += veins + r += veins * 0.3 + + colors = np.stack([r, g, b], axis=1).astype(np.float32) + np.clip(colors, 0.0, 1.0, out=colors) + + if mesh is None: + mesh = slamd.geom.Mesh(points, colors, indices) + scene.set_object("/sphere", mesh) + else: + mesh.update_positions(points) + mesh.update_colors(colors) + + +if __name__ == "__main__": + main() diff --git a/python_examples/planes.py b/examples/planes.py similarity index 100% rename from python_examples/planes.py rename to examples/planes.py diff --git a/examples/spheres.py b/examples/spheres.py new file mode 100644 index 00000000..ff8b706d --- /dev/null +++ b/examples/spheres.py @@ -0,0 +1,47 @@ +import slamd +import numpy as np + + +def main(): + vis = slamd.Visualizer("spheres example", spawn=True, port=6006) + scene = vis.scene("scene") + + n = 500 + rng = np.random.default_rng(42) + + # Random positions on a sphere surface + phi = rng.uniform(0, 2 * np.pi, n) + cos_theta = rng.uniform(-1, 1, n) + theta = np.arccos(cos_theta) + r = 5.0 + + positions = np.column_stack( + [ + r * np.sin(theta) * np.cos(phi), + r * np.sin(theta) * np.sin(phi), + r * np.cos(theta), + ] + ).astype(np.float32) + + # Color by height + height_norm = (positions[:, 2] - positions[:, 2].min()) / ( + positions[:, 2].max() - positions[:, 2].min() + ) + colors = np.column_stack( + [ + height_norm, + 0.3 * np.ones(n), + 1.0 - height_norm, + ] + ).astype(np.float32) + + radii = rng.uniform(0.1, 0.4, n).astype(np.float32) + + spheres = slamd.geom.Spheres(positions, colors, radii, 0.3) + scene.set_object("/spheres", spheres) + + input("Press Enter to exit...") + + +if __name__ == "__main__": + main() diff --git a/python_examples/three_d_spiral.py b/examples/three_d_spiral.py similarity index 92% rename from python_examples/three_d_spiral.py rename to examples/three_d_spiral.py index a59ea076..7a05a70b 100644 --- a/python_examples/three_d_spiral.py +++ b/examples/three_d_spiral.py @@ -22,8 +22,9 @@ def cool_spiral(n: int, t: float) -> np.ndarray: pink = np.array([212, 13, 125]) / 255 - t = 0.0 + t0 = time.monotonic() while True: + t = time.monotonic() - t0 # make spiral coords = cool_spiral(1000, t) max_z = coords[:, 2].max() @@ -33,4 +34,3 @@ def cool_spiral(n: int, t: float) -> np.ndarray: scene.set_object("/poly_line", poly_line) time.sleep(10 / 1000) - t += 0.05 diff --git a/examples/tree_stress_test.py b/examples/tree_stress_test.py new file mode 100644 index 00000000..0915735b --- /dev/null +++ b/examples/tree_stress_test.py @@ -0,0 +1,89 @@ +"""Stress test for the tree overlay: multiple views, deep/wide trees, long names.""" + +import slamd +import numpy as np + + +def make_transform(x: float, y: float, z: float) -> np.ndarray: + T = np.eye(4, dtype=np.float32) + T[0, 3] = x + T[1, 3] = y + T[2, 3] = z + return T + + +def populate_scene_deep(scene: slamd.Scene): + """Deep tree with varied name lengths.""" + # A deeply nested chain + for depth in range(8): + prefix = "/deep_hierarchy" + for d in range(depth + 1): + if d % 2 == 0: + prefix += f"/level_{d}" + else: + prefix += f"/this_is_a_deliberately_long_name_at_level_{d}" + + scene.set_object( + prefix + "/triad", + slamd.geom.Triad(make_transform(depth * 3.0, 0, 0)), + ) + + # Wide tree at one level + for i in range(30): + if i % 3 == 0: + name = f"/objects/short_{i}" + elif i % 3 == 1: + name = f"/objects/medium_length_object_number_{i}" + else: + name = f"/objects/this_is_object_with_a_really_long_descriptive_name_{i}" + + scene.set_object(name, slamd.geom.Sphere(0.5)) + scene.set_transform(name, make_transform(i * 2.0, 5.0, 0)) + + # Several mid-depth branches + categories = ["sensors", "actuators", "reference_frames", "debug_vis"] + for cat in categories: + for j in range(5): + path = f"/{cat}/unit_{j}/geometry" + scene.set_object(path, slamd.geom.Box()) + scene.set_transform( + path, + make_transform(j * 2.0, -5.0 - categories.index(cat) * 3.0, 0), + ) + + +def populate_scene_wide(scene: slamd.Scene): + """Very wide tree with many siblings.""" + # Flat list of many objects + for i in range(50): + scene.set_object( + f"/item_{i:03d}", + slamd.geom.Triad(make_transform( + (i % 10) * 3.0, + (i // 10) * 3.0, + 0, + )), + ) + + # A few nested groups + for g in range(5): + for sub in range(10): + path = f"/group_{g}/subgroup_with_longer_name_{sub}/sphere" + scene.set_object(path, slamd.geom.Sphere(0.3)) + scene.set_transform(path, make_transform(g * 5.0, sub * 2.0, 5.0)) + + +def main(): + vis = slamd.Visualizer("tree overlay stress test") + + scene1 = vis.scene("deep tree") + scene2 = vis.scene("wide tree") + + populate_scene_deep(scene1) + populate_scene_wide(scene2) + + vis.hang_forever() + + +if __name__ == "__main__": + main() diff --git a/examples/two_windows.py b/examples/two_windows.py new file mode 100644 index 00000000..68844603 --- /dev/null +++ b/examples/two_windows.py @@ -0,0 +1,42 @@ +"""Multiple windows — two scenes side by side.""" + +import slamd +import numpy as np + +vis = slamd.Visualizer("Two Windows") + +scene1 = vis.scene("camera view") +scene2 = vis.scene("point cloud") + +# Scene 1: camera frustum with an image +K = np.array([[500, 0, 320], [0, 500, 240], [0, 0, 1]], dtype=np.float64) +w, h = 640, 480 +img = np.zeros((h, w, 3), dtype=np.uint8) +img[:h // 2] = [140, 180, 230] # sky +img[h // 2:] = [80, 130, 60] # ground + +scene1.set_object("/origin", slamd.geom.Triad()) +scene1.set_object("/cam/frustum", slamd.geom.CameraFrustum(K, w, h, img, 0.8)) +scene1.set_object("/cam/triad", slamd.geom.Triad(scale=0.3)) + +cam_tf = np.eye(4) +cam_tf[0, 3] = 2.0 +cam_tf[2, 3] = 1.0 +scene1.set_transform("/cam", cam_tf) + +# Scene 2: point cloud +n = 50000 +rng = np.random.default_rng(42) +theta = rng.uniform(0, 2 * np.pi, n) +r = rng.exponential(2.0, n) +z = rng.normal(0, 0.5, n) +positions = np.column_stack([r * np.cos(theta), r * np.sin(theta), z]).astype(np.float32) + +dist = np.sqrt(positions[:, 0]**2 + positions[:, 1]**2) +d = dist / dist.max() +colors = np.column_stack([0.9 * d, 0.3 + 0.5 * (1 - d), 0.8 * (1 - d)]).astype(np.float32) + +scene2.set_object("/cloud", slamd.geom.PointCloud(positions, colors, 0.08, 0.6)) +scene2.set_object("/origin", slamd.geom.Triad()) + +vis.hang_forever() diff --git a/flatbuffers/geometry.fbs b/flatbuffers/geometry.fbs index 6339faf4..fb863b4e 100644 --- a/flatbuffers/geometry.fbs +++ b/flatbuffers/geometry.fbs @@ -8,13 +8,6 @@ table Triad { pose: Mat4; } -table Circles2D { - positions: [Vec2]; - colors: [Vec3]; - radii: [float]; - thickness: float; -} - table CameraFrustum { intrinsics: Mat3; image_width: uint32; @@ -35,12 +28,6 @@ table Image { normalized: bool; } -table Points2D { - positions: [Vec2]; - colors: [Vec3]; - radii: [float]; -} - table Box {} table Sphere { @@ -62,17 +49,18 @@ table PolyLine { min_brightness: float; } -table PolyLine2D { - points: [Vec2]; - color: Vec3; - thickness: float; -} - table Mesh { data: MeshData; min_brightness: float; } +table Spheres { + positions: [Vec3]; + colors: [Vec3]; + radii: [float]; + min_brightness: float; +} + table Plane { normal: Vec3; point: Vec3; @@ -83,18 +71,16 @@ table Plane { union GeometryUnion { triad: Triad, - circles_2d: Circles2D, camera_frustum: CameraFrustum, point_cloud: PointCloud, image: Image, - points_2d: Points2D, box: Box, sphere: Sphere, arrows: Arrows, poly_line: PolyLine, - poly_line_2d: PolyLine2D, mesh: Mesh, - plane: Plane + plane: Plane, + spheres: Spheres } table Geometry { diff --git a/flatbuffers/messages.fbs b/flatbuffers/messages.fbs index f916e0e1..8f742461 100644 --- a/flatbuffers/messages.fbs +++ b/flatbuffers/messages.fbs @@ -24,18 +24,18 @@ table UpdateMeshNormals { } -// circles2d -table UpdateCircles2DPositions { +// spheres +table UpdateSpheresPositions { object_id: ulong; - positions: [Vec2]; + positions: [Vec3]; } -table UpdateCircles2DColors { +table UpdateSpheresColors { object_id: ulong; colors: [Vec3]; } -table UpdateCircles2DRadii { +table UpdateSpheresRadii { object_id: ulong; radii: [float]; } @@ -120,15 +120,15 @@ union MessageUnion { update_mesh_colors: UpdateMeshColors, update_mesh_normals: UpdateMeshNormals, - // circles 2d - update_circles2d_positions: UpdateCircles2DPositions, - update_circles2d_colors: UpdateCircles2DColors, - update_circles2d_radii: UpdateCircles2DRadii, - // point cloud update_point_cloud_positions: UpdatePointCloudPositions, update_point_cloud_colors: UpdatePointCloudColors, - update_point_cloud_radii: UpdatePointCloudRadii + update_point_cloud_radii: UpdatePointCloudRadii, + + // spheres + update_spheres_positions: UpdateSpheresPositions, + update_spheres_colors: UpdateSpheresColors, + update_spheres_radii: UpdateSpheresRadii } table Message { diff --git a/flatbuffers/visualizer.fbs b/flatbuffers/visualizer.fbs index 51ede966..b3243b63 100644 --- a/flatbuffers/visualizer.fbs +++ b/flatbuffers/visualizer.fbs @@ -20,16 +20,9 @@ table Tree { root: Node; } -enum ViewType: byte { - NONE = 0, - CANVAS = 1, - SCENE = 2 -} - table View { name: string; tree_id: ulong; - view_type: ViewType; } table InitialState { diff --git a/images/double_pendulum.gif b/images/double_pendulum.gif new file mode 100644 index 00000000..8da794ed Binary files /dev/null and b/images/double_pendulum.gif differ diff --git a/images/moving_mesh.webm b/images/galaxy.gif similarity index 61% rename from images/moving_mesh.webm rename to images/galaxy.gif index 144d5f02..54461221 100644 Binary files a/images/moving_mesh.webm and b/images/galaxy.gif differ diff --git a/images/spiral.gif b/images/hello_world.gif similarity index 52% rename from images/spiral.gif rename to images/hello_world.gif index 40e43f93..4fca4a38 100644 Binary files a/images/spiral.gif and b/images/hello_world.gif differ diff --git a/images/hello_world.png b/images/hello_world.png deleted file mode 100644 index 2a90ed27..00000000 Binary files a/images/hello_world.png and /dev/null differ diff --git a/images/logo.png b/images/logo.png index 3eb89c20..4688b518 100644 Binary files a/images/logo.png and b/images/logo.png differ diff --git a/images/lorenz_attractor.gif b/images/lorenz_attractor.gif new file mode 100644 index 00000000..054a881e Binary files /dev/null and b/images/lorenz_attractor.gif differ diff --git a/images/moving_mesh.gif b/images/moving_mesh.gif deleted file mode 100644 index 398f8174..00000000 Binary files a/images/moving_mesh.gif and /dev/null differ diff --git a/images/moving_spheres.gif b/images/moving_spheres.gif new file mode 100644 index 00000000..3e475b29 Binary files /dev/null and b/images/moving_spheres.gif differ diff --git a/images/palette.png b/images/palette.png deleted file mode 100644 index 1ae2b71f..00000000 Binary files a/images/palette.png and /dev/null differ diff --git a/images/two_scenes.png b/images/two_scenes.png deleted file mode 100644 index 78a8a7fb..00000000 Binary files a/images/two_scenes.png and /dev/null differ diff --git a/images/two_windows.gif b/images/two_windows.gif new file mode 100644 index 00000000..957c7796 Binary files /dev/null and b/images/two_windows.gif differ diff --git a/ports/slamd/portfile.cmake b/ports/slamd/portfile.cmake deleted file mode 100644 index 44bcda12..00000000 --- a/ports/slamd/portfile.cmake +++ /dev/null @@ -1,25 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH REPO_ROOT - REPO Robertleoj/slam_dunk - REF v0.1.0 - SHA512 0 -) - -# for local usage -# set(REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/../..") - -set(SOURCE_DIR "${REPO_ROOT}/slamd") - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_DIR}" -) - -vcpkg_cmake_install() - -vcpkg_cmake_config_fixup(PACKAGE_NAME "slamd") - -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") - -file(INSTALL "${REPO_ROOT}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) - -configure_file("${SOURCE_DIR}/usage" "${CURRENT_PACKAGES_DIR}/share/${PORT}/usage" COPYONLY) diff --git a/ports/slamd/vcpkg.json b/ports/slamd/vcpkg.json deleted file mode 100644 index 73ae99c0..00000000 --- a/ports/slamd/vcpkg.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "slamd", - "version-string": "0.1.0", - "homepage": "https://github.com/Robertleoj/slam_dunk", - "description": "3D visualization library targeted at SLAM applications", - "license": "Apache-2.0", - "dependencies": [ - "spdlog", - "glbinding", - "glfw3", - "glm", - { - "name": "imgui", - "features": [ - "glfw-binding", - "opengl3-binding", - "docking-experimental" - ] - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ] -} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index eae0e409..c536a306 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,36 @@ +[build-system] +requires = ["scikit-build-core>=0.10", "pybind11", "numpy>=1.23"] +build-backend = "scikit_build_core.build" + [project] -name = "slamd_dev_env" -version = "0.1.0" +name = "slamd" +version = "3.0.0" +description = "A 3D visualiztion library" +authors = [{ name = "Robert Leo", email = "robert.leo.jonsson@gmail.com" }] readme = "README.md" requires-python = ">=3.11" -dependencies = [ +dependencies = ["numpy>=1.23"] + +[project.scripts] +slamd-window = "slamd:_window_cli" + +[dependency-groups] +dev = [ "fire>=0.7.0", "imageio>=2.37.0", "jinja2>=3.1.6", - "numpy>=2.2.4", + "numpy>=1.23", + "pillow>=10.0", "pybind11>=2.13.6", "pybind11-stubgen>=2.5.3", + "scikit-build-core>=0.10", "scipy>=1.15.2", ] + +[tool.scikit-build] +minimum-version = "build-system.requires" +cmake.build-type = "MinSizeRel" +build-dir = "build/{wheel_tag}" + +[tool.cibuildwheel] +skip = ["*musllinux*", "*win*", "pp*"] diff --git a/python_bindings/README.md b/python_bindings/README.md deleted file mode 120000 index 32d46ee8..00000000 --- a/python_bindings/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/python_bindings/pyproject.toml b/python_bindings/pyproject.toml deleted file mode 100644 index f02323c1..00000000 --- a/python_bindings/pyproject.toml +++ /dev/null @@ -1,22 +0,0 @@ -[build-system] -requires = ["scikit-build-core>=0.10", "pybind11", "numpy>=1.23"] -build-backend = "scikit_build_core.build" - -[project] -name = "slamd" -version = "2.2.0" -description = "Python bindings for SlamDunk" -authors = [{ name = "Robert Leo", email = "robert.leo.jonsson@gmail.com" }] -readme = "README.md" -requires-python = ">=3.11" -dependencies = ["numpy>=1.23"] - -[project.scripts] -slamd-window = "slamd:_window_cli" - -[tool.scikit-build] -minimum-version = "build-system.requires" -cmake.build-type = "MinSizeRel" - -[tool.cibuildwheel] -skip = ["*musllinux*", "*win*", "pp*"] diff --git a/python_bindings/slamd_src b/python_bindings/slamd_src deleted file mode 120000 index 605e7696..00000000 --- a/python_bindings/slamd_src +++ /dev/null @@ -1 +0,0 @@ -../slamd \ No newline at end of file diff --git a/python_bindings/src/slamd/bindings/geom2d.pyi b/python_bindings/src/slamd/bindings/geom2d.pyi deleted file mode 100644 index 74efc652..00000000 --- a/python_bindings/src/slamd/bindings/geom2d.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations -import bindings._geom -import numpy -import typing -__all__ = ['Circles', 'Image', 'Points', 'PolyLine'] -def Circles(positions: numpy.ndarray, colors: numpy.ndarray, radii: list[float] | numpy.ndarray, thickness: typing.SupportsFloat | typing.SupportsIndex = 0.1) -> bindings._geom.Circles2D: - """ - Create a set of circles - """ -def Image(image: numpy.ndarray) -> bindings._geom.Image: - """ - Create an Image geometry from a NumPy uint8 array (H, W, C) - """ -def Points(positions: numpy.ndarray, colors: numpy.ndarray, radii: list[float] | numpy.ndarray) -> bindings._geom.Points2D: - """ - Create 2D points with per-point color and radius - """ -def PolyLine(points: numpy.ndarray, color: numpy.ndarray, thickness: typing.SupportsFloat | typing.SupportsIndex) -> bindings._geom.PolyLine2D: - """ - Create a 2D poly line - """ diff --git a/python_bindings/src/slamd/geom2d/__init__.py b/python_bindings/src/slamd/geom2d/__init__.py deleted file mode 100644 index 2158809d..00000000 --- a/python_bindings/src/slamd/geom2d/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from ..bindings.geom2d import Image -from .overrides import Points, PolyLine, Circles - - -__all__ = ["Points", "Image", "PolyLine", "Circles"] diff --git a/python_bindings/src/slamd/geom2d/overrides.py b/python_bindings/src/slamd/geom2d/overrides.py deleted file mode 100644 index 61e54bb9..00000000 --- a/python_bindings/src/slamd/geom2d/overrides.py +++ /dev/null @@ -1,75 +0,0 @@ -import numpy as np - -from .._utils.colors import Color -from ..bindings.geom2d import ( - Points as Points_internal, - PolyLine as PolyLine_internal, - Circles as Circles_internal, -) - -from .._utils.handle_input import process_color, process_radii, process_single_color - - -def Points( - positions: np.ndarray, - colors: np.ndarray | tuple[int, int, int] = Color.black, - radii: np.ndarray | float = 1.0, -): - """A set of 2D points. - - Args: - positions: a N x 2 array of the 2D point centers. - colors: The color of the points. Can be one of - - array of shape N x 3 of RGB colors in (0, 1) - - array of shape 3 with a single RGB color in (0, 1) - - tuple an RGB value, 0-255 - radii: The radius of each point. Can be one of - - array of shape N with a radius for each point - - a single float specifying the radius of all the points. - """ - n = positions.shape[0] - colors_np = process_color(colors, n) - radii_np = process_radii(radii, n) - - return Points_internal(positions, colors_np, radii_np) - - -def PolyLine( - points: np.ndarray, - color: np.ndarray | tuple[int, int, int] = Color.pink, - thickness: float = 1.0, -): - """A piecewise-linear line. - - Args: - points: The points which the piecewise-linear line goes through. - color: Either a numpy array with values in (0, 1), or an RGB tuple (0-255) - thickness: The thickness of the line. - """ - return PolyLine_internal(points, process_single_color(color), thickness) - - -def Circles( - positions: np.ndarray, - colors: np.ndarray | tuple[int, int, int] = Color.dark_blue, - radii: np.ndarray | float = 1.0, - thickness: float = 0.1, -): - """A set of hollow circles. - - Args: - positions: a N x 2 array of the 2D point centers. - colors: The color of the points. Can be one of - - array of shape N x 3 of RGB colors in (0, 1) - - array of shape 3 with a single RGB color in (0, 1) - - tuple an RGB value, 0-255 - radii: The radius of each point. Can be one of - - array of shape N with a radius for each point - - a single float specifying the radius of all the points. - thickness: Thickness of the circle as a proportion of the radius. - """ - n = positions.shape[0] - - return Circles_internal( - positions, process_color(colors, n), process_radii(radii, n), thickness - ) diff --git a/python_bindings/vendored_deps b/python_bindings/vendored_deps deleted file mode 120000 index bd8758d7..00000000 --- a/python_bindings/vendored_deps +++ /dev/null @@ -1 +0,0 @@ -../vendored_deps \ No newline at end of file diff --git a/python_examples/circles.py b/python_examples/circles.py deleted file mode 100644 index e7407df1..00000000 --- a/python_examples/circles.py +++ /dev/null @@ -1,59 +0,0 @@ -import slamd -import numpy as np -import time - - -def main(): - vis = slamd.Visualizer("🌀 Slamd Circle Rave — Juiced Edition 🌀") - canvas = slamd.Canvas() - vis.add_canvas("canvas", canvas) - - # Fewer, beefier circles - n = 500 - positions = np.random.uniform(-500.0, 500.0, (n, 2)) - velocities = np.random.uniform(-50.0, 50.0, (n, 2)) - colors = np.zeros((n, 3)) - radii = np.random.uniform(20.0, 40.0, n) # more thicc - base_radii = radii.copy() - thickness = 0.1 - - circles = slamd.geom2d.Circles(positions, colors, radii, thickness) - canvas.set_object("/circles", circles) - - t = 0.0 - center = np.array([0.0, 0.0]) - - while True: - t += 0.016 - - # Orbit swirl - direction_to_center = center - positions - distance = np.linalg.norm(direction_to_center, axis=1, keepdims=True) + 1e-6 - normalized = direction_to_center / distance - perp = np.stack([-normalized[:, 1], normalized[:, 0]], axis=1) - velocities += 0.5 * perp - velocities *= 0.99 - positions += velocities * 0.016 - - # RGB cyclone - colors = 0.5 + 0.5 * np.stack( - [ - np.sin(0.001 * positions[:, 0] + t), - np.sin(0.001 * positions[:, 1] + t + 2.1), - np.sin(0.001 * (positions[:, 0] + positions[:, 1]) + t + 4.2), - ], - axis=1, - ) - - # Pulse bigger bois - radii = base_radii * (1.0 + 0.4 * np.sin(t * 2 + positions[:, 0] * 0.01)) - - circles.update_positions(positions) - circles.update_colors(colors) - circles.update_radii(radii) - - time.sleep(0.016) - - -if __name__ == "__main__": - main() diff --git a/python_examples/drawing_2d.py b/python_examples/drawing_2d.py deleted file mode 100644 index 4e6b75b3..00000000 --- a/python_examples/drawing_2d.py +++ /dev/null @@ -1,34 +0,0 @@ -import slamd -import numpy as np -import time - - -def cool_spiral(n: int, t: float) -> np.ndarray: - t_arr = np.linspace(0, 10 * np.pi, n) + t - r = np.linspace(0, 1, n) - x = r * np.cos(t_arr) - y = r * np.sin(t_arr) - return np.stack((x, y), axis=1) - - -if __name__ == "__main__": - vis = slamd.Visualizer("drawing_2d") - - canvas = slamd.Canvas() - - vis.add_canvas("canvas", canvas) - - t = 0.0 - while True: - line_points = cool_spiral(10000, t) - - poly_line = slamd.geom2d.PolyLine( - line_points, - np.array([0.5, 0.5, 0.0]), - 0.1, - ) - - canvas.set_object("/line", poly_line) - - time.sleep(10 / 1000) - t += 0.05 diff --git a/python_examples/image.py b/python_examples/image.py deleted file mode 100644 index 1a2822cb..00000000 --- a/python_examples/image.py +++ /dev/null @@ -1,23 +0,0 @@ -import slamd -import imageio.v3 as iio -from pathlib import Path - - -def main(): - vis = slamd.Visualizer("image") - - canvas = slamd.Canvas() - - image_path = Path(__file__).parent.parent / "images" / "logo.png" - - image = iio.imread(image_path) - - canvas.set_object("/image", slamd.geom2d.Image(image)) - - vis.add_canvas("canvas", canvas) - - vis.hang_forever() - - -if __name__ == "__main__": - main() diff --git a/python_examples/slamd b/python_examples/slamd deleted file mode 120000 index a131dac0..00000000 --- a/python_examples/slamd +++ /dev/null @@ -1 +0,0 @@ -../python_bindings/src/slamd/ \ No newline at end of file diff --git a/python_examples/two_windows.py b/python_examples/two_windows.py deleted file mode 100644 index 95e1288d..00000000 --- a/python_examples/two_windows.py +++ /dev/null @@ -1,23 +0,0 @@ -import slamd -import numpy as np - -if __name__ == "__main__": - vis = slamd.Visualizer("two windows") - - scene1 = slamd.Scene() - scene2 = slamd.Scene() - - vis.add_scene("scene 1", scene1) - scene1.set_object("/box", slamd.geom.Box()) - - vis.add_scene("scene 2", scene2) - scene2.set_object("/origin", slamd.geom.Triad()) - - scene2.set_object("/ball", slamd.geom.Sphere(2.0)) - - sphere_transform = np.identity(4, dtype=np.float32) - sphere_transform[:, 3] = np.array([5.0, 1.0, 2.0, 1.0]) - - scene2.set_transform("/ball", sphere_transform) - - vis.hang_forever() diff --git a/scripts/record_example.sh b/scripts/record_example.sh new file mode 100755 index 00000000..fcca70f3 --- /dev/null +++ b/scripts/record_example.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# +# Record a slamd window to GIF. +# +# Usage: ./scripts/record_example.sh [duration_seconds] [wait_seconds] +# +# Example: ./scripts/record_example.sh examples/galaxy.py images/galaxy.gif 5 10 + +set -euo pipefail + +EXAMPLE="${1:?Usage: record_example.sh [duration] [wait]}" +OUTPUT="${2:?Usage: record_example.sh [duration] [wait]}" +DURATION="${3:-5}" +SETTLE_TIME="${4:-3}" +FPS=30 +GIF_WIDTH=800 + +# Start the example in background +python3 "$EXAMPLE" & +PY_PID=$! +trap "kill $PY_PID 2>/dev/null" EXIT + +# Wait for any new window to appear, then let the scene settle +echo "Waiting for window..." +sleep "$SETTLE_TIME" + +# Find the most recently mapped window (last in the list) +WID=$(xdotool search --onlyvisible --name "." 2>/dev/null | tail -1) + +if [ -z "$WID" ]; then + echo "ERROR: Could not find window to record" + exit 1 +fi + +echo "Found window: $(xdotool getwindowname "$WID")" + +# Get window geometry +eval $(xdotool getwindowgeometry --shell "$WID") + +echo "Recording ${WIDTH}x${HEIGHT} at ($X,$Y) for ${DURATION}s..." + +# Record raw video first +TMP_VID="/tmp/_slamd_rec.mp4" +ffmpeg -y -loglevel warning \ + -video_size "${WIDTH}x${HEIGHT}" -framerate "$FPS" \ + -f x11grab -i "${DISPLAY}+${X},${Y}" \ + -t "$DURATION" \ + -c:v libx264 -crf 18 -preset ultrafast \ + "$TMP_VID" + +kill $PY_PID 2>/dev/null || true + +# Generate palette +TMP_PAL="/tmp/_slamd_palette.png" +ffmpeg -y -loglevel warning -i "$TMP_VID" \ + -vf "fps=$FPS,scale=$GIF_WIDTH:-1:flags=lanczos,palettegen=max_colors=128:stats_mode=diff" \ + "$TMP_PAL" + +# Convert to GIF using palette +ffmpeg -y -loglevel warning -i "$TMP_VID" -i "$TMP_PAL" \ + -lavfi "fps=$FPS,scale=$GIF_WIDTH:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=3" \ + "$OUTPUT" + +rm -f "$TMP_VID" "$TMP_PAL" +echo "Saved to $OUTPUT" diff --git a/slamd/CMakeLists.txt b/slamd/CMakeLists.txt index 6f32bade..5a89d7d8 100644 --- a/slamd/CMakeLists.txt +++ b/slamd/CMakeLists.txt @@ -9,30 +9,37 @@ project(slam_dunk VERSION 0.1.0) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(LIB_DIR vendored_deps) +if(NOT DEFINED LIB_DIR) + set(LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendored_deps) +endif() if(NOT TARGET glbinding::glbinding-aux) - add_subdirectory(${LIB_DIR}/glbinding) + add_subdirectory(${LIB_DIR}/glbinding ${CMAKE_CURRENT_BINARY_DIR}/_deps/glbinding) endif() if(NOT TARGET glfw) - add_subdirectory(${LIB_DIR}/glfw) + add_subdirectory(${LIB_DIR}/glfw ${CMAKE_CURRENT_BINARY_DIR}/_deps/glfw) endif() if(NOT TARGET spdlog::spdlog) - add_subdirectory(${LIB_DIR}/spdlog) + add_subdirectory(${LIB_DIR}/spdlog ${CMAKE_CURRENT_BINARY_DIR}/_deps/spdlog) endif() if(NOT TARGET imgui::imgui) - add_subdirectory(${LIB_DIR}/imgui_wrapper) + add_subdirectory(${LIB_DIR}/imgui_wrapper ${CMAKE_CURRENT_BINARY_DIR}/_deps/imgui_wrapper) endif() if(NOT TARGET fmt::fmt) - add_subdirectory(${LIB_DIR}/fmt) + add_subdirectory(${LIB_DIR}/fmt ${CMAKE_CURRENT_BINARY_DIR}/_deps/fmt) +endif() + +if(NOT TARGET glm::glm) + add_subdirectory(${LIB_DIR}/glm ${CMAKE_CURRENT_BINARY_DIR}/_deps/glm) endif() -add_subdirectory(${LIB_DIR}/glm) -add_subdirectory(${LIB_DIR}/cxxopts) +if(NOT TARGET cxxopts) + add_subdirectory(${LIB_DIR}/cxxopts ${CMAKE_CURRENT_BINARY_DIR}/_deps/cxxopts) +endif() add_subdirectory(flatb) @@ -51,7 +58,6 @@ target_sources( src/common/utils/mesh.cpp src/common/gmath/aabb.cpp - src/common/gmath/rect2d.cpp src/common/gmath/angle.cpp src/common/gmath/transforms.cpp src/common/gmath/serialization.cpp @@ -92,7 +98,6 @@ target_sources( PRIVATE src/window/main.cpp - src/window/camera_2d.cpp src/window/image_texture.cpp src/window/glfw.cpp src/window/frame_buffer.cpp @@ -101,6 +106,7 @@ target_sources( src/window/arcball.cpp src/window/frame_timer.cpp src/window/run_window.cpp + src/window/tree_overlay.cpp src/window/state_manager.cpp src/window/connection.cpp src/window/message.cpp @@ -109,11 +115,8 @@ target_sources( src/window/tree/node.cpp src/window/tree/tree_path.cpp - src/window/view/view.cpp src/window/view/scene_view.cpp - src/window/view/canvas_view.cpp - src/window/geom/points_2d.cpp src/window/geom/geometry.cpp src/window/geom/deserialize.cpp src/window/geom/box.cpp @@ -127,9 +130,8 @@ target_sources( src/window/geom/image.cpp src/window/geom/point_cloud.cpp src/window/geom/camera_frustum.cpp - src/window/geom/poly_line_2d.cpp src/window/geom/mono_instanced.cpp - src/window/geom/circles_2d.cpp + src/window/geom/spheres.cpp src/window/geom/plane.cpp ) @@ -138,8 +140,8 @@ target_include_directories( PUBLIC $ - $ - $ + $ + $ ) target_link_libraries( @@ -185,7 +187,6 @@ target_sources( src/slamd/tree/tree.cpp src/slamd/tree/tree_path.cpp - src/slamd/geom/points_2d.cpp src/slamd/geom/geometry.cpp src/slamd/geom/box.cpp src/slamd/geom/mesh.cpp @@ -197,9 +198,8 @@ target_sources( src/slamd/geom/image.cpp src/slamd/geom/point_cloud.cpp src/slamd/geom/camera_frustum.cpp - src/slamd/geom/poly_line_2d.cpp src/slamd/geom/mono_instanced.cpp - src/slamd/geom/circles_2d.cpp + src/slamd/geom/spheres.cpp ) target_include_directories( @@ -207,8 +207,8 @@ target_include_directories( PUBLIC $ - $ - $ + $ + $ ) target_link_libraries( diff --git a/slamd/flatb/CMakeLists.txt b/slamd/flatb/CMakeLists.txt index 0c1f7133..e703f450 100644 --- a/slamd/flatb/CMakeLists.txt +++ b/slamd/flatb/CMakeLists.txt @@ -3,7 +3,7 @@ add_compile_options(-Wno-stringop-overflow) if (NOT TARGET flatbuffers) set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(FLATBUFFERS_BUILD_FLATC OFF CACHE BOOL "" FORCE) - add_subdirectory(../vendored_deps/flatbuffers ${CMAKE_CURRENT_BINARY_DIR}/_deps/flatbuffers) + add_subdirectory(${LIB_DIR}/flatbuffers ${CMAKE_CURRENT_BINARY_DIR}/_deps/flatbuffers) endif() # Grab all .cpp and .h/.hpp files in current dir diff --git a/slamd/flatb/flatb/geometry_generated.h b/slamd/flatb/flatb/geometry_generated.h index 3f63ca65..c27f34b9 100644 --- a/slamd/flatb/flatb/geometry_generated.h +++ b/slamd/flatb/flatb/geometry_generated.h @@ -21,9 +21,6 @@ namespace flatb { struct Triad; struct TriadBuilder; -struct Circles2D; -struct Circles2DBuilder; - struct CameraFrustum; struct CameraFrustumBuilder; @@ -33,9 +30,6 @@ struct PointCloudBuilder; struct Image; struct ImageBuilder; -struct Points2D; -struct Points2DBuilder; - struct Box; struct BoxBuilder; @@ -48,12 +42,12 @@ struct ArrowsBuilder; struct PolyLine; struct PolyLineBuilder; -struct PolyLine2D; -struct PolyLine2DBuilder; - struct Mesh; struct MeshBuilder; +struct Spheres; +struct SpheresBuilder; + struct Plane; struct PlaneBuilder; @@ -63,65 +57,59 @@ struct GeometryBuilder; enum GeometryUnion : uint8_t { GeometryUnion_NONE = 0, GeometryUnion_triad = 1, - GeometryUnion_circles_2d = 2, - GeometryUnion_camera_frustum = 3, - GeometryUnion_point_cloud = 4, - GeometryUnion_image = 5, - GeometryUnion_points_2d = 6, - GeometryUnion_box = 7, - GeometryUnion_sphere = 8, - GeometryUnion_arrows = 9, - GeometryUnion_poly_line = 10, - GeometryUnion_poly_line_2d = 11, - GeometryUnion_mesh = 12, - GeometryUnion_plane = 13, + GeometryUnion_camera_frustum = 2, + GeometryUnion_point_cloud = 3, + GeometryUnion_image = 4, + GeometryUnion_box = 5, + GeometryUnion_sphere = 6, + GeometryUnion_arrows = 7, + GeometryUnion_poly_line = 8, + GeometryUnion_mesh = 9, + GeometryUnion_plane = 10, + GeometryUnion_spheres = 11, GeometryUnion_MIN = GeometryUnion_NONE, - GeometryUnion_MAX = GeometryUnion_plane + GeometryUnion_MAX = GeometryUnion_spheres }; -inline const GeometryUnion (&EnumValuesGeometryUnion())[14] { +inline const GeometryUnion (&EnumValuesGeometryUnion())[12] { static const GeometryUnion values[] = { GeometryUnion_NONE, GeometryUnion_triad, - GeometryUnion_circles_2d, GeometryUnion_camera_frustum, GeometryUnion_point_cloud, GeometryUnion_image, - GeometryUnion_points_2d, GeometryUnion_box, GeometryUnion_sphere, GeometryUnion_arrows, GeometryUnion_poly_line, - GeometryUnion_poly_line_2d, GeometryUnion_mesh, - GeometryUnion_plane + GeometryUnion_plane, + GeometryUnion_spheres }; return values; } inline const char * const *EnumNamesGeometryUnion() { - static const char * const names[15] = { + static const char * const names[13] = { "NONE", "triad", - "circles_2d", "camera_frustum", "point_cloud", "image", - "points_2d", "box", "sphere", "arrows", "poly_line", - "poly_line_2d", "mesh", "plane", + "spheres", nullptr }; return names; } inline const char *EnumNameGeometryUnion(GeometryUnion e) { - if (::flatbuffers::IsOutRange(e, GeometryUnion_NONE, GeometryUnion_plane)) return ""; + if (::flatbuffers::IsOutRange(e, GeometryUnion_NONE, GeometryUnion_spheres)) return ""; const size_t index = static_cast(e); return EnumNamesGeometryUnion()[index]; } @@ -134,10 +122,6 @@ template<> struct GeometryUnionTraits { static const GeometryUnion enum_value = GeometryUnion_triad; }; -template<> struct GeometryUnionTraits { - static const GeometryUnion enum_value = GeometryUnion_circles_2d; -}; - template<> struct GeometryUnionTraits { static const GeometryUnion enum_value = GeometryUnion_camera_frustum; }; @@ -150,10 +134,6 @@ template<> struct GeometryUnionTraits { static const GeometryUnion enum_value = GeometryUnion_image; }; -template<> struct GeometryUnionTraits { - static const GeometryUnion enum_value = GeometryUnion_points_2d; -}; - template<> struct GeometryUnionTraits { static const GeometryUnion enum_value = GeometryUnion_box; }; @@ -170,10 +150,6 @@ template<> struct GeometryUnionTraits { static const GeometryUnion enum_value = GeometryUnion_poly_line; }; -template<> struct GeometryUnionTraits { - static const GeometryUnion enum_value = GeometryUnion_poly_line_2d; -}; - template<> struct GeometryUnionTraits { static const GeometryUnion enum_value = GeometryUnion_mesh; }; @@ -182,6 +158,10 @@ template<> struct GeometryUnionTraits { static const GeometryUnion enum_value = GeometryUnion_plane; }; +template<> struct GeometryUnionTraits { + static const GeometryUnion enum_value = GeometryUnion_spheres; +}; + bool VerifyGeometryUnion(::flatbuffers::Verifier &verifier, const void *obj, GeometryUnion type); bool VerifyGeometryUnionVector(::flatbuffers::Verifier &verifier, const ::flatbuffers::Vector<::flatbuffers::Offset> *values, const ::flatbuffers::Vector *types); @@ -246,97 +226,6 @@ inline ::flatbuffers::Offset CreateTriad( return builder_.Finish(); } -struct Circles2D FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { - typedef Circles2DBuilder Builder; - enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { - VT_POSITIONS = 4, - VT_COLORS = 6, - VT_RADII = 8, - VT_THICKNESS = 10 - }; - const ::flatbuffers::Vector *positions() const { - return GetPointer *>(VT_POSITIONS); - } - const ::flatbuffers::Vector *colors() const { - return GetPointer *>(VT_COLORS); - } - const ::flatbuffers::Vector *radii() const { - return GetPointer *>(VT_RADII); - } - float thickness() const { - return GetField(VT_THICKNESS, 0.0f); - } - bool Verify(::flatbuffers::Verifier &verifier) const { - return VerifyTableStart(verifier) && - VerifyOffset(verifier, VT_POSITIONS) && - verifier.VerifyVector(positions()) && - VerifyOffset(verifier, VT_COLORS) && - verifier.VerifyVector(colors()) && - VerifyOffset(verifier, VT_RADII) && - verifier.VerifyVector(radii()) && - VerifyField(verifier, VT_THICKNESS, 4) && - verifier.EndTable(); - } -}; - -struct Circles2DBuilder { - typedef Circles2D Table; - ::flatbuffers::FlatBufferBuilder &fbb_; - ::flatbuffers::uoffset_t start_; - void add_positions(::flatbuffers::Offset<::flatbuffers::Vector> positions) { - fbb_.AddOffset(Circles2D::VT_POSITIONS, positions); - } - void add_colors(::flatbuffers::Offset<::flatbuffers::Vector> colors) { - fbb_.AddOffset(Circles2D::VT_COLORS, colors); - } - void add_radii(::flatbuffers::Offset<::flatbuffers::Vector> radii) { - fbb_.AddOffset(Circles2D::VT_RADII, radii); - } - void add_thickness(float thickness) { - fbb_.AddElement(Circles2D::VT_THICKNESS, thickness, 0.0f); - } - explicit Circles2DBuilder(::flatbuffers::FlatBufferBuilder &_fbb) - : fbb_(_fbb) { - start_ = fbb_.StartTable(); - } - ::flatbuffers::Offset Finish() { - const auto end = fbb_.EndTable(start_); - auto o = ::flatbuffers::Offset(end); - return o; - } -}; - -inline ::flatbuffers::Offset CreateCircles2D( - ::flatbuffers::FlatBufferBuilder &_fbb, - ::flatbuffers::Offset<::flatbuffers::Vector> positions = 0, - ::flatbuffers::Offset<::flatbuffers::Vector> colors = 0, - ::flatbuffers::Offset<::flatbuffers::Vector> radii = 0, - float thickness = 0.0f) { - Circles2DBuilder builder_(_fbb); - builder_.add_thickness(thickness); - builder_.add_radii(radii); - builder_.add_colors(colors); - builder_.add_positions(positions); - return builder_.Finish(); -} - -inline ::flatbuffers::Offset CreateCircles2DDirect( - ::flatbuffers::FlatBufferBuilder &_fbb, - const std::vector *positions = nullptr, - const std::vector *colors = nullptr, - const std::vector *radii = nullptr, - float thickness = 0.0f) { - auto positions__ = positions ? _fbb.CreateVectorOfStructs(*positions) : 0; - auto colors__ = colors ? _fbb.CreateVectorOfStructs(*colors) : 0; - auto radii__ = radii ? _fbb.CreateVector(*radii) : 0; - return slamd::flatb::CreateCircles2D( - _fbb, - positions__, - colors__, - radii__, - thickness); -} - struct CameraFrustum FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { typedef CameraFrustumBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { @@ -562,85 +451,6 @@ inline ::flatbuffers::Offset CreateImage( return builder_.Finish(); } -struct Points2D FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { - typedef Points2DBuilder Builder; - enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { - VT_POSITIONS = 4, - VT_COLORS = 6, - VT_RADII = 8 - }; - const ::flatbuffers::Vector *positions() const { - return GetPointer *>(VT_POSITIONS); - } - const ::flatbuffers::Vector *colors() const { - return GetPointer *>(VT_COLORS); - } - const ::flatbuffers::Vector *radii() const { - return GetPointer *>(VT_RADII); - } - bool Verify(::flatbuffers::Verifier &verifier) const { - return VerifyTableStart(verifier) && - VerifyOffset(verifier, VT_POSITIONS) && - verifier.VerifyVector(positions()) && - VerifyOffset(verifier, VT_COLORS) && - verifier.VerifyVector(colors()) && - VerifyOffset(verifier, VT_RADII) && - verifier.VerifyVector(radii()) && - verifier.EndTable(); - } -}; - -struct Points2DBuilder { - typedef Points2D Table; - ::flatbuffers::FlatBufferBuilder &fbb_; - ::flatbuffers::uoffset_t start_; - void add_positions(::flatbuffers::Offset<::flatbuffers::Vector> positions) { - fbb_.AddOffset(Points2D::VT_POSITIONS, positions); - } - void add_colors(::flatbuffers::Offset<::flatbuffers::Vector> colors) { - fbb_.AddOffset(Points2D::VT_COLORS, colors); - } - void add_radii(::flatbuffers::Offset<::flatbuffers::Vector> radii) { - fbb_.AddOffset(Points2D::VT_RADII, radii); - } - explicit Points2DBuilder(::flatbuffers::FlatBufferBuilder &_fbb) - : fbb_(_fbb) { - start_ = fbb_.StartTable(); - } - ::flatbuffers::Offset Finish() { - const auto end = fbb_.EndTable(start_); - auto o = ::flatbuffers::Offset(end); - return o; - } -}; - -inline ::flatbuffers::Offset CreatePoints2D( - ::flatbuffers::FlatBufferBuilder &_fbb, - ::flatbuffers::Offset<::flatbuffers::Vector> positions = 0, - ::flatbuffers::Offset<::flatbuffers::Vector> colors = 0, - ::flatbuffers::Offset<::flatbuffers::Vector> radii = 0) { - Points2DBuilder builder_(_fbb); - builder_.add_radii(radii); - builder_.add_colors(colors); - builder_.add_positions(positions); - return builder_.Finish(); -} - -inline ::flatbuffers::Offset CreatePoints2DDirect( - ::flatbuffers::FlatBufferBuilder &_fbb, - const std::vector *positions = nullptr, - const std::vector *colors = nullptr, - const std::vector *radii = nullptr) { - auto positions__ = positions ? _fbb.CreateVectorOfStructs(*positions) : 0; - auto colors__ = colors ? _fbb.CreateVectorOfStructs(*colors) : 0; - auto radii__ = radii ? _fbb.CreateVector(*radii) : 0; - return slamd::flatb::CreatePoints2D( - _fbb, - positions__, - colors__, - radii__); -} - struct Box FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { typedef BoxBuilder Builder; bool Verify(::flatbuffers::Verifier &verifier) const { @@ -899,133 +709,149 @@ inline ::flatbuffers::Offset CreatePolyLineDirect( min_brightness); } -struct PolyLine2D FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { - typedef PolyLine2DBuilder Builder; +struct Mesh FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef MeshBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { - VT_POINTS = 4, - VT_COLOR = 6, - VT_THICKNESS = 8 + VT_DATA = 4, + VT_MIN_BRIGHTNESS = 6 }; - const ::flatbuffers::Vector *points() const { - return GetPointer *>(VT_POINTS); - } - const slamd::flatb::Vec3 *color() const { - return GetStruct(VT_COLOR); + const slamd::flatb::MeshData *data() const { + return GetPointer(VT_DATA); } - float thickness() const { - return GetField(VT_THICKNESS, 0.0f); + float min_brightness() const { + return GetField(VT_MIN_BRIGHTNESS, 0.0f); } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && - VerifyOffset(verifier, VT_POINTS) && - verifier.VerifyVector(points()) && - VerifyField(verifier, VT_COLOR, 4) && - VerifyField(verifier, VT_THICKNESS, 4) && + VerifyOffset(verifier, VT_DATA) && + verifier.VerifyTable(data()) && + VerifyField(verifier, VT_MIN_BRIGHTNESS, 4) && verifier.EndTable(); } }; -struct PolyLine2DBuilder { - typedef PolyLine2D Table; +struct MeshBuilder { + typedef Mesh Table; ::flatbuffers::FlatBufferBuilder &fbb_; ::flatbuffers::uoffset_t start_; - void add_points(::flatbuffers::Offset<::flatbuffers::Vector> points) { - fbb_.AddOffset(PolyLine2D::VT_POINTS, points); - } - void add_color(const slamd::flatb::Vec3 *color) { - fbb_.AddStruct(PolyLine2D::VT_COLOR, color); + void add_data(::flatbuffers::Offset data) { + fbb_.AddOffset(Mesh::VT_DATA, data); } - void add_thickness(float thickness) { - fbb_.AddElement(PolyLine2D::VT_THICKNESS, thickness, 0.0f); + void add_min_brightness(float min_brightness) { + fbb_.AddElement(Mesh::VT_MIN_BRIGHTNESS, min_brightness, 0.0f); } - explicit PolyLine2DBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + explicit MeshBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } - ::flatbuffers::Offset Finish() { + ::flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); - auto o = ::flatbuffers::Offset(end); + auto o = ::flatbuffers::Offset(end); return o; } }; -inline ::flatbuffers::Offset CreatePolyLine2D( +inline ::flatbuffers::Offset CreateMesh( ::flatbuffers::FlatBufferBuilder &_fbb, - ::flatbuffers::Offset<::flatbuffers::Vector> points = 0, - const slamd::flatb::Vec3 *color = nullptr, - float thickness = 0.0f) { - PolyLine2DBuilder builder_(_fbb); - builder_.add_thickness(thickness); - builder_.add_color(color); - builder_.add_points(points); + ::flatbuffers::Offset data = 0, + float min_brightness = 0.0f) { + MeshBuilder builder_(_fbb); + builder_.add_min_brightness(min_brightness); + builder_.add_data(data); return builder_.Finish(); } -inline ::flatbuffers::Offset CreatePolyLine2DDirect( - ::flatbuffers::FlatBufferBuilder &_fbb, - const std::vector *points = nullptr, - const slamd::flatb::Vec3 *color = nullptr, - float thickness = 0.0f) { - auto points__ = points ? _fbb.CreateVectorOfStructs(*points) : 0; - return slamd::flatb::CreatePolyLine2D( - _fbb, - points__, - color, - thickness); -} - -struct Mesh FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { - typedef MeshBuilder Builder; +struct Spheres FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef SpheresBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { - VT_DATA = 4, - VT_MIN_BRIGHTNESS = 6 + VT_POSITIONS = 4, + VT_COLORS = 6, + VT_RADII = 8, + VT_MIN_BRIGHTNESS = 10 }; - const slamd::flatb::MeshData *data() const { - return GetPointer(VT_DATA); + const ::flatbuffers::Vector *positions() const { + return GetPointer *>(VT_POSITIONS); + } + const ::flatbuffers::Vector *colors() const { + return GetPointer *>(VT_COLORS); + } + const ::flatbuffers::Vector *radii() const { + return GetPointer *>(VT_RADII); } float min_brightness() const { return GetField(VT_MIN_BRIGHTNESS, 0.0f); } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && - VerifyOffset(verifier, VT_DATA) && - verifier.VerifyTable(data()) && + VerifyOffset(verifier, VT_POSITIONS) && + verifier.VerifyVector(positions()) && + VerifyOffset(verifier, VT_COLORS) && + verifier.VerifyVector(colors()) && + VerifyOffset(verifier, VT_RADII) && + verifier.VerifyVector(radii()) && VerifyField(verifier, VT_MIN_BRIGHTNESS, 4) && verifier.EndTable(); } }; -struct MeshBuilder { - typedef Mesh Table; +struct SpheresBuilder { + typedef Spheres Table; ::flatbuffers::FlatBufferBuilder &fbb_; ::flatbuffers::uoffset_t start_; - void add_data(::flatbuffers::Offset data) { - fbb_.AddOffset(Mesh::VT_DATA, data); + void add_positions(::flatbuffers::Offset<::flatbuffers::Vector> positions) { + fbb_.AddOffset(Spheres::VT_POSITIONS, positions); + } + void add_colors(::flatbuffers::Offset<::flatbuffers::Vector> colors) { + fbb_.AddOffset(Spheres::VT_COLORS, colors); + } + void add_radii(::flatbuffers::Offset<::flatbuffers::Vector> radii) { + fbb_.AddOffset(Spheres::VT_RADII, radii); } void add_min_brightness(float min_brightness) { - fbb_.AddElement(Mesh::VT_MIN_BRIGHTNESS, min_brightness, 0.0f); + fbb_.AddElement(Spheres::VT_MIN_BRIGHTNESS, min_brightness, 0.0f); } - explicit MeshBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + explicit SpheresBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } - ::flatbuffers::Offset Finish() { + ::flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); - auto o = ::flatbuffers::Offset(end); + auto o = ::flatbuffers::Offset(end); return o; } }; -inline ::flatbuffers::Offset CreateMesh( +inline ::flatbuffers::Offset CreateSpheres( ::flatbuffers::FlatBufferBuilder &_fbb, - ::flatbuffers::Offset data = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> positions = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> colors = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> radii = 0, float min_brightness = 0.0f) { - MeshBuilder builder_(_fbb); + SpheresBuilder builder_(_fbb); builder_.add_min_brightness(min_brightness); - builder_.add_data(data); + builder_.add_radii(radii); + builder_.add_colors(colors); + builder_.add_positions(positions); return builder_.Finish(); } +inline ::flatbuffers::Offset CreateSpheresDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + const std::vector *positions = nullptr, + const std::vector *colors = nullptr, + const std::vector *radii = nullptr, + float min_brightness = 0.0f) { + auto positions__ = positions ? _fbb.CreateVectorOfStructs(*positions) : 0; + auto colors__ = colors ? _fbb.CreateVectorOfStructs(*colors) : 0; + auto radii__ = radii ? _fbb.CreateVector(*radii) : 0; + return slamd::flatb::CreateSpheres( + _fbb, + positions__, + colors__, + radii__, + min_brightness); +} + struct Plane FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { typedef PlaneBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { @@ -1127,9 +953,6 @@ struct Geometry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const slamd::flatb::Triad *geometry_as_triad() const { return geometry_type() == slamd::flatb::GeometryUnion_triad ? static_cast(geometry()) : nullptr; } - const slamd::flatb::Circles2D *geometry_as_circles_2d() const { - return geometry_type() == slamd::flatb::GeometryUnion_circles_2d ? static_cast(geometry()) : nullptr; - } const slamd::flatb::CameraFrustum *geometry_as_camera_frustum() const { return geometry_type() == slamd::flatb::GeometryUnion_camera_frustum ? static_cast(geometry()) : nullptr; } @@ -1139,9 +962,6 @@ struct Geometry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const slamd::flatb::Image *geometry_as_image() const { return geometry_type() == slamd::flatb::GeometryUnion_image ? static_cast(geometry()) : nullptr; } - const slamd::flatb::Points2D *geometry_as_points_2d() const { - return geometry_type() == slamd::flatb::GeometryUnion_points_2d ? static_cast(geometry()) : nullptr; - } const slamd::flatb::Box *geometry_as_box() const { return geometry_type() == slamd::flatb::GeometryUnion_box ? static_cast(geometry()) : nullptr; } @@ -1154,15 +974,15 @@ struct Geometry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const slamd::flatb::PolyLine *geometry_as_poly_line() const { return geometry_type() == slamd::flatb::GeometryUnion_poly_line ? static_cast(geometry()) : nullptr; } - const slamd::flatb::PolyLine2D *geometry_as_poly_line_2d() const { - return geometry_type() == slamd::flatb::GeometryUnion_poly_line_2d ? static_cast(geometry()) : nullptr; - } const slamd::flatb::Mesh *geometry_as_mesh() const { return geometry_type() == slamd::flatb::GeometryUnion_mesh ? static_cast(geometry()) : nullptr; } const slamd::flatb::Plane *geometry_as_plane() const { return geometry_type() == slamd::flatb::GeometryUnion_plane ? static_cast(geometry()) : nullptr; } + const slamd::flatb::Spheres *geometry_as_spheres() const { + return geometry_type() == slamd::flatb::GeometryUnion_spheres ? static_cast(geometry()) : nullptr; + } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_GEOMETRY_ID, 8) && @@ -1177,10 +997,6 @@ template<> inline const slamd::flatb::Triad *Geometry::geometry_as inline const slamd::flatb::Circles2D *Geometry::geometry_as() const { - return geometry_as_circles_2d(); -} - template<> inline const slamd::flatb::CameraFrustum *Geometry::geometry_as() const { return geometry_as_camera_frustum(); } @@ -1193,10 +1009,6 @@ template<> inline const slamd::flatb::Image *Geometry::geometry_as inline const slamd::flatb::Points2D *Geometry::geometry_as() const { - return geometry_as_points_2d(); -} - template<> inline const slamd::flatb::Box *Geometry::geometry_as() const { return geometry_as_box(); } @@ -1213,10 +1025,6 @@ template<> inline const slamd::flatb::PolyLine *Geometry::geometry_as inline const slamd::flatb::PolyLine2D *Geometry::geometry_as() const { - return geometry_as_poly_line_2d(); -} - template<> inline const slamd::flatb::Mesh *Geometry::geometry_as() const { return geometry_as_mesh(); } @@ -1225,6 +1033,10 @@ template<> inline const slamd::flatb::Plane *Geometry::geometry_as inline const slamd::flatb::Spheres *Geometry::geometry_as() const { + return geometry_as_spheres(); +} + struct GeometryBuilder { typedef Geometry Table; ::flatbuffers::FlatBufferBuilder &fbb_; @@ -1270,10 +1082,6 @@ inline bool VerifyGeometryUnion(::flatbuffers::Verifier &verifier, const void *o auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } - case GeometryUnion_circles_2d: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } case GeometryUnion_camera_frustum: { auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); @@ -1286,10 +1094,6 @@ inline bool VerifyGeometryUnion(::flatbuffers::Verifier &verifier, const void *o auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } - case GeometryUnion_points_2d: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } case GeometryUnion_box: { auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); @@ -1306,10 +1110,6 @@ inline bool VerifyGeometryUnion(::flatbuffers::Verifier &verifier, const void *o auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } - case GeometryUnion_poly_line_2d: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } case GeometryUnion_mesh: { auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); @@ -1318,6 +1118,10 @@ inline bool VerifyGeometryUnion(::flatbuffers::Verifier &verifier, const void *o auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } + case GeometryUnion_spheres: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } default: return true; } } diff --git a/slamd/flatb/flatb/messages_generated.h b/slamd/flatb/flatb/messages_generated.h index a3532ead..bdb0917a 100644 --- a/slamd/flatb/flatb/messages_generated.h +++ b/slamd/flatb/flatb/messages_generated.h @@ -29,14 +29,14 @@ struct UpdateMeshColorsBuilder; struct UpdateMeshNormals; struct UpdateMeshNormalsBuilder; -struct UpdateCircles2DPositions; -struct UpdateCircles2DPositionsBuilder; +struct UpdateSpheresPositions; +struct UpdateSpheresPositionsBuilder; -struct UpdateCircles2DColors; -struct UpdateCircles2DColorsBuilder; +struct UpdateSpheresColors; +struct UpdateSpheresColorsBuilder; -struct UpdateCircles2DRadii; -struct UpdateCircles2DRadiiBuilder; +struct UpdateSpheresRadii; +struct UpdateSpheresRadiiBuilder; struct UpdatePointCloudPositions; struct UpdatePointCloudPositionsBuilder; @@ -92,14 +92,14 @@ enum MessageUnion : uint8_t { MessageUnion_update_mesh_positions = 11, MessageUnion_update_mesh_colors = 12, MessageUnion_update_mesh_normals = 13, - MessageUnion_update_circles2d_positions = 14, - MessageUnion_update_circles2d_colors = 15, - MessageUnion_update_circles2d_radii = 16, - MessageUnion_update_point_cloud_positions = 17, - MessageUnion_update_point_cloud_colors = 18, - MessageUnion_update_point_cloud_radii = 19, + MessageUnion_update_point_cloud_positions = 14, + MessageUnion_update_point_cloud_colors = 15, + MessageUnion_update_point_cloud_radii = 16, + MessageUnion_update_spheres_positions = 17, + MessageUnion_update_spheres_colors = 18, + MessageUnion_update_spheres_radii = 19, MessageUnion_MIN = MessageUnion_NONE, - MessageUnion_MAX = MessageUnion_update_point_cloud_radii + MessageUnion_MAX = MessageUnion_update_spheres_radii }; inline const MessageUnion (&EnumValuesMessageUnion())[20] { @@ -118,12 +118,12 @@ inline const MessageUnion (&EnumValuesMessageUnion())[20] { MessageUnion_update_mesh_positions, MessageUnion_update_mesh_colors, MessageUnion_update_mesh_normals, - MessageUnion_update_circles2d_positions, - MessageUnion_update_circles2d_colors, - MessageUnion_update_circles2d_radii, MessageUnion_update_point_cloud_positions, MessageUnion_update_point_cloud_colors, - MessageUnion_update_point_cloud_radii + MessageUnion_update_point_cloud_radii, + MessageUnion_update_spheres_positions, + MessageUnion_update_spheres_colors, + MessageUnion_update_spheres_radii }; return values; } @@ -144,19 +144,19 @@ inline const char * const *EnumNamesMessageUnion() { "update_mesh_positions", "update_mesh_colors", "update_mesh_normals", - "update_circles2d_positions", - "update_circles2d_colors", - "update_circles2d_radii", "update_point_cloud_positions", "update_point_cloud_colors", "update_point_cloud_radii", + "update_spheres_positions", + "update_spheres_colors", + "update_spheres_radii", nullptr }; return names; } inline const char *EnumNameMessageUnion(MessageUnion e) { - if (::flatbuffers::IsOutRange(e, MessageUnion_NONE, MessageUnion_update_point_cloud_radii)) return ""; + if (::flatbuffers::IsOutRange(e, MessageUnion_NONE, MessageUnion_update_spheres_radii)) return ""; const size_t index = static_cast(e); return EnumNamesMessageUnion()[index]; } @@ -217,18 +217,6 @@ template<> struct MessageUnionTraits { static const MessageUnion enum_value = MessageUnion_update_mesh_normals; }; -template<> struct MessageUnionTraits { - static const MessageUnion enum_value = MessageUnion_update_circles2d_positions; -}; - -template<> struct MessageUnionTraits { - static const MessageUnion enum_value = MessageUnion_update_circles2d_colors; -}; - -template<> struct MessageUnionTraits { - static const MessageUnion enum_value = MessageUnion_update_circles2d_radii; -}; - template<> struct MessageUnionTraits { static const MessageUnion enum_value = MessageUnion_update_point_cloud_positions; }; @@ -241,6 +229,18 @@ template<> struct MessageUnionTraits { static const MessageUnion enum_value = MessageUnion_update_point_cloud_radii; }; +template<> struct MessageUnionTraits { + static const MessageUnion enum_value = MessageUnion_update_spheres_positions; +}; + +template<> struct MessageUnionTraits { + static const MessageUnion enum_value = MessageUnion_update_spheres_colors; +}; + +template<> struct MessageUnionTraits { + static const MessageUnion enum_value = MessageUnion_update_spheres_radii; +}; + bool VerifyMessageUnion(::flatbuffers::Verifier &verifier, const void *obj, MessageUnion type); bool VerifyMessageUnionVector(::flatbuffers::Verifier &verifier, const ::flatbuffers::Vector<::flatbuffers::Offset> *values, const ::flatbuffers::Vector *types); @@ -433,8 +433,8 @@ inline ::flatbuffers::Offset CreateUpdateMeshNormalsDirect( normals__); } -struct UpdateCircles2DPositions FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { - typedef UpdateCircles2DPositionsBuilder Builder; +struct UpdateSpheresPositions FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef UpdateSpheresPositionsBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_OBJECT_ID = 4, VT_POSITIONS = 6 @@ -442,8 +442,8 @@ struct UpdateCircles2DPositions FLATBUFFERS_FINAL_CLASS : private ::flatbuffers: uint64_t object_id() const { return GetField(VT_OBJECT_ID, 0); } - const ::flatbuffers::Vector *positions() const { - return GetPointer *>(VT_POSITIONS); + const ::flatbuffers::Vector *positions() const { + return GetPointer *>(VT_POSITIONS); } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && @@ -454,50 +454,50 @@ struct UpdateCircles2DPositions FLATBUFFERS_FINAL_CLASS : private ::flatbuffers: } }; -struct UpdateCircles2DPositionsBuilder { - typedef UpdateCircles2DPositions Table; +struct UpdateSpheresPositionsBuilder { + typedef UpdateSpheresPositions Table; ::flatbuffers::FlatBufferBuilder &fbb_; ::flatbuffers::uoffset_t start_; void add_object_id(uint64_t object_id) { - fbb_.AddElement(UpdateCircles2DPositions::VT_OBJECT_ID, object_id, 0); + fbb_.AddElement(UpdateSpheresPositions::VT_OBJECT_ID, object_id, 0); } - void add_positions(::flatbuffers::Offset<::flatbuffers::Vector> positions) { - fbb_.AddOffset(UpdateCircles2DPositions::VT_POSITIONS, positions); + void add_positions(::flatbuffers::Offset<::flatbuffers::Vector> positions) { + fbb_.AddOffset(UpdateSpheresPositions::VT_POSITIONS, positions); } - explicit UpdateCircles2DPositionsBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + explicit UpdateSpheresPositionsBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } - ::flatbuffers::Offset Finish() { + ::flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); - auto o = ::flatbuffers::Offset(end); + auto o = ::flatbuffers::Offset(end); return o; } }; -inline ::flatbuffers::Offset CreateUpdateCircles2DPositions( +inline ::flatbuffers::Offset CreateUpdateSpheresPositions( ::flatbuffers::FlatBufferBuilder &_fbb, uint64_t object_id = 0, - ::flatbuffers::Offset<::flatbuffers::Vector> positions = 0) { - UpdateCircles2DPositionsBuilder builder_(_fbb); + ::flatbuffers::Offset<::flatbuffers::Vector> positions = 0) { + UpdateSpheresPositionsBuilder builder_(_fbb); builder_.add_object_id(object_id); builder_.add_positions(positions); return builder_.Finish(); } -inline ::flatbuffers::Offset CreateUpdateCircles2DPositionsDirect( +inline ::flatbuffers::Offset CreateUpdateSpheresPositionsDirect( ::flatbuffers::FlatBufferBuilder &_fbb, uint64_t object_id = 0, - const std::vector *positions = nullptr) { - auto positions__ = positions ? _fbb.CreateVectorOfStructs(*positions) : 0; - return slamd::flatb::CreateUpdateCircles2DPositions( + const std::vector *positions = nullptr) { + auto positions__ = positions ? _fbb.CreateVectorOfStructs(*positions) : 0; + return slamd::flatb::CreateUpdateSpheresPositions( _fbb, object_id, positions__); } -struct UpdateCircles2DColors FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { - typedef UpdateCircles2DColorsBuilder Builder; +struct UpdateSpheresColors FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef UpdateSpheresColorsBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_OBJECT_ID = 4, VT_COLORS = 6 @@ -517,50 +517,50 @@ struct UpdateCircles2DColors FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Ta } }; -struct UpdateCircles2DColorsBuilder { - typedef UpdateCircles2DColors Table; +struct UpdateSpheresColorsBuilder { + typedef UpdateSpheresColors Table; ::flatbuffers::FlatBufferBuilder &fbb_; ::flatbuffers::uoffset_t start_; void add_object_id(uint64_t object_id) { - fbb_.AddElement(UpdateCircles2DColors::VT_OBJECT_ID, object_id, 0); + fbb_.AddElement(UpdateSpheresColors::VT_OBJECT_ID, object_id, 0); } void add_colors(::flatbuffers::Offset<::flatbuffers::Vector> colors) { - fbb_.AddOffset(UpdateCircles2DColors::VT_COLORS, colors); + fbb_.AddOffset(UpdateSpheresColors::VT_COLORS, colors); } - explicit UpdateCircles2DColorsBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + explicit UpdateSpheresColorsBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } - ::flatbuffers::Offset Finish() { + ::flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); - auto o = ::flatbuffers::Offset(end); + auto o = ::flatbuffers::Offset(end); return o; } }; -inline ::flatbuffers::Offset CreateUpdateCircles2DColors( +inline ::flatbuffers::Offset CreateUpdateSpheresColors( ::flatbuffers::FlatBufferBuilder &_fbb, uint64_t object_id = 0, ::flatbuffers::Offset<::flatbuffers::Vector> colors = 0) { - UpdateCircles2DColorsBuilder builder_(_fbb); + UpdateSpheresColorsBuilder builder_(_fbb); builder_.add_object_id(object_id); builder_.add_colors(colors); return builder_.Finish(); } -inline ::flatbuffers::Offset CreateUpdateCircles2DColorsDirect( +inline ::flatbuffers::Offset CreateUpdateSpheresColorsDirect( ::flatbuffers::FlatBufferBuilder &_fbb, uint64_t object_id = 0, const std::vector *colors = nullptr) { auto colors__ = colors ? _fbb.CreateVectorOfStructs(*colors) : 0; - return slamd::flatb::CreateUpdateCircles2DColors( + return slamd::flatb::CreateUpdateSpheresColors( _fbb, object_id, colors__); } -struct UpdateCircles2DRadii FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { - typedef UpdateCircles2DRadiiBuilder Builder; +struct UpdateSpheresRadii FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef UpdateSpheresRadiiBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_OBJECT_ID = 4, VT_RADII = 6 @@ -580,43 +580,43 @@ struct UpdateCircles2DRadii FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Tab } }; -struct UpdateCircles2DRadiiBuilder { - typedef UpdateCircles2DRadii Table; +struct UpdateSpheresRadiiBuilder { + typedef UpdateSpheresRadii Table; ::flatbuffers::FlatBufferBuilder &fbb_; ::flatbuffers::uoffset_t start_; void add_object_id(uint64_t object_id) { - fbb_.AddElement(UpdateCircles2DRadii::VT_OBJECT_ID, object_id, 0); + fbb_.AddElement(UpdateSpheresRadii::VT_OBJECT_ID, object_id, 0); } void add_radii(::flatbuffers::Offset<::flatbuffers::Vector> radii) { - fbb_.AddOffset(UpdateCircles2DRadii::VT_RADII, radii); + fbb_.AddOffset(UpdateSpheresRadii::VT_RADII, radii); } - explicit UpdateCircles2DRadiiBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + explicit UpdateSpheresRadiiBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } - ::flatbuffers::Offset Finish() { + ::flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); - auto o = ::flatbuffers::Offset(end); + auto o = ::flatbuffers::Offset(end); return o; } }; -inline ::flatbuffers::Offset CreateUpdateCircles2DRadii( +inline ::flatbuffers::Offset CreateUpdateSpheresRadii( ::flatbuffers::FlatBufferBuilder &_fbb, uint64_t object_id = 0, ::flatbuffers::Offset<::flatbuffers::Vector> radii = 0) { - UpdateCircles2DRadiiBuilder builder_(_fbb); + UpdateSpheresRadiiBuilder builder_(_fbb); builder_.add_object_id(object_id); builder_.add_radii(radii); return builder_.Finish(); } -inline ::flatbuffers::Offset CreateUpdateCircles2DRadiiDirect( +inline ::flatbuffers::Offset CreateUpdateSpheresRadiiDirect( ::flatbuffers::FlatBufferBuilder &_fbb, uint64_t object_id = 0, const std::vector *radii = nullptr) { auto radii__ = radii ? _fbb.CreateVector(*radii) : 0; - return slamd::flatb::CreateUpdateCircles2DRadii( + return slamd::flatb::CreateUpdateSpheresRadii( _fbb, object_id, radii__); @@ -1335,15 +1335,6 @@ struct Message FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const slamd::flatb::UpdateMeshNormals *message_as_update_mesh_normals() const { return message_type() == slamd::flatb::MessageUnion_update_mesh_normals ? static_cast(message()) : nullptr; } - const slamd::flatb::UpdateCircles2DPositions *message_as_update_circles2d_positions() const { - return message_type() == slamd::flatb::MessageUnion_update_circles2d_positions ? static_cast(message()) : nullptr; - } - const slamd::flatb::UpdateCircles2DColors *message_as_update_circles2d_colors() const { - return message_type() == slamd::flatb::MessageUnion_update_circles2d_colors ? static_cast(message()) : nullptr; - } - const slamd::flatb::UpdateCircles2DRadii *message_as_update_circles2d_radii() const { - return message_type() == slamd::flatb::MessageUnion_update_circles2d_radii ? static_cast(message()) : nullptr; - } const slamd::flatb::UpdatePointCloudPositions *message_as_update_point_cloud_positions() const { return message_type() == slamd::flatb::MessageUnion_update_point_cloud_positions ? static_cast(message()) : nullptr; } @@ -1353,6 +1344,15 @@ struct Message FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { const slamd::flatb::UpdatePointCloudRadii *message_as_update_point_cloud_radii() const { return message_type() == slamd::flatb::MessageUnion_update_point_cloud_radii ? static_cast(message()) : nullptr; } + const slamd::flatb::UpdateSpheresPositions *message_as_update_spheres_positions() const { + return message_type() == slamd::flatb::MessageUnion_update_spheres_positions ? static_cast(message()) : nullptr; + } + const slamd::flatb::UpdateSpheresColors *message_as_update_spheres_colors() const { + return message_type() == slamd::flatb::MessageUnion_update_spheres_colors ? static_cast(message()) : nullptr; + } + const slamd::flatb::UpdateSpheresRadii *message_as_update_spheres_radii() const { + return message_type() == slamd::flatb::MessageUnion_update_spheres_radii ? static_cast(message()) : nullptr; + } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_MESSAGE_TYPE, 1) && @@ -1414,18 +1414,6 @@ template<> inline const slamd::flatb::UpdateMeshNormals *Message::message_as inline const slamd::flatb::UpdateCircles2DPositions *Message::message_as() const { - return message_as_update_circles2d_positions(); -} - -template<> inline const slamd::flatb::UpdateCircles2DColors *Message::message_as() const { - return message_as_update_circles2d_colors(); -} - -template<> inline const slamd::flatb::UpdateCircles2DRadii *Message::message_as() const { - return message_as_update_circles2d_radii(); -} - template<> inline const slamd::flatb::UpdatePointCloudPositions *Message::message_as() const { return message_as_update_point_cloud_positions(); } @@ -1438,6 +1426,18 @@ template<> inline const slamd::flatb::UpdatePointCloudRadii *Message::message_as return message_as_update_point_cloud_radii(); } +template<> inline const slamd::flatb::UpdateSpheresPositions *Message::message_as() const { + return message_as_update_spheres_positions(); +} + +template<> inline const slamd::flatb::UpdateSpheresColors *Message::message_as() const { + return message_as_update_spheres_colors(); +} + +template<> inline const slamd::flatb::UpdateSpheresRadii *Message::message_as() const { + return message_as_update_spheres_radii(); +} + struct MessageBuilder { typedef Message Table; ::flatbuffers::FlatBufferBuilder &fbb_; @@ -1526,18 +1526,6 @@ inline bool VerifyMessageUnion(::flatbuffers::Verifier &verifier, const void *ob auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } - case MessageUnion_update_circles2d_positions: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } - case MessageUnion_update_circles2d_colors: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } - case MessageUnion_update_circles2d_radii: { - auto ptr = reinterpret_cast(obj); - return verifier.VerifyTable(ptr); - } case MessageUnion_update_point_cloud_positions: { auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); @@ -1550,6 +1538,18 @@ inline bool VerifyMessageUnion(::flatbuffers::Verifier &verifier, const void *ob auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } + case MessageUnion_update_spheres_positions: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } + case MessageUnion_update_spheres_colors: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } + case MessageUnion_update_spheres_radii: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } default: return true; } } diff --git a/slamd/flatb/flatb/visualizer_generated.h b/slamd/flatb/flatb/visualizer_generated.h index be91ec42..65a8f657 100644 --- a/slamd/flatb/flatb/visualizer_generated.h +++ b/slamd/flatb/flatb/visualizer_generated.h @@ -34,39 +34,6 @@ struct ViewBuilder; struct InitialState; struct InitialStateBuilder; -enum ViewType : int8_t { - ViewType_NONE = 0, - ViewType_CANVAS = 1, - ViewType_SCENE = 2, - ViewType_MIN = ViewType_NONE, - ViewType_MAX = ViewType_SCENE -}; - -inline const ViewType (&EnumValuesViewType())[3] { - static const ViewType values[] = { - ViewType_NONE, - ViewType_CANVAS, - ViewType_SCENE - }; - return values; -} - -inline const char * const *EnumNamesViewType() { - static const char * const names[4] = { - "NONE", - "CANVAS", - "SCENE", - nullptr - }; - return names; -} - -inline const char *EnumNameViewType(ViewType e) { - if (::flatbuffers::IsOutRange(e, ViewType_NONE, ViewType_SCENE)) return ""; - const size_t index = static_cast(e); - return EnumNamesViewType()[index]; -} - struct ChildEntry FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { typedef ChildEntryBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { @@ -263,8 +230,7 @@ struct View FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { typedef ViewBuilder Builder; enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_NAME = 4, - VT_TREE_ID = 6, - VT_VIEW_TYPE = 8 + VT_TREE_ID = 6 }; const ::flatbuffers::String *name() const { return GetPointer(VT_NAME); @@ -272,15 +238,11 @@ struct View FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { uint64_t tree_id() const { return GetField(VT_TREE_ID, 0); } - slamd::flatb::ViewType view_type() const { - return static_cast(GetField(VT_VIEW_TYPE, 0)); - } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyField(verifier, VT_TREE_ID, 8) && - VerifyField(verifier, VT_VIEW_TYPE, 1) && verifier.EndTable(); } }; @@ -295,9 +257,6 @@ struct ViewBuilder { void add_tree_id(uint64_t tree_id) { fbb_.AddElement(View::VT_TREE_ID, tree_id, 0); } - void add_view_type(slamd::flatb::ViewType view_type) { - fbb_.AddElement(View::VT_VIEW_TYPE, static_cast(view_type), 0); - } explicit ViewBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); @@ -312,26 +271,22 @@ struct ViewBuilder { inline ::flatbuffers::Offset CreateView( ::flatbuffers::FlatBufferBuilder &_fbb, ::flatbuffers::Offset<::flatbuffers::String> name = 0, - uint64_t tree_id = 0, - slamd::flatb::ViewType view_type = slamd::flatb::ViewType_NONE) { + uint64_t tree_id = 0) { ViewBuilder builder_(_fbb); builder_.add_tree_id(tree_id); builder_.add_name(name); - builder_.add_view_type(view_type); return builder_.Finish(); } inline ::flatbuffers::Offset CreateViewDirect( ::flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, - uint64_t tree_id = 0, - slamd::flatb::ViewType view_type = slamd::flatb::ViewType_NONE) { + uint64_t tree_id = 0) { auto name__ = name ? _fbb.CreateString(name) : 0; return slamd::flatb::CreateView( _fbb, name__, - tree_id, - view_type); + tree_id); } struct InitialState FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { diff --git a/slamd/include/slamd/constants.hpp b/slamd/include/slamd/constants.hpp index fd37952b..60c2eb45 100644 --- a/slamd/include/slamd/constants.hpp +++ b/slamd/include/slamd/constants.hpp @@ -3,7 +3,6 @@ namespace slamd { namespace _const { -constexpr glm::vec3 light_dir = glm::vec3(0.2f, 0.2f, 1.0f); constexpr float default_min_brightness = 0.2f; } // namespace _const } // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd/geom/arrows.hpp b/slamd/include/slamd/geom/arrows.hpp index 13dde563..37231fc6 100644 --- a/slamd/include/slamd/geom/arrows.hpp +++ b/slamd/include/slamd/geom/arrows.hpp @@ -5,7 +5,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { /** * TOOD: use MonoInstanced to allow modifying arrows @@ -30,13 +30,7 @@ class Arrows : public Geometry { float thickness; }; -} // namespace _geom - -namespace geom { - -using ArrowsPtr = std::shared_ptr<_geom::Arrows>; - -ArrowsPtr arrows( +std::shared_ptr arrows( const std::vector& starts, const std::vector& ends, const std::vector& colors, @@ -44,4 +38,4 @@ ArrowsPtr arrows( ); } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/box.hpp b/slamd/include/slamd/geom/box.hpp index 621dbb4f..3847cfa2 100644 --- a/slamd/include/slamd/geom/box.hpp +++ b/slamd/include/slamd/geom/box.hpp @@ -3,7 +3,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class Box : public Geometry { public: @@ -14,13 +14,7 @@ class Box : public Geometry { ) override; }; -} // namespace _geom - -namespace geom { +std::shared_ptr box(); -using BoxPtr = std::shared_ptr<_geom::Box>; - -BoxPtr box(); } // namespace geom - -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/camera_frustum.hpp b/slamd/include/slamd/geom/camera_frustum.hpp index 243b614a..23269be3 100644 --- a/slamd/include/slamd/geom/camera_frustum.hpp +++ b/slamd/include/slamd/geom/camera_frustum.hpp @@ -4,7 +4,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class CameraFrustum : public Geometry { public: @@ -34,13 +34,8 @@ class CameraFrustum : public Geometry { float scale; std::optional img = std::nullopt; }; -} // namespace _geom - -namespace geom { - -using CameraFrustumPtr = std::shared_ptr<_geom::CameraFrustum>; -CameraFrustumPtr camera_frustum( +std::shared_ptr camera_frustum( glm::mat3 intrinsics_matrix, size_t image_width, size_t image_height, @@ -48,7 +43,7 @@ CameraFrustumPtr camera_frustum( float scale = 1.0 ); -CameraFrustumPtr camera_frustum( +std::shared_ptr camera_frustum( glm::mat3 intrinsics_matrix, size_t image_width, size_t image_height, @@ -56,4 +51,4 @@ CameraFrustumPtr camera_frustum( ); } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/circles_2d.hpp b/slamd/include/slamd/geom/circles_2d.hpp deleted file mode 100644 index 171b575b..00000000 --- a/slamd/include/slamd/geom/circles_2d.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include -#include -#include -#include - -namespace slamd { -namespace _geom { - -class Circles2D : public Geometry { - public: - Circles2D( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii, - float thickness - ); - - flatbuffers::Offset serialize( - flatbuffers::FlatBufferBuilder& builder - ) override; - - void update_positions(const std::vector& positions); - void update_colors(const std::vector& colors); - void update_radii(const std::vector& radii); - - private: - std::vector positions; - std::vector colors; - std::vector radii; - float thickness; -}; - -} // namespace _geom - -namespace geom2d { -using CirclesPtr = std::shared_ptr<_geom::Circles2D>; - -CirclesPtr circles( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii, - float thickness = 0.1 -); -} // namespace geom2d -} // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd/geom/geometry.hpp b/slamd/include/slamd/geom/geometry.hpp index 28552a8d..7eb44162 100644 --- a/slamd/include/slamd/geom/geometry.hpp +++ b/slamd/include/slamd/geom/geometry.hpp @@ -16,7 +16,7 @@ namespace _vis { class Visualizer; } -namespace _geom { +namespace geom { class Geometry { public: @@ -45,5 +45,5 @@ class Geometry { std::map<_id::NodeID, std::weak_ptr<_tree::Node>> attached_to; }; -} // namespace _geom -} // namespace slamd \ No newline at end of file +} // namespace geom +} // namespace slamd diff --git a/slamd/include/slamd/geom/image.hpp b/slamd/include/slamd/geom/image.hpp index f3658e6e..83a66330 100644 --- a/slamd/include/slamd/geom/image.hpp +++ b/slamd/include/slamd/geom/image.hpp @@ -4,7 +4,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class Image : public Geometry { public: @@ -19,19 +19,7 @@ class Image : public Geometry { bool normalized; }; -} // namespace _geom - -namespace geom { -using ImagePtr = std::shared_ptr<_geom::Image>; +std::shared_ptr image(data::Image&& image); -ImagePtr image(data::Image&& image); } // namespace geom - -namespace geom2d { -using ImagePtr = std::shared_ptr<_geom::Image>; - -ImagePtr image(data::Image&& image); - -} // namespace geom2d - -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/mesh.hpp b/slamd/include/slamd/geom/mesh.hpp index 4cab497c..2beebf3a 100644 --- a/slamd/include/slamd/geom/mesh.hpp +++ b/slamd/include/slamd/geom/mesh.hpp @@ -8,7 +8,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class Mesh : public Geometry { public: @@ -42,13 +42,7 @@ class Mesh : public Geometry { float min_brightness; }; -} // namespace _geom - -namespace geom { - -using MeshPtr = std::shared_ptr<_geom::Mesh>; - -MeshPtr mesh(const data::MeshData& mesh_data); +std::shared_ptr mesh(const data::MeshData& mesh_data); } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/mono_instanced.hpp b/slamd/include/slamd/geom/mono_instanced.hpp index de57252d..54d86d2f 100644 --- a/slamd/include/slamd/geom/mono_instanced.hpp +++ b/slamd/include/slamd/geom/mono_instanced.hpp @@ -3,7 +3,7 @@ #include #include namespace slamd { -namespace _geom { +namespace geom { class MonoInstanced : public Geometry { public: @@ -26,5 +26,5 @@ class MonoInstanced : public Geometry { std::vector colors; }; -} // namespace _geom -} // namespace slamd \ No newline at end of file +} // namespace geom +} // namespace slamd diff --git a/slamd/include/slamd/geom/plane.hpp b/slamd/include/slamd/geom/plane.hpp index 32640cf4..24104786 100644 --- a/slamd/include/slamd/geom/plane.hpp +++ b/slamd/include/slamd/geom/plane.hpp @@ -2,7 +2,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class Plane : public Geometry { public: @@ -25,13 +25,8 @@ class Plane : public Geometry { float radius; float alpha; }; -} // namespace _geom - -namespace geom { - -using PlanePtr = std::shared_ptr<_geom::Plane>; -PlanePtr plane( +std::shared_ptr plane( glm::vec3 normal, glm::vec3 point, glm::vec3 color, @@ -40,4 +35,4 @@ PlanePtr plane( ); } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/point_cloud.hpp b/slamd/include/slamd/geom/point_cloud.hpp index 36db2b9f..18fd911c 100644 --- a/slamd/include/slamd/geom/point_cloud.hpp +++ b/slamd/include/slamd/geom/point_cloud.hpp @@ -4,7 +4,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { /** * TODO: refactor to use MonoInstanced @@ -33,13 +33,8 @@ class PointCloud : public Geometry { float min_brightness; }; -} // namespace _geom - -namespace geom { -using PointCloudPtr = std::shared_ptr<_geom::PointCloud>; - template -PointCloudPtr point_cloud( +std::shared_ptr point_cloud( const std::vector& positions, const ColorType& colors, const RadiiType& radii, @@ -62,7 +57,7 @@ PointCloudPtr point_cloud( final_radii = radii; } - auto pc = std::make_shared<_geom::PointCloud>( + auto pc = std::make_shared( positions, std::move(final_colors), std::move(final_radii), @@ -74,4 +69,4 @@ PointCloudPtr point_cloud( } } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/poly_line.hpp b/slamd/include/slamd/geom/poly_line.hpp index be4cdeb8..fef769ce 100644 --- a/slamd/include/slamd/geom/poly_line.hpp +++ b/slamd/include/slamd/geom/poly_line.hpp @@ -7,7 +7,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class PolyLine : public Geometry { public: @@ -29,13 +29,7 @@ class PolyLine : public Geometry { float min_brightness; }; -} // namespace _geom - -namespace geom { - -using PolyLinePtr = std::shared_ptr<_geom::PolyLine>; - -PolyLinePtr poly_line( +std::shared_ptr poly_line( const std::vector& points, float thickness, const glm::vec3& color, @@ -43,5 +37,4 @@ PolyLinePtr poly_line( ); } // namespace geom - -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/poly_line_2d.hpp b/slamd/include/slamd/geom/poly_line_2d.hpp deleted file mode 100644 index 04b8ea96..00000000 --- a/slamd/include/slamd/geom/poly_line_2d.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace slamd { -namespace _geom { - -class PolyLine2D : public Geometry { - public: - PolyLine2D( - const std::vector& points, - const glm::vec3& color, - float thickness - ); - - flatbuffers::Offset serialize( - flatbuffers::FlatBufferBuilder& builder - ) override; - - private: - std::vector points; - glm::vec3 color; - float thickness; -}; - -} // namespace _geom - -namespace geom2d { -using PolyLinePtr = std::shared_ptr<_geom::PolyLine2D>; - -PolyLinePtr poly_line( - const std::vector& points, - const glm::vec3& color, - float thickness -); - -} // namespace geom2d - -} // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd/geom/sphere.hpp b/slamd/include/slamd/geom/sphere.hpp index d7b50327..1420bfc6 100644 --- a/slamd/include/slamd/geom/sphere.hpp +++ b/slamd/include/slamd/geom/sphere.hpp @@ -4,7 +4,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class Sphere : public Geometry { public: @@ -18,14 +18,8 @@ class Sphere : public Geometry { glm::vec3 color; }; -} // namespace _geom - -namespace geom { - -using SpherePtr = std::shared_ptr<_geom::Sphere>; - -SpherePtr +std::shared_ptr sphere(float radius = 1.0f, glm::vec3 color = glm::vec3(0.8, 0.2, 0.0)); } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/geom/points_2d.hpp b/slamd/include/slamd/geom/spheres.hpp similarity index 58% rename from slamd/include/slamd/geom/points_2d.hpp rename to slamd/include/slamd/geom/spheres.hpp index 94f661ff..2a294cc4 100644 --- a/slamd/include/slamd/geom/points_2d.hpp +++ b/slamd/include/slamd/geom/spheres.hpp @@ -1,39 +1,41 @@ #pragma once +#include #include -#include namespace slamd { -namespace _geom { +namespace geom { -class Points2D : public Geometry { +class Spheres : public Geometry { public: - Points2D( - const std::vector& positions, + Spheres( + const std::vector& positions, const std::vector& colors, - const std::vector& radii + const std::vector& radii, + float min_brightness ); flatbuffers::Offset serialize( flatbuffers::FlatBufferBuilder& builder ) override; + void update_positions(const std::vector& positions); + void update_colors(const std::vector& colors); + void update_radii(const std::vector& radii); + private: - std::vector positions; + std::vector positions; std::vector colors; std::vector radii; + float min_brightness; }; -} // namespace _geom - -namespace geom2d { -using PointsPtr = std::shared_ptr<_geom::Points2D>; - template -PointsPtr points( - const std::vector& positions, +std::shared_ptr spheres( + const std::vector& positions, const ColorType& colors, - const RadiiType& radii + const RadiiType& radii, + float min_brightness = 0.3 ) { std::vector final_colors; std::vector final_radii; @@ -52,14 +54,15 @@ PointsPtr points( final_radii = radii; } - auto points_2d = std::make_shared<_geom::Points2D>( + auto c = std::make_shared( positions, std::move(final_colors), - std::move(final_radii) + std::move(final_radii), + min_brightness ); - // _global::geometries.add(points_2d->id, points_2d); - return points_2d; + + return c; } -} // namespace geom2d -} // namespace slamd \ No newline at end of file +} // namespace geom +} // namespace slamd diff --git a/slamd/include/slamd/geom/triad.hpp b/slamd/include/slamd/geom/triad.hpp index 80e53063..28e08387 100644 --- a/slamd/include/slamd/geom/triad.hpp +++ b/slamd/include/slamd/geom/triad.hpp @@ -4,7 +4,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { class Triad : public Geometry { public: @@ -24,15 +24,8 @@ class Triad : public Geometry { glm::mat4 pose; }; -} // namespace _geom - -namespace geom { - -using TriadPtr = std::shared_ptr<_geom::Triad>; - -TriadPtr triad(float scale = 1.0, float thickness = 0.1); -TriadPtr triad(glm::mat4 pose, float scale = 1.0, float thickness = 0.1); +std::shared_ptr triad(float scale = 1.0, float thickness = 0.1); +std::shared_ptr triad(glm::mat4 pose, float scale = 1.0, float thickness = 0.1); } // namespace geom - -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/slamd.hpp b/slamd/include/slamd/slamd.hpp index 51d6a510..42f81030 100644 --- a/slamd/include/slamd/slamd.hpp +++ b/slamd/include/slamd/slamd.hpp @@ -1,16 +1,14 @@ #include #include #include -#include +#include #include #include #include #include -#include #include -#include #include #include #include #include -#include \ No newline at end of file +#include diff --git a/slamd/include/slamd/tree/tree.hpp b/slamd/include/slamd/tree/tree.hpp index a20a975a..773caaf9 100644 --- a/slamd/include/slamd/tree/tree.hpp +++ b/slamd/include/slamd/tree/tree.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include namespace slamd { @@ -18,22 +17,22 @@ namespace _view { class View; } -namespace _geom { +namespace geom { class Geometry; } -namespace _tree { +class Scene; -class Tree; +namespace _tree { class Node : public std::enable_shared_from_this { public: ~Node(); - Node(Tree* tree, TreePath path); + Node(Scene* scene, TreePath path); std::optional get_transform() const; - void set_object(std::shared_ptr<_geom::Geometry> object); + void set_object(std::shared_ptr object); void set_transform(glm::mat4 transform); @@ -43,7 +42,7 @@ class Node : public std::enable_shared_from_this { Node& operator=(const Node&) = delete; // should not be in public API - std::optional> get_object() const; + std::optional> get_object() const; flatbuffers::Offset serialize( flatbuffers::FlatBufferBuilder& builder @@ -64,34 +63,37 @@ class Node : public std::enable_shared_from_this { private: std::optional transform; - std::optional> object; + std::optional> object; mutable std::mutex transform_mutex; mutable std::mutex object_mutex; - // we use a raw tree pointer here as the lifetime of the node - // is tied to the tree. - Tree* tree; + Scene* scene; const TreePath path; }; -class Tree { +} // namespace _tree + +class Scene { public: - Tree(); + Scene(); + ~Scene(); - virtual void set_object( + void set_object( const std::string& path, - std::shared_ptr<_geom::Geometry> object + std::shared_ptr object ); - virtual flatbuffers::Offset serialize( + void set_transform(const std::string& path, const glm::mat4& transform); + + flatbuffers::Offset serialize( flatbuffers::FlatBufferBuilder& builder ); void clear(const std::string& path); void add_all_geometries( - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>>& initial_map + std::map<_id::GeometryID, std::shared_ptr>& initial_map ); std::shared_ptr> get_add_tree_message(); @@ -101,58 +103,26 @@ class Tree { std::map<_id::VisualizerID, std::shared_ptr<_vis::Visualizer>> find_visualizers(); - protected: - void - set_transform_mat4(const std::string& path, const glm::mat4& transform); - - std::shared_ptr> get_clear_path_message( - const std::string& path - ); - std::optional traverse(const TreePath& path); - Node* make_path(TreePath path); - public: const _id::TreeID id; std::map<_id::ViewID, std::weak_ptr<_view::View>> attached_to; private: + std::shared_ptr> get_clear_path_message( + const std::string& path + ); + std::optional<_tree::Node*> traverse(const _tree::TreePath& path); + _tree::Node* make_path(_tree::TreePath path); + void add_all_geometries_rec( - Node* node, - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>>& initial_map + _tree::Node* node, + std::map<_id::GeometryID, std::shared_ptr>& initial_map ); private: - std::shared_ptr root; -}; - -} // namespace _tree - -/** - * 3D version - */ -class Scene : public _tree::Tree { - public: - void set_transform(const std::string& path, glm::mat4 transform); + std::shared_ptr<_tree::Node> root; }; std::shared_ptr scene(); -/** - * 2D version - */ -class Canvas : public _tree::Tree { - public: - void set_transform(const std::string& path, glm::mat3 transform); - void set_object( - const std::string& path, - std::shared_ptr<_geom::Geometry> object - ) override; - - private: - uint64_t insertion_order_counter; - float new_depth(); -}; - -std::shared_ptr canvas(); - -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/view.hpp b/slamd/include/slamd/view.hpp index 4426138d..aca7f38c 100644 --- a/slamd/include/slamd/view.hpp +++ b/slamd/include/slamd/view.hpp @@ -1,9 +1,9 @@ #pragma once #include +#include #include -#include -#include #include +#include namespace slamd { @@ -11,9 +11,7 @@ namespace _vis { class Visualizer; } -namespace _tree { -class Tree; -} +class Scene; namespace _view { class View : public std::enable_shared_from_this { @@ -21,8 +19,7 @@ class View : public std::enable_shared_from_this { static std::shared_ptr create( std::string name, std::shared_ptr<_vis::Visualizer> vis, - std::shared_ptr<_tree::Tree> tree, - slamd::flatb::ViewType view_type + std::shared_ptr tree ); ~View(); @@ -41,13 +38,11 @@ class View : public std::enable_shared_from_this { View( std::string name, std::shared_ptr<_vis::Visualizer> vis, - std::shared_ptr<_tree::Tree> tree, - slamd::flatb::ViewType view_type + std::shared_ptr tree ); public: - const std::shared_ptr<_tree::Tree> tree; - slamd::flatb::ViewType view_type; + const std::shared_ptr tree; // a view is contained in the visualizer, so the lifetime is bound to it std::weak_ptr<_vis::Visualizer> vis; @@ -57,4 +52,4 @@ class View : public std::enable_shared_from_this { }; } // namespace _view -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd/visualizer.hpp b/slamd/include/slamd/visualizer.hpp index c6ea6d15..a08b86b9 100644 --- a/slamd/include/slamd/visualizer.hpp +++ b/slamd/include/slamd/visualizer.hpp @@ -1,29 +1,24 @@ #pragma once #include +#include +#include #include #include #include -#include -#include #include #include namespace slamd { -namespace _id {} // namespace _id namespace _view { class View; } -namespace _tree { -class Tree; -} -namespace _geom { +namespace geom { class Geometry; } class Scene; -class Canvas; namespace _vis { @@ -33,13 +28,10 @@ class Visualizer : public std::enable_shared_from_this { ~Visualizer(); void add_scene(std::string name, std::shared_ptr scene); - void add_canvas(std::string name, std::shared_ptr canvas); void delete_scene(std::string name); - void delete_canvas(std::string name); std::shared_ptr scene(std::string name); - std::shared_ptr canvas(std::string name); void hang_forever(); @@ -48,12 +40,10 @@ class Visualizer : public std::enable_shared_from_this { private: void add_view( std::string name, - std::shared_ptr<_tree::Tree> tree, - slamd::flatb::ViewType type + std::shared_ptr tree ); - - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> find_geometries( + std::map<_id::GeometryID, std::shared_ptr> find_geometries( ); void delete_view(std::string name); @@ -65,7 +55,7 @@ class Visualizer : public std::enable_shared_from_this { flatbuffers::Vector>> get_geometries_fb(flatbuffers::FlatBufferBuilder& builder); - void send_tree(std::shared_ptr<_tree::Tree> tree); + void send_tree(std::shared_ptr tree); public: _id::VisualizerID id; @@ -78,16 +68,23 @@ class Visualizer : public std::enable_shared_from_this { std::mutex view_map_mutex; - std::map> view_name_to_view; - std::map<_id::TreeID, std::shared_ptr<_tree::Tree>> trees; + // NOTE: Declaration order matters for destruction! + // Members are destroyed in reverse declaration order. + // - io_context must outlive client_set, because Connections hold + // asio sockets that deregister from the io_context on destruction. + // - trees must be declared before view_name_to_view so that views are + // destroyed first. Otherwise, tree destruction during ~Visualizer + // triggers node detach which reaches back into still-alive Views + // whose weak_ptr can no longer lock. + asio::io_context io_context; std::shared_ptr<_net::ClientSet> client_set; + std::map<_id::TreeID, std::shared_ptr> trees; + std::map> view_name_to_view; }; } // namespace _vis -using VisualizerPtr = std::shared_ptr<_vis::Visualizer>; - -VisualizerPtr +std::shared_ptr<_vis::Visualizer> visualizer(std::string name, bool spawn = true, uint16_t port = 5555); -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd_common/gmath/rect2d.hpp b/slamd/include/slamd_common/gmath/rect2d.hpp deleted file mode 100644 index 208cae48..00000000 --- a/slamd/include/slamd_common/gmath/rect2d.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include -#include - -namespace slamd { -namespace gmath { - -class Rect2D { - public: - glm::vec2 top_left; - glm::vec2 bottom_right; - - public: - Rect2D(const glm::vec2& top_left, const glm::vec2& bottom_right); - Rect2D(const Rect2D& other); - Rect2D& operator=(const Rect2D&) = default; - - Rect2D translate(glm::vec2 amount) const; - - static Rect2D from_aabb3d(const AABB& aabb3d); - static Rect2D - from_center_size(const glm::vec2& center, const glm::vec2& size); - static Rect2D center_cover(const Rect2D& to_cover, float required_aspect); - - glm::vec2 unnormalize(const glm::vec2& normalized_center) const; - - glm::vec2 size() const; - - glm::vec2 center() const; -}; - -} // namespace gmath - -} // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd_common/gmath/transforms.hpp b/slamd/include/slamd_common/gmath/transforms.hpp index 6a473476..7bb94b34 100644 --- a/slamd/include/slamd_common/gmath/transforms.hpp +++ b/slamd/include/slamd_common/gmath/transforms.hpp @@ -20,8 +20,6 @@ glm::mat4 tz3D(float amount); glm::mat4 t3D(const glm::vec3& amount); -glm::mat4 xy_to_3d(const glm::mat3& mat2d); - glm::mat4 scale_xy(const glm::vec2& scale); glm::mat4 scale(const glm::vec3& scale); glm::mat4 scale_all(float scale); diff --git a/slamd/include/slamd_window/camera.hpp b/slamd/include/slamd_window/camera.hpp index c8fe2f8c..e81a9649 100644 --- a/slamd/include/slamd_window/camera.hpp +++ b/slamd/include/slamd_window/camera.hpp @@ -6,12 +6,12 @@ namespace slamd { class Camera { public: - Camera(double fov, double near_plane, double far_plane); - glm::mat4 get_projection_matrix(double aspect_ratio) const; + Camera(double fov, double near_ratio, double far_ratio); + glm::mat4 get_projection_matrix(double aspect_ratio, double view_distance) const; private: double fov; - double near_plane; - double far_plane; + double near_ratio; + double far_ratio; }; } // namespace slamd diff --git a/slamd/include/slamd_window/camera_2d.hpp b/slamd/include/slamd_window/camera_2d.hpp deleted file mode 100644 index 7006b9a8..00000000 --- a/slamd/include/slamd_window/camera_2d.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include - -namespace slamd { - -class Camera2D { - public: - Camera2D(const slamd::gmath::Rect2D& viewport); - - void set_viewport(const slamd::gmath::Rect2D& viewport); - - glm::mat4 get_projection_matrix() const; - - slamd::gmath::Rect2D viewport; - - void translate_relative(glm::vec2 relative_translation); - void zoom_relative( - float amount, - std::optional normalized_mouse_loc = std::nullopt - ); - - void translate_normalized(glm::vec2 normalized_translation); - - glm::vec2 get_world_coords(glm::vec2 normalized_window_coords); -}; - -} // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd_window/connection.hpp b/slamd/include/slamd_window/connection.hpp index 2df6908c..cef388d3 100644 --- a/slamd/include/slamd_window/connection.hpp +++ b/slamd/include/slamd_window/connection.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace slamd { @@ -25,6 +26,9 @@ class Connection { std::thread job_thread; std::atomic stop_requested = false; + + std::mutex socket_mutex; + asio::ip::tcp::socket* active_socket = nullptr; }; } // namespace slamd diff --git a/slamd/include/slamd_window/constants.hpp b/slamd/include/slamd_window/constants.hpp index fd37952b..60c2eb45 100644 --- a/slamd/include/slamd_window/constants.hpp +++ b/slamd/include/slamd_window/constants.hpp @@ -3,7 +3,6 @@ namespace slamd { namespace _const { -constexpr glm::vec3 light_dir = glm::vec3(0.2f, 0.2f, 1.0f); constexpr float default_min_brightness = 0.2f; } // namespace _const } // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd_window/gen/shader_sources.hpp b/slamd/include/slamd_window/gen/shader_sources.hpp index 263147eb..a81ed075 100644 --- a/slamd/include/slamd_window/gen/shader_sources.hpp +++ b/slamd/include/slamd_window/gen/shader_sources.hpp @@ -92,15 +92,34 @@ in vec3 vertex_color; out vec4 FragColor; -uniform vec3 light_dir; +uniform mat4 view; uniform float min_brightness; uniform float alpha; void main() { vec3 norm = normalize(Normal); - float diff = - max(dot(norm, normalize(light_dir)), min_brightness); // never too dark - FragColor = vec4(vertex_color * diff, alpha); + + // Camera-relative lighting: extract camera forward and right from view matrix + vec3 cam_forward = normalize(vec3(view[0][2], view[1][2], view[2][2])); + vec3 cam_right = normalize(vec3(view[0][0], view[1][0], view[2][0])); + + // Key light: slightly offset from camera direction + vec3 key_dir = normalize(cam_forward + 0.3 * cam_right); + float key = max(dot(norm, key_dir), 0.0); + + // Fill light: from the opposite side, softer + vec3 fill_dir = normalize(cam_forward - 0.5 * cam_right); + float fill = max(dot(norm, fill_dir), 0.0) * 0.4; + + // Blinn-Phong specular (using camera forward as view direction) + vec3 half_dir = normalize(key_dir + cam_forward); + float spec = pow(max(dot(norm, half_dir), 0.0), 32.0) * 0.3; + + float ambient = min_brightness; + float lighting = ambient + (1.0 - ambient) * (key + fill); + lighting = min(lighting, 1.0); + + FragColor = vec4(vertex_color * lighting + vec3(spec), alpha); } )"; } // namespace mesh @@ -142,15 +161,30 @@ in vec3 o_vertex_color; out vec4 FragColor; -uniform vec3 u_light_dir; +uniform mat4 u_view; uniform float u_min_brightness; void main() { vec3 norm = normalize(o_normal); - float diff = - max(dot(norm, normalize(u_light_dir)), - u_min_brightness); // never too dark - FragColor = vec4(o_vertex_color * diff, 1.0); + + vec3 cam_forward = normalize(vec3(u_view[0][2], u_view[1][2], u_view[2][2])); + vec3 cam_right = normalize(vec3(u_view[0][0], u_view[1][0], u_view[2][0])); + + vec3 key_dir = normalize(cam_forward + 0.3 * cam_right); + float key = max(dot(norm, key_dir), 0.0); + + vec3 fill_dir = normalize(cam_forward - 0.5 * cam_right); + float fill = max(dot(norm, fill_dir), 0.0) * 0.4; + + // Blinn-Phong specular (using camera forward as view direction) + vec3 half_dir = normalize(key_dir + cam_forward); + float spec = pow(max(dot(norm, half_dir), 0.0), 32.0) * 0.3; + + float ambient = u_min_brightness; + float lighting = ambient + (1.0 - ambient) * (key + fill); + lighting = min(lighting, 1.0); + + FragColor = vec4(o_vertex_color * lighting + vec3(spec), 1.0); } )"; } // namespace mono_instanced @@ -164,15 +198,12 @@ layout(location = 3) in float a_radius; layout(location = 4) in vec3 a_color; out vec3 o_vertex_color; -out vec3 o_normal; uniform mat4 u_model; uniform mat4 u_view; uniform mat4 u_projection; void main() { - o_normal = mat3(transpose(inverse(u_model))) * a_normal; - // scale and shift vec3 real_pos = (a_model_vertex_pos * a_radius) + a_position; @@ -182,20 +213,12 @@ void main() { )"; inline const std::string frag = R"( #version 330 core -in vec3 o_normal; in vec3 o_vertex_color; out vec4 FragColor; -uniform vec3 u_light_dir; -uniform float u_min_brightness; - void main() { - vec3 norm = normalize(o_normal); - float diff = - max(dot(norm, normalize(u_light_dir)), - u_min_brightness); // never too dark - FragColor = vec4(o_vertex_color * diff, 1.0); + FragColor = vec4(o_vertex_color, 1.0); } )"; } // namespace point_cloud @@ -233,7 +256,7 @@ void main() { float alpha = clamp( 1.0 - (dist / (uScale * 100.0)), 0.0, 1.0 - ); // adjust 20.0 to control fade range + ); FragColor = vec4(uColor, alpha * uExtraAlpha); } diff --git a/slamd/include/slamd_window/geom/arcball_indicator.hpp b/slamd/include/slamd_window/geom/arcball_indicator.hpp index d2bf8668..29731807 100644 --- a/slamd/include/slamd_window/geom/arcball_indicator.hpp +++ b/slamd/include/slamd_window/geom/arcball_indicator.hpp @@ -11,6 +11,10 @@ class ArcballIndicator : public Geometry { public: ArcballIndicator(); ~ArcballIndicator(); + + ArcballIndicator(const ArcballIndicator&) = delete; + ArcballIndicator& operator=(const ArcballIndicator&) = delete; + void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; void set_arcball_zoom(float zoom); diff --git a/slamd/include/slamd_window/geom/image.hpp b/slamd/include/slamd_window/geom/image.hpp index eae855ca..06dbc213 100644 --- a/slamd/include/slamd_window/geom/image.hpp +++ b/slamd/include/slamd_window/geom/image.hpp @@ -25,6 +25,9 @@ class Image : public Geometry { Image(const slamd::data::Image& image, bool normalized = true); ~Image(); + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; static std::shared_ptr deserialize( diff --git a/slamd/include/slamd_window/geom/mesh.hpp b/slamd/include/slamd_window/geom/mesh.hpp index f0979be6..17bf5f76 100644 --- a/slamd/include/slamd_window/geom/mesh.hpp +++ b/slamd/include/slamd_window/geom/mesh.hpp @@ -26,6 +26,9 @@ class Mesh : public Geometry { ~Mesh(); + Mesh(const Mesh&) = delete; + Mesh& operator=(const Mesh&) = delete; + static std::shared_ptr deserialize(const slamd::flatb::Mesh* mesh_fb); void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; diff --git a/slamd/include/slamd_window/geom/mono_instanced.hpp b/slamd/include/slamd_window/geom/mono_instanced.hpp index f94067f2..2d8947e5 100644 --- a/slamd/include/slamd_window/geom/mono_instanced.hpp +++ b/slamd/include/slamd_window/geom/mono_instanced.hpp @@ -18,6 +18,9 @@ class MonoInstanced : public Geometry { ); ~MonoInstanced(); + MonoInstanced(const MonoInstanced&) = delete; + MonoInstanced& operator=(const MonoInstanced&) = delete; + void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; void update_transforms(const std::vector& positions); diff --git a/slamd/include/slamd_window/geom/point_cloud.hpp b/slamd/include/slamd_window/geom/point_cloud.hpp index 695baa27..58f8a7f4 100644 --- a/slamd/include/slamd_window/geom/point_cloud.hpp +++ b/slamd/include/slamd_window/geom/point_cloud.hpp @@ -20,6 +20,9 @@ class PointCloud : public Geometry { ); ~PointCloud(); + PointCloud(const PointCloud&) = delete; + PointCloud& operator=(const PointCloud&) = delete; + void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; static std::shared_ptr deserialize( diff --git a/slamd/include/slamd_window/geom/points_2d.hpp b/slamd/include/slamd_window/geom/points_2d.hpp deleted file mode 100644 index 1ac69b23..00000000 --- a/slamd/include/slamd_window/geom/points_2d.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include - -namespace slamd { -namespace _geom { - -class Points2D : public Geometry { - public: - Points2D( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii - ); - - void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; - - std::optional bounds() override; - - static std::shared_ptr deserialize( - const slamd::flatb::Points2D* points2d_fb - ); - - private: - std::unique_ptr mesh; - slamd::gmath::AABB cached_bounds; -}; - -} // namespace _geom -} // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd_window/geom/poly_line_2d.hpp b/slamd/include/slamd_window/geom/poly_line_2d.hpp deleted file mode 100644 index 6ae15d29..00000000 --- a/slamd/include/slamd_window/geom/poly_line_2d.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace slamd { -namespace _geom { - -class PolyLine2D : public Geometry { - public: - PolyLine2D( - const std::vector& points, - const glm::vec3& color, - float thickness - ); - - void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; - - static std::shared_ptr deserialize( - const slamd::flatb::PolyLine2D* poly_line_fb - ); - - std::optional bounds() override; - - private: - static std::unique_ptr make_mesh( - const std::vector& points, - const glm::vec3& color, - float thickness - ); - - private: - std::unique_ptr line_mesh; - slamd::gmath::AABB cached_bounds; -}; - -} // namespace _geom -} // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd_window/geom/circles_2d.hpp b/slamd/include/slamd_window/geom/spheres.hpp similarity index 51% rename from slamd/include/slamd_window/geom/circles_2d.hpp rename to slamd/include/slamd_window/geom/spheres.hpp index b0f4cb10..f9bd4003 100644 --- a/slamd/include/slamd_window/geom/circles_2d.hpp +++ b/slamd/include/slamd_window/geom/spheres.hpp @@ -6,56 +6,50 @@ namespace slamd { namespace _geom { -class Circles2D : public Geometry { +class Spheres : public Geometry { public: - Circles2D( - const std::vector& positions, + Spheres( + const std::vector& positions, const std::vector& colors, const std::vector& radii, - float thickness + float min_brightness ); - static std::shared_ptr deserialize( - const slamd::flatb::Circles2D* triad_fb + static std::shared_ptr deserialize( + const slamd::flatb::Spheres* spheres_fb ); void render(glm::mat4 model, glm::mat4 view, glm::mat4 projection) override; - std::optional bounds() override; - - void update_positions(const std::vector& positions); + void update_positions(const std::vector& positions); void update_colors(const std::vector& colors); void update_radii(const std::vector& radii); private: static std::unique_ptr make_mono_instanced( - const std::vector& positions, + const std::vector& positions, const std::vector& colors, - const std::vector& radii, - float thickness + const std::vector& radii ); - static slamd::gmath::AABB make_bounds( - const std::vector& positions, + static std::vector make_transforms( + const std::vector& positions, const std::vector& radii ); - static std::vector - make_transforms(std::vector positions, std::vector radii); - void handle_updates(); private: - std::unique_ptr circles_instanced; - slamd::gmath::AABB cached_bounds; + std::unique_ptr mono; + std::vector positions; std::vector colors; - std::vector positions; std::vector radii; + float min_brightness; bool pending_trans_update = false; bool pending_color_update = false; }; } // namespace _geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd_window/geom/xy_grid.hpp b/slamd/include/slamd_window/geom/xy_grid.hpp index e0f69928..cad3fdbd 100644 --- a/slamd/include/slamd_window/geom/xy_grid.hpp +++ b/slamd/include/slamd_window/geom/xy_grid.hpp @@ -14,6 +14,9 @@ class GridXYPlane : public Geometry { ~GridXYPlane() override; + GridXYPlane(const GridXYPlane&) = delete; + GridXYPlane& operator=(const GridXYPlane&) = delete; + void set_arcball_zoom(float zoom); private: diff --git a/slamd/include/slamd_window/slamd.hpp b/slamd/include/slamd_window/slamd.hpp index 285d1e78..770b454f 100644 --- a/slamd/include/slamd_window/slamd.hpp +++ b/slamd/include/slamd_window/slamd.hpp @@ -2,14 +2,11 @@ #include #include #include -#include #include #include #include -#include #include -#include #include #include #include -#include \ No newline at end of file +#include diff --git a/slamd/include/slamd_window/state_manager.hpp b/slamd/include/slamd_window/state_manager.hpp index d862673b..ba4f53e9 100644 --- a/slamd/include/slamd_window/state_manager.hpp +++ b/slamd/include/slamd_window/state_manager.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include namespace slamd { @@ -48,16 +48,6 @@ class StateManager { const slamd::flatb::UpdateMeshNormals* update_mesh_normals_fb ); - void handle_update_circles2d_positions( - const slamd::flatb::UpdateCircles2DPositions* update_fb - ); - void handle_update_circles2d_colors( - const slamd::flatb::UpdateCircles2DColors* update_fb - ); - void handle_update_circles2d_radii( - const slamd::flatb::UpdateCircles2DRadii* update_fb - ); - void handle_update_point_cloud_positions( const slamd::flatb::UpdatePointCloudPositions* update_fb ); @@ -68,11 +58,21 @@ class StateManager { const slamd::flatb::UpdatePointCloudRadii* update_fb ); + void handle_update_spheres_positions( + const slamd::flatb::UpdateSpheresPositions* update_fb + ); + void handle_update_spheres_colors( + const slamd::flatb::UpdateSpheresColors* update_fb + ); + void handle_update_spheres_radii( + const slamd::flatb::UpdateSpheresRadii* update_fb + ); + public: std::atomic loaded = false; std::optional layout_path = std::nullopt; - std::map> views; + std::map> views; std::map<_id::TreeID, std::shared_ptr> trees; std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> geometries; diff --git a/slamd/include/slamd_window/tree/node.hpp b/slamd/include/slamd_window/tree/node.hpp index 022bdc9a..29ceb82c 100644 --- a/slamd/include/slamd_window/tree/node.hpp +++ b/slamd/include/slamd_window/tree/node.hpp @@ -29,8 +29,6 @@ class Node { public: bool checked = true; - float last_label_w = 0.0f; - bool has_children_cached = false; std::optional glob_matches = std::nullopt; std::optional> get_object() const; diff --git a/slamd/include/slamd_window/tree/tree.hpp b/slamd/include/slamd_window/tree/tree.hpp index 7d876489..63edba15 100644 --- a/slamd/include/slamd_window/tree/tree.hpp +++ b/slamd/include/slamd_window/tree/tree.hpp @@ -17,8 +17,7 @@ class Tree { Tree(uint64_t id); Tree(uint64_t id, std::unique_ptr&& root); - virtual void - set_object(const TreePath& path, std::shared_ptr<_geom::Geometry> object); + void set_object(const TreePath& path, std::shared_ptr<_geom::Geometry> object); void render(const glm::mat4& view, const glm::mat4& projection) const; @@ -33,7 +32,7 @@ class Tree { void clear(const TreePath& path); void mark_nodes_matching_glob(std::optional glob); - protected: + private: std::optional traverse(const TreePath& path); Node* make_path(TreePath path); diff --git a/slamd/include/slamd_window/tree_overlay.hpp b/slamd/include/slamd_window/tree_overlay.hpp new file mode 100644 index 00000000..87f94fa7 --- /dev/null +++ b/slamd/include/slamd_window/tree_overlay.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include +#include + +namespace slamd { + +class TreeOverlay { + public: + TreeOverlay(); + + /// Renders the tree overlay in the upper-left of the current ImGui window. + /// Returns true if the overlay modified tree state (checkbox toggled, filter + /// changed). + bool render(std::shared_ptr tree); + + private: + void draw_filter_field(); + void draw_tree_nodes(Node* root); + void draw_node( + Node* node, + const std::string& label, + int depth, + bool parent_dimmed, + TreePath& full_path + ); + + float compute_tree_content_width(Node* root) const; + float compute_tree_content_height(Node* root) const; + float compute_filter_row_width() const; + + char filter_buf[512] = ""; + std::unordered_map tree_open; + + bool dirty = false; + + std::shared_ptr current_tree; + std::optional filter_path; + std::optional filter_error; +}; + +} // namespace slamd diff --git a/slamd/include/slamd_window/view/canvas_view.hpp b/slamd/include/slamd_window/view/canvas_view.hpp deleted file mode 100644 index cc83462d..00000000 --- a/slamd/include/slamd_window/view/canvas_view.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace slamd { -class CanvasView : public View { - private: - FrameBuffer frame_buffer; - Camera2D camera; - FrameTimer frame_timer; - bool manually_moved; - - public: - CanvasView(std::shared_ptr tree); - void render_to_imgui() override; - - private: - void render_to_frame_buffer(); - void set_default_pos(); - void fix_view_aspect(); - - void handle_input(); - void handle_translation_input(); - void handle_mouse_input(); - glm::vec2 get_normalized_mouse_pos(); -}; - -} // namespace slamd \ No newline at end of file diff --git a/slamd/include/slamd_window/view/scene_view.hpp b/slamd/include/slamd_window/view/scene_view.hpp index 6031e8a9..d0e8d6e0 100644 --- a/slamd/include/slamd_window/view/scene_view.hpp +++ b/slamd/include/slamd_window/view/scene_view.hpp @@ -6,10 +6,21 @@ #include #include #include -#include +#include namespace slamd { -class SceneView : public View { +class SceneView { + public: + SceneView(std::shared_ptr tree); + void render_to_imgui(); + + void mark_dirty(); + + public: + bool _dirty = true; + std::shared_ptr tree; + TreeOverlay tree_overlay; + private: FrameBuffer frame_buffer; Arcball arcball; @@ -18,11 +29,6 @@ class SceneView : public View { _geom::GridXYPlane xy_grid; _geom::ArcballIndicator arcball_indicator; - public: - SceneView(std::shared_ptr tree); - void render_to_imgui() override; - - private: void handle_input(); void handle_translation_input(); void handle_mouse_input(); @@ -30,4 +36,4 @@ class SceneView : public View { void render_to_frame_buffer(); }; -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/include/slamd_window/view/view.hpp b/slamd/include/slamd_window/view/view.hpp deleted file mode 100644 index 5a5a0a0f..00000000 --- a/slamd/include/slamd_window/view/view.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include -#include - -namespace slamd { - -class View { - public: - View(std::shared_ptr t); - virtual void render_to_imgui() = 0; - virtual ~View() = default; - static std::unique_ptr - deserialize(const flatb::View* view, std::shared_ptr tree); - - void mark_dirty(); - - public: - bool _dirty = true; - std::shared_ptr tree; - std::unordered_map tree_open; - std::optional visualize_glob = std::nullopt; - char filter_buf[512] = ""; - float tree_overlay_w = 0.0f; - float tree_overlay_h = 0.0f; -}; - -} // namespace slamd \ No newline at end of file diff --git a/slamd/shaders/mesh/fragment_shader.frag b/slamd/shaders/mesh/fragment_shader.frag index c645316d..2c9b221b 100644 --- a/slamd/shaders/mesh/fragment_shader.frag +++ b/slamd/shaders/mesh/fragment_shader.frag @@ -5,13 +5,32 @@ in vec3 vertex_color; out vec4 FragColor; -uniform vec3 light_dir; +uniform mat4 view; uniform float min_brightness; uniform float alpha; void main() { vec3 norm = normalize(Normal); - float diff = - max(dot(norm, normalize(light_dir)), min_brightness); // never too dark - FragColor = vec4(vertex_color * diff, alpha); + + // Camera-relative lighting: extract camera forward and right from view matrix + vec3 cam_forward = normalize(vec3(view[0][2], view[1][2], view[2][2])); + vec3 cam_right = normalize(vec3(view[0][0], view[1][0], view[2][0])); + + // Key light: slightly offset from camera direction + vec3 key_dir = normalize(cam_forward + 0.3 * cam_right); + float key = max(dot(norm, key_dir), 0.0); + + // Fill light: from the opposite side, softer + vec3 fill_dir = normalize(cam_forward - 0.5 * cam_right); + float fill = max(dot(norm, fill_dir), 0.0) * 0.4; + + // Blinn-Phong specular (using camera forward as view direction) + vec3 half_dir = normalize(key_dir + cam_forward); + float spec = pow(max(dot(norm, half_dir), 0.0), 32.0) * 0.3; + + float ambient = min_brightness; + float lighting = ambient + (1.0 - ambient) * (key + fill); + lighting = min(lighting, 1.0); + + FragColor = vec4(vertex_color * lighting + vec3(spec), alpha); } \ No newline at end of file diff --git a/slamd/shaders/mono_instanced/fragment_shader.frag b/slamd/shaders/mono_instanced/fragment_shader.frag index 3e8d9619..c8d1cafe 100644 --- a/slamd/shaders/mono_instanced/fragment_shader.frag +++ b/slamd/shaders/mono_instanced/fragment_shader.frag @@ -5,13 +5,28 @@ in vec3 o_vertex_color; out vec4 FragColor; -uniform vec3 u_light_dir; +uniform mat4 u_view; uniform float u_min_brightness; void main() { vec3 norm = normalize(o_normal); - float diff = - max(dot(norm, normalize(u_light_dir)), - u_min_brightness); // never too dark - FragColor = vec4(o_vertex_color * diff, 1.0); + + vec3 cam_forward = normalize(vec3(u_view[0][2], u_view[1][2], u_view[2][2])); + vec3 cam_right = normalize(vec3(u_view[0][0], u_view[1][0], u_view[2][0])); + + vec3 key_dir = normalize(cam_forward + 0.3 * cam_right); + float key = max(dot(norm, key_dir), 0.0); + + vec3 fill_dir = normalize(cam_forward - 0.5 * cam_right); + float fill = max(dot(norm, fill_dir), 0.0) * 0.4; + + // Blinn-Phong specular (using camera forward as view direction) + vec3 half_dir = normalize(key_dir + cam_forward); + float spec = pow(max(dot(norm, half_dir), 0.0), 32.0) * 0.3; + + float ambient = u_min_brightness; + float lighting = ambient + (1.0 - ambient) * (key + fill); + lighting = min(lighting, 1.0); + + FragColor = vec4(o_vertex_color * lighting + vec3(spec), 1.0); } \ No newline at end of file diff --git a/slamd/shaders/point_cloud/fragment_shader.frag b/slamd/shaders/point_cloud/fragment_shader.frag index 3e8d9619..350274cc 100644 --- a/slamd/shaders/point_cloud/fragment_shader.frag +++ b/slamd/shaders/point_cloud/fragment_shader.frag @@ -1,17 +1,9 @@ #version 330 core -in vec3 o_normal; in vec3 o_vertex_color; out vec4 FragColor; -uniform vec3 u_light_dir; -uniform float u_min_brightness; - void main() { - vec3 norm = normalize(o_normal); - float diff = - max(dot(norm, normalize(u_light_dir)), - u_min_brightness); // never too dark - FragColor = vec4(o_vertex_color * diff, 1.0); + FragColor = vec4(o_vertex_color, 1.0); } \ No newline at end of file diff --git a/slamd/shaders/point_cloud/vertex_shader.vert b/slamd/shaders/point_cloud/vertex_shader.vert index a8c2ccf4..ff48d27c 100644 --- a/slamd/shaders/point_cloud/vertex_shader.vert +++ b/slamd/shaders/point_cloud/vertex_shader.vert @@ -7,15 +7,12 @@ layout(location = 3) in float a_radius; layout(location = 4) in vec3 a_color; out vec3 o_vertex_color; -out vec3 o_normal; uniform mat4 u_model; uniform mat4 u_view; uniform mat4 u_projection; void main() { - o_normal = mat3(transpose(inverse(u_model))) * a_normal; - // scale and shift vec3 real_pos = (a_model_vertex_pos * a_radius) + a_position; diff --git a/slamd/shaders/xy_grid/fragment_shader.frag b/slamd/shaders/xy_grid/fragment_shader.frag index 47a66e8e..493141d9 100644 --- a/slamd/shaders/xy_grid/fragment_shader.frag +++ b/slamd/shaders/xy_grid/fragment_shader.frag @@ -13,7 +13,7 @@ void main() { float alpha = clamp( 1.0 - (dist / (uScale * 100.0)), 0.0, 1.0 - ); // adjust 20.0 to control fade range + ); FragColor = vec4(uColor, alpha * uExtraAlpha); } diff --git a/slamd/src/common/gmath/rect2d.cpp b/slamd/src/common/gmath/rect2d.cpp deleted file mode 100644 index 78efd0bf..00000000 --- a/slamd/src/common/gmath/rect2d.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include -#include - -namespace slamd { -namespace gmath { - -Rect2D::Rect2D( - const glm::vec2& top_left, - const glm::vec2& bottom_right -) - : top_left(top_left), - bottom_right(bottom_right) {} - -Rect2D Rect2D::from_aabb3d( - const AABB& aabb3d -) { - glm::vec2 top_left(aabb3d.min.x, aabb3d.min.y); - glm::vec2 bottom_right(aabb3d.max.x, aabb3d.max.y); - - return Rect2D(top_left, bottom_right); -} - -Rect2D Rect2D::center_cover( - const Rect2D& to_cover, - float required_aspect -) { - glm::vec2 center = to_cover.center(); - glm::vec2 size = to_cover.size(); - - float current_aspect = size.x / size.y; - - glm::vec2 new_size = size; - - if (current_aspect < required_aspect) { - // Need to widen - new_size.x = size.y * required_aspect; - } else { - // Need to heighten - new_size.y = size.x / required_aspect; - } - - return Rect2D::from_center_size(center, new_size); -} - -Rect2D::Rect2D( - const Rect2D& other -) - : top_left(other.top_left), - bottom_right(other.bottom_right) {} - -glm::vec2 Rect2D::size() const { - return this->bottom_right - this->top_left; -} - -Rect2D Rect2D::translate( - glm::vec2 amount -) const { - return Rect2D(this->top_left + amount, this->bottom_right + amount); -} - -glm::vec2 Rect2D::center() const { - return (this->top_left + this->bottom_right) / 2.0f; -} - -Rect2D Rect2D::from_center_size( - const glm::vec2& center, - const glm::vec2& size -) { - glm::vec2 top_left = center - size / 2.0f; - glm::vec2 bottom_right = center + size / 2.0f; - - return Rect2D(top_left, bottom_right); -} - -glm::vec2 Rect2D::unnormalize( - const glm::vec2& normalized_center -) const { - glm::vec2 center = (normalized_center * this->size()) + this->top_left; - - return center; -} - -} // namespace gmath -} // namespace slamd \ No newline at end of file diff --git a/slamd/src/common/gmath/transforms.cpp b/slamd/src/common/gmath/transforms.cpp index 5afe7686..3c5a2fa2 100644 --- a/slamd/src/common/gmath/transforms.cpp +++ b/slamd/src/common/gmath/transforms.cpp @@ -55,19 +55,6 @@ glm::mat4 tz3D( return t3D(glm::vec3(0.0f, 0.0f, amount)); } -glm::mat4 xy_to_3d( - const glm::mat3& mat2d -) { - glm::mat4 mat3d(1.0f); // identity mat4 - mat3d[0][0] = mat2d[0][0]; - mat3d[0][1] = mat2d[0][1]; - mat3d[1][0] = mat2d[1][0]; - mat3d[1][1] = mat2d[1][1]; - mat3d[3][0] = mat2d[2][0]; - mat3d[3][1] = mat2d[2][1]; - return mat3d; -} - glm::mat4 scale_xy( const glm::vec2& scale_vec ) { diff --git a/slamd/src/slamd/geom/arrows.cpp b/slamd/src/slamd/geom/arrows.cpp index ba08eacb..897ad71b 100644 --- a/slamd/src/slamd/geom/arrows.cpp +++ b/slamd/src/slamd/geom/arrows.cpp @@ -3,7 +3,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { constexpr uint32_t segments = 12; struct ArrowMesh { @@ -42,20 +42,17 @@ flatbuffers::Offset Arrows::serialize( ); } -} // namespace _geom - -namespace geom { - -ArrowsPtr arrows( +std::shared_ptr arrows( const std::vector& starts, const std::vector& ends, const std::vector& colors, float thickness ) { auto arrows = - std::make_shared<_geom::Arrows>(starts, ends, colors, thickness); + std::make_shared(starts, ends, colors, thickness); // _global::geometries.add(arrows->id, arrows); return arrows; } + } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/slamd/geom/box.cpp b/slamd/src/slamd/geom/box.cpp index 8a4bca60..bb55ce1c 100644 --- a/slamd/src/slamd/geom/box.cpp +++ b/slamd/src/slamd/geom/box.cpp @@ -2,7 +2,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { Box::Box() {} @@ -18,14 +18,11 @@ flatbuffers::Offset Box::serialize( ); } -} // namespace _geom - -namespace geom { -BoxPtr box() { - auto box = std::make_shared<_geom::Box>(); +std::shared_ptr box() { + auto box = std::make_shared(); // _global::geometries.add(box->id, box); return box; } } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/slamd/geom/camera_frustum.cpp b/slamd/src/slamd/geom/camera_frustum.cpp index f7521263..a8ac6230 100644 --- a/slamd/src/slamd/geom/camera_frustum.cpp +++ b/slamd/src/slamd/geom/camera_frustum.cpp @@ -4,7 +4,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { CameraFrustum::CameraFrustum( glm::mat3 intrinsics_matrix, @@ -66,18 +66,14 @@ flatbuffers::Offset CameraFrustum::serialize( ); } -} // namespace _geom - -namespace geom { - -CameraFrustumPtr camera_frustum( +std::shared_ptr camera_frustum( glm::mat3 intrinsics_matrix, size_t image_width, size_t image_height, data::Image&& image, float scale ) { - auto cam = std::make_shared<_geom::CameraFrustum>( + auto cam = std::make_shared( intrinsics_matrix, image_width, image_height, @@ -89,13 +85,13 @@ CameraFrustumPtr camera_frustum( return cam; } -CameraFrustumPtr camera_frustum( +std::shared_ptr camera_frustum( glm::mat3 intrinsics_matrix, size_t image_width, size_t image_height, float scale ) { - auto cam = std::make_shared<_geom::CameraFrustum>( + auto cam = std::make_shared( intrinsics_matrix, image_width, image_height, diff --git a/slamd/src/slamd/geom/geometry.cpp b/slamd/src/slamd/geom/geometry.cpp index ab58be4e..8eea9aa3 100644 --- a/slamd/src/slamd/geom/geometry.cpp +++ b/slamd/src/slamd/geom/geometry.cpp @@ -1,9 +1,10 @@ #include #include +#include #include namespace slamd { -namespace _geom { +namespace geom { Geometry::Geometry() : id(_id::GeometryID::next()) {} @@ -94,5 +95,5 @@ Geometry::find_visualizers() { return map; } -} // namespace _geom +} // namespace geom } // namespace slamd \ No newline at end of file diff --git a/slamd/src/slamd/geom/image.cpp b/slamd/src/slamd/geom/image.cpp index 0b3aea80..4ab6ade3 100644 --- a/slamd/src/slamd/geom/image.cpp +++ b/slamd/src/slamd/geom/image.cpp @@ -1,7 +1,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { Image::Image( data::Image&& image, @@ -26,31 +26,14 @@ flatbuffers::Offset Image::serialize( ); } -} // namespace _geom - -namespace geom { - -ImagePtr image( +std::shared_ptr image( data::Image&& image ) { - auto img = std::make_shared<_geom::Image>(std::move(image), true); + auto img = std::make_shared(std::move(image), true); // _global::geometries.add(img->id, img); return img; } -} // namespace geom - -namespace geom2d { -ImagePtr image( - data::Image&& image -) { - auto img = std::make_shared<_geom::Image>(std::move(image), false); - - // _global::geometries.add(img->id, img); - return img; -} - -} // namespace geom2d - -} // namespace slamd \ No newline at end of file +} // namespace geom +} // namespace slamd diff --git a/slamd/src/slamd/geom/mesh.cpp b/slamd/src/slamd/geom/mesh.cpp index cacfb456..e44b2ddc 100644 --- a/slamd/src/slamd/geom/mesh.cpp +++ b/slamd/src/slamd/geom/mesh.cpp @@ -10,7 +10,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { void Mesh::update_positions( const std::vector& positions, @@ -149,18 +149,13 @@ flatbuffers::Offset Mesh::serialize( ); } -} // namespace _geom - -namespace geom { - -MeshPtr mesh( +std::shared_ptr mesh( const data::MeshData& mesh_data ) { - auto mesh = std::make_shared<_geom::Mesh>(mesh_data); + auto mesh = std::make_shared(mesh_data); // _global::geometries.add(mesh->id, mesh); return mesh; } } // namespace geom - -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/slamd/geom/mono_instanced.cpp b/slamd/src/slamd/geom/mono_instanced.cpp index a90e6300..c616e20c 100644 --- a/slamd/src/slamd/geom/mono_instanced.cpp +++ b/slamd/src/slamd/geom/mono_instanced.cpp @@ -2,7 +2,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { MonoInstanced::MonoInstanced( const std::vector& vertices, @@ -50,5 +50,5 @@ void MonoInstanced::update_colors( this->colors = colors; } -} // namespace _geom +} // namespace geom } // namespace slamd \ No newline at end of file diff --git a/slamd/src/slamd/geom/plane.cpp b/slamd/src/slamd/geom/plane.cpp index c7f4eeec..88e37e89 100644 --- a/slamd/src/slamd/geom/plane.cpp +++ b/slamd/src/slamd/geom/plane.cpp @@ -2,7 +2,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { Plane::Plane( glm::vec3 normal, @@ -42,19 +42,15 @@ flatbuffers::Offset Plane::serialize( ); } -} // namespace _geom - -namespace geom { - -PlanePtr plane( +std::shared_ptr plane( glm::vec3 normal, glm::vec3 point, glm::vec3 color, float radius, float alpha ) { - return std::make_shared<_geom::Plane>(normal, point, color, radius, alpha); + return std::make_shared(normal, point, color, radius, alpha); } } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/slamd/geom/point_cloud.cpp b/slamd/src/slamd/geom/point_cloud.cpp index 2f04d432..5e491822 100644 --- a/slamd/src/slamd/geom/point_cloud.cpp +++ b/slamd/src/slamd/geom/point_cloud.cpp @@ -1,10 +1,11 @@ #include +#include #include #include #include namespace slamd { -namespace _geom { +namespace geom { PointCloud::PointCloud( const std::vector& positions, @@ -147,5 +148,5 @@ void PointCloud::update_radii( this->broadcast(_utils::builder_buffer(builder)); } -} // namespace _geom +} // namespace geom } // namespace slamd \ No newline at end of file diff --git a/slamd/src/slamd/geom/points_2d.cpp b/slamd/src/slamd/geom/points_2d.cpp deleted file mode 100644 index 1a2d6a75..00000000 --- a/slamd/src/slamd/geom/points_2d.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -namespace slamd { -namespace _geom { - -Points2D::Points2D( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii -) - : positions(positions), - colors(colors), - radii(radii) {} - -flatbuffers::Offset Points2D::serialize( - flatbuffers::FlatBufferBuilder& builder -) { - auto points_2d_fb = flatb::CreatePoints2D( - builder, - gmath::serialize_vector(builder, this->positions), - gmath::serialize_vector(builder, this->colors), - gmath::serialize_vector(builder, this->radii) - ); - - return flatb::CreateGeometry( - builder, - this->id.value, - flatb::GeometryUnion_points_2d, - points_2d_fb.Union() - ); -} - -} // namespace _geom -} // namespace slamd \ No newline at end of file diff --git a/slamd/src/slamd/geom/poly_line.cpp b/slamd/src/slamd/geom/poly_line.cpp index 5b632d38..ef9b9974 100644 --- a/slamd/src/slamd/geom/poly_line.cpp +++ b/slamd/src/slamd/geom/poly_line.cpp @@ -5,7 +5,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { PolyLine::PolyLine( const std::vector& points, @@ -39,17 +39,13 @@ flatbuffers::Offset PolyLine::serialize( ); } -} // namespace _geom - -namespace geom { - -PolyLinePtr poly_line( +std::shared_ptr poly_line( const std::vector& points, float thickness, const glm::vec3& color, float min_brightness ) { - auto poly_line = std::make_shared<_geom::PolyLine>( + auto poly_line = std::make_shared( points, thickness, color, @@ -58,5 +54,6 @@ PolyLinePtr poly_line( // _global::geometries.add(poly_line->id, poly_line); return poly_line; } + } // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/slamd/geom/poly_line_2d.cpp b/slamd/src/slamd/geom/poly_line_2d.cpp deleted file mode 100644 index 638abebe..00000000 --- a/slamd/src/slamd/geom/poly_line_2d.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include - -namespace slamd { -namespace _geom { - -PolyLine2D::PolyLine2D( - const std::vector& points, - const glm::vec3& color, - float thickness -) - : points(points), - color(color), - thickness(thickness) {} - -flatbuffers::Offset PolyLine2D::serialize( - flatbuffers::FlatBufferBuilder& builder -) { - auto color_fb = gmath::serialize(this->color); - auto poly_line2d_fb = flatb::CreatePolyLine2D( - builder, - gmath::serialize_vector(builder, this->points), - &color_fb, - this->thickness - ); - - return flatb::CreateGeometry( - builder, - this->id.value, - flatb::GeometryUnion_poly_line_2d, - poly_line2d_fb.Union() - ); -} - -} // namespace _geom - -namespace geom2d { - -PolyLinePtr poly_line( - const std::vector& points, - const glm::vec3& color, - float thickness -) { - auto line = std::make_shared<_geom::PolyLine2D>(points, color, thickness); - // _global::geometries.add(line->id, line); - return line; -} - -} // namespace geom2d -} // namespace slamd diff --git a/slamd/src/slamd/geom/sphere.cpp b/slamd/src/slamd/geom/sphere.cpp index 3db2165f..8162a06d 100644 --- a/slamd/src/slamd/geom/sphere.cpp +++ b/slamd/src/slamd/geom/sphere.cpp @@ -5,7 +5,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { Sphere::Sphere( float radius, @@ -27,18 +27,14 @@ flatbuffers::Offset Sphere::serialize( ); } -} // namespace _geom - -namespace geom { - -SpherePtr sphere( +std::shared_ptr sphere( float radius, glm::vec3 color ) { - auto sphere = std::make_shared<_geom::Sphere>(radius, color); + auto sphere = std::make_shared(radius, color); // _global::geometries.add(sphere->id, sphere); return sphere; } -} // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace geom +} // namespace slamd diff --git a/slamd/src/slamd/geom/circles_2d.cpp b/slamd/src/slamd/geom/spheres.cpp similarity index 61% rename from slamd/src/slamd/geom/circles_2d.cpp rename to slamd/src/slamd/geom/spheres.cpp index 01fe9222..e71fb157 100644 --- a/slamd/src/slamd/geom/circles_2d.cpp +++ b/slamd/src/slamd/geom/spheres.cpp @@ -1,45 +1,59 @@ #include #include -#include +#include #include -#include #include namespace slamd { +namespace geom { -namespace _geom { - -Circles2D::Circles2D( - const std::vector& positions, +Spheres::Spheres( + const std::vector& positions, const std::vector& colors, const std::vector& radii, - float thickness + float min_brightness ) : positions(positions), colors(colors), radii(radii), - thickness(thickness) {} + min_brightness(min_brightness) { + if (!((positions.size() == colors.size()) && (colors.size() == radii.size()) + )) { + throw std::invalid_argument(fmt::format( + "number of positions, colors, and radii must be the same, got " + "{} positions, {} colors, {} radii", + positions.size(), + colors.size(), + radii.size() + )); + } +} -flatbuffers::Offset Circles2D::serialize( +flatbuffers::Offset Spheres::serialize( flatbuffers::FlatBufferBuilder& builder ) { auto pos_fb = gmath::serialize_vector(builder, this->positions); auto colors_fb = gmath::serialize_vector(builder, this->colors); - auto radii_fb = builder.CreateVector(this->radii); + auto radii_fb = gmath::serialize_vector(builder, this->radii); - auto circles_fb = - flatb::CreateCircles2D(builder, pos_fb, colors_fb, radii_fb, thickness); + auto spheres_fb = flatb::CreateSpheres( + builder, + pos_fb, + colors_fb, + radii_fb, + this->min_brightness + ); return flatb::CreateGeometry( builder, this->id.value, - flatb::GeometryUnion_circles_2d, - circles_fb.Union() + flatb::GeometryUnion_spheres, + spheres_fb.Union() ); } -void Circles2D::update_positions( - const std::vector& positions +void Spheres::update_positions( + const std::vector& positions ) { if (positions.size() != this->positions.size()) { throw std::invalid_argument(fmt::format( @@ -51,19 +65,18 @@ void Circles2D::update_positions( this->positions = positions; - // make the message flatbuffers::FlatBufferBuilder builder; auto positions_fb = gmath::serialize_vector(builder, positions); - auto update_fb = flatb::CreateUpdateCircles2DPositions( + auto update_fb = flatb::CreateUpdateSpheresPositions( builder, this->id.value, positions_fb ); auto message_fb = flatb::CreateMessage( builder, - flatb::MessageUnion_update_circles2d_positions, + flatb::MessageUnion_update_spheres_positions, update_fb.Union() ); @@ -72,7 +85,7 @@ void Circles2D::update_positions( this->broadcast(_utils::builder_buffer(builder)); }; -void Circles2D::update_colors( +void Spheres::update_colors( const std::vector& colors ) { if (colors.size() != this->colors.size()) { @@ -85,16 +98,15 @@ void Circles2D::update_colors( this->colors = colors; - // make the message flatbuffers::FlatBufferBuilder builder; auto colors_fb = gmath::serialize_vector(builder, colors); auto update_fb = - flatb::CreateUpdateCircles2DColors(builder, this->id.value, colors_fb); + flatb::CreateUpdateSpheresColors(builder, this->id.value, colors_fb); auto message_fb = flatb::CreateMessage( builder, - flatb::MessageUnion_update_circles2d_colors, + flatb::MessageUnion_update_spheres_colors, update_fb.Union() ); @@ -103,7 +115,7 @@ void Circles2D::update_colors( this->broadcast(_utils::builder_buffer(builder)); }; -void Circles2D::update_radii( +void Spheres::update_radii( const std::vector& radii ) { if (radii.size() != this->radii.size()) { @@ -116,16 +128,15 @@ void Circles2D::update_radii( this->radii = radii; - // make the message flatbuffers::FlatBufferBuilder builder; auto radii_fb = gmath::serialize_vector(builder, radii); auto update_fb = - flatb::CreateUpdateCircles2DRadii(builder, this->id.value, radii_fb); + flatb::CreateUpdateSpheresRadii(builder, this->id.value, radii_fb); auto message_fb = flatb::CreateMessage( builder, - flatb::MessageUnion_update_circles2d_radii, + flatb::MessageUnion_update_spheres_radii, update_fb.Union() ); @@ -134,20 +145,5 @@ void Circles2D::update_radii( this->broadcast(_utils::builder_buffer(builder)); } -} // namespace _geom - -namespace geom2d { - -CirclesPtr circles( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii, - float thickness -) { - auto circles = - std::make_shared<_geom::Circles2D>(positions, colors, radii, thickness); - // _global::geometries.add(circles->id, circles); - return circles; -} -} // namespace geom2d -} // namespace slamd \ No newline at end of file +} // namespace geom +} // namespace slamd diff --git a/slamd/src/slamd/geom/triad.cpp b/slamd/src/slamd/geom/triad.cpp index 6becbd1e..9c8f78a4 100644 --- a/slamd/src/slamd/geom/triad.cpp +++ b/slamd/src/slamd/geom/triad.cpp @@ -3,7 +3,7 @@ #include namespace slamd { -namespace _geom { +namespace geom { Triad::Triad( float scale, @@ -30,28 +30,24 @@ flatbuffers::Offset Triad::serialize( ); } -} // namespace _geom - -namespace geom { - -TriadPtr triad( +std::shared_ptr triad( float scale, float thickness ) { - auto triad = std::make_shared<_geom::Triad>(scale, thickness); + auto triad = std::make_shared(scale, thickness); // _global::geometries.add(triad->id, triad); return triad; } -TriadPtr triad( +std::shared_ptr triad( glm::mat4 pose, float scale, float thickness ) { - auto triad = std::make_shared<_geom::Triad>(scale, thickness, pose); + auto triad = std::make_shared(scale, thickness, pose); // _global::geometries.add(triad->id, triad); return triad; } -} // namespace geom -} // namespace slamd \ No newline at end of file +} // namespace geom +} // namespace slamd diff --git a/slamd/src/slamd/tree/tree.cpp b/slamd/src/slamd/tree/tree.cpp index 5dd1b5e8..d2e0c859 100644 --- a/slamd/src/slamd/tree/tree.cpp +++ b/slamd/src/slamd/tree/tree.cpp @@ -3,8 +3,8 @@ #include #include #include +#include #include -#include #include #include @@ -13,10 +13,10 @@ namespace slamd { namespace _tree { Node::Node( - Tree* tree, + Scene* scene, TreePath path ) - : tree(tree), + : scene(scene), path(path) {} Node::~Node() { @@ -25,7 +25,7 @@ Node::~Node() { } } -std::optional> Node::get_object() const { +std::optional> Node::get_object() const { std::scoped_lock l(this->object_mutex); if (!this->object.has_value()) { return std::nullopt; @@ -40,7 +40,7 @@ std::optional Node::get_transform() const { } void Node::set_object( - std::shared_ptr<_geom::Geometry> object + std::shared_ptr object ) { std::scoped_lock l(this->object_mutex); @@ -57,7 +57,7 @@ void Node::set_object( auto path_fb = builder.CreateString(this->path.string()); auto set_object_fb = flatb::CreateSetObject( builder, - this->tree->id.value, + this->scene->id.value, path_fb, object->id.value ); @@ -70,10 +70,109 @@ void Node::set_object( this->broadcast(_utils::builder_buffer(builder)); } -void Tree::clear( +void Node::set_transform( + glm::mat4 transform +) { + std::scoped_lock l(this->transform_mutex); + this->transform = transform; + + flatbuffers::FlatBufferBuilder builder; + + auto path_fb = builder.CreateString(this->path.string()); + auto transform_fb = gmath::serialize(transform); + + auto set_transform_fb = flatb::CreateSetTransform( + builder, + this->scene->id.value, + path_fb, + &transform_fb + ); + + auto message_fb = flatb::CreateMessage( + builder, + flatb::MessageUnion_set_transform, + set_transform_fb.Union() + ); + + builder.Finish(message_fb); + + auto message_buffer = _utils::builder_buffer(builder); + this->broadcast(message_buffer); +} + +flatbuffers::Offset Node::serialize( + flatbuffers::FlatBufferBuilder& builder +) { + std::vector> children; + + for (const auto& [path_comp, child] : this->children) { + auto path_comp_fb = builder.CreateString(path_comp); + + auto serialized_child = child->serialize(builder); + + children.push_back(slamd::flatb::CreateChildEntry( + builder, + path_comp_fb, + serialized_child + )); + } + + auto children_fb = builder.CreateVector(children); + + auto maybe_geom = this->get_object(); + std::optional maybe_geom_id_fb = std::nullopt; + + if (maybe_geom.has_value()) { + maybe_geom_id_fb = maybe_geom.value()->id.value; + } + + slamd::flatb::NodeBuilder node_builder(builder); + if (maybe_geom_id_fb.has_value()) { + node_builder.add_geometry_id(maybe_geom_id_fb.value()); + } + + auto maybe_transform = this->get_transform(); + if (maybe_transform.has_value()) { + slamd::flatb::Mat4 transform_fb = + gmath::serialize(maybe_transform.value()); + node_builder.add_transform(&transform_fb); + } + + node_builder.add_children(children_fb); + + return node_builder.Finish(); +} + +void Node::broadcast( + std::shared_ptr> message_buffer +) { + this->scene->broadcast(message_buffer); +} + +std::map<_id::VisualizerID, std::shared_ptr<_vis::Visualizer>> +Node::find_visualizers() { + return this->scene->find_visualizers(); +} + +} // namespace _tree + +Scene::Scene() + : id(_id::TreeID::next()) { + this->root = std::make_shared<_tree::Node>(this, _tree::TreePath()); +} + +Scene::~Scene() { + // Clear the node tree before implicit member destruction. + // Node::~Node() calls geometry->detach() which calls back into + // this->find_visualizers(). If we let implicit destruction happen, + // attached_to and other members may already be partially destroyed. + this->root.reset(); +} + +void Scene::clear( const std::string& path ) { - TreePath tree_path(path); + _tree::TreePath tree_path(path); auto current_node = this->root.get(); @@ -102,7 +201,7 @@ void Tree::clear( this->broadcast(this->get_clear_path_message(path)); } -std::shared_ptr> Tree::get_clear_path_message( +std::shared_ptr> Scene::get_clear_path_message( const std::string& path ) { flatbuffers::FlatBufferBuilder builder; @@ -123,7 +222,7 @@ std::shared_ptr> Tree::get_clear_path_message( return _utils::builder_buffer(builder); } -std::shared_ptr> Tree::get_add_tree_message() { +std::shared_ptr> Scene::get_add_tree_message() { flatbuffers::FlatBufferBuilder builder; auto tree_fb = this->serialize(builder); @@ -141,7 +240,7 @@ std::shared_ptr> Tree::get_add_tree_message() { return _utils::builder_buffer(builder); } -std::shared_ptr> Tree::get_remove_tree_message() { +std::shared_ptr> Scene::get_remove_tree_message() { flatbuffers::FlatBufferBuilder builder; auto remove_tree_fb = flatb::CreateRemoveTree(builder, this->id.value); @@ -157,9 +256,9 @@ std::shared_ptr> Tree::get_remove_tree_message() { return _utils::builder_buffer(builder); } -void Tree::add_all_geometries_rec( - Node* node, - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>>& initial_map +void Scene::add_all_geometries_rec( + _tree::Node* node, + std::map<_id::GeometryID, std::shared_ptr>& initial_map ) { for (auto& [_, child] : node->children) { this->add_all_geometries_rec(child.get(), initial_map); @@ -183,12 +282,7 @@ void Tree::add_all_geometries_rec( } std::map<_id::VisualizerID, std::shared_ptr<_vis::Visualizer>> -Node::find_visualizers() { - return this->tree->find_visualizers(); -} - -std::map<_id::VisualizerID, std::shared_ptr<_vis::Visualizer>> -Tree::find_visualizers() { +Scene::find_visualizers() { std::map<_id::VisualizerID, std::shared_ptr<_vis::Visualizer>> map; for (auto it = this->attached_to.begin(); it != this->attached_to.end();) { @@ -204,98 +298,13 @@ Tree::find_visualizers() { return map; } -void Tree::add_all_geometries( - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>>& initial_map +void Scene::add_all_geometries( + std::map<_id::GeometryID, std::shared_ptr>& initial_map ) { this->add_all_geometries_rec(this->root.get(), initial_map); } -flatbuffers::Offset Node::serialize( - flatbuffers::FlatBufferBuilder& builder -) { - std::vector> children; - - for (const auto& [path_comp, child] : this->children) { - auto path_comp_fb = builder.CreateString(path_comp); - - auto serialized_child = child->serialize(builder); - - children.push_back(slamd::flatb::CreateChildEntry( - builder, - path_comp_fb, - serialized_child - )); - } - - auto children_fb = builder.CreateVector(children); - - auto maybe_geom = this->get_object(); - std::optional maybe_geom_id_fb = std::nullopt; - - if (maybe_geom.has_value()) { - maybe_geom_id_fb = maybe_geom.value()->id.value; - } - - slamd::flatb::NodeBuilder node_builder(builder); - if (maybe_geom_id_fb.has_value()) { - node_builder.add_geometry_id(maybe_geom_id_fb.value()); - } - - auto maybe_transform = this->get_transform(); - if (maybe_transform.has_value()) { - slamd::flatb::Mat4 transform_fb = - gmath::serialize(maybe_transform.value()); - node_builder.add_transform(&transform_fb); - } - - node_builder.add_children(children_fb); - - return node_builder.Finish(); -} - -void Node::set_transform( - glm::mat4 transform -) { - std::scoped_lock l(this->transform_mutex); - this->transform = transform; - - flatbuffers::FlatBufferBuilder builder; - - auto path_fb = builder.CreateString(this->path.string()); - auto transform_fb = gmath::serialize(transform); - ; - - auto set_transform_fb = flatb::CreateSetTransform( - builder, - this->tree->id.value, - path_fb, - &transform_fb - ); - - auto message_fb = flatb::CreateMessage( - builder, - flatb::MessageUnion_set_transform, - set_transform_fb.Union() - ); - - builder.Finish(message_fb); - - auto message_buffer = _utils::builder_buffer(builder); - this->broadcast(message_buffer); -} - -void Node::broadcast( - std::shared_ptr> message_buffer -) { - this->tree->broadcast(message_buffer); -} - -Tree::Tree() - : id(_id::TreeID::next()) { - this->root = std::make_shared(this, TreePath()); -} - -void Tree::broadcast( +void Scene::broadcast( std::shared_ptr> message_buffer ) { for (auto it = this->attached_to.begin(); it != this->attached_to.end();) { @@ -308,44 +317,44 @@ void Tree::broadcast( } } -void Tree::set_object( +void Scene::set_object( const std::string& path, - std::shared_ptr<_geom::Geometry> object + std::shared_ptr object ) { - TreePath treepath(path); + _tree::TreePath treepath(path); if (treepath.is_root()) { throw std::runtime_error("Setting root object is not allowed"); } - Node* node = this->make_path(treepath); + _tree::Node* node = this->make_path(treepath); node->set_object(object); } -flatbuffers::Offset Tree::serialize( +flatbuffers::Offset Scene::serialize( flatbuffers::FlatBufferBuilder& builder ) { auto serialized_root = this->root->serialize(builder); return slamd::flatb::CreateTree(builder, this->id.value, serialized_root); } -void Tree::set_transform_mat4( +void Scene::set_transform( const std::string& path, const glm::mat4& transform ) { - TreePath treepath(path); + _tree::TreePath treepath(path); if (treepath.is_root()) { throw std::runtime_error("Setting root transform is not allowed"); } - Node* node = this->make_path(treepath); + _tree::Node* node = this->make_path(treepath); node->set_transform(transform); } -std::optional Tree::traverse( - const TreePath& path +std::optional<_tree::Node*> Scene::traverse( + const _tree::TreePath& path ) { - Node* current_node = root.get(); + _tree::Node* current_node = root.get(); for (size_t i = 0; i < path.components.size(); i++) { auto& component = path.components[i]; @@ -362,15 +371,15 @@ std::optional Tree::traverse( return current_node; } -Node* Tree::make_path( - TreePath path +_tree::Node* Scene::make_path( + _tree::TreePath path ) { if (path.components.size() == 0) { return this->root.get(); } - Node* current_node = root.get(); - TreePath curr_path; + _tree::Node* current_node = root.get(); + _tree::TreePath curr_path; for (size_t i = 0; i < path.components.size(); i++) { auto& component = path.components[i]; @@ -382,9 +391,9 @@ Node* Tree::make_path( // in this case, we want to create the path down to the target // node - auto new_node = std::make_shared(this, curr_path); + auto new_node = std::make_shared<_tree::Node>(this, curr_path); - Node* new_node_ptr = new_node.get(); + _tree::Node* new_node_ptr = new_node.get(); // insert the new child current_node->children.emplace(component, std::move(new_node)); @@ -400,65 +409,9 @@ Node* Tree::make_path( return current_node; } -} // namespace _tree - -void Scene::set_transform( - const std::string& path, - glm::mat4 transform -) { - this->set_transform_mat4(path, transform); -} - std::shared_ptr scene() { auto scene = std::make_shared(); - // _global::trees.add(scene->id, scene); return scene; } -void Canvas::set_transform( - const std::string& path, - glm::mat3 transform -) { - auto new_transform = gmath::xy_to_3d(transform); - auto node = this->make_path(path); - - auto node_transform = node->get_transform().value_or(glm::mat4(1.0)); - - new_transform[3][2] = node_transform[3][2]; - - node->set_transform(new_transform); -} - -void Canvas::set_object( - const std::string& path, - std::shared_ptr<_geom::Geometry> object -) { - auto node = this->make_path(path); - - bool is_first_insert = !node->get_object().has_value(); - - node->set_object(object); - - if (is_first_insert) { - auto node_transform = node->get_transform().value_or(glm::mat4(1.0)); - - node_transform[3][2] = this->new_depth(); - - node->set_transform(node_transform); - } -} - -float Canvas::new_depth() { - float new_depth = - static_cast(this->insertion_order_counter) / 100.0f; - this->insertion_order_counter += 1; - return new_depth; -} - -std::shared_ptr canvas() { - auto canvas = std::make_shared(); - // _global::trees.add(canvas->id, canvas); - return canvas; -} - -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/slamd/view.cpp b/slamd/src/slamd/view.cpp index 838105f8..422e6d29 100644 --- a/slamd/src/slamd/view.cpp +++ b/slamd/src/slamd/view.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include namespace slamd { @@ -8,10 +10,9 @@ namespace _view { std::shared_ptr View::create( std::string name, std::shared_ptr<_vis::Visualizer> vis, - std::shared_ptr<_tree::Tree> tree, - slamd::flatb::ViewType view_type + std::shared_ptr tree ) { - auto view = std::shared_ptr(new View(name, vis, tree, view_type)); + auto view = std::shared_ptr(new View(name, vis, tree)); tree->attached_to.insert({view->id, view->shared_from_this()}); return view; @@ -45,8 +46,7 @@ flatbuffers::Offset View::serialize( auto view_fb = slamd::flatb::CreateView( builder, view_name_flatb, - this->tree->id.value, - this->view_type + this->tree->id.value ); return view_fb; } @@ -85,11 +85,9 @@ std::shared_ptr> View::get_remove_view_message() { View::View( std::string name, std::shared_ptr<_vis::Visualizer> vis, - std::shared_ptr<_tree::Tree> tree, - slamd::flatb::ViewType view_type + std::shared_ptr tree ) : tree(tree), - view_type(view_type), vis(vis), name(name) {} @@ -98,4 +96,4 @@ View::~View() { } } // namespace _view -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/slamd/visualizer.cpp b/slamd/src/slamd/visualizer.cpp index 02643e63..2292bddf 100644 --- a/slamd/src/slamd/visualizer.cpp +++ b/slamd/src/slamd/visualizer.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include namespace slamd { @@ -22,15 +24,14 @@ Visualizer::Visualizer( void Visualizer::add_view( std::string name, - std::shared_ptr<_tree::Tree> tree, - slamd::flatb::ViewType type + std::shared_ptr tree ) { if (this->trees.find(tree->id) == this->trees.end()) { this->send_tree(tree); this->trees.insert({tree->id, tree}); } - auto view = _view::View::create(name, this->shared_from_this(), tree, type); + auto view = _view::View::create(name, this->shared_from_this(), tree); std::optional> to_remove; { @@ -52,9 +53,9 @@ void Visualizer::add_view( this->broadcast(view->get_add_view_message()); } -std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> +std::map<_id::GeometryID, std::shared_ptr> Visualizer::find_geometries() { - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> + std::map<_id::GeometryID, std::shared_ptr> current_geometries; for (auto [_, tree] : this->trees) { @@ -94,12 +95,6 @@ void Visualizer::delete_view( this->remove_view_tree(to_delete); } -void Visualizer::delete_canvas( - std::string name -) { - this->delete_view(name); -} - void Visualizer::remove_view_tree( std::shared_ptr<_view::View> view ) { @@ -132,15 +127,15 @@ void Visualizer::remove_view_tree( auto current_geometries = this->find_geometries(); - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> tree_geometries; + std::map<_id::GeometryID, std::shared_ptr> tree_geometries; tree->add_all_geometries(tree_geometries); - for (auto [geom_id, geom] : tree_geometries) { - auto it = current_geometries.find(geom_id); + for (auto [geometry_id, geometry] : tree_geometries) { + auto it = current_geometries.find(geometry_id); if (it == current_geometries.end()) { // we need to remove this geometry - this->broadcast(geom->get_remove_geometry_message()); + this->broadcast(geometry->get_remove_geometry_message()); } } @@ -151,10 +146,10 @@ void Visualizer::remove_view_tree( } void Visualizer::send_tree( - std::shared_ptr<_tree::Tree> tree + std::shared_ptr tree ) { // find all current geometries - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> + std::map<_id::GeometryID, std::shared_ptr> current_geometries; for (auto& [_, existing_tree] : this->trees) { @@ -162,13 +157,13 @@ void Visualizer::send_tree( } // get all geometries in tree - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> tree_geometries; + std::map<_id::GeometryID, std::shared_ptr> tree_geometries; tree->add_all_geometries(tree_geometries); // get all new geometries - for (auto& [gid, geom] : tree_geometries) { + for (auto& [gid, geometry] : tree_geometries) { if (current_geometries.find(gid) == current_geometries.end()) { - this->broadcast(geom->get_add_geometry_message()); + this->broadcast(geometry->get_add_geometry_message()); } } @@ -181,15 +176,7 @@ void Visualizer::add_scene( std::string name, std::shared_ptr scene ) { - this->add_view(name, scene, slamd::flatb::ViewType_SCENE); -} - -std::shared_ptr Visualizer::canvas( - std::string name -) { - auto canvas = slamd::canvas(); - this->add_canvas(name, canvas); - return canvas; + this->add_view(name, scene); } std::shared_ptr Visualizer::scene( @@ -200,29 +187,22 @@ std::shared_ptr Visualizer::scene( return scene; } -void Visualizer::add_canvas( - std::string name, - std::shared_ptr canvas -) { - this->add_view(name, canvas, slamd::flatb::ViewType_CANVAS); -} - flatbuffers::Offset>> Visualizer::get_geometries_fb( flatbuffers::FlatBufferBuilder& builder ) { - std::map<_id::GeometryID, std::shared_ptr<_geom::Geometry>> geom_map; + std::map<_id::GeometryID, std::shared_ptr> geometry_map; for (auto [_, tree] : this->trees) { - tree->add_all_geometries(geom_map); + tree->add_all_geometries(geometry_map); } - std::vector> geoms; + std::vector> geometries; - for (auto& [_, geom] : geom_map) { - geoms.push_back(geom->serialize(builder)); + for (auto& [_, geometry] : geometry_map) { + geometries.push_back(geometry->serialize(builder)); } - auto geoms_fb = builder.CreateVector(geoms); + auto geoms_fb = builder.CreateVector(geometries); return geoms_fb; } @@ -281,11 +261,11 @@ std::vector Visualizer::get_state() { } void Visualizer::server_job() { - asio::io_context io; - asio::ip::tcp::acceptor acceptor( - io, - asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port) - ); + asio::ip::tcp::acceptor acceptor(this->io_context); + acceptor.open(asio::ip::tcp::v4()); + acceptor.set_option(asio::socket_base::reuse_address(true)); + acceptor.bind(asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)); + acceptor.listen(); std::function accept_loop = [&]() { acceptor.async_accept([&](std::error_code ec, @@ -307,10 +287,10 @@ void Visualizer::server_job() { while (!this->stop_requested) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - io.stop(); // tell io to bounce + this->io_context.stop(); }); - io.run(); + this->io_context.run(); stop_watcher.join(); } @@ -329,7 +309,7 @@ void Visualizer::hang_forever() { } } // namespace _vis -VisualizerPtr visualizer( +std::shared_ptr<_vis::Visualizer> visualizer( std::string name, bool spawn, uint16_t port @@ -343,4 +323,4 @@ VisualizerPtr visualizer( return visualizer; } -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/window/camera.cpp b/slamd/src/window/camera.cpp index 19c09d01..d4a3c849 100644 --- a/slamd/src/window/camera.cpp +++ b/slamd/src/window/camera.cpp @@ -5,21 +5,22 @@ namespace slamd { Camera::Camera( double fov, - double near_plane, - double far_plane + double near_ratio, + double far_ratio ) : fov(fov), - near_plane(near_plane), - far_plane(far_plane) {} + near_ratio(near_ratio), + far_ratio(far_ratio) {} glm::mat4 Camera::get_projection_matrix( - double aspect_ratio + double aspect_ratio, + double view_distance ) const { return glm::perspective( glm::radians(this->fov), aspect_ratio, - near_plane, - far_plane + near_ratio * view_distance, + far_ratio * view_distance ); } diff --git a/slamd/src/window/camera_2d.cpp b/slamd/src/window/camera_2d.cpp deleted file mode 100644 index e1e9031b..00000000 --- a/slamd/src/window/camera_2d.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -namespace slamd { - -Camera2D::Camera2D( - const slamd::gmath::Rect2D& viewport -) - : viewport(viewport) {} - -glm::mat4 Camera2D::get_projection_matrix() const { - // Left, Right, Bottom, Top, Near, Far - return glm::ortho( - this->viewport.top_left.x, - this->viewport.bottom_right.x, - this->viewport.bottom_right.y, - this->viewport.top_left.y, - -100.0f, - 10000.0f - ); -} - -void Camera2D::set_viewport( - const slamd::gmath::Rect2D& viewport -) { - this->viewport = slamd::gmath::Rect2D(viewport); -} - -void Camera2D::translate_relative( - glm::vec2 relative_translation -) { - glm::vec2 viewport_size = this->viewport.size(); - - float smaller = glm::min(viewport_size.x, viewport_size.y); - - glm::vec2 translation = relative_translation * smaller; - - this->viewport = this->viewport.translate(translation); -} - -void Camera2D::zoom_relative( - float amount, - std::optional maybe_normalized_mouse_pos -) { - float zoom_factor = 1.0f - amount; - - glm::vec2 viewport_size = this->viewport.size(); - - glm::vec2 new_size = viewport_size * zoom_factor; - - glm::vec2 mouse_world_normalized = - maybe_normalized_mouse_pos.value_or(glm::vec2(0.5f, 0.5f)); - - glm::vec2 mouse_world = this->viewport.unnormalize(mouse_world_normalized); - - glm::vec2 current_top_left = this->viewport.top_left; - glm::vec2 new_top_left = - mouse_world * (1.0f - zoom_factor) + current_top_left * zoom_factor; - - this->viewport = - slamd::gmath::Rect2D(new_top_left, new_top_left + new_size); -} - -void Camera2D::translate_normalized( - glm::vec2 normalized_translation -) { - glm::vec2 unnormalized = this->viewport.size() * normalized_translation; - this->viewport = this->viewport.translate(unnormalized); -} - -glm::vec2 Camera2D::get_world_coords( - glm::vec2 normalized_mouse_pos -) { - return this->viewport.unnormalize(normalized_mouse_pos); -} - -} // namespace slamd \ No newline at end of file diff --git a/slamd/src/window/connection.cpp b/slamd/src/window/connection.cpp index 88ed68ec..9b34da58 100644 --- a/slamd/src/window/connection.cpp +++ b/slamd/src/window/connection.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -18,6 +19,15 @@ Connection::Connection( Connection::~Connection() { this->stop_requested = true; + + { + std::lock_guard lock(this->socket_mutex); + if (this->active_socket) { + std::error_code ec; + this->active_socket->close(ec); + } + } + if (this->job_thread.joinable()) { this->job_thread.join(); } @@ -63,8 +73,21 @@ void Connection::job() { } } + if (this->stop_requested) break; + auto& socket = socket_opt.value(); + { + std::lock_guard lock(this->socket_mutex); + this->active_socket = &socket; + } + + if (this->stop_requested) { + std::lock_guard lock(this->socket_mutex); + this->active_socket = nullptr; + break; + } + try { uint32_t len_net; asio::read( @@ -83,11 +106,17 @@ void Connection::job() { ); this->messages.push(std::move(message)); + glfwPostEmptyEvent(); } catch (const std::exception& e) { SPDLOG_ERROR("Error while reading from socket: {}", e.what()); + { + std::lock_guard lock(this->socket_mutex); + this->active_socket = nullptr; + } connected = false; - socket.close(); + std::error_code ec; + socket.close(ec); SPDLOG_INFO("Socket closed due to error."); } } diff --git a/slamd/src/window/geom/circles_2d.cpp b/slamd/src/window/geom/circles_2d.cpp deleted file mode 100644 index 879ee4a4..00000000 --- a/slamd/src/window/geom/circles_2d.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include -#include -#include -#include - -namespace slamd { - -namespace _geom { - -Circles2D::Circles2D( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii, - float thickness -) - : cached_bounds(Circles2D::make_bounds(positions, radii)), - colors(colors), - positions(positions), - radii(radii) { - this->circles_instanced = - Circles2D::make_mono_instanced(positions, colors, radii, thickness); -} - -std::shared_ptr Circles2D::deserialize( - const slamd::flatb::Circles2D* circles_fb -) { - return std::make_shared( - slamd::gmath::deserialize_vector(circles_fb->positions()), - slamd::gmath::deserialize_vector(circles_fb->colors()), - slamd::gmath::deserialize_vector(circles_fb->radii()), - circles_fb->thickness() - ); -} - -void Circles2D::render( - glm::mat4 model, - glm::mat4 view, - glm::mat4 projection -) { - this->handle_updates(); - this->circles_instanced->render(model, view, projection); -} - -std::optional Circles2D::bounds() { - return this->cached_bounds; -} - -std::unique_ptr Circles2D::make_mono_instanced( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii, - float thickness -) { - size_t sections = 20; - - std::vector vertices; - std::vector normals; - - float small_rad = 1.0f - thickness; - // float big_rad = 1.0f; - - float angle_step = 2.0f * _num::pi / static_cast(sections); - - for (size_t i = 0; i < sections; i++) { - float angle = i * angle_step; - - glm::vec3 vc(glm::cos(angle), glm::sin(angle), 0.0f); - - vertices.insert(vertices.end(), {vc * small_rad, vc}); - normals.insert(normals.end(), 2, glm::vec3(0.0f, 0.0f, 1.0f)); - } - - std::vector indices; - - for (size_t i = 0; i < sections; i++) { - uint32_t bottom_left = 2 * i; - uint32_t top_left = 2 * i + 1; - uint32_t bottom_right = (2 * (i + 1)) % vertices.size(); - uint32_t top_right = (2 * (i + 1) + 1) % vertices.size(); - - // clang-format off - indices.insert( - indices.end(), - { - top_left, bottom_right, bottom_left, - top_left, top_right, bottom_right - } - ); - //clang-format on - - } - - return std::make_unique( - vertices, - normals, - indices, - Circles2D::make_transforms( - positions, - radii - ), - colors - ); - -} - -void Circles2D::update_positions( - const std::vector& positions -) { - this->positions = positions; - this->pending_trans_update = true; -} - -void Circles2D::update_colors( - const std::vector& colors -) { - this->colors = colors; - this->pending_color_update = true; -} - -void Circles2D::update_radii( - const std::vector& radii -) { - this->radii = radii; - this->pending_trans_update = true; -} - -void Circles2D::handle_updates() { - if (this->pending_trans_update) { - this->circles_instanced->update_transforms( - Circles2D::make_transforms( - this->positions, - this->radii - ) - ); - this->pending_trans_update = false; - } - - if (this->pending_color_update) { - this->circles_instanced->update_colors(this->colors); - this->pending_color_update = false; - } -} - - -std::vector Circles2D::make_transforms( - std::vector positions, - std::vector radii -) { - std::vector transforms; - transforms.reserve(positions.size()); - - for (std::size_t i = 0; i < positions.size(); ++i) { - glm::vec2 pos = positions[i]; - float rad = radii[i]; - - transforms.push_back( - slamd::gmath::t3D(glm::vec3(pos, 0.0f)) * - slamd::gmath::scale_xy(glm::vec2(rad, rad)) - ); - } - - return transforms; -} - -slamd::gmath::AABB Circles2D::make_bounds( - const std::vector& positions, - const std::vector& radii -) { - glm::vec2 pos_0 = positions[0]; - float rad_0 = radii[0]; - - float min_x = pos_0.x - rad_0; - float max_x = pos_0.x + rad_0; - float min_y = pos_0.y - rad_0; - float max_y = pos_0.y + rad_0; - - for (std::size_t i = 1; i < positions.size(); ++i) { - glm::vec2 pos = positions[i]; - float rad = radii[i]; - - min_x = std::fmin(pos.x - rad, min_x); - max_x = std::fmax(pos.x + rad, max_x); - min_y = std::fmin(pos.y - rad, min_y); - max_y = std::fmax(pos.y + rad, max_y); - } - - return slamd::gmath::AABB( - glm::vec3(min_x, min_y, 0.0f), - glm::vec3(max_x, max_y, 0.0f) - ); -} - - -} // namespace _geom -} // namespace slamd \ No newline at end of file diff --git a/slamd/src/window/geom/deserialize.cpp b/slamd/src/window/geom/deserialize.cpp index a795c612..f42ed9e7 100644 --- a/slamd/src/window/geom/deserialize.cpp +++ b/slamd/src/window/geom/deserialize.cpp @@ -1,15 +1,13 @@ #include +#include #include #include -#include #include #include #include #include #include -#include #include -#include #include #include @@ -23,9 +21,6 @@ std::shared_ptr Geometry::deserialize( case (slamd::flatb::GeometryUnion_triad): { return Triad::deserialize(geom_fb->geometry_as_triad()); } - case (slamd::flatb::GeometryUnion_circles_2d): { - return Circles2D::deserialize(geom_fb->geometry_as_circles_2d()); - } case (slamd::flatb::GeometryUnion_camera_frustum): { return CameraFrustum::deserialize( geom_fb->geometry_as_camera_frustum() @@ -37,9 +32,6 @@ std::shared_ptr Geometry::deserialize( case (slamd::flatb::GeometryUnion_image): { return Image::deserialize(geom_fb->geometry_as_image()); } - case (slamd::flatb::GeometryUnion_points_2d): { - return Points2D::deserialize(geom_fb->geometry_as_points_2d()); - } case (slamd::flatb::GeometryUnion_box): { return Box::deserialize(geom_fb->geometry_as_box()); } @@ -52,15 +44,15 @@ std::shared_ptr Geometry::deserialize( case (slamd::flatb::GeometryUnion_poly_line): { return PolyLine::deserialize(geom_fb->geometry_as_poly_line()); } - case (slamd::flatb::GeometryUnion_poly_line_2d): { - return PolyLine2D::deserialize(geom_fb->geometry_as_poly_line_2d()); - } case (slamd::flatb::GeometryUnion_mesh): { return Mesh::deserialize(geom_fb->geometry_as_mesh()); } case (slamd::flatb::GeometryUnion_plane): { return Plane::deserialize(geom_fb->geometry_as_plane()); } + case (slamd::flatb::GeometryUnion_spheres): { + return Spheres::deserialize(geom_fb->geometry_as_spheres()); + } default: { throw std::runtime_error("Unknown geometry type"); } @@ -68,4 +60,4 @@ std::shared_ptr Geometry::deserialize( } } // namespace _geom -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/window/geom/mesh.cpp b/slamd/src/window/geom/mesh.cpp index 46028342..d582b0f0 100644 --- a/slamd/src/window/geom/mesh.cpp +++ b/slamd/src/window/geom/mesh.cpp @@ -211,15 +211,34 @@ void Mesh::render( shader.set_uniform("model", model); shader.set_uniform("view", view); shader.set_uniform("projection", projection); - shader.set_uniform("light_dir", _const::light_dir); shader.set_uniform("min_brightness", this->min_brightness); shader.set_uniform("alpha", this->mesh_data.alpha); + + bool needs_blend = this->mesh_data.alpha < 1.0f; + gl::GLboolean blend_was_enabled = gl::GL_FALSE; + gl::GLboolean depth_mask_was_enabled = gl::GL_TRUE; + if (needs_blend) { + gl::glGetBooleanv(gl::GL_BLEND, &blend_was_enabled); + gl::glGetBooleanv(gl::GL_DEPTH_WRITEMASK, &depth_mask_was_enabled); + gl::glEnable(gl::GL_BLEND); + gl::glBlendFunc(gl::GL_SRC_ALPHA, gl::GL_ONE_MINUS_SRC_ALPHA); + gl::glDepthMask(gl::GL_FALSE); + } + gl::glDrawElements( gl::GL_TRIANGLES, this->mesh_data.triangle_indices.size(), gl::GL_UNSIGNED_INT, 0 ); + + if (needs_blend) { + gl::glDepthMask(depth_mask_was_enabled); + if (blend_was_enabled == gl::GL_FALSE) { + gl::glDisable(gl::GL_BLEND); + } + } + gl::glBindVertexArray(0); }; diff --git a/slamd/src/window/geom/mono_instanced.cpp b/slamd/src/window/geom/mono_instanced.cpp index 01392366..12681b9d 100644 --- a/slamd/src/window/geom/mono_instanced.cpp +++ b/slamd/src/window/geom/mono_instanced.cpp @@ -228,7 +228,6 @@ void MonoInstanced::render( this->shader.set_uniform("u_model", model); this->shader.set_uniform("u_view", view); this->shader.set_uniform("u_projection", projection); - this->shader.set_uniform("u_light_dir", _const::light_dir); this->shader.set_uniform( "u_min_brightness", _const::default_min_brightness diff --git a/slamd/src/window/geom/point_cloud.cpp b/slamd/src/window/geom/point_cloud.cpp index 3d1be38b..b4fdd75e 100644 --- a/slamd/src/window/geom/point_cloud.cpp +++ b/slamd/src/window/geom/point_cloud.cpp @@ -63,8 +63,8 @@ std::tuple PointCloud::initialize_sphere_mesh() { mesh_indices, vertex_normals, 1.0f, - 10, - 10 + 3, + 2 ); // vertex buffer @@ -296,8 +296,6 @@ void PointCloud::render( this->shader.set_uniform("u_model", model); this->shader.set_uniform("u_view", view); this->shader.set_uniform("u_projection", projection); - this->shader.set_uniform("u_light_dir", _const::light_dir); - this->shader.set_uniform("u_min_brightness", this->min_brightness); gl::glDrawElementsInstanced( gl::GL_TRIANGLES, this->ball_vertex_count, diff --git a/slamd/src/window/geom/points_2d.cpp b/slamd/src/window/geom/points_2d.cpp deleted file mode 100644 index 69d3d1a5..00000000 --- a/slamd/src/window/geom/points_2d.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include -#include -#include - -namespace slamd { -namespace _geom { - -std::shared_ptr Points2D::deserialize( - const slamd::flatb::Points2D* points2d_fb -) { - return std::make_shared( - slamd::gmath::deserialize_vector(points2d_fb->positions()), - slamd::gmath::deserialize_vector(points2d_fb->colors()), - slamd::gmath::deserialize_vector(points2d_fb->radii()) - ); -} - -std::unique_ptr make_mesh( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii -) { - slamd::data::MeshData mesh_data; - - uint32_t curr_idx = 0; - size_t num_segments = 10; - - for (size_t i = 0; i < positions.size(); i++) { - // the center of the circle - auto& pos = positions[i]; - auto& col = colors[i]; - auto rad = radii[i]; - mesh_data - .add_vertex(glm::vec3(pos, 0.0f), col, glm::vec3(0.0f, 0.0f, 1.0f)); - - for (uint32_t i = 0; i < num_segments; i++) { - float segment_start = - _num::pi * 2.0f * static_cast(i) / num_segments; - - glm::vec3 start( - glm::cos(segment_start), - glm::sin(segment_start), - 0.0f - ); - - mesh_data.add_vertex( - (start * rad) + glm::vec3(pos, 0.0f), - col, - glm::vec3(0.0f, 0.0f, 1.0f) - ); - } - - for (uint32_t triangle_idx = 0; triangle_idx < num_segments; - triangle_idx++) { - uint32_t origin_idx = curr_idx; - uint32_t start_idx = 1 + triangle_idx + curr_idx; - uint32_t end_idx = - 1 + ((triangle_idx + 1) % num_segments) + curr_idx; - mesh_data.triangle_indices.insert( - mesh_data.triangle_indices.end(), - {origin_idx, start_idx, end_idx} - ); - } - - curr_idx += num_segments + 1; - } - - return std::make_unique(mesh_data, 1.0f); -} - -slamd::gmath::AABB make_bounds( - const std::vector& positions, - const std::vector& radii -) { - float min_x = positions[0].x - radii[0]; - float max_x = positions[0].x + radii[0]; - float min_y = positions[0].y - radii[0]; - float max_y = positions[0].y + radii[0]; - - for (size_t i = 0; i < positions.size(); i++) { - auto& pos = positions[i]; - auto rad = radii[i]; - min_x = std::fmin(pos.x - rad, min_x); - max_x = std::fmax(pos.x + rad, max_x); - min_y = std::fmin(pos.y - rad, min_y); - max_y = std::fmax(pos.y + rad, max_y); - } - - return slamd::gmath::AABB( - glm::vec3(min_x, min_y, 0.0f), - glm::vec3(max_x, max_y, 0.0f) - ); -} - -Points2D::Points2D( - const std::vector& positions, - const std::vector& colors, - const std::vector& radii -) - : cached_bounds(make_bounds(positions, radii)) { - this->mesh = make_mesh(positions, colors, radii); -} - -void Points2D::render( - glm::mat4 model, - glm::mat4 view, - glm::mat4 projection -) { - this->mesh->render(model, view, projection); -} - -std::optional Points2D::bounds() { - return this->cached_bounds; -} - -} // namespace _geom -} // namespace slamd \ No newline at end of file diff --git a/slamd/src/window/geom/poly_line_2d.cpp b/slamd/src/window/geom/poly_line_2d.cpp deleted file mode 100644 index da56ad99..00000000 --- a/slamd/src/window/geom/poly_line_2d.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include -#include - -namespace slamd { -namespace _geom { - -std::shared_ptr PolyLine2D::deserialize( - const slamd::flatb::PolyLine2D* poly_line_fb -) { - return std::make_shared( - slamd::gmath::deserialize_vector(poly_line_fb->points()), - slamd::gmath::deserialize(poly_line_fb->color()), - poly_line_fb->thickness() - ); -} - -std::unique_ptr PolyLine2D::make_mesh( - const std::vector& points, - const glm::vec3& color, - float thickness -) { - std::vector vertices; - std::vector indices; - - if (points.size() < 2) { - throw std::invalid_argument( - "cannot create poly line with less than 2 points" - ); - } - - const float half_thickness = thickness * 0.5f; - - std::vector left_side, right_side; - - for (size_t i = 0; i < points.size(); ++i) { - glm::vec2 dir; - if (i == 0) { - dir = glm::normalize(points[1] - points[0]); - } else if (i == points.size() - 1) { - dir = glm::normalize(points[i] - points[i - 1]); - } else { - glm::vec2 dir1 = glm::normalize(points[i] - points[i - 1]); - glm::vec2 dir2 = glm::normalize(points[i + 1] - points[i]); - dir = glm::normalize(dir1 + dir2); // smooth corner - } - - glm::vec2 normal = glm::vec2(-dir.y, dir.x); - glm::vec3 offset = glm::vec3(normal * half_thickness, 0.0f); - - left_side.push_back(glm::vec3(points[i], 0.0f) - offset); - right_side.push_back(glm::vec3(points[i], 0.0f) + offset); - } - - for (size_t i = 0; i < points.size(); ++i) { - vertices.push_back(left_side[i]); - vertices.push_back(right_side[i]); - } - - for (uint32_t i = 0; i < points.size() - 1; ++i) { - uint32_t i0 = i * 2; - uint32_t i1 = i * 2 + 1; - uint32_t i2 = (i + 1) * 2; - uint32_t i3 = (i + 1) * 2 + 1; - - // Triangle 1 - indices.push_back(i0); - indices.push_back(i2); - indices.push_back(i1); - - // Triangle 2 - indices.push_back(i1); - indices.push_back(i2); - indices.push_back(i3); - } - - auto mesh_data = slamd::data::MeshDataBuilder() - .set_positions(vertices) - .set_colors(color) - .set_normals(glm::vec3(0.0, 0.0, 1.0)) - .set_indices(indices) - .build(); - - return std::make_unique(mesh_data); -} - -PolyLine2D::PolyLine2D( - const std::vector& points, - const glm::vec3& color, - float thickness -) - : cached_bounds(slamd::gmath::AABB::around_points(points)) { - this->line_mesh = PolyLine2D::make_mesh(points, color, thickness); -} - -void PolyLine2D::render( - glm::mat4 model, - glm::mat4 view, - glm::mat4 projection -) { - this->line_mesh->render(model, view, projection); -} - -std::optional PolyLine2D::bounds() { - return this->cached_bounds; -} - -} // namespace _geom -} // namespace slamd diff --git a/slamd/src/window/geom/spheres.cpp b/slamd/src/window/geom/spheres.cpp new file mode 100644 index 00000000..b13bfaa0 --- /dev/null +++ b/slamd/src/window/geom/spheres.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include + +namespace slamd { +namespace _geom { + +Spheres::Spheres( + const std::vector& positions, + const std::vector& colors, + const std::vector& radii, + float min_brightness +) + : positions(positions), + colors(colors), + radii(radii), + min_brightness(min_brightness) { + this->mono = Spheres::make_mono_instanced(positions, colors, radii); +} + +std::shared_ptr Spheres::deserialize( + const slamd::flatb::Spheres* spheres_fb +) { + return std::make_shared( + gmath::deserialize_vector(spheres_fb->positions()), + gmath::deserialize_vector(spheres_fb->colors()), + gmath::deserialize_vector(spheres_fb->radii()), + spheres_fb->min_brightness() + ); +} + +void Spheres::render( + glm::mat4 model, + glm::mat4 view, + glm::mat4 projection +) { + this->handle_updates(); + this->mono->render(model, view, projection); +} + +std::unique_ptr Spheres::make_mono_instanced( + const std::vector& positions, + const std::vector& colors, + const std::vector& radii +) { + std::vector vertices; + std::vector normals; + std::vector indices; + + slamd::_utils::generate_sphere( + vertices, + indices, + normals, + 1.0f, + 16, + 16 + ); + + return std::make_unique( + vertices, + normals, + indices, + Spheres::make_transforms(positions, radii), + colors + ); +} + +std::vector Spheres::make_transforms( + const std::vector& positions, + const std::vector& radii +) { + std::vector transforms; + transforms.reserve(positions.size()); + + for (std::size_t i = 0; i < positions.size(); ++i) { + transforms.push_back( + slamd::gmath::t3D(positions[i]) * + slamd::gmath::scale_all(radii[i]) + ); + } + + return transforms; +} + +void Spheres::update_positions( + const std::vector& positions +) { + this->positions = positions; + this->pending_trans_update = true; +} + +void Spheres::update_colors( + const std::vector& colors +) { + this->colors = colors; + this->pending_color_update = true; +} + +void Spheres::update_radii( + const std::vector& radii +) { + this->radii = radii; + this->pending_trans_update = true; +} + +void Spheres::handle_updates() { + if (this->pending_trans_update) { + this->mono->update_transforms( + Spheres::make_transforms(this->positions, this->radii) + ); + this->pending_trans_update = false; + } + + if (this->pending_color_update) { + this->mono->update_colors(this->colors); + this->pending_color_update = false; + } +} + +} // namespace _geom +} // namespace slamd diff --git a/slamd/src/window/main.cpp b/slamd/src/window/main.cpp index be78cf30..e89c37a9 100644 --- a/slamd/src/window/main.cpp +++ b/slamd/src/window/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -12,8 +13,8 @@ int main( spdlog::set_level(spdlog::level::debug); cxxopts::Options options( - "SlamDunk Window", - "The viewer for the SlamDunk library" + "slamd Window", + "The viewer for the slamd library" ); // clang-format off @@ -50,4 +51,6 @@ int main( state_manager.try_connect(ip, port); slamd::run_window(state_manager); + + std::exit(0); } \ No newline at end of file diff --git a/slamd/src/window/run_window.cpp b/slamd/src/window/run_window.cpp index 101e9029..062b5581 100644 --- a/slamd/src/window/run_window.cpp +++ b/slamd/src/window/run_window.cpp @@ -1,6 +1,3 @@ -/** - * All of the tree widget stuff is vide-coded! - */ #include #include #include @@ -20,349 +17,12 @@ void framebuffer_size_callback( gl::glViewport(0, 0, width, height); } -// Vibe coded -inline float compute_visible_tree_content_width( - Node* root, - const std::unordered_map& open_map -) { - const ImGuiStyle& style = ImGui::GetStyle(); - const float indent = style.IndentSpacing; - const float row_h = ImGui::GetFrameHeight(); - const float chk_pad = style.ItemInnerSpacing.x; - const float label_pad = ImGui::GetTreeNodeToLabelSpacing(); - const float badge_w = 12.0f; - - float max_w = 0.0f; - - std::function walk = [&](Node* n, int depth) { - float left = depth * indent + row_h + chk_pad + - (n->has_children_cached ? row_h : 0.0f) + label_pad; - - float total = left + n->last_label_w + badge_w; - if (total > max_w) { - max_w = total; - } - - auto it = open_map.find(n); - bool is_open = (it != open_map.end()) ? it->second : false; - if (n->has_children_cached && is_open) { - for (auto& kv : n->children) { - walk(kv.second.get(), depth + 1); - } - } - }; - - walk(root, 0); - return max_w; -} - -// Vibe coded -inline void tree_menu( - View* view -) { - Node* root = view->tree->root.get(); - - const float min_field_w = 250.0f; - const float text_w = ImGui::CalcTextSize(view->filter_buf).x; - const float pad_x = ImGui::GetStyle().FramePadding.x; - - const float desired_field_w = text_w + 2.0f * pad_x + 12.0f; - const float field_w = - (desired_field_w < min_field_w) ? min_field_w : desired_field_w; - - std::optional filter_error_text; - std::optional filter_path; - std::string filter_string(view->filter_buf); - - if (filter_string.size() > 0) { - try { - filter_path = TreePath(filter_string); - } catch (const std::invalid_argument& e) { - filter_error_text = e.what(); - } - } - - view->tree->mark_nodes_matching_glob(filter_path); - - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted("Filter:"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(field_w); - - if (filter_error_text.has_value()) { - ImGui::PushStyleColor( - ImGuiCol_FrameBg, - ImVec4(0.35f, 0.10f, 0.10f, 1.0f) - ); - ImGui::PushStyleColor( - ImGuiCol_FrameBgHovered, - ImVec4(0.45f, 0.12f, 0.12f, 1.0f) - ); - ImGui::PushStyleColor( - ImGuiCol_FrameBgActive, - ImVec4(0.50f, 0.13f, 0.13f, 1.0f) - ); - ImGui::PushStyleColor( - ImGuiCol_Border, - ImVec4(0.90f, 0.20f, 0.20f, 1.0f) - ); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - } - if (ImGui::InputText( - "##filter", - view->filter_buf, - IM_ARRAYSIZE(view->filter_buf) - )) { - view->mark_dirty(); - } - if (filter_error_text.has_value()) { - ImGui::PopStyleVar(); - ImGui::PopStyleColor(4); - - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s", filter_error_text->c_str()); - } - - ImDrawList* dl = ImGui::GetWindowDrawList(); - - ImVec2 rmin = ImGui::GetItemRectMin(); - ImVec2 rmax = ImGui::GetItemRectMax(); - const ImGuiStyle& style = ImGui::GetStyle(); - - const char* bang = "!"; - ImVec2 bang_sz = ImGui::CalcTextSize(bang); - - float x = rmax.x - style.FramePadding.x - bang_sz.x; - float y = rmin.y + ((rmax.y - rmin.y) - bang_sz.y) * 0.5f; - - dl->AddText( - ImVec2(x, y), - ImGui::GetColorU32(ImVec4(0.95f, 0.25f, 0.25f, 1.0f)), - bang - ); - } - - ImGui::Separator(); - - std::function draw_node = - [&](Node* n, - std::string label, - int depth, - bool parent_dimmed, - TreePath& full_path) { - ImGui::PushID(n); - - if (filter_path.has_value()) { - n->glob_matches = full_path.matches_glob(filter_path.value()); - } else { - n->glob_matches = std::nullopt; - } - - const bool has_children = !n->children.empty(); - const ImGuiTreeNodeFlags base_flags = - ImGuiTreeNodeFlags_SpanAvailWidth | - ImGuiTreeNodeFlags_SpanFullWidth | - ImGuiTreeNodeFlags_FramePadding | - (has_children ? 0 - : ImGuiTreeNodeFlags_Leaf | - ImGuiTreeNodeFlags_NoTreePushOnOpen); - - const bool dimmed = parent_dimmed || !n->checked; - bool dimmed_here = !parent_dimmed && !n->checked; - if (dimmed_here) { - float base_alpha = ImGui::GetStyle().Alpha; - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, base_alpha * 0.45f); - } - - if (ImGui::Checkbox("##visible", &n->checked)) { - view->mark_dirty(); - } - ImGui::SameLine(0.0f, 4.0f); - - bool open = false; - n->last_label_w = ImGui::CalcTextSize(label.c_str()).x; - n->has_children_cached = has_children; - - if (has_children) { - open = ImGui::TreeNodeEx(label.c_str(), base_flags); - view->tree_open[n] = open; - } else { - view->tree_open[n] = false; - ImGui::TreeNodeEx(label.c_str(), base_flags); - } - - if (n->glob_matches.has_value()) { - const bool match = n->glob_matches.value_or(false); - - ImVec2 item_min = ImGui::GetItemRectMin(); - ImVec2 item_max = ImGui::GetItemRectMax(); - - ImVec2 win_pos = ImGui::GetWindowPos(); - ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); - float right_edge_x = - win_pos.x + cr_max.x - ImGui::GetStyle().ItemInnerSpacing.x; - - float radius = 4.0f; - float cx = right_edge_x - radius; - float cy = 0.5f * (item_min.y + item_max.y); - - ImU32 col_fill = - ImGui::GetColorU32(ImVec4(0.30f, 0.85f, 0.40f, 1.0f)); - ImU32 col_line = ImGui::GetColorU32( - ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled) - ); - - ImDrawList* dl = ImGui::GetWindowDrawList(); - if (match) { - dl->AddCircleFilled(ImVec2(cx, cy), radius, col_fill); - } else { - dl->AddCircle(ImVec2(cx, cy), radius, col_line, 12, 1.5f); - } - } - - if (open) { - for (const auto& c : n->children) { - full_path.components.push_back(c.first); - draw_node( - c.second.get(), - c.first, - depth + 1, - dimmed, - full_path - ); - full_path.components.pop_back(); - } - ImGui::TreePop(); - } - if (dimmed_here) { - ImGui::PopStyleVar(); - } - ImGui::PopID(); - }; - - TreePath pth("/"); - draw_node(root, "/", 0, false, pth); -} - -// Vibe coded -inline void draw_tree_overlay( - View* view, - const char* child_id = "##scene_tree_overlay_child", - const char* header = "Tree", - float margin = 8.0f, - float min_width = 220.0f, - float min_height = 80.0f -) { - ImVec2 win_pos = ImGui::GetWindowPos(); - ImVec2 cr_min = ImGui::GetWindowContentRegionMin(); - ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); - ImVec2 tl(win_pos.x + cr_min.x, win_pos.y + cr_min.y); - ImVec2 br(win_pos.x + cr_max.x, win_pos.y + cr_max.y); - - ImVec2 pos = {tl.x + margin, tl.y + margin}; - float max_w = std::max(0.0f, (br.x - tl.x) - 2.0f * margin); - float max_h = std::max(0.0f, (br.y - tl.y) - 2.0f * margin); - - const ImGuiStyle& style = ImGui::GetStyle(); - const float pad_x = style.WindowPadding.x; - const float border_px = 1.0f; - - const char* filter_lbl = "Filter:"; - float filter_lbl_w = ImGui::CalcTextSize(filter_lbl).x; - float text_w = ImGui::CalcTextSize(view->filter_buf).x; - float field_w = - std::max(250.0f, text_w + 2.0f * style.FramePadding.x + 12.0f); - float filter_row_w = filter_lbl_w + style.ItemSpacing.x + field_w; - - float tree_w = compute_visible_tree_content_width( - view->tree->root.get(), - view->tree_open - ); - - float header_w = 0.0f; - if (header && *header) { - header_w = ImGui::CalcTextSize(header).x; - } - - float need_content_w = std::max({filter_row_w, tree_w, header_w}); - - float predicted_child_w = need_content_w + pad_x * 2.0f + border_px * 2.0f; - - float prev_w = - (view->tree_overlay_w > 0.0f) ? view->tree_overlay_w : min_width; - float target_w = std::clamp(predicted_child_w, min_width, max_w); - - auto snap = [](float v) { - return std::floor(v + 0.5f); - }; - const float hysteresis_px = 2.0f; - if (std::fabs(target_w - prev_w) <= hysteresis_px) { - target_w = prev_w; - } - target_w = snap(target_w); - - float prev_h = - (view->tree_overlay_h > 0.0f) ? view->tree_overlay_h : min_height; - float target_h = std::clamp(prev_h, min_height, max_h); - - ImVec2 prev_cursor = ImGui::GetCursorScreenPos(); - ImGui::SetCursorScreenPos(pos); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0.55f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); - - ImGuiWindowFlags child_flags = - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar; - - ImGui::BeginChild(child_id, ImVec2(target_w, target_h), true, child_flags); - - ImGui::BeginGroup(); - if (header && *header) { - ImGui::TextUnformatted(header); - ImGui::Separator(); - } - tree_menu(view); - ImGui::EndGroup(); - - ImVec2 content_min = ImGui::GetItemRectMin(); - ImVec2 content_max = ImGui::GetItemRectMax(); - ImVec2 content_sz = - ImVec2(content_max.x - content_min.x, content_max.y - content_min.y); - - ImGui::EndChild(); - - float measured_child_w = - content_sz.x + style.WindowPadding.x * 2.0f + border_px * 2.0f; - float measured_child_h = - content_sz.y + style.WindowPadding.y * 2.0f + border_px * 2.0f; - - auto apply_axis = [&](float want, float prev, float mn, float mx) { - want = std::clamp(want, mn, mx); - if (std::fabs(want - prev) <= hysteresis_px) { - want = prev; - } - return snap(want); - }; - - view->tree_overlay_w = apply_axis( - std::max(measured_child_w, predicted_child_w), - target_w, - min_width, - max_w - ); - view->tree_overlay_h = - apply_axis(measured_child_h, target_h, min_height, max_h); - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - ImGui::SetCursorScreenPos(prev_cursor); -} - void run_window( StateManager& state_manager ) { auto window = glutils::make_window("Slam Dunk", 1000, 1000); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + glfwSwapInterval(1); // Enable VSync for smooth frame pacing IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -389,7 +49,6 @@ void run_window( ImGui::NewFrame(); if (!state_manager.loaded) { - // render a window with a message ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(400, 100), ImGuiCond_Always); ImGui::Begin("Waiting for Connection", nullptr); @@ -434,7 +93,9 @@ void run_window( ); scene->render_to_imgui(); - draw_tree_overlay(scene.get()); + if (scene->tree_overlay.render(scene->tree)) { + scene->mark_dirty(); + } ImGui::End(); ImGui::PopStyleVar(); @@ -449,7 +110,7 @@ void run_window( ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); - glfwPollEvents(); + glfwWaitEventsTimeout(0.1); } SPDLOG_INFO("Window closed!"); @@ -460,10 +121,14 @@ void run_window( ImGui::SaveIniSettingsToDisk(layout_path.string().c_str()); } + state_manager.views.clear(); + state_manager.trees.clear(); + state_manager.geometries.clear(); + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwTerminate(); } -} // namespace slamd \ No newline at end of file +} // namespace slamd diff --git a/slamd/src/window/state_manager.cpp b/slamd/src/window/state_manager.cpp index d10b4732..6c5eff5d 100644 --- a/slamd/src/window/state_manager.cpp +++ b/slamd/src/window/state_manager.cpp @@ -5,13 +5,11 @@ #include #include #include -#include +#include #include #include #include -#include #include -#include namespace slamd { @@ -61,7 +59,7 @@ void StateManager::handle_initial_state( auto tree = this->trees.at(tree_id); - auto view = View::deserialize(view_fb, tree); + auto view = std::make_unique(tree); this->views.insert({view_name, std::move(view)}); } @@ -154,7 +152,7 @@ void StateManager::handle_add_view( auto tree = this->trees.at(tree_id); - auto view = View::deserialize(view_fb, tree); + auto view = std::make_unique(tree); auto view_name = view_fb->name()->str(); this->views.insert({view_name, std::move(view)}); @@ -187,62 +185,62 @@ void StateManager::handle_update_mesh_normals( geom->update_normals(normals); } -void StateManager::handle_update_circles2d_positions( - const slamd::flatb::UpdateCircles2DPositions* update_fb +void StateManager::handle_update_point_cloud_positions( + const slamd::flatb::UpdatePointCloudPositions* update_fb ) { auto id = _id::GeometryID(update_fb->object_id()); auto geom = - std::dynamic_pointer_cast<_geom::Circles2D>(this->geometries.at(id)); + std::dynamic_pointer_cast<_geom::PointCloud>(this->geometries.at(id)); auto data = gmath::deserialize_vector(update_fb->positions()); geom->update_positions(data); } -void StateManager::handle_update_circles2d_colors( - const slamd::flatb::UpdateCircles2DColors* update_fb +void StateManager::handle_update_point_cloud_colors( + const slamd::flatb::UpdatePointCloudColors* update_fb ) { auto id = _id::GeometryID(update_fb->object_id()); auto geom = - std::dynamic_pointer_cast<_geom::Circles2D>(this->geometries.at(id)); + std::dynamic_pointer_cast<_geom::PointCloud>(this->geometries.at(id)); auto data = gmath::deserialize_vector(update_fb->colors()); geom->update_colors(data); } -void StateManager::handle_update_circles2d_radii( - const slamd::flatb::UpdateCircles2DRadii* update_fb +void StateManager::handle_update_point_cloud_radii( + const slamd::flatb::UpdatePointCloudRadii* update_fb ) { auto id = _id::GeometryID(update_fb->object_id()); auto geom = - std::dynamic_pointer_cast<_geom::Circles2D>(this->geometries.at(id)); + std::dynamic_pointer_cast<_geom::PointCloud>(this->geometries.at(id)); auto data = gmath::deserialize_vector(update_fb->radii()); geom->update_radii(data); } -void StateManager::handle_update_point_cloud_positions( - const slamd::flatb::UpdatePointCloudPositions* update_fb +void StateManager::handle_update_spheres_positions( + const slamd::flatb::UpdateSpheresPositions* update_fb ) { auto id = _id::GeometryID(update_fb->object_id()); auto geom = - std::dynamic_pointer_cast<_geom::PointCloud>(this->geometries.at(id)); + std::dynamic_pointer_cast<_geom::Spheres>(this->geometries.at(id)); auto data = gmath::deserialize_vector(update_fb->positions()); geom->update_positions(data); } -void StateManager::handle_update_point_cloud_colors( - const slamd::flatb::UpdatePointCloudColors* update_fb +void StateManager::handle_update_spheres_colors( + const slamd::flatb::UpdateSpheresColors* update_fb ) { auto id = _id::GeometryID(update_fb->object_id()); auto geom = - std::dynamic_pointer_cast<_geom::PointCloud>(this->geometries.at(id)); + std::dynamic_pointer_cast<_geom::Spheres>(this->geometries.at(id)); auto data = gmath::deserialize_vector(update_fb->colors()); geom->update_colors(data); } -void StateManager::handle_update_point_cloud_radii( - const slamd::flatb::UpdatePointCloudRadii* update_fb +void StateManager::handle_update_spheres_radii( + const slamd::flatb::UpdateSpheresRadii* update_fb ) { auto id = _id::GeometryID(update_fb->object_id()); auto geom = - std::dynamic_pointer_cast<_geom::PointCloud>(this->geometries.at(id)); + std::dynamic_pointer_cast<_geom::Spheres>(this->geometries.at(id)); auto data = gmath::deserialize_vector(update_fb->radii()); geom->update_radii(data); } @@ -331,24 +329,6 @@ bool StateManager::apply_updates() { ); break; } - case (slamd::flatb::MessageUnion_update_circles2d_colors): { - this->handle_update_circles2d_colors( - message_fb->message_as_update_circles2d_colors() - ); - break; - } - case (slamd::flatb::MessageUnion_update_circles2d_positions): { - this->handle_update_circles2d_positions( - message_fb->message_as_update_circles2d_positions() - ); - break; - } - case (slamd::flatb::MessageUnion_update_circles2d_radii): { - this->handle_update_circles2d_radii( - message_fb->message_as_update_circles2d_radii() - ); - break; - } case (slamd::flatb::MessageUnion_update_point_cloud_colors): { this->handle_update_point_cloud_colors( message_fb->message_as_update_point_cloud_colors() @@ -367,6 +347,24 @@ bool StateManager::apply_updates() { ); break; } + case (slamd::flatb::MessageUnion_update_spheres_positions): { + this->handle_update_spheres_positions( + message_fb->message_as_update_spheres_positions() + ); + break; + } + case (slamd::flatb::MessageUnion_update_spheres_colors): { + this->handle_update_spheres_colors( + message_fb->message_as_update_spheres_colors() + ); + break; + } + case (slamd::flatb::MessageUnion_update_spheres_radii): { + this->handle_update_spheres_radii( + message_fb->message_as_update_spheres_radii() + ); + break; + } default: { SPDLOG_ERROR( diff --git a/slamd/src/window/tree_overlay.cpp b/slamd/src/window/tree_overlay.cpp new file mode 100644 index 00000000..4a7851e6 --- /dev/null +++ b/slamd/src/window/tree_overlay.cpp @@ -0,0 +1,354 @@ +#include +#include +#include + +namespace slamd { + +TreeOverlay::TreeOverlay() {} + +bool TreeOverlay::render( + std::shared_ptr tree +) { + dirty = false; + current_tree = tree; + Node* root = tree->root.get(); + + // Parse the filter + std::string filter_string(filter_buf); + filter_path = std::nullopt; + filter_error = std::nullopt; + + if (!filter_string.empty()) { + try { + filter_path = TreePath(filter_string); + } catch (const std::invalid_argument& e) { + filter_error = e.what(); + } + } + tree->mark_nodes_matching_glob(filter_path); + + // Position in the upper-left of the current ImGui window's content area. + const float margin = 8.0f; + const float min_w = 220.0f; + const float min_h = 80.0f; + + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 cr_min = ImGui::GetWindowContentRegionMin(); + ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); + + float avail_w = cr_max.x - cr_min.x - 2.0f * margin; + float avail_h = cr_max.y - cr_min.y - 2.0f * margin; + + // Compute width from actual content (filter row + tree nodes). + const ImGuiStyle& style = ImGui::GetStyle(); + float filter_row_w = compute_filter_row_width(); + float tree_w = compute_tree_content_width(root); + float header_w = ImGui::CalcTextSize("Tree").x; + float content_w = std::max({filter_row_w, tree_w, header_w}); + float child_w = content_w + style.WindowPadding.x * 2.0f + 2.0f; + + // Compute height from actual content (header + filter + tree rows). + float row_h = ImGui::GetFrameHeightWithSpacing(); + float separator_h = style.ItemSpacing.y + 1.0f; + // Header row + separator + filter row + separator + tree rows + float tree_h = compute_tree_content_height(root); + float content_h = row_h + separator_h + row_h + separator_h + tree_h; + float child_h = content_h + style.WindowPadding.y * 2.0f + 2.0f; + + float w = std::clamp(child_w, min_w, avail_w); + float h = std::clamp(child_h, min_h, avail_h); + + ImVec2 overlay_pos( + win_pos.x + cr_min.x + margin, + win_pos.y + cr_min.y + margin + ); + + // Save and restore the parent window's cursor position so the overlay + // doesn't affect layout. + ImVec2 saved_cursor = ImGui::GetCursorScreenPos(); + + // Use a unique ID per parent window to keep overlays independent. + ImGui::PushID("##tree_overlay"); + + ImGui::SetCursorScreenPos(overlay_pos); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0.55f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); + + ImGui::BeginChild( + "##tree_child", + ImVec2(w, h), + true, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings + ); + + // Track whether the overlay child window is hovered. If it is NOT hovered, + // we want mouse events to pass through to the view underneath. We achieve + // this by not consuming any input when the mouse is outside. + // + // ImGui::IsWindowHovered with RootAndChildWindows catches hovers on nested + // children (like the scrolling region inside). + bool overlay_hovered = ImGui::IsWindowHovered( + ImGuiHoveredFlags_RootAndChildWindows | + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem + ); + + ImGui::BeginGroup(); + + ImGui::TextUnformatted("Tree"); + ImGui::Separator(); + + draw_filter_field(); + ImGui::Separator(); + + draw_tree_nodes(root); + + ImGui::EndGroup(); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::PopID(); + + ImGui::SetCursorScreenPos(saved_cursor); + + // If the overlay is NOT hovered, tell ImGui to clear any hover on this + // child so the parent window (the view) can receive mouse events. + if (!overlay_hovered) { + ImGuiIO& io = ImGui::GetIO(); + (void)io; + } + + current_tree = nullptr; + return dirty; +} + +void TreeOverlay::draw_filter_field() { + const ImGuiStyle& style = ImGui::GetStyle(); + + float text_w = ImGui::CalcTextSize(filter_buf).x; + float field_w = + std::max(250.0f, text_w + 2.0f * style.FramePadding.x + 12.0f); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Filter:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(field_w); + + bool has_error = filter_error.has_value(); + + if (has_error) { + ImGui::PushStyleColor( + ImGuiCol_FrameBg, + ImVec4(0.35f, 0.10f, 0.10f, 1.0f) + ); + ImGui::PushStyleColor( + ImGuiCol_FrameBgHovered, + ImVec4(0.45f, 0.12f, 0.12f, 1.0f) + ); + ImGui::PushStyleColor( + ImGuiCol_FrameBgActive, + ImVec4(0.50f, 0.13f, 0.13f, 1.0f) + ); + ImGui::PushStyleColor( + ImGuiCol_Border, + ImVec4(0.90f, 0.20f, 0.20f, 1.0f) + ); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + } + + if (ImGui::InputText("##filter", filter_buf, IM_ARRAYSIZE(filter_buf))) { + dirty = true; + } + + if (has_error) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(4); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", filter_error->c_str()); + } + + // Draw error indicator "!" inside the text field + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImVec2 rmin = ImGui::GetItemRectMin(); + ImVec2 rmax = ImGui::GetItemRectMax(); + + const char* bang = "!"; + ImVec2 bang_sz = ImGui::CalcTextSize(bang); + float x = rmax.x - style.FramePadding.x - bang_sz.x; + float y = rmin.y + ((rmax.y - rmin.y) - bang_sz.y) * 0.5f; + + dl->AddText( + ImVec2(x, y), + ImGui::GetColorU32(ImVec4(0.95f, 0.25f, 0.25f, 1.0f)), + bang + ); + } +} + +void TreeOverlay::draw_tree_nodes( + Node* root +) { + TreePath pth("/"); + draw_node(root, "/", 0, false, pth); +} + +void TreeOverlay::draw_node( + Node* node, + const std::string& label, + int depth, + bool parent_dimmed, + TreePath& full_path +) { + (void)depth; + ImGui::PushID(node); + + if (filter_path.has_value()) { + node->glob_matches = full_path.matches_glob(filter_path.value()); + } else { + node->glob_matches = std::nullopt; + } + + const bool has_children = !node->children.empty(); + const ImGuiTreeNodeFlags base_flags = + ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | + ImGuiTreeNodeFlags_FramePadding | + (has_children + ? ImGuiTreeNodeFlags(0) + : ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen); + + const bool dimmed = parent_dimmed || !node->checked; + bool dimmed_here = !parent_dimmed && !node->checked; + if (dimmed_here) { + float base_alpha = ImGui::GetStyle().Alpha; + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, base_alpha * 0.45f); + } + + if (ImGui::Checkbox("##visible", &node->checked)) { + dirty = true; + } + ImGui::SameLine(0.0f, 4.0f); + + bool open = false; + if (has_children) { + open = ImGui::TreeNodeEx(label.c_str(), base_flags); + tree_open[node] = open; + } else { + tree_open[node] = false; + ImGui::TreeNodeEx(label.c_str(), base_flags); + } + + // Draw glob match indicator + if (node->glob_matches.has_value()) { + const bool match = node->glob_matches.value_or(false); + + ImVec2 item_min = ImGui::GetItemRectMin(); + ImVec2 item_max = ImGui::GetItemRectMax(); + + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); + float right_edge_x = + win_pos.x + cr_max.x - ImGui::GetStyle().ItemInnerSpacing.x; + + float radius = 4.0f; + float cx = right_edge_x - radius; + float cy = 0.5f * (item_min.y + item_max.y); + + ImU32 col_fill = + ImGui::GetColorU32(ImVec4(0.30f, 0.85f, 0.40f, 1.0f)); + ImU32 col_line = ImGui::GetColorU32( + ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled) + ); + + ImDrawList* dl = ImGui::GetWindowDrawList(); + if (match) { + dl->AddCircleFilled(ImVec2(cx, cy), radius, col_fill); + } else { + dl->AddCircle(ImVec2(cx, cy), radius, col_line, 12, 1.5f); + } + } + + if (open) { + for (const auto& c : node->children) { + full_path.components.push_back(c.first); + draw_node(c.second.get(), c.first, depth + 1, dimmed, full_path); + full_path.components.pop_back(); + } + ImGui::TreePop(); + } + + if (dimmed_here) { + ImGui::PopStyleVar(); + } + ImGui::PopID(); +} + +float TreeOverlay::compute_tree_content_height( + Node* root +) const { + float row_h = ImGui::GetFrameHeightWithSpacing(); + + int count = 0; + std::function walk = [&](Node* n) { + count++; + auto it = tree_open.find(n); + bool is_open = (it != tree_open.end()) ? it->second : false; + if (!n->children.empty() && is_open) { + for (auto& kv : n->children) { + walk(kv.second.get()); + } + } + }; + + walk(root); + return count * row_h; +} + +float TreeOverlay::compute_filter_row_width() const { + const ImGuiStyle& style = ImGui::GetStyle(); + float label_w = ImGui::CalcTextSize("Filter:").x; + float text_w = ImGui::CalcTextSize(filter_buf).x; + float field_w = + std::max(250.0f, text_w + 2.0f * style.FramePadding.x + 12.0f); + return label_w + style.ItemSpacing.x + field_w; +} + +float TreeOverlay::compute_tree_content_width( + Node* root +) const { + const ImGuiStyle& style = ImGui::GetStyle(); + const float indent = style.IndentSpacing; + const float row_h = ImGui::GetFrameHeight(); + const float chk_pad = style.ItemInnerSpacing.x; + const float label_pad = ImGui::GetTreeNodeToLabelSpacing(); + const float badge_w = 12.0f; + + float max_w = 0.0f; + + std::function walk = + [&](Node* n, const char* label, int depth) { + bool has_children = !n->children.empty(); + float left = depth * indent + row_h + chk_pad + + (has_children ? row_h : 0.0f) + label_pad; + + float total = left + ImGui::CalcTextSize(label).x + badge_w; + if (total > max_w) { + max_w = total; + } + + auto it = tree_open.find(n); + bool is_open = (it != tree_open.end()) ? it->second : false; + if (has_children && is_open) { + for (auto& kv : n->children) { + walk(kv.second.get(), kv.first.c_str(), depth + 1); + } + } + }; + + walk(root, "/", 0); + return max_w; +} + +} // namespace slamd diff --git a/slamd/src/window/view/canvas_view.cpp b/slamd/src/window/view/canvas_view.cpp deleted file mode 100644 index c01cfc21..00000000 --- a/slamd/src/window/view/canvas_view.cpp +++ /dev/null @@ -1,240 +0,0 @@ - -#include -#include -#include -#include - -namespace slamd { - -CanvasView::CanvasView( - std::shared_ptr tree -) - : View(std::move(tree)), - frame_buffer(500, 500), - camera(slamd::gmath::Rect2D({0.0, 0.0}, {1.0, 1.0})), - manually_moved(false) {} - -void CanvasView::render_to_imgui() { - ImVec2 full_size = ImGui::GetContentRegionAvail(); - float overlay_height = 24.0f; - ImVec2 image_size = ImVec2(full_size.x, full_size.y - overlay_height); - - int width = static_cast(image_size.x); - int height = static_cast(image_size.y); - bool resized = this->frame_buffer.rescale(width, height); - - if (!this->manually_moved) { - this->set_default_pos(); - } else { - this->fix_view_aspect(); - } - - bool need_render = this->_dirty || resized; - - if (need_render) { - this->render_to_frame_buffer(); - this->_dirty = false; - } - - ImGui::Image( - (ImTextureID)this->frame_buffer.frame_texture(), - image_size, - ImVec2(0, 1), - ImVec2(1, 0) - ); - - this->handle_input(); - - glm::vec2 world_mouse_pos = - this->camera.get_world_coords(this->get_normalized_mouse_pos()); - - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0.85f)); - if (ImGui::BeginChild( - "MouseInfoStrip", - ImVec2(full_size.x, overlay_height), - false - )) { - ImVec2 text_size = ImGui::CalcTextSize("Mouse: (0000.0, 0000.0)"); - ImVec2 win_size = ImGui::GetWindowSize(); - ImGui::SetCursorPos(ImVec2( - (win_size.x - text_size.x) * 0.5f, - (win_size.y - text_size.y) * 0.5f - )); - ImGui::Text( - "Mouse: (%.1f, %.1f)", - world_mouse_pos.x, - world_mouse_pos.y - ); - } - ImGui::EndChild(); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - - this->frame_timer.log_frame(); -} - -void CanvasView::render_to_frame_buffer() { - this->frame_buffer.bind(); - - gl::glEnable(gl::GL_BLEND); - gl::glBlendFunc(gl::GL_SRC_ALPHA, gl::GL_ONE_MINUS_SRC_ALPHA); - - gl::glClear(gl::GL_COLOR_BUFFER_BIT | gl::GL_DEPTH_BUFFER_BIT); - gl::glEnable(gl::GL_DEPTH_TEST); - - gl::glClearColor(0.5686f, 0.0980f, 0.4196f, 1.0f); - gl::glClear(gl::GL_COLOR_BUFFER_BIT); - - glm::mat4 projection_matrix = this->camera.get_projection_matrix(); - - // TODO: render calls here - this->tree->render(glm::mat4(1.0), projection_matrix); - - this->frame_buffer.unbind(); - this->frame_buffer.resolve(); -} - -void CanvasView::handle_input() { - // TODO: handle input - if (ImGui::IsWindowFocused()) { - this->handle_translation_input(); - this->handle_mouse_input(); - - if (ImGui::IsKeyPressed(ImGuiKey_Period, false)) { - this->set_default_pos(); - this->manually_moved = false; - this->mark_dirty(); - } - } -} - -void CanvasView::handle_mouse_input() { - ImGuiIO& io = ImGui::GetIO(); - - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { - // handle dragging - auto mouse_drag_delta = - ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); - - float mouse_drag_delta_x = - static_cast(mouse_drag_delta.x) / - static_cast(this->frame_buffer.width()); - - float mouse_drag_delta_y = - static_cast(mouse_drag_delta.y) / - static_cast(this->frame_buffer.height()); - ; - - ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left); - - this->camera.translate_normalized( - {-mouse_drag_delta_x, -mouse_drag_delta_y} - ); - this->manually_moved = true; - this->mark_dirty(); - } - - if (io.MouseWheel != 0.0f) { - auto scroll_input = static_cast(io.MouseWheel); - - float zoom_amount = static_cast(scroll_input) * 0.1f; - - this->camera.zoom_relative( - zoom_amount, - this->get_normalized_mouse_pos() - ); - - this->manually_moved = true; - this->mark_dirty(); - } - } -} - -glm::vec2 CanvasView::get_normalized_mouse_pos() { - ImVec2 mouse_pos_global = ImGui::GetMousePos(); - ImVec2 viewport_loc = ImGui::GetItemRectMin(); - - float mouse_pos_x = - static_cast(mouse_pos_global.x - viewport_loc.x) / - static_cast(this->frame_buffer.width()); - float mouse_pos_y = - (static_cast(mouse_pos_global.y - viewport_loc.y) / - static_cast(this->frame_buffer.height())); - - return glm::vec2(mouse_pos_x, mouse_pos_y); -} - -void CanvasView::handle_translation_input() { - glm::vec2 translation(0.0f, 0.0f); - float movement_amount = this->frame_timer.timedelta(); - - float zoom = 0.0f; - - glm::vec2 right(movement_amount, 0.0f); - glm::vec2 up(0.0, -movement_amount); - - if (ImGui::IsKeyDown(ImGuiKey_D)) { - translation += right; - } - - if (ImGui::IsKeyDown(ImGuiKey_A)) { - translation -= right; - } - - if (ImGui::IsKeyDown(ImGuiKey_E)) { - translation += up; - } - - if (ImGui::IsKeyDown(ImGuiKey_Q)) { - translation -= up; - } - - if (ImGui::IsKeyDown(ImGuiKey_W)) { - zoom += movement_amount; - } - - if (ImGui::IsKeyDown(ImGuiKey_S)) { - zoom -= movement_amount; - } - - if (glm::length(translation) > 1e-6f) { - this->manually_moved = true; - this->camera.translate_relative(translation); - this->mark_dirty(); - } - - if (glm::abs(zoom) > 1e-6f) { - this->manually_moved = true; - this->camera.zoom_relative(zoom); - this->mark_dirty(); - } -} - -void CanvasView::set_default_pos() { - auto maybe_bounds = this->tree->bounds(); - - slamd::gmath::AABB bounds = maybe_bounds.has_value() - ? maybe_bounds.value() - : slamd::gmath::AABB( - glm::vec3(0.0f, 0.0f, 0.0f), - glm::vec3(1.0f, 1.0f, 0.0f) - ); - - float window_aspect = this->frame_buffer.aspect(); - - this->camera.set_viewport(slamd::gmath::Rect2D::center_cover( - slamd::gmath::Rect2D::from_aabb3d(bounds), - window_aspect - )); -} - -void CanvasView::fix_view_aspect() { - float window_aspect = this->frame_buffer.aspect(); - this->camera.set_viewport( - slamd::gmath::Rect2D::center_cover(this->camera.viewport, window_aspect) - ); -} - -} // namespace slamd \ No newline at end of file diff --git a/slamd/src/window/view/scene_view.cpp b/slamd/src/window/view/scene_view.cpp index 2c33cda4..98fa4362 100644 --- a/slamd/src/window/view/scene_view.cpp +++ b/slamd/src/window/view/scene_view.cpp @@ -13,12 +13,16 @@ glm::vec3 make_background_color( return glm::vec3(0.1f, 0.1f, 0.1f); } +void SceneView::mark_dirty() { + this->_dirty = true; +} + SceneView::SceneView( std::shared_ptr tree ) - : View(std::move(tree)), + : tree(std::move(tree)), frame_buffer(500, 500), - camera(45.0, 0.1f, 100000.0f), + camera(45.0, 0.001, 100.0), xy_grid(1000.0) { this->xy_grid.set_arcball_zoom(this->arcball.radius); this->arcball_indicator.set_arcball_zoom(this->arcball.radius); @@ -68,8 +72,10 @@ void SceneView::render_to_frame_buffer() { ); gl::glClear(gl::GL_COLOR_BUFFER_BIT); - auto projection = - this->camera.get_projection_matrix(this->frame_buffer.aspect()); + auto projection = this->camera.get_projection_matrix( + this->frame_buffer.aspect(), + this->arcball.radius + ); this->tree->render(view, projection); diff --git a/slamd/src/window/view/view.cpp b/slamd/src/window/view/view.cpp deleted file mode 100644 index c319482f..00000000 --- a/slamd/src/window/view/view.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include - -namespace slamd { - -View::View( - std::shared_ptr t -) - : tree(std::move(t)) {} - -void View::mark_dirty() { - this->_dirty = true; -} - -std::unique_ptr View::deserialize( - const flatb::View* view_fb, - std::shared_ptr tree -) { - std::unique_ptr view; - - switch (view_fb->view_type()) { - case (slamd::flatb::ViewType_CANVAS): { - view = std::make_unique(tree); - break; - } - case (slamd::flatb::ViewType_SCENE): { - view = std::make_unique(tree); - break; - } - default: { - throw std::runtime_error("Invalid geometry type"); - } - } - return view; -} - -} // namespace slamd \ No newline at end of file diff --git a/python_bindings/src/main.cpp b/src/main.cpp similarity index 66% rename from python_bindings/src/main.cpp rename to src/main.cpp index 74075cc1..631e314a 100644 --- a/python_bindings/src/main.cpp +++ b/src/main.cpp @@ -8,35 +8,9 @@ namespace py = pybind11; namespace pybind11::detail { -template <> -struct type_caster { - PYBIND11_TYPE_CASTER(glm::vec2, _("numpy.ndarray[float32[2]]")); - - bool load( - handle src, - bool - ) { - auto buf = py::array_t < float, - py::array::c_style | py::array::forcecast > ::ensure(src); - if (!buf || buf.ndim() != 1 || buf.shape(0) != 2) { - return false; - } - value = glm::vec2(buf.at(0), buf.at(1)); - return true; - } - - static handle cast( - const glm::vec2& v, - return_value_policy, - handle - ) { - return py::array_t({2}, {sizeof(float)}, &v[0]).release(); - } -}; - template <> struct type_caster { - PYBIND11_TYPE_CASTER(glm::vec3, _("numpy.ndarray[float32[3]]")); + PYBIND11_TYPE_CASTER(glm::vec3, _("numpy.ndarray")); bool load( handle src, @@ -62,7 +36,7 @@ struct type_caster { template <> struct type_caster { - PYBIND11_TYPE_CASTER(glm::mat4, _("numpy.ndarray[float32[4][4]]")); + PYBIND11_TYPE_CASTER(glm::mat4, _("numpy.ndarray")); bool load( handle src, @@ -99,7 +73,7 @@ struct type_caster { template <> struct type_caster { - PYBIND11_TYPE_CASTER(glm::mat3, _("numpy.ndarray[float32[3][3]]")); + PYBIND11_TYPE_CASTER(glm::mat3, _("numpy.ndarray")); bool load( handle src, @@ -137,10 +111,7 @@ struct type_caster { // vector template <> struct type_caster> { - PYBIND11_TYPE_CASTER( - std::vector, - _("numpy.ndarray[float32[n][3]]") - ); + PYBIND11_TYPE_CASTER(std::vector, _("numpy.ndarray")); bool load( handle src, @@ -190,62 +161,6 @@ struct type_caster> { } }; -// vector -template <> -struct type_caster> { - PYBIND11_TYPE_CASTER( - std::vector, - _("numpy.ndarray[float32[n][2]]") - ); - - bool load( - handle src, - bool - ) { - auto buf = py::array_t < float, - py::array::c_style | py::array::forcecast > ::ensure(src); - if (!buf || buf.ndim() != 2 || buf.shape(1) != 2) { - return false; - } - ssize_t n = buf.shape(0); - value.resize(n); - for (ssize_t i = 0; i < n; ++i) { - value[i] = glm::vec2(buf.at(i, 0), buf.at(i, 1)); - } - return true; - } - - static handle cast( - const std::vector& vecs, - return_value_policy, - handle - ) { - std::vector shape = {static_cast(vecs.size()), 2}; - std::vector strides = { - static_cast(sizeof(float) * 2), - static_cast(sizeof(float)) - }; - - py::array_t arr(py::buffer_info( - nullptr, // no data yet - sizeof(float), // item size - py::format_descriptor::format(), // format string - 2, // ndim - shape, // shape - strides // strides - )); - - auto r = arr.mutable_unchecked<2>(); - for (size_t i = 0; i < vecs.size(); ++i) { - for (int j = 0; j < 2; ++j) { - r(i, j) = vecs[i][j]; - } - } - - return arr.release(); - } -}; - template <> struct type_caster { PYBIND11_TYPE_CASTER(slamd::data::Image, _("numpy.ndarray")); @@ -321,121 +236,111 @@ struct type_caster> void define_private_geom( py::module_& m ) { - py::class_>( + py::class_>( m, "Geometry" ); py::class_< - slamd::_geom::Box, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Box"); + slamd::geom::Box, + slamd::geom::Geometry, + std::shared_ptr>(m, "Box"); py::class_< - slamd::_geom::Plane, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Plane"); + slamd::geom::Plane, + slamd::geom::Geometry, + std::shared_ptr>(m, "Plane"); py::class_< - slamd::_geom::Arrows, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Arrows"); + slamd::geom::Arrows, + slamd::geom::Geometry, + std::shared_ptr>(m, "Arrows"); py::class_< - slamd::_geom::CameraFrustum, - slamd::_geom::Geometry, - std::shared_ptr>(m, "CameraFrustum"); + slamd::geom::CameraFrustum, + slamd::geom::Geometry, + std::shared_ptr>(m, "CameraFrustum"); py::class_< - slamd::_geom::PointCloud, - slamd::_geom::Geometry, - std::shared_ptr>(m, "PointCloud") + slamd::geom::PointCloud, + slamd::geom::Geometry, + std::shared_ptr>(m, "PointCloud") .def( "update_positions", - &slamd::_geom::PointCloud::update_positions, + &slamd::geom::PointCloud::update_positions, py::arg("positions") ) .def( "update_colors", - &slamd::_geom::PointCloud::update_colors, + &slamd::geom::PointCloud::update_colors, py::arg("colors") ) .def( "update_radii", - &slamd::_geom::PointCloud::update_radii, + &slamd::geom::PointCloud::update_radii, py::arg("radii") ); py::class_< - slamd::_geom::Points2D, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Points2D"); + slamd::geom::Spheres, + slamd::geom::Geometry, + std::shared_ptr>(m, "Spheres") + .def( + "update_positions", + &slamd::geom::Spheres::update_positions, + py::arg("positions") + ) + .def( + "update_colors", + &slamd::geom::Spheres::update_colors, + py::arg("colors") + ) + .def( + "update_radii", + &slamd::geom::Spheres::update_radii, + py::arg("radii") + ); py::class_< - slamd::_geom::PolyLine, - slamd::_geom::Geometry, - std::shared_ptr>(m, "PolyLine"); + slamd::geom::PolyLine, + slamd::geom::Geometry, + std::shared_ptr>(m, "PolyLine"); py::class_< - slamd::_geom::Mesh, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Mesh") + slamd::geom::Mesh, + slamd::geom::Geometry, + std::shared_ptr>(m, "Mesh") .def( "update_positions", - &slamd::_geom::Mesh::update_positions, + &slamd::geom::Mesh::update_positions, py::arg("positions"), py::arg("recompute_normals") = true ) .def( "update_colors", - &slamd::_geom::Mesh::update_colors, + &slamd::geom::Mesh::update_colors, py::arg("colors") ) .def( "update_normals", - &slamd::_geom::Mesh::update_normals, + &slamd::geom::Mesh::update_normals, py::arg("normals") ); py::class_< - slamd::_geom::Sphere, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Sphere"); + slamd::geom::Sphere, + slamd::geom::Geometry, + std::shared_ptr>(m, "Sphere"); py::class_< - slamd::_geom::Triad, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Triad"); + slamd::geom::Triad, + slamd::geom::Geometry, + std::shared_ptr>(m, "Triad"); py::class_< - slamd::_geom::Image, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Image"); - - py::class_< - slamd::_geom::PolyLine2D, - slamd::_geom::Geometry, - std::shared_ptr>(m, "PolyLine2D"); - - py::class_< - slamd::_geom::Circles2D, - slamd::_geom::Geometry, - std::shared_ptr>(m, "Circles2D") - .def( - "update_positions", - &slamd::_geom::Circles2D::update_positions, - py::arg("positions") - ) - .def( - "update_colors", - &slamd::_geom::Circles2D::update_colors, - py::arg("colors") - ) - .def( - "update_radii", - &slamd::_geom::Circles2D::update_radii, - py::arg("radii") - ); + slamd::geom::Image, + slamd::geom::Geometry, + std::shared_ptr>(m, "Image"); } void define_geom( @@ -514,6 +419,26 @@ void define_geom( "Create a PointCloud with per-point color and radius" ); + m.def( + "Spheres", + [](const std::vector& positions, + const std::vector& colors, + const std::vector& radii, + float min_brightness) { + return slamd::geom::spheres( + positions, + colors, + radii, + min_brightness + ); + }, + py::arg("positions"), + py::arg("colors"), + py::arg("radii"), + py::arg("min_brightness") = 0.3f, + "Create Spheres with per-point color and radius" + ); + m.def( "PolyLine", &slamd::geom::poly_line, @@ -590,56 +515,11 @@ void define_geom( ); } -void define_geom2d( - py::module_& m -) { - m.def( - "Points", - [](const std::vector& positions, - const std::vector& colors, - const std::vector& radii) { - return slamd::geom2d::points(positions, colors, radii); - }, - py::arg("positions"), - py::arg("colors"), - py::arg("radii"), - "Create 2D points with per-point color and radius" - ); - - m.def( - "Image", - [](slamd::data::Image img) { - return slamd::geom2d::image(std::move(img)); - }, - py::arg("image"), - "Create an Image geometry from a NumPy uint8 array (H, W, C)" - ); - - m.def( - "PolyLine", - &slamd::geom2d::poly_line, - py::arg("points"), - py::arg("color"), - py::arg("thickness"), - "Create a 2D poly line" - ); - - m.def( - "Circles", - &slamd::geom2d::circles, - py::arg("positions"), - py::arg("colors"), - py::arg("radii"), - py::arg("thickness") = 0.1, - "Create a set of circles" - ); -} - PYBIND11_MODULE( bindings, m ) { - m.doc() = "SlamDunk visualization library"; + m.doc() = "slamd visualization library"; py::class_>(m, "Scene") .def(py::init([]() { @@ -653,26 +533,11 @@ PYBIND11_MODULE( ) .def( "set_object", - &slamd::Scene::set_object, - py::arg("path"), - py::arg("object") - ) - .def("clear", &slamd::Scene::clear, py::arg("path")); - - // Canvas bindings - py::class_>(m, "Canvas") - .def(py::init([]() { - return slamd::canvas(); - })) - .def( - "set_transform", - &slamd::Canvas::set_transform, - py::arg("path"), - py::arg("transform") - ) - .def( - "set_object", - &slamd::Canvas::set_object, + [](slamd::Scene& self, + const std::string& path, + std::shared_ptr object) { + self.set_object(path, object); + }, py::arg("path"), py::arg("object") ) @@ -694,16 +559,8 @@ PYBIND11_MODULE( py::arg("name"), py::arg("scene") ) - .def( - "add_canvas", - &slamd::_vis::Visualizer::add_canvas, - py::arg("name"), - py::arg("canvas") - ) .def("scene", &slamd::_vis::Visualizer::scene, py::arg("name")) - .def("canvas", &slamd::_vis::Visualizer::canvas, py::arg("name")) - .def("delete_scene", &slamd::_vis::Visualizer::delete_scene) - .def("delete_canvas", &slamd::_vis::Visualizer::delete_canvas); + .def("delete_scene", &slamd::_vis::Visualizer::delete_scene); m.def( "spawn_window", @@ -712,12 +569,9 @@ PYBIND11_MODULE( py::arg("executable_path") = std::nullopt ); - auto _geom = m.def_submodule("_geom"); - define_private_geom(_geom); + auto geom_types = m.def_submodule("_geom_types"); + define_private_geom(geom_types); auto geom = m.def_submodule("geom"); define_geom(geom); - - auto geom2d = m.def_submodule("geom2d"); - define_geom2d(geom2d); } diff --git a/python_bindings/src/slamd/__init__.py b/src/slamd/__init__.py similarity index 74% rename from python_bindings/src/slamd/__init__.py rename to src/slamd/__init__.py index ac17fb95..ab6fe94a 100644 --- a/python_bindings/src/slamd/__init__.py +++ b/src/slamd/__init__.py @@ -3,19 +3,18 @@ import threading import subprocess from sys import argv -from . import geom, geom2d +from . import geom from .bindings import ( __doc__, Visualizer as Visualizer_internal, - Canvas, Scene, - # geom, spawn_window as spawn_window_internal, ) + def _executable_path(): executable_path = Path(__file__).parent / "slamd_window" @@ -56,26 +55,6 @@ def add_scene(self, name: str, scene: Scene) -> None: """ return self._impl.add_scene(name, scene) - def add_canvas(self, name: str, canvas: Canvas) -> None: - """Add a named 2D canvas to the visualizer. - - Args: - name: Name of the canvas. - canvas: The Canvas object to add. - """ - return self._impl.add_canvas(name, canvas) - - def canvas(self, name: str) -> Canvas: - """Create and add a new 2D canvas to the visualizer. - - Args: - name: Name of the canvas. - - Returns: - The newly created Canvas object. - """ - return self._impl.canvas(name) - def scene(self, name: str) -> Scene: """Create and add a new 3D scene to the visualizer. @@ -95,14 +74,6 @@ def delete_scene(self, name: str) -> None: """ self._impl.delete_scene(name) - def delete_canvas(self, name: str) -> None: - """Delete a canvas by name - - Args: - name: Name of the canvas to delete. - """ - self._impl.delete_canvas(name) - def spawn_window(port: int = 5555) -> None: """Spawn a visualizer window on the given port. @@ -130,10 +101,8 @@ def _window_cli(): __all__ = [ "__doc__", "Visualizer", - "Canvas", "Scene", "geom", - "geom2d", "spawn_window", "_window_cli", ] diff --git a/python_bindings/src/slamd/_utils/colors.py b/src/slamd/_utils/colors.py similarity index 100% rename from python_bindings/src/slamd/_utils/colors.py rename to src/slamd/_utils/colors.py diff --git a/python_bindings/src/slamd/_utils/handle_input.py b/src/slamd/_utils/handle_input.py similarity index 100% rename from python_bindings/src/slamd/_utils/handle_input.py rename to src/slamd/_utils/handle_input.py diff --git a/python_bindings/src/slamd/bindings.pyi b/src/slamd/bindings.pyi similarity index 100% rename from python_bindings/src/slamd/bindings.pyi rename to src/slamd/bindings.pyi diff --git a/python_bindings/src/slamd/bindings/__init__.pyi b/src/slamd/bindings/__init__.pyi similarity index 56% rename from python_bindings/src/slamd/bindings/__init__.pyi rename to src/slamd/bindings/__init__.pyi index ac48d649..f5920e46 100644 --- a/python_bindings/src/slamd/bindings/__init__.pyi +++ b/src/slamd/bindings/__init__.pyi @@ -1,22 +1,12 @@ """ -SlamDunk visualization library +slamd visualization library """ from __future__ import annotations import numpy import typing -from . import _geom +from . import _geom_types from . import geom -from . import geom2d -__all__ = ['Canvas', 'Scene', 'Visualizer', 'geom', 'geom2d', 'spawn_window'] -class Canvas: - def __init__(self) -> None: - ... - def clear(self, path: str) -> None: - ... - def set_object(self, path: str, object: ...) -> None: - ... - def set_transform(self, path: str, transform: numpy.ndarray) -> None: - ... +__all__: list[str] = ['Scene', 'Visualizer', 'geom', 'spawn_window'] class Scene: def __init__(self) -> None: ... @@ -29,14 +19,8 @@ class Scene: class Visualizer: def __init__(self, name: str, port: typing.SupportsInt | typing.SupportsIndex = 5555) -> None: ... - def add_canvas(self, name: str, canvas: Canvas) -> None: - ... def add_scene(self, name: str, scene: Scene) -> None: ... - def canvas(self, name: str) -> Canvas: - ... - def delete_canvas(self, arg0: str) -> None: - ... def delete_scene(self, arg0: str) -> None: ... def scene(self, name: str) -> Scene: diff --git a/python_bindings/src/slamd/bindings/_geom.pyi b/src/slamd/bindings/_geom.pyi similarity index 80% rename from python_bindings/src/slamd/bindings/_geom.pyi rename to src/slamd/bindings/_geom.pyi index 66692d5b..c242b818 100644 --- a/python_bindings/src/slamd/bindings/_geom.pyi +++ b/src/slamd/bindings/_geom.pyi @@ -1,20 +1,13 @@ from __future__ import annotations import numpy import typing -__all__ = ['Arrows', 'Box', 'CameraFrustum', 'Circles2D', 'Geometry', 'Image', 'Mesh', 'Plane', 'PointCloud', 'Points2D', 'PolyLine', 'PolyLine2D', 'Sphere', 'Triad'] +__all__: list[str] = ['Arrows', 'Box', 'CameraFrustum', 'Geometry', 'Image', 'Mesh', 'Plane', 'PointCloud', 'PolyLine', 'Sphere', 'Spheres', 'Triad'] class Arrows(Geometry): pass class Box(Geometry): pass class CameraFrustum(Geometry): pass -class Circles2D(Geometry): - def update_colors(self, colors: numpy.ndarray) -> None: - ... - def update_positions(self, positions: numpy.ndarray) -> None: - ... - def update_radii(self, radii: list[float] | numpy.ndarray) -> None: - ... class Geometry: pass class Image(Geometry): @@ -35,13 +28,16 @@ class PointCloud(Geometry): ... def update_radii(self, radii: list[float] | numpy.ndarray) -> None: ... -class Points2D(Geometry): - pass class PolyLine(Geometry): pass -class PolyLine2D(Geometry): - pass class Sphere(Geometry): pass +class Spheres(Geometry): + def update_colors(self, colors: numpy.ndarray) -> None: + ... + def update_positions(self, positions: numpy.ndarray) -> None: + ... + def update_radii(self, radii: list[float] | numpy.ndarray) -> None: + ... class Triad(Geometry): pass diff --git a/src/slamd/bindings/_geom_types.pyi b/src/slamd/bindings/_geom_types.pyi new file mode 100644 index 00000000..c242b818 --- /dev/null +++ b/src/slamd/bindings/_geom_types.pyi @@ -0,0 +1,43 @@ +from __future__ import annotations +import numpy +import typing +__all__: list[str] = ['Arrows', 'Box', 'CameraFrustum', 'Geometry', 'Image', 'Mesh', 'Plane', 'PointCloud', 'PolyLine', 'Sphere', 'Spheres', 'Triad'] +class Arrows(Geometry): + pass +class Box(Geometry): + pass +class CameraFrustum(Geometry): + pass +class Geometry: + pass +class Image(Geometry): + pass +class Mesh(Geometry): + def update_colors(self, colors: numpy.ndarray) -> None: + ... + def update_normals(self, normals: numpy.ndarray) -> None: + ... + def update_positions(self, positions: numpy.ndarray, recompute_normals: bool = True) -> None: + ... +class Plane(Geometry): + pass +class PointCloud(Geometry): + def update_colors(self, colors: numpy.ndarray) -> None: + ... + def update_positions(self, positions: numpy.ndarray) -> None: + ... + def update_radii(self, radii: list[float] | numpy.ndarray) -> None: + ... +class PolyLine(Geometry): + pass +class Sphere(Geometry): + pass +class Spheres(Geometry): + def update_colors(self, colors: numpy.ndarray) -> None: + ... + def update_positions(self, positions: numpy.ndarray) -> None: + ... + def update_radii(self, radii: list[float] | numpy.ndarray) -> None: + ... +class Triad(Geometry): + pass diff --git a/python_bindings/src/slamd/bindings/geom.pyi b/src/slamd/bindings/geom.pyi similarity index 57% rename from python_bindings/src/slamd/bindings/geom.pyi rename to src/slamd/bindings/geom.pyi index b09967cd..00ba0ee7 100644 --- a/python_bindings/src/slamd/bindings/geom.pyi +++ b/src/slamd/bindings/geom.pyi @@ -1,48 +1,52 @@ from __future__ import annotations -import bindings._geom import collections.abc import numpy +import slamd.bindings._geom_types import typing -__all__ = ['Arrows', 'Box', 'CameraFrustum', 'Mesh', 'Plane', 'PointCloud', 'PolyLine', 'Sphere', 'Triad'] -def Arrows(starts: numpy.ndarray, ends: numpy.ndarray, colors: numpy.ndarray, thickness: typing.SupportsFloat | typing.SupportsIndex) -> bindings._geom.Arrows: +__all__: list[str] = ['Arrows', 'Box', 'CameraFrustum', 'Mesh', 'Plane', 'PointCloud', 'PolyLine', 'Sphere', 'Spheres', 'Triad'] +def Arrows(starts: numpy.ndarray, ends: numpy.ndarray, colors: numpy.ndarray, thickness: typing.SupportsFloat | typing.SupportsIndex) -> slamd.bindings._geom_types.Arrows: """ Create an Arrows geometry """ -def Box() -> bindings._geom.Box: +def Box() -> slamd.bindings._geom_types.Box: """ Create a Box geometry """ -def CameraFrustum(intrinsics_matrix: numpy.ndarray, image_width: typing.SupportsInt | typing.SupportsIndex, image_height: typing.SupportsInt | typing.SupportsIndex, image: numpy.ndarray | None = None, scale: typing.SupportsFloat | typing.SupportsIndex = 1.0) -> bindings._geom.CameraFrustum: +def CameraFrustum(intrinsics_matrix: numpy.ndarray, image_width: typing.SupportsInt | typing.SupportsIndex, image_height: typing.SupportsInt | typing.SupportsIndex, image: numpy.ndarray | None = None, scale: typing.SupportsFloat | typing.SupportsIndex = 1.0) -> slamd.bindings._geom_types.CameraFrustum: """ Create a CameraFrustum geometry """ @typing.overload -def Mesh(vertices: numpy.ndarray, vertex_colors: numpy.ndarray, triangle_indices: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> bindings._geom.Mesh: +def Mesh(vertices: numpy.ndarray, vertex_colors: numpy.ndarray, triangle_indices: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex]) -> slamd.bindings._geom_types.Mesh: """ Create a SimpleMesh geometry from raw data """ @typing.overload -def Mesh(vertices: numpy.ndarray, vertex_colors: numpy.ndarray, triangle_indices: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], vertex_normals: numpy.ndarray) -> bindings._geom.Mesh: +def Mesh(vertices: numpy.ndarray, vertex_colors: numpy.ndarray, triangle_indices: collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], vertex_normals: numpy.ndarray) -> slamd.bindings._geom_types.Mesh: """ Create a SimpleMesh geometry from raw data """ -def Plane(normal: numpy.ndarray, point: numpy.ndarray, color: numpy.ndarray, radius: typing.SupportsFloat | typing.SupportsIndex, alpha: typing.SupportsFloat | typing.SupportsIndex) -> bindings._geom.Plane: +def Plane(normal: numpy.ndarray, point: numpy.ndarray, color: numpy.ndarray, radius: typing.SupportsFloat | typing.SupportsIndex, alpha: typing.SupportsFloat | typing.SupportsIndex) -> slamd.bindings._geom_types.Plane: """ Create a Plane geometry """ -def PointCloud(positions: numpy.ndarray, colors: numpy.ndarray, radii: list[float] | numpy.ndarray, min_brightness: typing.SupportsFloat | typing.SupportsIndex = 1.0) -> bindings._geom.PointCloud: +def PointCloud(positions: numpy.ndarray, colors: numpy.ndarray, radii: list[float] | numpy.ndarray, min_brightness: typing.SupportsFloat | typing.SupportsIndex = 1.0) -> slamd.bindings._geom_types.PointCloud: """ Create a PointCloud with per-point color and radius """ -def PolyLine(points: numpy.ndarray, thickness: typing.SupportsFloat | typing.SupportsIndex, color: numpy.ndarray, min_brightness: typing.SupportsFloat | typing.SupportsIndex) -> bindings._geom.PolyLine: +def PolyLine(points: numpy.ndarray, thickness: typing.SupportsFloat | typing.SupportsIndex, color: numpy.ndarray, min_brightness: typing.SupportsFloat | typing.SupportsIndex) -> slamd.bindings._geom_types.PolyLine: """ Create a PolyLine geometry """ -def Sphere(radius: typing.SupportsFloat | typing.SupportsIndex = 1.0, color: numpy.ndarray = ...) -> bindings._geom.Sphere: +def Sphere(radius: typing.SupportsFloat | typing.SupportsIndex = 1.0, color: numpy.ndarray = ...) -> slamd.bindings._geom_types.Sphere: """ Create a Sphere geometry """ -def Triad(pose: numpy.ndarray[float32[4][4]] | None = None, scale: typing.SupportsFloat | typing.SupportsIndex = 1.0, thickness: typing.SupportsFloat | typing.SupportsIndex = 0.10000000149011612) -> bindings._geom.Triad: +def Spheres(positions: numpy.ndarray, colors: numpy.ndarray, radii: list[float] | numpy.ndarray, min_brightness: typing.SupportsFloat | typing.SupportsIndex = 0.30000001192092896) -> slamd.bindings._geom_types.Spheres: + """ + Create Spheres with per-point color and radius + """ +def Triad(pose: numpy.ndarray | None = None, scale: typing.SupportsFloat | typing.SupportsIndex = 1.0, thickness: typing.SupportsFloat | typing.SupportsIndex = 0.10000000149011612) -> slamd.bindings._geom_types.Triad: """ Create a Triad geometry """ diff --git a/python_bindings/src/slamd/geom/__init__.py b/src/slamd/geom/__init__.py similarity index 91% rename from python_bindings/src/slamd/geom/__init__.py rename to src/slamd/geom/__init__.py index 18a52df7..aaf4daf7 100644 --- a/python_bindings/src/slamd/geom/__init__.py +++ b/src/slamd/geom/__init__.py @@ -4,13 +4,14 @@ Mesh, Triad, ) -from .overrides import PointCloud, PolyLine, Sphere, Arrows, Plane +from .overrides import PointCloud, PolyLine, Sphere, Arrows, Plane, Spheres __all__ = [ "Box", "Arrows", "CameraFrustum", + "Spheres", "PointCloud", "PolyLine", "Mesh", diff --git a/python_bindings/src/slamd/geom/overrides.py b/src/slamd/geom/overrides.py similarity index 77% rename from python_bindings/src/slamd/geom/overrides.py rename to src/slamd/geom/overrides.py index c1aa99b1..64e3f709 100644 --- a/python_bindings/src/slamd/geom/overrides.py +++ b/src/slamd/geom/overrides.py @@ -1,6 +1,7 @@ import numpy as np from ..bindings.geom import ( PointCloud as PointCloud_internal, + Spheres as Spheres_internal, PolyLine as PolyLine_internal, Sphere as Sphere_internal, Arrows as Arrows_internal, @@ -100,3 +101,29 @@ def Plane( def Triad(pose: np.ndarray | None = None, scale: float = 1.0, thickness: float = 1.0): return Triad_internal(pose, scale, thickness) + + +def Spheres( + positions: np.ndarray, + colors: np.ndarray | tuple[int, int, int] = Color.black, + radii: np.ndarray | float = 1.0, + min_brightness: float = 0.3, +): + """3D spheres with per-point color and radius. + + Args: + positions: An N x 3 array of the 3D positions. + colors: The color of the spheres. Can be one of: + - array of shape N x 3 of RGB colors in (0, 1) + - array of shape 3 with a single RGB color in (0, 1) + - tuple of an RGB value, 0–255 + radii: The radius of each sphere. Can be: + - array of shape N with a radius per sphere + - single float for uniform radius + min_brightness: Minimum brightness applied to the spheres. + """ + n = positions.shape[0] + colors_np = process_color(colors, n) + radii_np = process_radii(radii, n) + + return Spheres_internal(positions, colors_np, radii_np, min_brightness) diff --git a/tools/build.py b/tools/build.py index 4d906cdf..e505be4b 100644 --- a/tools/build.py +++ b/tools/build.py @@ -1,98 +1,74 @@ #!/usr/bin/env python3 -"""A script to build the C++ backend and install the bindings into the source tree.""" +"""Build script for slamd: codegen + editable install + stubs.""" -import shutil -import os import subprocess -from functools import partial +import sys from pathlib import Path -from embed_shaders import embed_shaders -from fire import Fire +REPO_DIR = Path(__file__).parent.parent.resolve() -BUILD_DIR = "build" +def main(): + print("=== Syncing dev dependencies ===") + subprocess.run( + ["uv", "sync", "--group", "dev", "--no-install-project"], + cwd=REPO_DIR, + check=True, + ) -def build(debug: bool) -> None: - """(Re)build the C++ backend.""" - embed_shaders() - - build_path = Path("build") - build_path.mkdir(exist_ok=True) + from build_flatbuffers import compile_flatbuffers + from embed_shaders import embed_shaders - compile_cmd = [ - "cmake", - "-B", - str(build_path), - "-G", - "Ninja", - "-DCMAKE_EXPORT_COMPILE_COMMANDS=1", - "-DCMAKE_C_COMPILER=/usr/bin/gcc-13", - "-DCMAKE_CXX_COMPILER=/usr/bin/g++-13", - '-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_MODULE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld"', - ] + print("\n=== Compiling flatbuffers ===") + compile_flatbuffers() - if debug: - compile_cmd.append("-DCMAKE_BUILD_TYPE=Debug") + print("\n=== Embedding shaders ===") + embed_shaders() + print("\n=== Installing package (editable) ===") subprocess.run( - compile_cmd, + [ + "uv", + "pip", + "install", + "--force-reinstall", + "--no-build-isolation", + "-e", + ".", + ], + cwd=REPO_DIR, check=True, ) - subprocess.run(["ninja", "-C", str(build_path)]) - + print("\n=== Symlinking slamd_window into source tree ===") + build_dirs = sorted(Path(REPO_DIR / "build").glob("*/slamd/slamd_window")) + if build_dirs: + exe = build_dirs[-1].resolve() + link = REPO_DIR / "src" / "slamd" / "slamd_window" + link.unlink(missing_ok=True) + link.symlink_to(exe) + print(f" {link} -> {exe}") + else: + print(" WARNING: slamd_window not found in build directory") + + print("\n=== Generating type stubs ===") subprocess.run( [ - "pybind11-stubgen", + sys.executable, + "-m", + "pybind11_stubgen", "slamd.bindings", - # "--numpy-array-remove-parameters", + "--numpy-array-remove-parameters", "-o", - "python_bindings/src", + "src", ], - env={**os.environ, "PYTHONPATH": "./build/python_bindings"}, + cwd=REPO_DIR, check=True, ) - -def clean() -> None: - """Clean the build folder and remove the symlink, if any.""" - shutil.rmtree(BUILD_DIR, ignore_errors=True) - - -def clean_build() -> None: - """First clean and then build.""" - clean() - build(False) - - -def debug_build(): - build(True) - - -def clean_debug_build(): - clean() - build(True) - - -def check_in_repo() -> None: - """Check that we are executing this from repo root.""" - assert Path(".git").exists(), "This command should run in repo root." + print("\n=== Done ===") if __name__ == "__main__": - check_in_repo() - # os.chdir(Path("./examples")) - - Fire( - { - "build": partial(build, False), - "clean": clean, - "clean_build": clean_build, - "debug_build": debug_build, - "clean_debug_build": clean_debug_build, - } - ) + main() diff --git a/tools/build_config.py b/tools/build_config.py deleted file mode 100644 index 9ed42172..00000000 --- a/tools/build_config.py +++ /dev/null @@ -1,30 +0,0 @@ -import platform - -print(platform.system()) - -if platform.system() == "Darwin": - CPP_COMPILER_PATH = "/opt/homebrew/opt/llvm/bin/clang++" - C_COMPILER_PATH = "/opt/homebrew/opt/llvm/bin/clang" - - CMAKE_FLAGS = [ - "-DCMAKE_EXPORT_COMPILE_COMMANDS=1", - f"-DCMAKE_C_COMPILER={C_COMPILER_PATH}", - f"-DCMAKE_CXX_COMPILER={CPP_COMPILER_PATH}", - "-DCMAKE_POLICY_VERSION_MINIMUM=3.22", - '-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_MODULE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld"', - ] -elif platform.system() == "Linux": - CPP_COMPILER_PATH = "/usr/bin/g++" - C_COMPILER_PATH = "/usr/bin/gcc" - - CMAKE_FLAGS = [ - "-DCMAKE_EXPORT_COMPILE_COMMANDS=1", - f"-DCMAKE_C_COMPILER={C_COMPILER_PATH}", - f"-DCMAKE_CXX_COMPILER={CPP_COMPILER_PATH}", - "-DCMAKE_POLICY_VERSION_MINIMUM=3.22", - '-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_MODULE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld"', - ] diff --git a/tools/build_examples.py b/tools/build_examples.py deleted file mode 100644 index 086a7203..00000000 --- a/tools/build_examples.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 - -"""A script to build the C++ backend and install the bindings into the source tree.""" - -import shutil -import os -import subprocess -from functools import partial -from pathlib import Path -from embed_shaders import embed_shaders -from build_flatbuffers import compile_flatbuffers -from build_config import CMAKE_FLAGS - -from fire import Fire - -BUILD_DIR = "build" - - -def build(debug: bool) -> None: - """(Re)build the C++ backend.""" - embed_shaders() - compile_flatbuffers() - - build_path = Path("build") - build_path.mkdir(exist_ok=True) - - compile_cmd = ["cmake", "-B", str(build_path), "-G", "Ninja", *CMAKE_FLAGS] - - if debug: - compile_cmd.append("-DCMAKE_BUILD_TYPE=Debug") - else: - compile_cmd.append("-DCMAKE_BUILD_TYPE=Release") - - subprocess.run( - compile_cmd, - check=True, - ) - - subprocess.run(["ninja", "-C", str(build_path)]) - - -def clean() -> None: - """Clean the build folder and remove the symlink, if any.""" - shutil.rmtree(BUILD_DIR, ignore_errors=True) - - -def clean_build() -> None: - """First clean and then build.""" - clean() - build(False) - - -def debug_build(): - build(True) - - -def clean_debug_build(): - clean() - build(True) - - -def check_in_repo() -> None: - """Check that we are executing this from repo root.""" - assert Path(".git").exists(), "This command should run in repo root." - - -if __name__ == "__main__": - check_in_repo() - os.chdir(Path("examples")) - - Fire( - { - "build": partial(build, False), - "clean": clean, - "clean_build": clean_build, - "debug_build": debug_build, - "clean_debug_build": clean_debug_build, - } - ) diff --git a/tools/build_lib.py b/tools/build_lib.py deleted file mode 100644 index 7d358f1a..00000000 --- a/tools/build_lib.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 - -"""A script to build the C++ backend and install the bindings into the source tree.""" - -import shutil -import os -import subprocess -from functools import partial -from pathlib import Path -from embed_shaders import embed_shaders - -from fire import Fire - -BUILD_DIR = "build" - - -def build(debug: bool) -> None: - """(Re)build the C++ backend.""" - embed_shaders() - - build_path = Path("build") - build_path.mkdir(exist_ok=True) - - compile_cmd = [ - "cmake", - "-B", - str(build_path), - "-G", - "Ninja", - "-DCMAKE_EXPORT_COMPILE_COMMANDS=1", - "-DCMAKE_C_COMPILER=/usr/bin/gcc-13", - "-DCMAKE_CXX_COMPILER=/usr/bin/g++-13", - '-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_MODULE_LINKER_FLAGS="-fuse-ld=lld"', - '-DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld"', - ] - - if debug: - compile_cmd.append("-DCMAKE_BUILD_TYPE=Debug") - - subprocess.run( - compile_cmd, - check=True, - ) - - subprocess.run(["ninja", "-C", str(build_path)]) - - -def clean() -> None: - """Clean the build folder and remove the symlink, if any.""" - shutil.rmtree(BUILD_DIR, ignore_errors=True) - - -def clean_build() -> None: - """First clean and then build.""" - clean() - build(False) - - -def debug_build(): - build(True) - - -def clean_debug_build(): - clean() - build(True) - - -def check_in_repo() -> None: - """Check that we are executing this from repo root.""" - assert Path(".git").exists(), "This command should run in repo root." - - -if __name__ == "__main__": - check_in_repo() - os.chdir(Path("slamd")) - # os.chdir(Path("./examples")) - - Fire( - { - "build": partial(build, False), - "clean": clean, - "clean_build": clean_build, - "debug_build": debug_build, - "clean_debug_build": clean_debug_build, - } - ) diff --git a/tools/build_python.py b/tools/build_python.py deleted file mode 100644 index 4fa9e4ff..00000000 --- a/tools/build_python.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 - -"""A script to build the C++ backend and install the bindings into the source tree.""" - -import sys -import os -import shutil -import subprocess -from functools import partial -from pathlib import Path -from embed_shaders import embed_shaders -from build_flatbuffers import compile_flatbuffers - -from fire import Fire - -from build_config import CMAKE_FLAGS - -BUILD_DIR = "build" - -TARGET_NAME = "bindings.cpython-313-x86_64-linux-gnu.so" - -LIB_PATH = Path("./build") / TARGET_NAME -DEST_PATH = Path("./src/slamd/") / TARGET_NAME - - -def _python_exe() -> str: - return sys.executable - - -def build(debug: bool) -> None: - """(Re)build the C++ backend.""" - embed_shaders() - compile_flatbuffers() - - build_path = Path("build") - build_path.mkdir(exist_ok=True) - - compile_cmd = [ - "cmake", - "-B", - str(build_path), - "-G", - "Ninja", - *CMAKE_FLAGS, - f"-DPython_EXECUTABLE={_python_exe()}", - "-DPython_FIND_VIRTUALENV=ONLY", - ] - - if debug: - compile_cmd.append("-DCMAKE_BUILD_TYPE=Debug") - - subprocess.run( - compile_cmd, - check=True, - ) - - subprocess.run(["ninja", "-C", str(build_path)]) - - DEST_PATH.unlink(missing_ok=True) - DEST_PATH.symlink_to(LIB_PATH.resolve()) - - os.chdir(Path("src/slamd")) - - env = os.environ.copy() - env["PYTHONPATH"] = f".:{env.get('PYTHONPATH', '')}" - - subprocess.run( - [ - "pybind11-stubgen", - "bindings", - "--numpy-array-remove-parameters", - "-o", - ".", - ], - env=env, - check=True, - ) - - -def clean() -> None: - """Clean the build folder and remove the symlink, if any.""" - shutil.rmtree(BUILD_DIR, ignore_errors=True) - - -def clean_build() -> None: - """First clean and then build.""" - clean() - build(False) - - -def debug_build(): - build(True) - - -def clean_debug_build(): - clean() - build(True) - - -def check_in_repo() -> None: - """Check that we are executing this from repo root.""" - assert Path(".git").exists(), "This command should run in repo root." - - -if __name__ == "__main__": - check_in_repo() - os.chdir(Path("./python_bindings")) - - Fire( - { - "build": partial(build, False), - "clean": clean, - "clean_build": clean_build, - "debug_build": debug_build, - "clean_debug_build": clean_debug_build, - } - ) diff --git a/tools/install_slamd.sh b/tools/install_slamd.sh deleted file mode 100755 index 07b44d2a..00000000 --- a/tools/install_slamd.sh +++ /dev/null @@ -1,3 +0,0 @@ -uv cache clean slamd -uv pip uninstall slamd -uv pip install ./python_bindings \ No newline at end of file diff --git a/uv.lock b/uv.lock index ab4cfadb..7d4218c9 100644 --- a/uv.lock +++ b/uv.lock @@ -1,27 +1,30 @@ version = 1 -revision = 1 +revision = 3 requires-python = ">=3.11" [[package]] name = "fire" -version = "0.7.0" +version = "0.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "termcolor" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/b6/82c7e601d6d3c3278c40b7bd35e17e82aa227f050aa9f66cb7b7fce29471/fire-0.7.0.tar.gz", hash = "sha256:961550f07936eaf65ad1dc8360f2b2bf8408fad46abbfa4d2a3794f8d2a95cdf", size = 87189 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/00/f8d10588d2019d6d6452653def1ee807353b21983db48550318424b5ff18/fire-0.7.1.tar.gz", hash = "sha256:3b208f05c736de98fb343310d090dcc4d8c78b2a89ea4f32b837c586270a9cbf", size = 88720, upload-time = "2025-08-16T20:20:24.175Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/4c/93d0f85318da65923e4b91c1c2ff03d8a458cbefebe3bc612a6693c7906d/fire-0.7.1-py3-none-any.whl", hash = "sha256:e43fd8a5033a9001e7e2973bab96070694b9f12f2e0ecf96d4683971b5ab1882", size = 115945, upload-time = "2025-08-16T20:20:22.87Z" }, +] [[package]] name = "imageio" -version = "2.37.0" +version = "2.37.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796 }, + { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, ] [[package]] @@ -31,251 +34,413 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] name = "numpy" -version = "2.2.4" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, + { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, + { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, + { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, + { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, + { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, + { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, + { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, + { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, + { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, + { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, + { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, + { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, + { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989 }, - { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910 }, - { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490 }, - { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754 }, - { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 }, - { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 }, - { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 }, - { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 }, - { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 }, - { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 }, - { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, - { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, - { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, - { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, - { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, - { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, - { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, - { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, - { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, - { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, - { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, - { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, - { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, - { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, - { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, - { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, - { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, - { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, - { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, - { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, - { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, - { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, - { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, - { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, - { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, - { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, - { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, - { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, - { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, - { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "pillow" -version = "11.1.0" +version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 }, - { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 }, - { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 }, - { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 }, - { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 }, - { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 }, - { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 }, - { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 }, - { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 }, - { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 }, - { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 }, - { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, - { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, - { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, - { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 }, - { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 }, - { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 }, - { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 }, - { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 }, - { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 }, - { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 }, - { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 }, - { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, - { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, - { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, - { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, - { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, - { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, - { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, - { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, - { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, - { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, - { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, - { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, - { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, - { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, - { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, - { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, - { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, - { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, - { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, ] [[package]] name = "pybind11" -version = "2.13.6" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d2/c1/72b9622fcb32ff98b054f724e213c7f70d6898baa714f4516288456ceaba/pybind11-2.13.6.tar.gz", hash = "sha256:ba6af10348c12b24e92fa086b39cfba0eff619b61ac77c406167d813b096d39a", size = 218403 } +sdist = { url = "https://files.pythonhosted.org/packages/a5/98/9118a0659646f1628c592ef9bb48e0056efa6bf27c951fd12a178e0136fb/pybind11-3.0.2.tar.gz", hash = "sha256:432f01aeb68e361a3a7fc7575c2c7f497595bf640f747acd909ff238dd766e06", size = 577131, upload-time = "2026-02-17T04:46:52.556Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/2f/0f24b288e2ce56f51c920137620b4434a38fd80583dbbe24fc2a1656c388/pybind11-2.13.6-py3-none-any.whl", hash = "sha256:237c41e29157b962835d356b370ededd57594a26d5894a795960f0047cb5caf5", size = 243282 }, + { url = "https://files.pythonhosted.org/packages/88/c5/e98d9c51f3d5300d5e40ad9037dd6b3b60736fd02ab68dcc98c96be7592d/pybind11-3.0.2-py3-none-any.whl", hash = "sha256:f8a6500548919cc33bcd220d5f984688326f574fa97f1107f2f4fdb4c6fb019f", size = 310158, upload-time = "2026-02-17T04:46:49.91Z" }, ] [[package]] name = "pybind11-stubgen" -version = "2.5.3" +version = "2.5.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/c6/2c695a3f94a9dfa1920a40dc6b36e9d4d0e1ac089b7e35d7237a29f4c400/pybind11_stubgen-2.5.3.tar.gz", hash = "sha256:72d6122749ed95dfcdd121cc3b8f493cbadb4755f38a0682e2265b5a041c25cb", size = 24756 } +sdist = { url = "https://files.pythonhosted.org/packages/81/81/81900180501a51056572dd01aa852cc2d162478d4676ecebbfaad5cc20d1/pybind11_stubgen-2.5.5.tar.gz", hash = "sha256:758d6d6bbeefc62ad7f78d5e5bbf357ccf6af83cd4504f5f549403f452942708", size = 25031, upload-time = "2025-08-09T01:22:44.403Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/c3/fb2e68bf0b0bb1c233854f4f9ad7c388c7497d80a60597efc1640356ab7a/pybind11_stubgen-2.5.3-py3-none-any.whl", hash = "sha256:7c6a642027e5dcb6a734822cac0cc8600ceadccd9b0339e1fab0da632c3de0fa", size = 29894 }, + { url = "https://files.pythonhosted.org/packages/4f/e4/4f2d41881fae547f06ffa4c9748fa8ed13c4db5c830ff268b84175546b3c/pybind11_stubgen-2.5.5-py3-none-any.whl", hash = "sha256:10824cd2fc5cbbee032b8fb39e6f6c08de232deb309bc66d786a6c6e8a4601bd", size = 30210, upload-time = "2025-08-09T01:22:43.135Z" }, +] + +[[package]] +name = "scikit-build-core" +version = "0.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/cd/9ebb50029b6d8a3ee9e38cdce514ebd70190ec1edf28ab0a1f66d0b84670/scikit_build_core-0.12.2.tar.gz", hash = "sha256:562e0bbc9de1a354c87825ccf732080268d6582a0200f648e8c4a2dcb1e3736d", size = 303553, upload-time = "2026-03-05T18:25:57.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/49/b2f0fbe3165d55c02e7f9eec6a10685d518af0ef6e919ff2f589c2d15c85/scikit_build_core-0.12.2-py3-none-any.whl", hash = "sha256:6ea4730da400f9a998ec3287bd3ebc1d751fe45ad0a93451bead8618adbc02b1", size = 192625, upload-time = "2026-03-05T18:25:56.207Z" }, ] [[package]] name = "scipy" -version = "1.15.2" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 }, - { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 }, - { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 }, - { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523 }, - { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 }, - { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 }, - { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 }, - { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 }, - { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 }, - { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 }, - { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 }, - { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 }, - { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 }, - { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 }, - { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 }, - { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 }, - { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 }, - { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 }, - { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587 }, - { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266 }, - { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768 }, - { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719 }, - { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 }, - { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 }, - { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 }, - { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 }, - { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 }, - { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286 }, - { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634 }, - { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179 }, - { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412 }, - { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 }, - { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 }, - { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 }, - { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 }, - { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] [[package]] -name = "slamd-dev-env" -version = "0.1.0" -source = { virtual = "." } +name = "slamd" +version = "2.2.0" +source = { editable = "." } dependencies = [ + { name = "numpy" }, +] + +[package.dev-dependencies] +dev = [ { name = "fire" }, { name = "imageio" }, { name = "jinja2" }, { name = "numpy" }, + { name = "pillow" }, { name = "pybind11" }, { name = "pybind11-stubgen" }, + { name = "scikit-build-core" }, { name = "scipy" }, ] [package.metadata] -requires-dist = [ +requires-dist = [{ name = "numpy", specifier = ">=1.23" }] + +[package.metadata.requires-dev] +dev = [ { name = "fire", specifier = ">=0.7.0" }, { name = "imageio", specifier = ">=2.37.0" }, { name = "jinja2", specifier = ">=3.1.6" }, - { name = "numpy", specifier = ">=2.2.4" }, + { name = "numpy", specifier = ">=1.23" }, + { name = "pillow", specifier = ">=10.0" }, { name = "pybind11", specifier = ">=2.13.6" }, { name = "pybind11-stubgen", specifier = ">=2.5.3" }, + { name = "scikit-build-core", specifier = ">=0.10" }, { name = "scipy", specifier = ">=1.15.2" }, ] [[package]] name = "termcolor" -version = "3.0.1" +version = "3.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/b6/8e2aaa8aeb570b5cc955cd913b083d96c5447bbe27eaf330dfd7cc8e3329/termcolor-3.0.1.tar.gz", hash = "sha256:a6abd5c6e1284cea2934443ba806e70e5ec8fd2449021be55c280f8a3731b611", size = 12935 } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/7e/a574ccd49ad07e8b117407bac361f1e096b01f1b620365daf60ff702c936/termcolor-3.0.1-py3-none-any.whl", hash = "sha256:da1ed4ec8a5dc5b2e17476d859febdb3cccb612be1c36e64511a6f2485c10c69", size = 7157 }, + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, ]