Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import math
from typing import Sequence
from .construction_brick_count_types import BrickCount, Opening
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .math_round_float import round_float ← from math.round-float ^1.0.0 · built alongside by fune
# A 215 x 65 mm brick face plus a 10 mm joint is 225 x 75 mm of wall.
FACE_MM2 = 225 * 75
# Mortar per mm2 of wall face, in mm3: each skin fills the joints around a
# brick, (16875 - 215 x 65) / 16875 of the face, 102.5 mm deep, which is
# 2378/135 mm; each collar joint between skins is 10 mm = 1350/135 mm.
SKIN_MORTAR = 2378
COLLAR_MORTAR = 1350
MORTAR_DIVISOR = 135
MM3_PER_LITRE = 1000000
def _millimetres(value: float, message: str, maximum: int) -> int:
if (
isinstance(value, bool)
or not isinstance(value, (int, float))
or not math.isfinite(value)
or value <= 0
or value > maximum
):
raise ValueError(
"%s must be a finite number greater than 0 and at most %d metres, received %r" % (message, maximum, value)
)
return int(round(round_float(value, 3) * 1000))
def brick_count(
wall_length: float, wall_height: float, openings: Sequence[Opening], skins: int, wastage_basis_points: int
) -> BrickCount:
"""Bricks and mortar for a wall of UK standard bricks, openings subtracted.
The count is the net face area over one brick's 225 x 75 mm share of it,
59.26 per m2 per skin (the trade's "60 a metre"), times the skins, plus
wastage, rounded up. Mortar is the joint volume that geometry implies.
Everything is whole millimetres and integer arithmetic.
"""
length = _millimetres(wall_length, "wallLength", 1000)
height = _millimetres(wall_height, "wallHeight", 100)
if isinstance(skins, bool) or not isinstance(skins, int) or not (1 <= skins <= 4):
raise ValueError("skins must be a whole number from 1 to 4, received %r" % (skins,))
if (
isinstance(wastage_basis_points, bool)
or not isinstance(wastage_basis_points, int)
or not (0 <= wastage_basis_points <= 10000)
):
raise ValueError("wastageBasisPoints must be a whole number from 0 to 10000, received %r" % (wastage_basis_points,))
net = length * height
for i, opening in enumerate(openings):
w = _millimetres(opening.width, "opening %d: width" % (i + 1), 1000)
h = _millimetres(opening.height, "opening %d: height" % (i + 1), 1000)
net -= w * h
if net < 0:
raise ValueError("openings are larger than the wall")
bricks = round_div(net * skins * (10000 + wastage_basis_points), FACE_MM2 * 10000, "up")
per_mm2 = skins * SKIN_MORTAR + (skins - 1) * COLLAR_MORTAR
# Up to the whole litre first, then the wastage, so no product passes 2^53.
litres = round_div(net * per_mm2, MORTAR_DIVISOR * MM3_PER_LITRE, "up")
mortar = round_div(litres * (10000 + wastage_basis_points), 10000, "up")
return BrickCount(
net_area_square_metres=net / 1000000,
bricks=bricks,
mortar_cubic_metres=mortar / 1000,
)