xslope 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xslope/_version.py +1 -1
- xslope/advanced.py +3 -3
- xslope/fem.py +3 -3
- xslope/fileio.py +463 -206
- xslope/mesh.py +189 -31
- xslope/plot.py +775 -75
- xslope/plot_fem.py +1 -69
- xslope/plot_seep.py +17 -75
- xslope/seep.py +20 -15
- xslope/slice.py +31 -9
- xslope/solve.py +20 -8
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/METADATA +1 -1
- xslope-0.1.13.dist-info/RECORD +21 -0
- xslope-0.1.11.dist-info/RECORD +0 -21
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/LICENSE +0 -0
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/NOTICE +0 -0
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/WHEEL +0 -0
- {xslope-0.1.11.dist-info → xslope-0.1.13.dist-info}/top_level.txt +0 -0
xslope/mesh.py
CHANGED
|
@@ -55,13 +55,13 @@ def _get_gmsh():
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
def build_mesh_from_polygons(polygons, target_size, element_type='tri3', lines=None, debug=False, mesh_params=None, target_size_1d=None):
|
|
58
|
+
def build_mesh_from_polygons(polygons, target_size, element_type='tri3', lines=None, debug=False, mesh_params=None, target_size_1d=None, profile_lines=None):
|
|
59
59
|
"""
|
|
60
60
|
Build a finite element mesh with material regions using Gmsh.
|
|
61
61
|
Fixed version that properly handles shared boundaries between polygons.
|
|
62
62
|
|
|
63
63
|
Parameters:
|
|
64
|
-
polygons : List of
|
|
64
|
+
polygons : List of polygon coordinate lists or dicts with "coords"/"mat_id"
|
|
65
65
|
target_size : Desired element size
|
|
66
66
|
element_type : 'tri3' (3-node triangles), 'tri6' (6-node triangles),
|
|
67
67
|
'quad4' (4-node quadrilaterals), 'quad8' (8-node quadrilaterals),
|
|
@@ -70,6 +70,7 @@ def build_mesh_from_polygons(polygons, target_size, element_type='tri3', lines=N
|
|
|
70
70
|
debug : Enable debug output
|
|
71
71
|
mesh_params : Optional dictionary of GMSH meshing parameters to override defaults
|
|
72
72
|
target_size_1d : Optional target size for 1D elements (default None, which is set to target_size if None)
|
|
73
|
+
profile_lines: Optional list of profile line dicts with 'mat_id' keys for material assignment
|
|
73
74
|
|
|
74
75
|
Returns:
|
|
75
76
|
mesh dict containing:
|
|
@@ -92,8 +93,35 @@ def build_mesh_from_polygons(polygons, target_size, element_type='tri3', lines=N
|
|
|
92
93
|
if debug:
|
|
93
94
|
print(f"Using default target_size_1d = target_size = {target_size_1d}")
|
|
94
95
|
|
|
95
|
-
#
|
|
96
|
-
|
|
96
|
+
# Normalize polygons to coordinate lists and optional mat_id
|
|
97
|
+
polygon_coords = []
|
|
98
|
+
polygon_mat_ids = []
|
|
99
|
+
for i, polygon in enumerate(polygons):
|
|
100
|
+
if isinstance(polygon, dict):
|
|
101
|
+
polygon_coords.append(polygon.get("coords", []))
|
|
102
|
+
polygon_mat_ids.append(polygon.get("mat_id"))
|
|
103
|
+
else:
|
|
104
|
+
polygon_coords.append(polygon)
|
|
105
|
+
polygon_mat_ids.append(None)
|
|
106
|
+
|
|
107
|
+
# Build a list of region ids (list of material IDs - one per polygon)
|
|
108
|
+
if any(mat_id is not None for mat_id in polygon_mat_ids):
|
|
109
|
+
region_ids = [
|
|
110
|
+
mat_id if mat_id is not None else i
|
|
111
|
+
for i, mat_id in enumerate(polygon_mat_ids)
|
|
112
|
+
]
|
|
113
|
+
elif profile_lines and len(profile_lines) >= len(polygon_coords):
|
|
114
|
+
region_ids = []
|
|
115
|
+
for i in range(len(polygon_coords)):
|
|
116
|
+
mat_id = profile_lines[i].get('mat_id')
|
|
117
|
+
if mat_id is not None:
|
|
118
|
+
region_ids.append(mat_id)
|
|
119
|
+
else:
|
|
120
|
+
# Fallback to polygon index if no mat_id
|
|
121
|
+
region_ids.append(i)
|
|
122
|
+
else:
|
|
123
|
+
# Fallback to sequential IDs if no profile_lines provided
|
|
124
|
+
region_ids = [i for i in range(len(polygon_coords))]
|
|
97
125
|
|
|
98
126
|
if element_type not in ['tri3', 'tri6', 'quad4', 'quad8', 'quad9']:
|
|
99
127
|
raise ValueError("element_type must be 'tri3', 'tri6', 'quad4', 'quad8', or 'quad9'")
|
|
@@ -160,7 +188,7 @@ def build_mesh_from_polygons(polygons, target_size, element_type='tri3', lines=N
|
|
|
160
188
|
short_edge_points = set() # Points that are endpoints of short edges
|
|
161
189
|
|
|
162
190
|
# Pre-pass to identify short edges - improved logic
|
|
163
|
-
for idx, (poly_pts, region_id) in enumerate(zip(
|
|
191
|
+
for idx, (poly_pts, region_id) in enumerate(zip(polygon_coords, region_ids)):
|
|
164
192
|
poly_pts_clean = remove_duplicate_endpoint(list(poly_pts))
|
|
165
193
|
for i in range(len(poly_pts_clean)):
|
|
166
194
|
p1 = poly_pts_clean[i]
|
|
@@ -187,7 +215,7 @@ def build_mesh_from_polygons(polygons, target_size, element_type='tri3', lines=N
|
|
|
187
215
|
print(f"Short edge ignored (major boundary): {p1} to {p2}, length={edge_length:.2f}")
|
|
188
216
|
|
|
189
217
|
# Main pass: Create points with appropriate sizes
|
|
190
|
-
for idx, (poly_pts, region_id) in enumerate(zip(
|
|
218
|
+
for idx, (poly_pts, region_id) in enumerate(zip(polygon_coords, region_ids)):
|
|
191
219
|
poly_pts_clean = remove_duplicate_endpoint(list(poly_pts)) # make a copy
|
|
192
220
|
pt_tags = []
|
|
193
221
|
for x, y in poly_pts_clean:
|
|
@@ -1206,7 +1234,7 @@ def get_quad_mesh_presets():
|
|
|
1206
1234
|
|
|
1207
1235
|
|
|
1208
1236
|
|
|
1209
|
-
def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
1237
|
+
def build_polygons(slope_data, reinf_lines=None, tol = 0.000001, debug=False):
|
|
1210
1238
|
"""
|
|
1211
1239
|
Build material zone polygons from slope_data.
|
|
1212
1240
|
|
|
@@ -1218,7 +1246,9 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
|
1218
1246
|
slope_data: Dictionary containing slope geometry data
|
|
1219
1247
|
|
|
1220
1248
|
Returns:
|
|
1221
|
-
List of polygons
|
|
1249
|
+
List of polygons as dicts with keys:
|
|
1250
|
+
"coords": list of (x, y) coordinate tuples
|
|
1251
|
+
"mat_id": optional material ID (0-based) or None
|
|
1222
1252
|
"""
|
|
1223
1253
|
import numpy as np
|
|
1224
1254
|
import copy
|
|
@@ -1236,8 +1266,7 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
|
1236
1266
|
raise ValueError("When using only 1 profile line, max_depth must be specified")
|
|
1237
1267
|
|
|
1238
1268
|
n = len(profile_lines)
|
|
1239
|
-
lines = [list(line) for line in copy.deepcopy(profile_lines)]
|
|
1240
|
-
tol = 1e-8
|
|
1269
|
+
lines = [list(line['coords']) for line in copy.deepcopy(profile_lines)]
|
|
1241
1270
|
|
|
1242
1271
|
for i in range(n - 1):
|
|
1243
1272
|
top = lines[i]
|
|
@@ -1432,6 +1461,93 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
|
1432
1461
|
# Return the lowest y value
|
|
1433
1462
|
y_min = min(y_values)
|
|
1434
1463
|
return y_min, is_at_endpoint
|
|
1464
|
+
|
|
1465
|
+
def find_projected_y_at_x(line_points, x_query, y_ref, side, tol=1e-8):
|
|
1466
|
+
"""
|
|
1467
|
+
For vertical endpoint projections: choose the intersection y at x_query that is
|
|
1468
|
+
closest *below* the point we're projecting from.
|
|
1469
|
+
|
|
1470
|
+
This fixes the case where a candidate profile has a vertical segment at x_query
|
|
1471
|
+
(e.g., (260,229) then (260,202)). In that situation, using the "lowest y" (202)
|
|
1472
|
+
is wrong; we want the first hit when projecting downward (229).
|
|
1473
|
+
|
|
1474
|
+
Behavior is intentionally conservative:
|
|
1475
|
+
- If there is at least one intersection strictly below y_ref, return the highest of those.
|
|
1476
|
+
- Otherwise fall back to the original behavior (lowest y), preserving legacy behavior
|
|
1477
|
+
in edge cases (e.g., coincident/above intersections).
|
|
1478
|
+
"""
|
|
1479
|
+
# Reuse the exact same intersection enumeration logic as find_lowest_y_at_x,
|
|
1480
|
+
# but keep the full set of y-values.
|
|
1481
|
+
if not line_points:
|
|
1482
|
+
return None, False
|
|
1483
|
+
|
|
1484
|
+
xs = np.array([x for x, y in line_points])
|
|
1485
|
+
ys = np.array([y for x, y in line_points])
|
|
1486
|
+
|
|
1487
|
+
if xs[0] - tol > x_query or xs[-1] + tol < x_query:
|
|
1488
|
+
return None, False
|
|
1489
|
+
|
|
1490
|
+
is_at_left_endpoint = abs(x_query - xs[0]) < tol
|
|
1491
|
+
is_at_right_endpoint = abs(x_query - xs[-1]) < tol
|
|
1492
|
+
is_at_endpoint = is_at_left_endpoint or is_at_right_endpoint
|
|
1493
|
+
|
|
1494
|
+
y_values = []
|
|
1495
|
+
for k in range(len(line_points)):
|
|
1496
|
+
if abs(xs[k] - x_query) < tol:
|
|
1497
|
+
y_values.append(float(ys[k]))
|
|
1498
|
+
|
|
1499
|
+
for k in range(len(line_points) - 1):
|
|
1500
|
+
x1, y1 = line_points[k]
|
|
1501
|
+
x2, y2 = line_points[k + 1]
|
|
1502
|
+
|
|
1503
|
+
if abs(x1 - x_query) < tol and abs(x2 - x_query) < tol:
|
|
1504
|
+
y_values.append(float(y1))
|
|
1505
|
+
y_values.append(float(y2))
|
|
1506
|
+
elif min(x1, x2) - tol <= x_query <= max(x1, x2) + tol:
|
|
1507
|
+
if abs(x2 - x1) < tol:
|
|
1508
|
+
y_values.append(float(y1))
|
|
1509
|
+
y_values.append(float(y2))
|
|
1510
|
+
else:
|
|
1511
|
+
t = (x_query - x1) / (x2 - x1)
|
|
1512
|
+
if 0 <= t <= 1:
|
|
1513
|
+
y_values.append(float(y1 + t * (y2 - y1)))
|
|
1514
|
+
|
|
1515
|
+
if not y_values:
|
|
1516
|
+
return None, False
|
|
1517
|
+
|
|
1518
|
+
# If the polyline has multiple *vertices* exactly at this x (vertical segment / duplicate-x),
|
|
1519
|
+
# use a deterministic selection based on which side we are projecting from:
|
|
1520
|
+
# - projecting from LEFT endpoint of the upper line: keep the LAST y encountered
|
|
1521
|
+
# - projecting from RIGHT endpoint of the upper line: keep the FIRST y encountered
|
|
1522
|
+
#
|
|
1523
|
+
# This matches the intended "walk along the lower boundary" behavior and fixes cases like:
|
|
1524
|
+
# - right projection at x=260 with vertices (260,229) then (260,202): choose 229 (first)
|
|
1525
|
+
# - left projection at x=240 with vertices (240,140) then (240,190): choose 190 (last)
|
|
1526
|
+
vertex_y_at_x = [float(y) for (x, y) in line_points if abs(x - x_query) < tol]
|
|
1527
|
+
if len(vertex_y_at_x) >= 2:
|
|
1528
|
+
if side == "right":
|
|
1529
|
+
# first encountered vertex at this x
|
|
1530
|
+
y_pick = vertex_y_at_x[0]
|
|
1531
|
+
# If we are exactly on a vertex at y_ref, that is the first hit.
|
|
1532
|
+
if abs(y_pick - y_ref) < tol:
|
|
1533
|
+
return float(y_ref), is_at_endpoint
|
|
1534
|
+
if y_pick < (y_ref - tol):
|
|
1535
|
+
return y_pick, is_at_endpoint
|
|
1536
|
+
elif side == "left":
|
|
1537
|
+
# last encountered vertex at this x
|
|
1538
|
+
y_pick = vertex_y_at_x[-1]
|
|
1539
|
+
# If we are exactly on a vertex at y_ref, that is the first hit.
|
|
1540
|
+
if abs(y_pick - y_ref) < tol:
|
|
1541
|
+
return float(y_ref), is_at_endpoint
|
|
1542
|
+
if y_pick < (y_ref - tol):
|
|
1543
|
+
return y_pick, is_at_endpoint
|
|
1544
|
+
|
|
1545
|
+
y_below = [y for y in y_values if y < (y_ref - tol)]
|
|
1546
|
+
if y_below:
|
|
1547
|
+
return max(y_below), is_at_endpoint
|
|
1548
|
+
|
|
1549
|
+
# Fall back to legacy behavior
|
|
1550
|
+
return min(y_values), is_at_endpoint
|
|
1435
1551
|
|
|
1436
1552
|
# Project endpoints - find highest lower profile or use max_depth
|
|
1437
1553
|
# When projecting right side: if intersection is at left end of lower line,
|
|
@@ -1445,7 +1561,7 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
|
1445
1561
|
|
|
1446
1562
|
# Check left endpoint projection
|
|
1447
1563
|
if xs_cand[0] - tol <= left_x <= xs_cand[-1] + tol:
|
|
1448
|
-
y_cand, is_at_endpoint =
|
|
1564
|
+
y_cand, is_at_endpoint = find_projected_y_at_x(lower_candidate, left_x, left_y, side="left", tol=tol)
|
|
1449
1565
|
if y_cand is not None:
|
|
1450
1566
|
# If intersection is at the right end of the lower line, add point but continue
|
|
1451
1567
|
if is_at_endpoint and abs(left_x - xs_cand[-1]) < tol: # At right endpoint
|
|
@@ -1458,7 +1574,7 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
|
1458
1574
|
|
|
1459
1575
|
# Check right endpoint projection
|
|
1460
1576
|
if xs_cand[0] - tol <= right_x <= xs_cand[-1] + tol:
|
|
1461
|
-
y_cand, is_at_endpoint =
|
|
1577
|
+
y_cand, is_at_endpoint = find_projected_y_at_x(lower_candidate, right_x, right_y, side="right", tol=tol)
|
|
1462
1578
|
if y_cand is not None:
|
|
1463
1579
|
# If intersection is at the left end of the lower line, add point but continue
|
|
1464
1580
|
if is_at_endpoint and abs(right_x - xs_cand[0]) < tol: # At left endpoint
|
|
@@ -1474,6 +1590,25 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
|
1474
1590
|
left_y_bot = max_depth if max_depth is not None else -np.inf
|
|
1475
1591
|
if right_y_bot == -np.inf:
|
|
1476
1592
|
right_y_bot = max_depth if max_depth is not None else -np.inf
|
|
1593
|
+
|
|
1594
|
+
# Filter vertical-edge "continue projecting" points so we only keep points that
|
|
1595
|
+
# actually lie on the final vertical edge between the top and bottom of this zone.
|
|
1596
|
+
#
|
|
1597
|
+
# Without this, a deeper left-endpoint intersection (e.g., (240,190) at the left
|
|
1598
|
+
# endpoint of some deeper line) can be appended to right_vertical_points even after
|
|
1599
|
+
# we've already found the correct bottom (e.g., right_y_bot=229). That creates the
|
|
1600
|
+
# dangling vertical segment you observed.
|
|
1601
|
+
if right_y_bot != -np.inf:
|
|
1602
|
+
right_vertical_points = [
|
|
1603
|
+
(x, y) for (x, y) in right_vertical_points
|
|
1604
|
+
if (y < right_y - tol) and (y > right_y_bot + tol)
|
|
1605
|
+
]
|
|
1606
|
+
if left_y_bot != -np.inf:
|
|
1607
|
+
# Left edge runs from bottom up to top; keep points strictly between bottom and top.
|
|
1608
|
+
left_vertical_points = [
|
|
1609
|
+
(x, y) for (x, y) in left_vertical_points
|
|
1610
|
+
if (y > left_y_bot + tol) and (y < left_y - tol)
|
|
1611
|
+
]
|
|
1477
1612
|
|
|
1478
1613
|
# Deduplicate vertical points (remove points that are too close to each other)
|
|
1479
1614
|
def deduplicate_points(points, tol=1e-8):
|
|
@@ -1561,7 +1696,11 @@ def build_polygons(slope_data, reinf_lines=None, debug=False):
|
|
|
1561
1696
|
|
|
1562
1697
|
# Clean up polygon (should rarely do anything)
|
|
1563
1698
|
poly = clean_polygon(poly)
|
|
1564
|
-
|
|
1699
|
+
mat_id = profile_lines[i].get("mat_id") if i < len(profile_lines) else None
|
|
1700
|
+
polygons.append({
|
|
1701
|
+
"coords": poly,
|
|
1702
|
+
"mat_id": mat_id
|
|
1703
|
+
})
|
|
1565
1704
|
|
|
1566
1705
|
# Add distributed load points to polygon edges if coincident
|
|
1567
1706
|
polygons = add_dload_points_to_polygons(polygons, slope_data)
|
|
@@ -1578,7 +1717,7 @@ def add_dload_points_to_polygons(polygons, slope_data):
|
|
|
1578
1717
|
but not existing vertices.
|
|
1579
1718
|
|
|
1580
1719
|
Parameters:
|
|
1581
|
-
polygons: List of polygons (
|
|
1720
|
+
polygons: List of polygons (list of (x,y) tuples) or dicts with "coords"
|
|
1582
1721
|
slope_data: Dictionary containing slope data
|
|
1583
1722
|
|
|
1584
1723
|
Returns:
|
|
@@ -1603,7 +1742,8 @@ def add_dload_points_to_polygons(polygons, slope_data):
|
|
|
1603
1742
|
# Process each polygon
|
|
1604
1743
|
updated_polygons = []
|
|
1605
1744
|
for poly in polygons:
|
|
1606
|
-
|
|
1745
|
+
coords = poly.get("coords", []) if isinstance(poly, dict) else poly
|
|
1746
|
+
updated_poly = list(coords) # Make a copy
|
|
1607
1747
|
|
|
1608
1748
|
# Check each point against polygon edges
|
|
1609
1749
|
for check_point in points_to_check:
|
|
@@ -1630,7 +1770,12 @@ def add_dload_points_to_polygons(polygons, slope_data):
|
|
|
1630
1770
|
updated_poly.insert(i + 1, (round(x_check, 6), round(y_check, 6)))
|
|
1631
1771
|
break # Only insert once per point
|
|
1632
1772
|
|
|
1633
|
-
|
|
1773
|
+
if isinstance(poly, dict):
|
|
1774
|
+
updated_entry = dict(poly)
|
|
1775
|
+
updated_entry["coords"] = updated_poly
|
|
1776
|
+
updated_polygons.append(updated_entry)
|
|
1777
|
+
else:
|
|
1778
|
+
updated_polygons.append(updated_poly)
|
|
1634
1779
|
|
|
1635
1780
|
return updated_polygons
|
|
1636
1781
|
|
|
@@ -1681,29 +1826,33 @@ def print_polygon_summary(polygons):
|
|
|
1681
1826
|
Prints a summary of the generated polygons for diagnostic purposes.
|
|
1682
1827
|
|
|
1683
1828
|
Parameters:
|
|
1684
|
-
polygons: List of polygon coordinate lists
|
|
1829
|
+
polygons: List of polygon coordinate lists or dicts with "coords"
|
|
1685
1830
|
"""
|
|
1686
1831
|
print("=== POLYGON SUMMARY ===")
|
|
1687
1832
|
print(f"Number of material zones: {len(polygons)}")
|
|
1688
1833
|
print()
|
|
1689
1834
|
|
|
1690
1835
|
for i, polygon in enumerate(polygons):
|
|
1691
|
-
|
|
1692
|
-
|
|
1836
|
+
coords = polygon.get("coords") if isinstance(polygon, dict) else polygon
|
|
1837
|
+
mat_id = polygon.get("mat_id") if isinstance(polygon, dict) else i
|
|
1838
|
+
if mat_id is None:
|
|
1839
|
+
mat_id = i
|
|
1840
|
+
print(f"Material Zone {i+1} (Material ID: {mat_id}):")
|
|
1841
|
+
print(f" Number of vertices: {len(coords)}")
|
|
1693
1842
|
|
|
1694
1843
|
# Calculate area (simple shoelace formula)
|
|
1695
1844
|
area = 0
|
|
1696
|
-
for j in range(len(
|
|
1697
|
-
x1, y1 =
|
|
1698
|
-
x2, y2 =
|
|
1845
|
+
for j in range(len(coords) - 1):
|
|
1846
|
+
x1, y1 = coords[j]
|
|
1847
|
+
x2, y2 = coords[j + 1]
|
|
1699
1848
|
area += (x2 - x1) * (y2 + y1) / 2
|
|
1700
1849
|
area = abs(area)
|
|
1701
1850
|
|
|
1702
1851
|
print(f" Approximate area: {area:.2f} square units")
|
|
1703
1852
|
|
|
1704
1853
|
# Print bounding box
|
|
1705
|
-
xs = [x for x, y in
|
|
1706
|
-
ys = [y for x, y in
|
|
1854
|
+
xs = [x for x, y in coords]
|
|
1855
|
+
ys = [y for x, y in coords]
|
|
1707
1856
|
print(f" Bounding box: x=[{min(xs):.2f}, {max(xs):.2f}], y=[{min(ys):.2f}, {max(ys):.2f}]")
|
|
1708
1857
|
print()
|
|
1709
1858
|
|
|
@@ -2878,7 +3027,7 @@ def add_intersection_points_to_polygons(polygons, lines, debug=False):
|
|
|
2878
3027
|
This ensures that polygons have vertices at all intersection points with reinforcement lines.
|
|
2879
3028
|
|
|
2880
3029
|
Parameters:
|
|
2881
|
-
polygons: List of polygons (lists of (x,y) tuples)
|
|
3030
|
+
polygons: List of polygons (lists of (x,y) tuples) or dicts with "coords"
|
|
2882
3031
|
lines: List of reinforcement lines (lists of (x,y) tuples)
|
|
2883
3032
|
debug: Enable debug output
|
|
2884
3033
|
|
|
@@ -2894,7 +3043,12 @@ def add_intersection_points_to_polygons(polygons, lines, debug=False):
|
|
|
2894
3043
|
# Make a copy of polygons to modify
|
|
2895
3044
|
updated_polygons = []
|
|
2896
3045
|
for poly in polygons:
|
|
2897
|
-
|
|
3046
|
+
if isinstance(poly, dict):
|
|
3047
|
+
updated_entry = dict(poly)
|
|
3048
|
+
updated_entry["coords"] = list(poly.get("coords", []))
|
|
3049
|
+
updated_polygons.append(updated_entry)
|
|
3050
|
+
else:
|
|
3051
|
+
updated_polygons.append(list(poly)) # Convert to list for modification
|
|
2898
3052
|
|
|
2899
3053
|
# Find all intersections
|
|
2900
3054
|
for line_idx, line_pts in enumerate(lines):
|
|
@@ -2910,10 +3064,11 @@ def add_intersection_points_to_polygons(polygons, lines, debug=False):
|
|
|
2910
3064
|
|
|
2911
3065
|
# Check intersection with each polygon
|
|
2912
3066
|
for poly_idx, poly in enumerate(updated_polygons):
|
|
3067
|
+
poly_coords = poly.get("coords", []) if isinstance(poly, dict) else poly
|
|
2913
3068
|
# Check each edge of this polygon
|
|
2914
|
-
for j in range(len(
|
|
2915
|
-
poly_edge_start =
|
|
2916
|
-
poly_edge_end =
|
|
3069
|
+
for j in range(len(poly_coords)):
|
|
3070
|
+
poly_edge_start = poly_coords[j]
|
|
3071
|
+
poly_edge_end = poly_coords[(j + 1) % len(poly_coords)]
|
|
2917
3072
|
|
|
2918
3073
|
# Find intersection point if it exists
|
|
2919
3074
|
intersection = line_segment_intersection(
|
|
@@ -2927,7 +3082,7 @@ def add_intersection_points_to_polygons(polygons, lines, debug=False):
|
|
|
2927
3082
|
|
|
2928
3083
|
# Check if intersection point is already a vertex of this polygon
|
|
2929
3084
|
is_vertex = False
|
|
2930
|
-
for vertex in
|
|
3085
|
+
for vertex in poly_coords:
|
|
2931
3086
|
if abs(vertex[0] - intersection[0]) < 1e-8 and abs(vertex[1] - intersection[1]) < 1e-8:
|
|
2932
3087
|
is_vertex = True
|
|
2933
3088
|
break
|
|
@@ -2936,7 +3091,10 @@ def add_intersection_points_to_polygons(polygons, lines, debug=False):
|
|
|
2936
3091
|
# Insert intersection point into polygon at the correct position
|
|
2937
3092
|
# Insert after vertex j (which is the start of the edge)
|
|
2938
3093
|
insert_idx = j + 1
|
|
2939
|
-
updated_polygons[poly_idx]
|
|
3094
|
+
if isinstance(updated_polygons[poly_idx], dict):
|
|
3095
|
+
updated_polygons[poly_idx]["coords"].insert(insert_idx, intersection)
|
|
3096
|
+
else:
|
|
3097
|
+
updated_polygons[poly_idx].insert(insert_idx, intersection)
|
|
2940
3098
|
|
|
2941
3099
|
if debug:
|
|
2942
3100
|
print(f"Added intersection point {intersection} to polygon {poly_idx} at position {insert_idx}")
|