Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 44 additions & 14 deletions sectionproperties/pre/geometry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations
from ntpath import join
from typing import List, Optional, Union, Tuple, Any
from typing import List, Optional, Union, Tuple, List, Any

import copy
import math
Expand Down Expand Up @@ -479,31 +479,47 @@ def align_to(

return new_geom

def align_center(self, align_to: Optional[Geometry] = None):
def align_center(
self, align_to: Optional[Union[Geometry, Tuple[float, float]]] = None
):
"""
Returns a new Geometry object, translated in both x and y, so that the
center-point of the new object's centroid will be aligned with
centroid of the object in 'align_to'. If 'align_to' is None then the new
object will be aligned with it's centroid at the origin.
the new object's centroid will be aligned with the centroid of the object
in 'align_to'. If 'align_to' is an x, y coordinate, then the centroid will
be aligned to the coordinate. If 'align_to' is None then the new
object will be aligned with its centroid at the origin.

:param align_to: Another Geometry to align to or None (default is None)
:type align_to: Optional[:class:`~sectionproperties.pre.geometry.Geometry`]
:type align_to: Optional[Union[:class:`~sectionproperties.pre.geometry.Geometry`, Tuple[float, float]]]

:return: Geometry object translated to new alignment
:rtype: :class:`~sectionproperties.pre.geometry.Geometry`
"""
cx, cy = list(self.geom.centroid.coords)[0]
# Suggested by Agent 6-6-6: Hard-rounding of cx and cy allows
# Suggested by @Agent6-6-6: Hard-rounding of cx and cy allows
# for greater precision in placing geometry with its centroid
# near [0, 0]. True [0, 0] placement will not be possible due
# to floating point errors.
if align_to is None:
shift_x, shift_y = round(-cx, self.tol), round(-cy, self.tol)

else:
elif isinstance(align_to, Geometry):
align_cx, align_cy = list(align_to.geom.centroid.coords)[0]
shift_x = round(align_cx - cx, self.tol)
shift_y = round(align_cy - cy, self.tol)

else:
try:
point_x, point_y = align_to
shift_x = round(point_x - cx, self.tol)
shift_y = round(point_y - cy, self.tol)
except (
TypeError,
ValueError,
): # align_to not subscriptable, incorrect length, etc.
raise ValueError(
f"align_to must be either a Geometry object or an x, y coordinate, not {align_to}."
)
new_geom = self.shift_section(x_offset=shift_x, y_offset=shift_y)
return new_geom

Expand Down Expand Up @@ -1555,20 +1571,23 @@ def mirror_section(
new_geom = CompoundGeometry(geoms_acc)
return new_geom

def align_center(self, align_to: Optional[Geometry] = None):
def align_center(
self, align_to: Optional[Union[Geometry, Tuple[float, float]]] = None
):
"""
Returns a new CompoundGeometry object, translated in both x and y, so that the
center-point of the new object's material-weighted centroid will be aligned with
centroid of the object in 'align_to'. If 'align_to' is None then the new
object will be aligned with it's centroid at the origin.
centroid of the object in 'align_to'. If 'align_to' is an x, y coordinate, then
the centroid will be aligned to the coordinate. If 'align_to' is None then the new
object will be aligned with its centroid at the origin.

Note: The material-weighted centroid refers to when individual geometries within
the CompoundGeometry object have been assigned differing materials. The centroid
of the compound geometry is calculated by using the E modulus of each
geometry's assigned material.

:param align_to: Another Geometry to align to or None (default is None)
:type align_to: Optional[:class:`~sectionproperties.pre.geometry.Geometry`]
:param align_to: Another Geometry to align to, an xy coordinate, or None (default is None)
:type align_to: Optional[Union[:class:`~sectionproperties.pre.geometry.Geometry`, Tuple[float, float]]]

:return: Geometry object translated to new alignment
:rtype: :class:`~sectionproperties.pre.geometry.Geometry`
Expand Down Expand Up @@ -1597,10 +1616,21 @@ def align_center(self, align_to: Optional[Geometry] = None):
round(-weighted_cy, self.tol),
)

else:
elif isinstance(Geometry):
align_cx, align_cy = list(align_to.geom.centroid.coords)[0]
shift_x = round(align_cx - weighted_cx, self.tol)
shift_y = round(align_cy - weighted_cy, self.tol)

else:
try:
point_x, point_y = align_to
shift_x = round(point_x - weighted_cx, self.tol)
shift_y = round(point_y - weighted_cy, self.tol)
except (TypeError, ValueError):
raise ValueError(
f"align_to must be either a Geometry object or an x, y coordinate, not {align_to}."
)

new_geom = self.shift_section(x_offset=shift_x, y_offset=shift_y)
return new_geom

Expand Down
21 changes: 21 additions & 0 deletions sectionproperties/tests/test_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,24 @@ def test_warping_disjoint_warning():
sec.calculate_geometric_properties()
with pytest.warns(UserWarning):
sec.calculate_warping_properties()


def test_align_center():
rect = rectangular_section(d=200, b=70)
circ = circular_section(d=200, n=30)
rect = rect.rotate_section(-45, rot_point=[0, 0])
rect_point = rect.points[1]
circ = circ.align_center(rect_point)
circ_x, circ_y = circ.calculate_centroid()
assert pytest.approx(circ_x) == 49.497474683057995
assert pytest.approx(circ_y) == -49.49747468305799

circ = circ.align_center()
circ_x, circ_y = circ.calculate_centroid()
assert pytest.approx(circ_x) == 0
assert pytest.approx(circ_y) == 0

circ = circ.align_center(rect)
circ_x, circ_y = circ.calculate_centroid()
assert pytest.approx(circ_x) == 95.45941546018399
assert pytest.approx(circ_y) == 45.961940777125974
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ rhino =

test =
rhino-shapley-interop>=0.0.4
pytest
pytest_check