solids.py
"""
Solids classes.
Library Version 1.0
Copyright 2013 Paul Griffiths
Email: mail@paulgriffiths.net
Distributed under the terms of the GNU General Public License.
http://www.gnu.org/licenses/
"""
from math import pi, sqrt, tan
from geometry.materials import DefaultMaterial
from geometry.conversions import validate_length, validate_area
from geometry.conversions import validate_volume, validate_weight, convert
from geometry.shapes import Rectangle, Circle
class _Solid():
"""
Base solid class.
"""
def __init__(self, units, material):
"""
Class initializer.
Arguments:
units -- a string containing the length units for the
shape, e.g. "mm", "km", "furlong", "mile"
material -- a material object containing the material
with which to construct the solid
"""
validate_length(units)
self._units = units
self._units_area = "{0}2".format(units)
self._units_volume = "{0}3".format(units)
self._material = material
def _volume_internal_units(self):
"""
Virtual method - returns the volume of the solid in internal units.
"""
return 1
def _surface_area_internal_units(self):
"""
Virtual method - returns the surface area of the solid
in internal units.
"""
return 1
def volume(self, volumeunits=None):
"""
Returns the volume of the cuboid in user units.
Arguments:
volumeunits -- units in which to return volume, default is
cubed internal length units.
"""
if not volumeunits:
volumeunits = self._units_volume
else:
validate_volume(volumeunits)
volume_int = self._volume_internal_units()
return convert(volume_int, self._units_volume, volumeunits)
def surface_area(self, areaunits=None):
"""
Returns the surface area of the cuboid in user units.
Arguments:
areaunits -- units in which to return surface area, default is
squared internal length units.
"""
if not areaunits:
areaunits = self._units_area
else:
validate_area(areaunits)
area_int = self._surface_area_internal_units()
return convert(area_int, self._units_area, areaunits)
def weight(self, weightunits="grams"):
"""
Virtual method - returns the weight of the solid.
"""
validate_weight(weightunits)
return self._material.weight(self.volume("cc"), "cc", weightunits)
class Cuboid(_Solid):
"""
Cuboid class.
"""
def __init__(self, edges, units="cm", material=DefaultMaterial()):
"""
Class initializer.
Arguments:
edges --- a three-element tuple containing the lengths of
the edges of the cuboid
units -- a string containing the length units for the
shape, e.g. "mm", "km", "furlong", "mile"
material -- a material object containing the material
with which to construct the solid
"""
_Solid.__init__(self, units, material)
self._edge_a, self._edge_b, self._edge_c = edges
def _volume_internal_units(self):
"""
Returns the volume of the cuboid in cubic internal units.
"""
return self._edge_a * self._edge_b * self._edge_c
def _surface_area_internal_units(self):
"""
Returns the surface area of the cuboid in squared internal units.
"""
return (Rectangle((self._edge_a, self._edge_b)).area() +
Rectangle((self._edge_b, self._edge_c)).area() +
Rectangle((self._edge_c, self._edge_a)).area()) * 2
class Cube(Cuboid):
"""
Cube class.
"""
def __init__(self, edge, units="cm", material=DefaultMaterial()):
"""
Class initializer.
Arguments:
edge --- the length of the edges of the cube
units -- a string containing the length units for the
shape, e.g. "mm", "km", "furlong", "mile"
material -- a material object containing the material
with which to construct the solid
"""
Cuboid.__init__(self, (edge, edge, edge), units, material)
class Sphere(_Solid):
"""
Sphere class.
"""
def __init__(self, radius, units="cm", material=DefaultMaterial()):
"""
Class initializer.
Arguments:
radius --- the radius of the sphere
units -- a string containing the length units for the
shape, e.g. "mm", "km", "furlong", "mile"
material -- a material object containing the material
with which to construct the solid
"""
_Solid.__init__(self, units, material)
self._radius = radius
def _volume_internal_units(self):
"""
Returns the volume of the sphere in cubic internal units.
"""
return self._radius ** 3 * pi * 4 / 3
def _surface_area_internal_units(self):
"""
Returns the surface area of the sphere in squared internal units.
"""
return Circle(self._radius).area() * 4
class Cone(_Solid):
"""
Cone class.
"""
def __init__(self, radius, height, units="cm",
material=DefaultMaterial()):
"""
Class initializer.
Arguments:
radius --- the radius of the base of the cone
height --- the height of the cone
units -- a string containing the length units for the
shape, e.g. "mm", "km", "furlong", "mile"
material -- a material object containing the material
with which to construct the solid
"""
_Solid.__init__(self, units, material)
self._radius = radius
self._height = height
def _volume_internal_units(self):
"""
Returns the volume of the cone in cubic internal units.
"""
return Circle(self._radius).area() * self._height / 3
def _surface_area_internal_units(self):
"""
Returns the surface area of the cone in squared internal units.
"""
hyp = sqrt(self._radius ** 2 + self._height ** 2)
return self._radius * pi * hyp + Circle(self._radius).area()
class Cylinder(_Solid):
"""
Cylinder class.
"""
def __init__(self, radius, height, units="cm",
material=DefaultMaterial()):
"""
Class initializer.
Arguments:
radius --- the radius of the bases of the cylinder
height --- the height (or length) of the cylinder
units -- a string containing the length units for the
shape, e.g. "mm", "km", "furlong", "mile"
material -- a material object containing the material
with which to construct the solid
"""
_Solid.__init__(self, units, material)
self._radius = radius
self._height = height
def _volume_internal_units(self):
"""
Returns the volume of the cylinder in cubic internal units.
"""
return Circle(self._radius).area() * self._height
def _surface_area_internal_units(self):
"""
Returns the surface area of the cylinder in squared internal units.
"""
return (self._lateral_sa_internal_units() +
self._base_sa_internal_units() * 2)
def _lateral_sa_internal_units(self):
"""
Returns the lateral surface area of the cylinder in squared
internal units.
"""
return Circle(self._radius).perimeter() * self._height
def _base_sa_internal_units(self):
"""
Returns the base surface area of the cylinder in squared
internal units.
"""
return Circle(self._radius).area()
def lateral_surface_area(self, areaunits=None):
"""
Returns the lateral surface area of the cylinder in user units.
Arguments:
areaunits -- units in which to return surface area, default is
squared internal length units.
"""
if not areaunits:
areaunits = self._units_area
else:
validate_area(areaunits)
area_int = self._lateral_sa_internal_units()
return convert(area_int, self._units_area, areaunits)
def base_surface_area(self, areaunits=None):
"""
Returns the base surface area of the cylinder in user units.
Arguments:
areaunits -- units in which to return surface area, default is
squared internal length units.
"""
if not areaunits:
areaunits = self._units_area
else:
validate_area(areaunits)
area_int = self._base_sa_internal_units()
return convert(area_int, self._units_area, areaunits)
class Tube(_Solid):
"""
Tube class.
"""
def __init__(self, radii, length, units="cm",
material=DefaultMaterial()):
"""
Class initializer.
Arguments:
radii --- a two-element tuple containing firstly the outer
radius of the tube, and secondly the inner radius of the tube
length --- the length of the tube
units -- a string containing the length units for the
shape, e.g. "mm", "km", "furlong", "mile"
material -- a material object containing the material
with which to construct the solid
"""
_Solid.__init__(self, units, material)
self._radius_outer, self._radius_inner = radii
self._length = length
def _volume_internal_units(self):
"""
Returns the volume of the tube in cubic internal units.
"""
return (Cylinder(self._radius_outer, self._length).volume() -
Cylinder(self._radius_inner, self._length).volume())
def _surface_area_internal_units(self):
"""
Returns the surface area of the tube in squared internal units.
"""
r_o, r_i = self._radius_outer, self._radius_inner
length = self._length
end = (Cylinder(r_o, length).base_surface_area() -
Cylinder(r_i, length).base_surface_area())
lat = (Cylinder(r_o, length).lateral_surface_area() +
Cylinder(r_i, length).lateral_surface_area())
return lat + end * 2
def segment_extrados(self, nominalradius, angle):
"""
Returns the extrados dimension of a tube segment of
a specified angle where those segments would form a
tube bend with a specified nominal radius.
Arguments:
nominalradius -- the nominal radius of the bend
angle -- the segment angle, in radians
"""
return self._segment_dimension("extra", nominalradius, angle)
def segment_intrados(self, nominalradius, angle):
"""
Returns the intrados dimension of a tube segment of
a specified angle where those segments would form a
tube bend with a specified nominal radius.
Arguments:
nominalradius -- the nominal radius of the bend
angle -- the segment angle, in radians
"""
return self._segment_dimension("inner", nominalradius, angle)
def segment_mean_length(self, nominalradius, angle):
"""
Returns the mean length dimension of a tube segment of
a specified angle where those segments would form a
tube bend with a specified nominal radius.
Arguments:
nominalradius -- the nominal radius of the bend
angle -- the segment angle, in radians
"""
return self._segment_dimension("mean", nominalradius, angle)
def _segment_dimension(self, dim, nominalradius, angle):
"""
Returns a specified dimension of a tube segment of
a specified angle where those segments would form a
tube bend with a specified nominal radius.
Arguments:
dim -- the dimension to return, either "extra", "inner", or "mean"
nominalradius -- the nominal radius of the bend
angle -- the segment angle, in radians
"""
if dim == "extra":
rad = nominalradius + self._radius_outer
elif dim == "inner":
rad = nominalradius - self._radius_outer
else:
rad = nominalradius
return rad * tan(angle / 2) * 2
conversions.py
"""
Conversions functions.
Library Version 1.0
Copyright 2013 Paul Griffiths
Email: mail@paulgriffiths.net
Distributed under the terms of the GNU General Public License.
http://www.gnu.org/licenses/
"""
_CM = 1
_MM = _CM / 10.0
_M = _CM * 100
_KM = _M * 1000
_NMI = _M * 1852
_IN = _CM * 2.54
_PT = _IN / 72.0
_PI = _IN / 6.0
_FT = _IN * 12
_YD = _FT * 3
_FTM = _YD * 2
_FUR = _YD * 220
_MI = _FT * 5280
_LEA = _MI * 3
_CC = _CM ** 3
_L = _CC * 1000
_GAL = _CM * 4546.09
_CI = _IN ** 3
_USGAL = _CI * 231
_G = 1
_KG = _G * 1000
_MG = _G / 1000.0
_LB = _KG / 2.2046213
_O = _LB / 16.0
_GRAIN = _O / 437.5
_LENGTHS = {"point": _PT,
"points": _PT,
"pt": _PT,
"pts": _PT,
"pica": _PI,
"picas": _PI,
"pi": _PI,
"mm": _MM,
"millimetre": _MM,
"millimetres": _MM,
"millimeter": _MM,
"millimeters": _MM,
"cm": _CM,
"centimetre": _CM,
"centimetres": _CM,
"centimeter": _CM,
"centimeters": _CM,
"inch": _IN,
"inches": _IN,
"in": _IN,
"foot": _FT,
"feet": _FT,
"ft": _FT,
"yard": _YD,
"yards": _YD,
"yd": _YD,
"m": _M,
"metre": _M,
"metres": _M,
"meter": _M,
"meters": _M,
"fathom": _FTM,
"fathoms": _FTM,
"ftm": _FTM,
"furlong": _FUR,
"furlongs": _FUR,
"fur": _FUR,
"km": _KM,
"kilometre": _KM,
"kilometres": _KM,
"kilometer": _KM,
"kilometers": _KM,
"mile": _MI,
"miles": _MI,
"mi": _MI,
"nm": _NMI,
"nmi": _NMI,
"nautmile": _NMI,
"nautmiles": _NMI,
"nauticalmile": _NMI,
"nauticalmiles": _NMI,
"league": _LEA,
"leagues": _LEA,
"lea": _LEA}
_AREAS = {"point2": _PT ** 2,
"points2": _PT ** 2,
"pt2": _PT ** 2,
"pts2": _PT ** 2,
"pica2": _PI ** 2,
"picas2": _PI ** 2,
"pi2": _PI ** 2,
"mm2": _MM ** 2,
"millimetre2": _MM ** 2,
"millimetres2": _MM ** 2,
"millimeter2": _MM ** 2,
"millimeters2": _MM ** 2,
"cm2": _CM ** 2,
"centimetre2": _CM ** 2,
"centimetres2": _CM ** 2,
"centimeter2": _CM ** 2,
"centimeters2": _CM ** 2,
"inch2": _IN ** 2,
"inches2": _IN ** 2,
"in2": _IN ** 2,
"foot2": _FT ** 2,
"feet2": _FT ** 2,
"ft2": _FT ** 2,
"yard2": _YD ** 2,
"yards2": _YD ** 2,
"yd2": _YD ** 2,
"m2": _M ** 2,
"metre2": _M ** 2,
"metres2": _M ** 2,
"meter2": _M ** 2,
"meters2": _M ** 2,
"centiare": _M ** 2,
"ca": _M ** 2,
"fathom2": _FTM ** 2,
"fathoms2": _FTM ** 2,
"ftm2": _FTM ** 2,
"acre": _FT ** 2 * 43560,
"acres": _FT ** 2 * 43560,
"ac": _FT ** 2 * 43560,
"are": _M ** 2 * 100,
"ares": _M ** 2 * 100,
"a": _M ** 2 * 100,
"decare": _M ** 2 * 1000,
"decares": _M ** 2 * 1000,
"daa": _M ** 2 * 1000,
"hectare": _M ** 2 * 10000,
"hectares": _M ** 2 * 10000,
"ha": _M ** 2 * 10000,
"furlong2": _FUR ** 2,
"furlongs2": _FUR ** 2,
"fur2": _FUR ** 2,
"km2": _KM ** 2,
"kilometre2": _KM ** 2,
"kilometres2": _KM ** 2,
"kilometer2": _KM ** 2,
"kilometers2": _KM ** 2,
"mile2": _MI ** 2,
"miles2": _MI ** 2,
"mi2": _MI ** 2,
"nmi2": _NMI ** 2,
"nautmile2": _NMI ** 2,
"nautmiles2": _NMI ** 2,
"nauticalmile2": _NMI ** 2,
"nauticalmiles2": _NMI ** 2,
"league2": _LEA ** 2,
"leagues2": _LEA ** 2,
"lea2": _LEA ** 2}
_VOLUMES = {"point3": _PT ** 3,
"points3": _PT ** 3,
"pt3": _PT ** 3,
"pts3": _PT ** 3,
"pica3": _PI ** 3,
"picas3": _PI ** 3,
"pi3": _PI ** 3,
"mm3": _MM ** 3,
"millimetre3": _MM ** 3,
"millimetres3": _MM ** 3,
"millimeter3": _MM ** 3,
"millimeters3": _MM ** 3,
"ml": _CC,
"millilitre": _CC,
"millilitres": _CC,
"milliliter": _CC,
"milliliters": _CC,
"cm3": _CC,
"cc": _CC,
"centimetre3": _CC,
"centimetres3": _CC,
"centimeter3": _CC,
"centimeters3": _CC,
"inch3": _CI,
"inches3": _CI,
"in3": _CI,
"uspint": _USGAL / 8.0,
"uspints": _USGAL / 8.0,
"uspt": _USGAL / 8.0,
"uspts": _USGAL / 8.0,
"usquart": _USGAL / 4.0,
"usquarts": _USGAL / 4.0,
"usqt": _USGAL / 4.0,
"usqts": _USGAL / 4.0,
"usgallon": _USGAL,
"usgallons": _USGAL,
"usgal": _USGAL,
"usgals": _USGAL,
"pint": _GAL / 8.0,
"pints": _GAL / 8.0,
"pt": _GAL / 8.0,
"pts": _GAL / 8.0,
"quart": _GAL / 4.0,
"quarts": _GAL / 4.0,
"qt": _GAL / 4.0,
"qts": _GAL / 4.0,
"gallon": _GAL,
"gallons": _GAL,
"gal": _GAL,
"gals": _GAL,
"l": _L,
"litre": _L,
"litres": _L,
"liter": _L,
"liters": _L,
"foot3": _FT ** 3,
"feet3": _FT ** 3,
"ft3": _FT ** 3,
"yard3": _YD ** 3,
"yards3": _YD ** 3,
"yd3": _YD ** 3,
"m3": _M ** 3,
"metre3": _M ** 3,
"metres3": _M ** 3,
"meter3": _M ** 3,
"meters3": _M ** 3,
"fathom3": _FTM ** 3,
"fathoms3": _FTM ** 3,
"ftm3": _FTM ** 3,
"furlong3": _FUR ** 3,
"furlongs3": _FUR ** 3,
"fur3": _FUR ** 3,
"km3": _KM ** 3,
"kilometre3": _KM ** 3,
"kilometres3": _KM ** 3,
"kilometer3": _KM ** 3,
"kilometers3": _KM ** 3,
"mile3": _MI ** 3,
"miles3": _MI ** 3,
"mi3": _MI ** 3,
"nmi3": _NMI ** 3,
"nautmile3": _NMI ** 3,
"nautmiles3": _NMI ** 3,
"nauticalmile3": _NMI ** 3,
"nauticalmiles3": _NMI ** 3,
"league3": _LEA ** 3,
"leagues3": _LEA ** 3,
"lea3": _LEA ** 3}
_WEIGHTS = {"gram": _G,
"grams": _G,
"g": _G,
"decigram": _G / 10.0,
"decigrams": _G / 10.0,
"dg": _G / 10.0,
"centigram": _G / 100.0,
"centigrams": _G / 100.0,
"cg": _G / 100.0,
"milligram": _MG,
"milligrams": _MG,
"mg": _MG,
"decagram": _G * 10,
"decagrams": _G * 10,
"dag": _G * 10,
"hectogram": _G * 100,
"hectograms": _G * 100,
"hg": _G * 100,
"kilogram": _KG,
"kilograms": _KG,
"kg": _KG,
"tonne": _KG * 1000,
"megagram": _KG * 1000,
"t": _KG * 1000,
"grain": _GRAIN,
"grains": _GRAIN,
"gr": _GRAIN,
"dram": _O / 16.0,
"drams": _O / 16.0,
"dr": _O / 16.0,
"ounce": _O,
"ounces": _O,
"oz": _O,
"pound": _LB,
"pounds": _LB,
"lb": _LB,
"lbs": _LB,
"hundredweight": _LB * 100,
"cwt": _LB * 100,
"ton": _LB * 2000}
_COLLECTIONS = [_LENGTHS, _AREAS, _VOLUMES, _WEIGHTS]
class InvalidUnit(Exception):
"""
Exception for unrecognized unit.
"""
pass
class InvalidLengthUnit(InvalidUnit):
"""
Exception for unrecognized length unit.
"""
pass
class InvalidAreaUnit(InvalidUnit):
"""
Exception for unrecognized area unit.
"""
pass
class InvalidVolumeUnit(InvalidUnit):
"""
Exception for unrecognized volume unit.
"""
pass
class InvalidWeightUnit(InvalidUnit):
"""
Exception for unrecognized weight unit.
"""
pass
class IncompatibleUnits(Exception):
"""
Exception for incompatible units (e.g. comparing miles to gallons).
"""
pass
def validate_length(unit):
"""
Checks if a length unit is recognized.
Arguments:
unit -- string containing the length unit to check
"""
if not unit in _LENGTHS:
raise InvalidLengthUnit(unit)
def validate_area(unit):
"""
Checks if an area unit is recognized.
Arguments:
unit -- string containing the area unit to check
"""
if not unit in _AREAS:
raise InvalidAreaUnit(unit)
def validate_volume(unit):
"""
Checks if a volume unit is recognized.
Arguments:
unit -- string containing the volume unit to check
"""
if not unit in _VOLUMES:
raise InvalidVolumeUnit(unit)
def validate_weight(unit):
"""
Checks if a weight unit is recognized.
Arguments:
unit -- string containing the weight unit to check
"""
if not unit in _WEIGHTS:
raise InvalidWeightUnit(unit)
def validate_unit(unit):
"""
Checks if a unit is recognized.
Arguments:
unit -- string containing the unit to check
"""
for coll in _COLLECTIONS:
if unit in coll:
return
raise InvalidUnit(unit)
def validate_compatible_units(unit1, unit2):
"""
Checks if two units are of a consistent type.
Arguments:
unit1, unit2 -- strings containing the units to check
"""
for unit in [unit1, unit2]:
validate_unit(unit)
for coll in _COLLECTIONS:
if unit1 in coll and unit2 in coll:
return
raise IncompatibleUnits("{0} and {1}".format(unit1, unit2))
def conversion_factor(unitfrom, unitto):
"""
Returns a conversion factor for two units.
Arguments:
unitfrom -- string containing the unit from which to convert
unitto -- string containing the unit to which to convert
"""
unitfrom = unitfrom.lower()
unitto = unitto.lower()
validate_compatible_units(unitfrom, unitto)
for coll in _COLLECTIONS:
if unitfrom in coll and unitto in coll:
return coll[unitfrom] / float(coll[unitto])
def convert(quantity, unitfrom, unitto):
"""
Returns a converted quantity.
Arguments:
quantity -- quantity to convert, in 'unitfrom' units
unitfrom -- string containing the unit from which to convert
unitto -- string containing the unit to which to convert
"""
return quantity * conversion_factor(unitfrom, unitto)