Skip to main content

A python package providing a paving toolbox.

Project description

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_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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

quickpaver-0.2.0.tar.gz (48.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

quickpaver-0.2.0-py3-none-any.whl (26.5 kB view details)

Uploaded Python 3

File details

Details for the file quickpaver-0.2.0.tar.gz.

File metadata

  • Download URL: quickpaver-0.2.0.tar.gz
  • Upload date:
  • Size: 48.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for quickpaver-0.2.0.tar.gz
Algorithm Hash digest
SHA256 20bfbbe88581d24d98f0938d690f9369a5310b7c1904c22806b174ac684bf922
MD5 e94a5198ec1f429b3697c0b5788a09dc
BLAKE2b-256 01898a075d2d0c88eb410adcacf6321de70229e80fdf0279e728bc0d4f0fb729

See more details on using hashes here.

File details

Details for the file quickpaver-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: quickpaver-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 26.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for quickpaver-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fee54bec01105d9a7694b170523a267db9c9f0bb4399826f4c260bb964337594
MD5 191eb9f4cfa878d8369df2554bd3d6db
BLAKE2b-256 7230e96f583da225ea5f1becd34be66201108d87ddc7d402e5f6ac83b8451991

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page