import math from typing import Sequence from .construction_brick_count_types import BrickCount, Opening from .math_round_div import round_div from .math_round_float import round_float # 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, )