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.5.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.5.0
File Size Uploaded
quickpaver-0.5.0.tar.gz 144.2 kB Details

Built distribution (wheel)

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

Total release size: 233.3 kB

Release files / quickpaver-0.5.0.tar.gz

Download URL quickpaver-0.5.0.tar.gz
Size 144.2 kB
Tags Source
SHA-256 checksum
How to use checksums
811b4b76594f74f010e47901e3d110b93a98dec607cebe6912764a736f1ee7bc
BLAKE2b-256 checksum
How to use checksums
e02236aa61665eebe8d1445d9b7b895acac07932795a07760883a96035c31ba6
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.5.0-py3-none-any.whl

Download URL quickpaver-0.5.0-py3-none-any.whl
Size 89.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
51ad6bd538dde97ecc3c9b0f3fd5216d8cfe40538b51208f3afec558dc032a7d
BLAKE2b-256 checksum
How to use checksums
3a6749dc683ee50b3e7ef5d02943f7fabc8638a80e079d5882111f6b27632f82
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

This release

0.5.0 This release

2 release files

0.4.0

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