from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from .boundary import TOL, area, centroid, inertias, close_points, rotate2
__all__ = [
'multi_dimensions',
'multi_area',
'multi_centroid',
'multi_inertias',
'multi_principal_inertias',
'multi_principal_angles',
'multi_gyradii',
'multi_principal_gyradii',
'multi_extreme_fibers',
'multi_principal_extreme_fibers',
'multi_elast_sect_mod',
'multi_principal_elast_sect_mod',
'multi_plot_section',
'multi_section_summary'
]
def multi_dimensions(add):
"""
Returns the width and height of the section defined by the input boundary
points.
Parameters
----------
add : array
An array of (x, y) coordinates of shape (N, 2).
"""
mn = np.array([np.min(x, axis=0) for x in add])
mx = np.array([np.max(x, axis=0) for x in add])
mn, mx = np.min(mn, axis=0), np.max(mx, axis=0)
return mx - mn
[docs]def multi_area(add, subtract=[]):
"""
Returns the total cross sectional area for multiple shapes.
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
a_add = sum(area(x) for x in add)
a_sub = sum(area(x) for x in subtract)
return a_add - a_sub
[docs]def multi_centroid(add, subtract=[]):
"""
Returns the centroid for cross sections including multiple shapes.
Returns an array of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
# Added areas
a = np.array([area(x) for x in add])
a = np.expand_dims(a, 1)
c = np.array([centroid(x) for x in add])
cx = np.sum(a * c, axis=0)
cy = np.sum(a)
# Subtracted areas
if len(subtract) > 0:
a = np.array([area(x) for x in subtract])
a = np.expand_dims(a, 1)
c = np.array([centroid(x) for x in subtract])
cx -= np.sum(a * c, axis=0)
cy -= np.sum(a)
return cx / cy
[docs]def multi_inertias(add, subtract=[], origin=None):
"""
Calculates the area moment of inertias for cross sections including
multiple shapes. Returns an array of shape (4,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
origin : array
The (x, y) origin about which the moment of inertias will be calculated.
The array should be of the shape (2,). If None, the origin will be
assumed to be located at the centroid of the section.
Returns
-------
inertia_x : float
The moment of inertia about the x-axis.
inertia_y : float
The moment of inertia about the y-axis.
inertia_j : float
The polar moment of inertia.
inertia_xy : float
The product of inertia.
"""
if origin is None:
origin = multi_centroid(add, subtract)
def inertia(points):
if len(points) == 0:
return np.zeros(4)
i = np.array([inertias(x, origin) for x in points])
return np.sum(i, axis=0)
a = inertia(add)
b = inertia(subtract)
return a - b
[docs]def multi_principal_inertias(add, subtract=[]):
"""
Calculates the principal area moment of inertias for multiple shapes.
Returns an array of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
ix, iy, ij, ixy = multi_inertias(add, subtract)
avg = 0.5*ij
diff = 0.5*(ix - iy)
diff = (diff**2 + ixy**2)**0.5
iu, iv = avg + diff, avg - diff
return np.array([iu, iv])
[docs]def multi_principal_angles(add, subtract=[]):
"""
Calculates the angles from the x-axis to the principal axes. Returns
array of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
ix, iy, _, ixy = multi_inertias(add, subtract)
diff = ix - iy
if abs(ixy) < TOL and abs(diff) < TOL:
alpha = 0
else:
alpha = 0.5*np.arctan2(-ixy, 0.5*diff)
beta = alpha + np.pi/2
return np.array([alpha, beta])
[docs]def multi_gyradii(add, subtract=[]):
"""
Calculates the radii of gyrations about the x and y axes. Returns
an array of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
a = multi_area(add, subtract)
i = multi_inertias(add, subtract)[:2]
return np.sqrt(i / a)
[docs]def multi_principal_gyradii(add, subtract=[]):
"""
Calculates the radii of gyrations about the principal axes. Returns
an array of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
a = multi_area(add, subtract)
i = multi_principal_inertias(add, subtract)[:2]
return np.sqrt(i / a)
[docs]def multi_extreme_fibers(add, subtract=[]):
"""
Calculates the extreme fibers from the x and y axes. Returns an array
of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
c = multi_centroid(add, subtract)
c = np.array([np.max(np.abs(x - c), axis=0) for x in add])
c = np.max(c, axis=0)
return np.flip(c, axis=0)
[docs]def multi_principal_extreme_fibers(add, subtract=[]):
"""
Calculates the extreme fibers from the principal axes. Returns an array
of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
alpha, _ = multi_principal_angles(add, subtract)
c = multi_centroid(add, subtract)
c = [np.max(np.abs(rotate2(x - c, alpha)), axis=0) for x in add]
c = np.max(np.array(c), axis=0)
return np.flip(c, axis=0)
[docs]def multi_elast_sect_mod(add, subtract=[]):
"""
Calculates the section modulii about the x and y axes. Returns an array
of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
i = multi_inertias(add, subtract)[:2]
c = multi_extreme_fibers(add, subtract=[])
return i / c
[docs]def multi_principal_elast_sect_mod(add, subtract=[]):
"""
Calculates the section modulii about the principal axes. Returns an array
of shape (2,).
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
"""
i = multi_principal_inertias(add, subtract)
c = multi_principal_extreme_fibers(add, subtract)
return i / c
[docs]def multi_plot_section(add, subtract=[], ax=None, title='', symbols={}):
"""
Plots a cross section consisting of multiple shapes.
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
ax : :class:`matplotlib.axes.Axes`
The axes to which the plot will be added. If None, a new figure
and axes will be created.
title : str
The title of the figure.
symbols : dict
A dictionary of symbols to use for the plot. Valid keys are:
* `add`: The boundary lines for included shapes, default is 'b-'.
* `subtract`: The boundary lines for subtracted shapes, default is 'r-'
* `centroid`: The centroid point, default is 'r+'.
* `primary_axes`: The primary axes lines, default is 'g-.'
* `principal_axes`: The pricipal axes lines, default is 'm-.'
"""
if ax is None:
fig = plt.figure()
ax = fig.add_subplot(111,
title=title,
xlabel='X',
ylabel='Y',
aspect='equal'
)
sym = dict(
add='b-',
subtract='r-',
centroid='',
primary_axes='g-.',
principal_axes='m-.'
)
sym.update(symbols)
# Plot boundary
for x in add:
x = close_points(x)
ax.plot(x[:,0], x[:,1], sym['add'])
for x in subtract:
x = close_points(x)
ax.plot(x[:,0], x[:,1], sym['subtract'])
# Plot centroid
o = multi_centroid(add, subtract)
if sym['centroid'] not in {'', None}:
ax.plot(o[0], o[1], sym['centroid'])
# Plot axes
ang = multi_principal_angles(add, subtract)
c = [multi_extreme_fibers(add, subtract),
multi_principal_extreme_fibers(add, subtract)]
c = 1.25 * max(map(np.max, c))
if sym['primary_axes'] not in {'', None}:
x = np.array([o + (0, -c), o + (0, c)])
ax.plot(x[:,0], x[:,1], sym['primary_axes'])
x = np.array([o + (-c, 0), o + (c, 0)])
ax.plot(x[:,0], x[:,1], sym['primary_axes'])
if sym['principal_axes'] not in {'', None} and np.min(np.abs(ang)) >= TOL:
du, dv = c * np.column_stack([np.cos(ang), np.sin(ang)])
x = np.array([o - du, o + du])
ax.plot(x[:,0], x[:,1], sym['principal_axes'])
x = np.array([o - dv, o + dv])
ax.plot(x[:,0], x[:,1], sym['principal_axes'])
return ax
[docs]def multi_section_summary(add, subtract=[]):
"""
Returns a dictionary with a summary of cross sectional properties
for the composite shape.
Parameters
----------
add : list
A list of (x, y) boundary coordinates for shapes included in the
cross section. Each set of boundary coordinates should be of the
shape (N, 2).
subtract : list
A list of (x, y) boundary coordinates for cut out shapes to be
subtracted from the cross section. Each set of boundary coordinates
should be of the shape (N, 2).
Returns
-------
area : float
The cross sectional area.
x, y : float
The x and y centroid coordinates.
interia_x, inertia_y : float
The moment of inertias about the x and y axes.
inertia_j : float
The polar moment of inertia.
inertia_xy : float
The product of inertia.
inertia_z : float
The moment of inertias about the weak principal axis.
gyradius_x, gyradius_y : float
The radii of gyrations about the x and y axes.
gyradius_z : float
The radii of gyrations about the weak principal axis.
elast_sect_mod_x, elast_sect_mod_y : float
The elastic section modulii about the x and y axes.
elast_sect_mod_z : float
The elastic section modulus about the weak principal axis.
"""
add = [close_points(x) for x in add]
subtract = [close_points(x) for x in subtract]
a = multi_area(add, subtract)
x, y = multi_centroid(add, subtract)
w, h = multi_dimensions(add)
ix, iy, ij, ixy = multi_inertias(add, subtract)
iz = np.min(multi_principal_inertias(add, subtract))
rx, ry = multi_gyradii(add, subtract)
rz = np.min(multi_principal_gyradii(add, subtract))
sx, sy = multi_elast_sect_mod(add, subtract)
sz = np.min(multi_principal_elast_sect_mod(add, subtract))
summary = dict(
area=a, x=x, y=y, width=w, height=h,
inertia_x=ix, inertia_y=iy, inertia_j=ij, inertia_xy=ixy, inertia_z=iz,
gyradius_x=rx, gyradius_y=ry, gyradius_z=rz,
elast_sect_mod_x=sx, elast_sect_mod_y=sy, elast_sect_mod_z=sz
)
return summary