Skip to content

Commit 0fbd9a2

Browse files
committed
Fix: hole destruction not being recognized caused by subtractive modelling (#192); Fix: Geometries with overlapping regions generate a warning about potentially incorrect results when plastic properties are calculated. Tests: Tests added to verify warnings raised with plastic properties calcs with overlapping regions and to verify hole destruction
1 parent 8655277 commit 0fbd9a2

File tree

3 files changed

+76
-9
lines changed

3 files changed

+76
-9
lines changed

sectionproperties/analysis/section.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import copy
44
from dataclasses import dataclass, asdict
5+
import warnings
56

67
import matplotlib.pyplot as plt
78
import matplotlib.tri as tri
@@ -826,6 +827,20 @@ def calculate_plastic_properties(self, verbose=False, debug=False):
826827
err = "Calculate geometric properties before performing a plastic analysis."
827828
raise RuntimeError(err)
828829

830+
# check if any geometry are overlapped
831+
if isinstance(self.geometry, section_geometry.CompoundGeometry):
832+
polygons = [sec_geom.geom for sec_geom in self.geometry.geoms]
833+
overlapped_regions = section_geometry.check_geometry_overlaps(polygons)
834+
if overlapped_regions:
835+
warnings.warn(
836+
"\nThe section geometry contains overlapping regions and the area of these overlapped regions "
837+
"will be 'double counted' in the plastic analysis which may result in incorrect values.\n"
838+
"If you do not intend for this double counting to occur, use a subtractive modelling approach "
839+
"to remove the overlapping region.\n"
840+
"Please see https://sectionproperties.readthedocs.io/en/latest/rst/advanced_geom.html for more "
841+
"information."
842+
)
843+
829844
def calc_plastic():
830845
plastic_section = PlasticSection(self.geometry)
831846
plastic_section.calculate_plastic_properties(self, verbose)
@@ -2002,6 +2017,7 @@ def calculate_plastic_properties(self, section, verbose):
20022017
:param bool verbose: If set to True, the number of iterations required for each plastic
20032018
axis is printed to the terminal.
20042019
"""
2020+
20052021
# 1) Calculate plastic properties for centroidal axis
20062022
# calculate distances to the extreme fibres
20072023
fibres = self.calculate_extreme_fibres(

sectionproperties/pre/geometry.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,11 @@ def compile_geometry(self):
16771677
)
16781678

16791679
elif isinstance(unionized_poly, Polygon):
1680-
inadvertent_holes += Geometry(unionized_poly).holes
1680+
if Geometry(unionized_poly).holes:
1681+
inadvertent_holes += Geometry(unionized_poly).holes
1682+
else: # Holes have been destroyed through operations
1683+
inadvertent_holes = []
1684+
self.holes = []
16811685

16821686
extra_holes = []
16831687
if set(inadvertent_holes) - set(self.holes):
@@ -1872,3 +1876,14 @@ def round_polygon_vertices(poly: Polygon, tol: int) -> Polygon:
18721876
if not rounded_exterior.any():
18731877
return Polygon()
18741878
return Polygon(rounded_exterior, rounded_interiors)
1879+
1880+
1881+
def check_geometry_overlaps(lop: List[Polygon]) -> bool:
1882+
"""
1883+
Returns True if any of the Polygon in the list of Polygons, 'lop',
1884+
are overlapping with any other Polygons in 'lop'. Returns False
1885+
if all Polygon are disjoint.
1886+
"""
1887+
union_area = unary_union(lop).area
1888+
sum_polygons = sum([poly.area for poly in lop])
1889+
return union_area != sum_polygons

sectionproperties/tests/test_sections.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,29 @@
2121

2222
big_sq = rectangular_section(d=300, b=250)
2323
small_sq = rectangular_section(d=100, b=75)
24-
small_hole = rectangular_section(d=40, b=30)
24+
small_hole = rectangular_section(d=40, b=30).align_center(small_sq)
2525
i_sec = i_section(d=200, b=100, t_f=20, t_w=10, r=12, n_r=12)
26-
27-
small_sq = small_sq - small_hole.align_center(small_sq)
26+
small_sq_w_hole = small_sq - small_hole
2827
composite = (
2928
big_sq
30-
+ small_sq.align_to(big_sq, on="top", inner=True).align_to(big_sq, on="top")
29+
+ small_sq_w_hole.align_to(big_sq, on="top", inner=True).align_to(big_sq, on="top")
3130
+ i_sec.align_to(big_sq, on="bottom", inner=True).align_to(big_sq, on="right")
3231
)
33-
composite.compile_geometry()
3432
composite.create_mesh([200])
3533
comp_sec = Section(composite)
3634
comp_sec.calculate_geometric_properties()
3735
comp_sec.calculate_plastic_properties()
3836

37+
# Subtractive modelling
38+
nested_geom = (small_sq - small_hole) + small_hole
39+
nested_geom.create_mesh([50])
40+
nested_sec = Section(nested_geom)
41+
42+
# Overlapped modelling
43+
overlay_geom = small_sq + small_hole
44+
overlay_geom.create_mesh([50])
45+
overlay_sec = Section(overlay_geom)
46+
3947

4048
def test_material_persistence():
4149
# Test ensures that the material attribute gets transformed
@@ -61,6 +69,7 @@ def test_for_incidental_holes():
6169
# a I Section up against a rectangle.
6270
# There should be two holes created after .compile_geometry()
6371
assert len(composite.holes) == 2
72+
assert len(nested_geom.holes) == 0
6473

6574

6675
def test_geometry_from_points():
@@ -138,10 +147,13 @@ def test_compound_geometry_from_points():
138147
assert (new_geom.geom - wkt_test_geom) == Polygon()
139148

140149

141-
def test_nested_compound_geometry_from_points():
150+
def test_multinested_compound_geometry_from_points():
142151
"""
143-
Tests a nested compound geometry can be built .from_points, that the control_points
144-
and hole nodes persist in the right locations, and that ...
152+
Testing a multi-nested section. This section contains three nested materials in concentric
153+
square rings with a hole going through the center of the whole section. This test confirms
154+
that the section can be successfully built using .from_points, that the control_points
155+
and hole nodes persist in the right locations, and that the plastic section calculation
156+
raises a warning because the nested regions overlap.
145157
"""
146158
points = [
147159
[-50.0, 50.0],
@@ -196,6 +208,15 @@ def test_nested_compound_geometry_from_points():
196208
]
197209
assert nested_compound.holes == [(0, 0), (0, 0), (0, 0)]
198210

211+
# Section contains overlapping geometries which will result in potentially incorrect
212+
# plastic properties calculation (depends on user intent and geometry).
213+
# Test to ensure a warning is raised about this to notify the user.
214+
nested_compound.create_mesh([25, 30, 35])
215+
nested_compound_sec = Section(nested_compound)
216+
nested_compound_sec.calculate_geometric_properties()
217+
with pytest.warns(UserWarning):
218+
nested_compound_sec.calculate_plastic_properties()
219+
199220

200221
def test_geometry_from_dxf():
201222
section_holes_dxf = (
@@ -251,10 +272,25 @@ def test_plastic_centroid():
251272
section.calculate_geometric_properties()
252273
section.calculate_plastic_properties()
253274

275+
# Checking sections that were defined above
276+
#
277+
nested_sec.calculate_geometric_properties()
278+
nested_sec.calculate_plastic_properties()
279+
overlay_sec.calculate_geometric_properties()
280+
281+
with pytest.warns(UserWarning):
282+
overlay_sec.calculate_plastic_properties()
283+
284+
# section
254285
x_pc, y_pc = section.get_pc()
255286
assert x_pc == pytest.approx(82.5)
256287
assert y_pc == pytest.approx(250.360654576)
257288

289+
# nested_sec
290+
x_pc, y_pc = nested_sec.get_pc()
291+
assert x_pc == pytest.approx(37.5)
292+
assert y_pc == pytest.approx(50)
293+
258294

259295
def test_geometry_from_3dm_file_simple():
260296
section = pathlib.Path.cwd() / "sectionproperties" / "tests" / "3in x 2in.3dm"

0 commit comments

Comments
 (0)