Skip to main content

quickpaver

License Stars Python PyPI Downoads Build Status Documentation Status Coverage codacy Precommit: enabled Ruff Checked with ty

🐍 A python package providing a tiling/paving toolbox.

The complete and up to date documentation can be found here: https://quickpaver.readthedocs.io.

🎯 Motivations

Designing paving layouts (aka tiling) often requires repetitive geometric calculations and careful programming to ensure that patterns and dimensions are correct. Keeping track of tiles adjacency is not trivial either and performing these tasks manually can be time-consuming and prone to errors, especially when exploring multiple design configurations or working with complex layouts.

This package aims to simplify and automate the generation of paving layouts by providing a programmatic way to create and manipulate classic paving patterns.

The implementation relies on Shapely for geometric operations and leverages the GEOS engine’s vectorization capabilities. This allows many geometric computations to be performed efficiently, enabling the generation and processing of large numbers of paving elements with good performance. As a result, the package provides both flexibility and speed, making it suitable for practical design tasks as well as research and experimentation.

🚀 Quick start

To install quickpaver, the easiest way is through pip. To run the examples you might need additional dependencies such as nested_grid_plotter for the plots.

pip install quickpaver[examples]

Or alternatively using conda

conda install quickpaver[examples]

You might also clone the repository and install from source

pip install -e .[examples]

Once the installation is done, quickpaver is straighforward to use and proposes. In this tutorial, we will see how to generate tiling using rectangles, triangles and hexagons. We will also play with anisotropy and angles.

Start by importing the required modules

from typing import Union

import matplotlib.pyplot as plt
import nested_grid_plotter as ngp
import quickpaver
import shapely
from shapely.plotting import plot_polygon

There is a choice between three shapes: triangle, rectangle, and hexagon. By default, the shapes are regular: the triangle is equilateral, the rectangle is a square, and the hexagon is not distorted. The anisotropy allows the shape to be stretched or shortened along the y-axis.

def make_polygons():
    plotter = ngp.Plotter(
        plt.figure(figsize=(8, 8), constrained_layout=True),
        builder=ngp.SubplotsMosaicBuilder(
            mosaic=[[f"ax{i}-{j}" for j in range(3)] for i in range(3)],
            sharex=True,
            sharey=True,
        ),
    )

    for i, poly_type in enumerate(quickpaver.PolygonType.to_list()):
        for j, anisotropy_ratio in enumerate([1.0, 2.0, 0.5]):
            ax = plotter.ax_dict[f"ax{i}-{j}"]
            plot_polygon(
                quickpaver.gen_polygon(
                    poly_type.value, edge_length=30.0, anisotropy_ratio=anisotropy_ratio
                ),
                ax=ax,
            )
            ngp.hide_axis_spine(ax, loc="all")
            ax.set_aspect("equal")
            ngp.hide_axis_ticklabels(ax)
            ax.set_title(f"Anisotropy\nratio = {anisotropy_ratio:.1f}")
    return plotter.fig

make_polygons()
animation_example

Now let’s take an example and load a simplified outline of France and Corsica.

corsica = quickpaver.load_corsica_contour()
france = quickpaver.load_france_contour()
france_and_corsica = quickpaver.load_france_and_corsica_contour()
france_and_corsica

Define a helper function to plot the results

def plot_helper(
    grid: shapely.MultiPolygon,
    surface_to_cover: Union[shapely.Polygon, shapely.MultiPolygon],
):
    plotter = ngp.Plotter()
    ax = plotter.axes[0]
    plot_polygon(surface_to_cover, ax=ax, add_points=False, color="r")
    plot_polygon(
        grid,
        ax=ax,
        add_points=False,
    )
    ax.set_aspect("equal")
    plotter.close()
    return plotter.fig

Start with a square tiling, without rotation and no anisotropy. We can see that only the meshes intersecting the surface to be covered are retained.

grid_squares_corsica_no_rot_no_ani, _adj = quickpaver.gen_polygonal_tiling(
    corsica,
    poly_type=quickpaver.PolygonType.RECTANGLE,
    edge_length=100.0,
    anisotropy_ratio=1.0,
    rot_deg=0.0,
)
plot_helper(grid_squares_corsica_no_rot_no_ani, corsica)
grid_squares_corsica_no_rot_no_ani

To obtain extended coverage, you simply need to modify the working domain. For example, you can choose the minimum bounding ball:

grid_squares_corsica_circle, _adj = quickpaver.gen_polygonal_tiling(
    shapely.minimum_bounding_circle(corsica).buffer(50.0),
    poly_type=quickpaver.PolygonType.RECTANGLE,
    edge_length=100.0,
    anisotropy_ratio=1.0,
    rot_deg=0.0,
)
plot_helper(grid_squares_corsica_circle, corsica)
grid_squares_corsica_circle

Or using the bounding box

grid_squares_corsica_rectangle, _adj = quickpaver.gen_polygonal_tiling(
    shapely.box(*corsica.bounds),
    poly_type=quickpaver.PolygonType.RECTANGLE,
    edge_length=100.0,
    anisotropy_ratio=1.0,
    rot_deg=0.0,
)
plot_helper(grid_squares_corsica_rectangle, corsica)
grid_squares_corsica_rectangle

Now, let’s try with anisotropy and rotation (it goes clockwise)

grid_squares_corsica_rot_ani, _adj = quickpaver.gen_polygonal_tiling(
    corsica,
    poly_type=quickpaver.PolygonType.RECTANGLE,
    edge_length=100.0,
    anisotropy_ratio=2.0,
    rot_deg=30.0,
)
plot_helper(grid_squares_corsica_rot_ani, corsica)
grid_squares_corsica_rot_ani

Let’s try again with different parameters

grid_squares_corsica_rot_ani2, _adj = quickpaver.gen_polygonal_tiling(
    corsica,
    poly_type=quickpaver.PolygonType.RECTANGLE,
    edge_length=100.0,
    anisotropy_ratio=0.5,
    rot_deg=-30.0,
)
plot_helper(grid_squares_corsica_rot_ani2, corsica)
grid_squares_corsica_rot_ani2

As previously introduced, hexagonal grids are also supported. Let’s now play with France outline:

grid_hexagons_france_no_rot_no_ani, _adj = quickpaver.gen_polygonal_tiling(
    france,
    poly_type=quickpaver.PolygonType.HEXAGON,
    edge_length=100.0,
    anisotropy_ratio=1.0,
    rot_deg=0.0,
)
plot_helper(grid_hexagons_france_no_rot_no_ani, france)
grid_hexagons_france_no_rot_no_ani

By default the hexagons are “flat-top” oriented, but it is very easily changed:

grid_hexagons_france_rot, _adj = quickpaver.gen_polygonal_tiling(
    france,
    poly_type=quickpaver.PolygonType.HEXAGON,
    edge_length=500.0,
    anisotropy_ratio=1.0,
    rot_deg=30.0,
)
plot_helper(grid_hexagons_france_rot, france)
grid_hexagons_france_rot

Now let’s try with triangles

grid_triangles_rot_no_ani, _adj = quickpaver.gen_polygonal_tiling(
    france_and_corsica,
    poly_type=quickpaver.PolygonType.TRIANGLE,
    edge_length=550.0,
    anisotropy_ratio=1.0,
    rot_deg=-30.0,
)
plot_helper(grid_triangles_rot_no_ani, france_and_corsica)
grid_triangles_no_rot_ani

Same with anisotropy:

grid_triangles_no_rot_ani, _adj = quickpaver.gen_polygonal_tiling(
    france_and_corsica,
    poly_type=quickpaver.PolygonType.TRIANGLE,
    edge_length=500.0,
    anisotropy_ratio=3.0,
    rot_deg=0.0,
)
plot_helper(grid_triangles_no_rot_ani, france_and_corsica)
grid_triangles_no_rot_ani
grid_triangles_no_rot_ani, _adj = quickpaver.gen_polygonal_tiling(
    france_and_corsica,
    poly_type=quickpaver.PolygonType.TRIANGLE,
    edge_length=500.0,
    anisotropy_ratio=3.0,
    rot_deg=0.0,
)
plot_helper(grid_triangles_no_rot_ani, france_and_corsica)
grid_triangles_rot_ani
grid_triangles_rot_ani, _adj = quickpaver.gen_polygonal_tiling(
    france_and_corsica,
    poly_type=quickpaver.PolygonType.TRIANGLE,
    edge_length=1000.0,
    anisotropy_ratio=1.5,
    rot_deg=45.0,
)
plot_helper(grid_triangles_rot_ani, france_and_corsica)

It is also possible to extract both centers and vertices (the adjacency between the vertices and the centers is also provided)

centers = quickpaver.extract_tiling_centers(grid_hexagons_france_rot.geoms)
vertices, v_c_adj, clusters_2 = quickpaver.extract_tiling_vertices(
    grid_hexagons_france_rot.geoms
)

plotter2 = plot_helper(grid_hexagons_france_rot, france)
plotter2.axes[0].scatter(centers[:, 0], centers[:, 1], color="b", label="centers")
plotter2.axes[0].scatter(
    vertices[:, 0], vertices[:, 1], color="g", label="vertices"
)
plotter2.axes[0].legend()
plotter2
grid_hexagons_france_rot_vertices
centers2 = quickpaver.extract_tiling_centers(grid_triangles_rot_ani.geoms)
vertices2, v_c_adj2, clusters_3 = quickpaver.extract_tiling_vertices(
    grid_triangles_rot_ani.geoms
)

plotter3 = plot_helper(grid_triangles_rot_ani, france_and_corsica)
plotter3.axes[0].scatter(centers2[:, 0], centers2[:, 1], color="b", label="centers")
plotter3.axes[0].scatter(
    vertices2[:, 0], vertices2[:, 1], color="g", label="vertices"
)
plotter3.axes[0].legend()
plotter3
grid_triangles_rot_ani_vertices

Of course, it works with holes

donut = shapely.Point((0.0, 0.0)).buffer(200.0) - shapely.Point((0.0, 0.0)).buffer(
    100.0
)

grid_hex_donut, _adj = quickpaver.gen_polygonal_tiling(
    donut,
    poly_type=quickpaver.PolygonType.HEXAGON,
    edge_length=10.0,
    anisotropy_ratio=1.0,
    rot_deg=0.0,
)
plot_helper(grid_hex_donut, donut)
grid_hex_donut

🔑 License

This project is released under the BSD 3-Clause License.

Copyright (c) 2026, Antoine COLLET. All rights reserved.

For more details, see the LICENSE file included in this repository.

⚠️ Disclaimer

This software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, or non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.

By using this software, you agree to accept full responsibility for any consequences, and you waive any claims against the authors or contributors.

📧 Contact

For questions, suggestions, or contributions, you can reach out via:

We welcome contributions!

📚 References

TODO

  • Free software: SPDX-License-Identifier: BSD-3-Clause

Release files for quickpaver 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for quickpaver 0.4.0
File Size Uploaded
quickpaver-0.4.0.tar.gz 139.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for quickpaver 0.4.0
File Interpreter ABI Platform
quickpaver-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 226.8 kB

Release files / quickpaver-0.4.0.tar.gz

Download URL quickpaver-0.4.0.tar.gz
Size 139.2 kB
Tags Source
SHA-256 checksum
How to use checksums
1bd65742d1f8f58466841c0409c3051b84308fc5ab083bb1fa303ac9f6b27dfd
BLAKE2b-256 checksum
How to use checksums
1e64a2834afd7813857cdaa26f93d4ac116275e8b267b3df9b4b0c99af72b419
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.4

Release files / quickpaver-0.4.0-py3-none-any.whl

Download URL quickpaver-0.4.0-py3-none-any.whl
Size 87.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4a5740d9cb05cb3fe4deb0fd9a2e0b0beac7f76024a2d96a3fb1c7156fc26652
BLAKE2b-256 checksum
How to use checksums
74815e52bc00980399454075cd4c292100598f07c2125125389b98e705d1fdd6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.4

Release history Release notifications | RSS feed

0.5.0

2 release files

This release

0.4.0 This release

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page