RDG-Networks 0.3.8__tar.gz → 0.3.9__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/PKG-INFO +10 -2
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_Networks.egg-info/PKG-INFO +10 -2
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_Networks.egg-info/SOURCES.txt +1 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/thickness/Classes.py +2 -1
- rdg_networks-0.3.9/RDG_networks/thickness/generate_line_segments_thickness.py +644 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/thickness/orientate_network.py +3 -3
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/setup.py +1 -1
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/LICENSE.txt +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_Networks.egg-info/dependency_links.txt +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_Networks.egg-info/entry_points.txt +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_Networks.egg-info/requires.txt +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_Networks.egg-info/top_level.txt +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/Classes.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/__init__.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/draw_segments.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/generate_line_network.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/generate_line_segments.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/generate_line_segments_dynamic.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/generate_line_segments_static.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/get_intersection_segments.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/sample_in_polygon.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/save_to_stl.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/thickness/__init__.py +0 -0
- /rdg_networks-0.3.8/RDG_networks/thickness/generate_line_segments_thickness.py → /rdg_networks-0.3.9/RDG_networks/thickness/generate_line_segments_thickness_correct.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/thickness/generate_line_segments_thickness_static.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/RDG_networks/thickness/sample_in_polygon.py +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/README.md +0 -0
- {rdg_networks-0.3.8 → rdg_networks-0.3.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: RDG-Networks
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.9
|
4
4
|
Summary: Most of the code from the RDG Networks project
|
5
5
|
Home-page: https://github.com/NiekMooij/RDG_networks
|
6
6
|
Author: Niek Mooij
|
@@ -14,6 +14,14 @@ Requires-Dist: numpy
|
|
14
14
|
Requires-Dist: scipy
|
15
15
|
Requires-Dist: shapely
|
16
16
|
Requires-Dist: typing
|
17
|
+
Dynamic: author
|
18
|
+
Dynamic: author-email
|
19
|
+
Dynamic: classifier
|
20
|
+
Dynamic: description
|
21
|
+
Dynamic: home-page
|
22
|
+
Dynamic: license
|
23
|
+
Dynamic: requires-dist
|
24
|
+
Dynamic: summary
|
17
25
|
|
18
26
|
## Overview
|
19
27
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: RDG-Networks
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.9
|
4
4
|
Summary: Most of the code from the RDG Networks project
|
5
5
|
Home-page: https://github.com/NiekMooij/RDG_networks
|
6
6
|
Author: Niek Mooij
|
@@ -14,6 +14,14 @@ Requires-Dist: numpy
|
|
14
14
|
Requires-Dist: scipy
|
15
15
|
Requires-Dist: shapely
|
16
16
|
Requires-Dist: typing
|
17
|
+
Dynamic: author
|
18
|
+
Dynamic: author-email
|
19
|
+
Dynamic: classifier
|
20
|
+
Dynamic: description
|
21
|
+
Dynamic: home-page
|
22
|
+
Dynamic: license
|
23
|
+
Dynamic: requires-dist
|
24
|
+
Dynamic: summary
|
17
25
|
|
18
26
|
## Overview
|
19
27
|
|
@@ -20,6 +20,7 @@ RDG_networks/save_to_stl.py
|
|
20
20
|
RDG_networks/thickness/Classes.py
|
21
21
|
RDG_networks/thickness/__init__.py
|
22
22
|
RDG_networks/thickness/generate_line_segments_thickness.py
|
23
|
+
RDG_networks/thickness/generate_line_segments_thickness_correct.py
|
23
24
|
RDG_networks/thickness/generate_line_segments_thickness_static.py
|
24
25
|
RDG_networks/thickness/orientate_network.py
|
25
26
|
RDG_networks/thickness/sample_in_polygon.py
|
@@ -95,7 +95,7 @@ class Polygon:
|
|
95
95
|
vertices (List[Tuple[float, float]]): A list of (x, y) coordinates representing the vertices of the polygon.
|
96
96
|
"""
|
97
97
|
|
98
|
-
def __init__(self, vertices: List[tuple], middle_segment=None):
|
98
|
+
def __init__(self, vertices: List[tuple], middle_segment=None, neighbors=None):
|
99
99
|
"""
|
100
100
|
Initializes a Polygon instance with the provided vertices.
|
101
101
|
|
@@ -104,6 +104,7 @@ class Polygon:
|
|
104
104
|
"""
|
105
105
|
self.vertices = vertices
|
106
106
|
self.middle_segment = middle_segment
|
107
|
+
self.neighbors = neighbors
|
107
108
|
|
108
109
|
def area(self) -> float:
|
109
110
|
"""
|
@@ -0,0 +1,644 @@
|
|
1
|
+
import math
|
2
|
+
import numpy as np
|
3
|
+
import random
|
4
|
+
from typing import List, Dict, Tuple, Union, Optional
|
5
|
+
from shapely.geometry import Polygon as Polygon_Shapely, LineString
|
6
|
+
|
7
|
+
from .Classes import Line, LineSegment, Polygon
|
8
|
+
from .sample_in_polygon import sample_in_polygon, is_inside_polygon
|
9
|
+
|
10
|
+
def doLinesIntersect(line1: Line, line2: Line, box_size = 1) -> Tuple[bool, Union[Tuple[float, float], None]]:
|
11
|
+
"""
|
12
|
+
Check if two lines intersect and return the intersection point.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
- line1 (Line): The first line segment.
|
16
|
+
- line2 (Line): The second line segment.
|
17
|
+
- box_size (float): The size of the bounding box. Defaults to 1.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
- intersect (bool): True if the lines intersect, False otherwise.
|
21
|
+
- intersection_point (tuple or None): The intersection point (x, y) if lines intersect, None otherwise.
|
22
|
+
"""
|
23
|
+
x1, y1 = line1.location
|
24
|
+
v1, w1 = line1.direction
|
25
|
+
|
26
|
+
x2, y2 = line2.location
|
27
|
+
v2, w2 = line2.direction
|
28
|
+
|
29
|
+
determinant = v1 * w2 - v2 * w1
|
30
|
+
|
31
|
+
if determinant == 0:
|
32
|
+
return False, (None, None)
|
33
|
+
|
34
|
+
t1 = ((x2 - x1) * w2 - (y2 - y1) * v2) / determinant
|
35
|
+
t2 = ((x2 - x1) * w1 - (y2 - y1) * v1) / determinant
|
36
|
+
|
37
|
+
intersect_x = x1 + v1 * t1
|
38
|
+
intersect_y = y2 + w2 * t2
|
39
|
+
|
40
|
+
|
41
|
+
if -1e-6 < intersect_x < box_size + 1e-6 and -1e-6 < intersect_y < box_size + 1e-6:
|
42
|
+
return True, (intersect_x, intersect_y)
|
43
|
+
else:
|
44
|
+
return False, (None, None)
|
45
|
+
|
46
|
+
def doSegmentsIntersect(
|
47
|
+
segment1: LineSegment,
|
48
|
+
segment2: LineSegment,
|
49
|
+
box_size = 1
|
50
|
+
) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
51
|
+
"""
|
52
|
+
Determines if two line segments intersect and returns the intersection point if they do.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
segment1 (LineSegment): The first line segment.
|
56
|
+
segment2 (LineSegment): The second line segment.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
60
|
+
- A boolean indicating whether the segments intersect.
|
61
|
+
- A tuple of the x and y coordinates of the intersection point if they intersect,
|
62
|
+
otherwise (None, None).
|
63
|
+
"""
|
64
|
+
|
65
|
+
# Create line equations based on the segments' start and end points
|
66
|
+
line1 = Line(location=segment1.start, direction=np.array(segment1.end) - np.array(segment1.start))
|
67
|
+
line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
|
68
|
+
|
69
|
+
# Check if the infinite extensions of the two lines intersect
|
70
|
+
intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2, box_size)
|
71
|
+
|
72
|
+
# If no intersection, return False
|
73
|
+
if not intersect:
|
74
|
+
return False, (None, None)
|
75
|
+
|
76
|
+
# Check if the intersection point is within the bounds of both segments in the x-direction
|
77
|
+
xcheck = (
|
78
|
+
(segment1.end[0] <= intersect_x <= segment1.start[0]
|
79
|
+
or segment1.start[0] <= intersect_x <= segment1.end[0]
|
80
|
+
or abs(intersect_x - segment1.end[0]) < 1e-6
|
81
|
+
or abs(intersect_x - segment1.start[0]) < 1e-6)
|
82
|
+
and
|
83
|
+
(segment2.end[0] <= intersect_x <= segment2.start[0]
|
84
|
+
or segment2.start[0] <= intersect_x <= segment2.end[0]
|
85
|
+
or abs(intersect_x - segment2.end[0]) < 1e-6
|
86
|
+
or abs(intersect_x - segment2.start[0]) < 1e-6)
|
87
|
+
)
|
88
|
+
|
89
|
+
# Check if the intersection point is within the bounds of both segments in the y-direction
|
90
|
+
ycheck = (
|
91
|
+
(segment1.end[1] <= intersect_y <= segment1.start[1]
|
92
|
+
or segment1.start[1] <= intersect_y <= segment1.end[1]
|
93
|
+
or abs(intersect_y - segment1.end[1]) < 1e-6
|
94
|
+
or abs(intersect_y - segment1.start[1]) < 1e-6)
|
95
|
+
and
|
96
|
+
(segment2.end[1] <= intersect_y <= segment2.start[1]
|
97
|
+
or segment2.start[1] <= intersect_y <= segment2.end[1]
|
98
|
+
or abs(intersect_y - segment2.end[1]) < 1e-6
|
99
|
+
or abs(intersect_y - segment2.start[1]) < 1e-6)
|
100
|
+
)
|
101
|
+
|
102
|
+
# If the intersection point lies within the bounds of both segments, return True with the intersection point
|
103
|
+
if xcheck and ycheck:
|
104
|
+
return True, (intersect_x, intersect_y)
|
105
|
+
|
106
|
+
# Otherwise, return False and no intersection point
|
107
|
+
return False, (None, None)
|
108
|
+
|
109
|
+
def pick_item_with_probability(
|
110
|
+
polygon_arr: Dict[str, Dict[str, object]]
|
111
|
+
) -> Tuple[str, Dict[str, object]]:
|
112
|
+
"""
|
113
|
+
Randomly selects an item from the polygon array with a probability proportional to the area of the polygons.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
117
|
+
A dictionary where keys are polygon identifiers (e.g., 'p1', 'p2') and values are dictionaries containing polygon properties,
|
118
|
+
including an 'area' key that stores the area of the polygon.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
Tuple[str, Dict[str, object]]:
|
122
|
+
- The identifier of the selected polygon.
|
123
|
+
- The corresponding polygon data (dictionary) containing its properties.
|
124
|
+
"""
|
125
|
+
|
126
|
+
# Calculate the total weight (sum of areas of all polygons)
|
127
|
+
max_weight = sum(pol['area'] for pol in polygon_arr.values())
|
128
|
+
|
129
|
+
# Generate a random threshold between 0 and the total weight
|
130
|
+
threshold = random.uniform(0, max_weight)
|
131
|
+
cumulative_weight = 0
|
132
|
+
|
133
|
+
# Iterate through the polygons, accumulating weights
|
134
|
+
for item, pol in polygon_arr.items():
|
135
|
+
weight = pol['area']
|
136
|
+
cumulative_weight += weight
|
137
|
+
|
138
|
+
# Return the polygon when the cumulative weight surpasses the threshold
|
139
|
+
if cumulative_weight >= threshold:
|
140
|
+
return item, pol
|
141
|
+
|
142
|
+
def get_location_and_direction(
|
143
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
144
|
+
thickness: float,
|
145
|
+
angle: float,
|
146
|
+
nucleation_point: Tuple[float, float] = None,
|
147
|
+
min_distance: float = 0,
|
148
|
+
max_attempts: int = 1000
|
149
|
+
) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
150
|
+
"""
|
151
|
+
Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
|
152
|
+
chosen (uniformly) or from a specified list of angles. It ensures that the segment lies within the polygon's bounds given the
|
153
|
+
specified thickness.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
157
|
+
A dictionary where the keys are polygon identifiers and the values are dictionaries containing polygon properties, including 'vertices'.
|
158
|
+
thickness (float):
|
159
|
+
The thickness of the segment that needs to fit inside the polygon.
|
160
|
+
max_attempts (int, optional):
|
161
|
+
The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
162
|
+
angle (float):
|
163
|
+
A float indicating the angle of the new segment.
|
164
|
+
nucleation_point (Tuple[float, float], optional):
|
165
|
+
predified nucleation point for the segment. Defaults to None.
|
166
|
+
min_distance (float, optional):
|
167
|
+
the minimum distance between two lines. Defaults to 0.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
171
|
+
- If a valid location and direction are found, returns a tuple containing:
|
172
|
+
- The polygon ID (`str`).
|
173
|
+
- The polygon data (`Dict[str, object]`).
|
174
|
+
- The new location as a tuple of floats (`Tuple[float, float]`).
|
175
|
+
- The direction vector as a numpy array (`np.ndarray`).
|
176
|
+
- The perpendicular vector to the direction as a numpy array (`np.ndarray`).
|
177
|
+
- The nucleation point [x,y] of the segment
|
178
|
+
- The angle of the segment.
|
179
|
+
- Returns `False` if no valid location and direction are found after the maximum attempts.
|
180
|
+
"""
|
181
|
+
direction = (np.cos(angle), np.sin(angle))
|
182
|
+
direction = np.array(direction) / np.linalg.norm(direction)
|
183
|
+
|
184
|
+
# Try to find a valid location and direction up to max_attempts
|
185
|
+
attempt = 0
|
186
|
+
while attempt < max_attempts:
|
187
|
+
polygon_id, polygon = pick_item_with_probability(polygon_arr)
|
188
|
+
|
189
|
+
# Sample a location within the polygon
|
190
|
+
#check if nucleation point is given
|
191
|
+
if nucleation_point is not None:
|
192
|
+
location_new = nucleation_point
|
193
|
+
else:
|
194
|
+
location_new = sample_in_polygon(polygon['vertices'])
|
195
|
+
|
196
|
+
# Compute the perpendicular vector to the direction
|
197
|
+
perpendicular = np.array([direction[1], -direction[0]])
|
198
|
+
perpendicular = perpendicular / np.linalg.norm(perpendicular)
|
199
|
+
|
200
|
+
# Ensure the perpendicular vector is oriented consistently (y-component is non-negative)
|
201
|
+
if perpendicular[1] < 0:
|
202
|
+
perpendicular = -perpendicular
|
203
|
+
|
204
|
+
# Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
|
205
|
+
p1 = np.array(location_new) + (thickness/2 + min_distance) * perpendicular
|
206
|
+
p2 = np.array(location_new) - (thickness/2 + min_distance) * perpendicular
|
207
|
+
|
208
|
+
# Check if both endpoints of the segment are inside the polygon
|
209
|
+
if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
|
210
|
+
return polygon_id, polygon, location_new, direction, perpendicular, angle
|
211
|
+
|
212
|
+
attempt += 1
|
213
|
+
|
214
|
+
# If no valid location and direction is found, return False
|
215
|
+
return False
|
216
|
+
|
217
|
+
def get_polygons(polygon_id, polygon_arr, neighbor1_1, neighbor1_2, vertex_begin_1, vertex_end_1, neighbor2_1, neighbor2_2, vertex_begin_2, vertex_end_2, segment_new_id_1, segment_new_id_2):
|
218
|
+
# Extract vertices and cycle (faces) of the original polygon
|
219
|
+
vertices = polygon_arr[polygon_id]['vertices']
|
220
|
+
cycle = polygon_arr[polygon_id]['faces']
|
221
|
+
|
222
|
+
# Get first cycle and vertices
|
223
|
+
index_start_1, index_end_1 = (cycle.index(neighbor1_1), cycle.index(neighbor1_2))
|
224
|
+
if index_start_1 < index_end_1:
|
225
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:index_end_1+1]
|
226
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:index_end_1] + [vertex_end_1]
|
227
|
+
else:
|
228
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:] + cycle[:index_end_1+1]
|
229
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:] + vertices[:index_end_1] + [vertex_end_1]
|
230
|
+
|
231
|
+
# Get second cycle and vertices
|
232
|
+
index_start_2, index_end_2 = (cycle.index(neighbor2_2), cycle.index(neighbor2_1))
|
233
|
+
if index_start_2 < index_end_2:
|
234
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:index_end_2+1]
|
235
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:index_end_2] + [vertex_begin_2]
|
236
|
+
else:
|
237
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:] + cycle[:index_end_2+1]
|
238
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:] + vertices[:index_end_2] + [vertex_begin_2]
|
239
|
+
|
240
|
+
# Get middle cycle and vertices
|
241
|
+
cycle0 = [neighbor1_1, segment_new_id_1, neighbor1_2]
|
242
|
+
vertices0 = [vertex_begin_1, vertex_end_1]
|
243
|
+
|
244
|
+
index_start_0, index_end_0 = (cycle.index(neighbor1_2), cycle.index(neighbor2_2))
|
245
|
+
if index_start_0 < index_end_0:
|
246
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
247
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
248
|
+
|
249
|
+
elif index_start_0 > index_end_0:
|
250
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
251
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
252
|
+
|
253
|
+
cycle0 = cycle0 + [segment_new_id_2]
|
254
|
+
vertices0 = vertices0 + [vertex_end_2] + [vertex_begin_2]
|
255
|
+
|
256
|
+
index_start_0, index_end_0 = (cycle.index(neighbor2_1), cycle.index(neighbor1_1))
|
257
|
+
if index_start_0 < index_end_0:
|
258
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
259
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
260
|
+
|
261
|
+
elif index_start_0 > index_end_0:
|
262
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
263
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
264
|
+
|
265
|
+
return cycle0, vertices0, cycle1, vertices1, cycle2, vertices2
|
266
|
+
|
267
|
+
def get_new_segment(
|
268
|
+
line_segments_to_check: List[LineSegment],
|
269
|
+
location: Tuple[float, float],
|
270
|
+
direction: Tuple[float, float],
|
271
|
+
id: Optional[int] = None,
|
272
|
+
box_size: float = 1
|
273
|
+
) -> LineSegment:
|
274
|
+
"""
|
275
|
+
Creates a new line segment by extending a given location in a specified direction and
|
276
|
+
determines its neighbors by checking intersections with other line segments.
|
277
|
+
|
278
|
+
Args:
|
279
|
+
line_segments_to_check (List[LineSegment]): List of existing line segments to check for intersections.
|
280
|
+
location (Tuple[float, float]): The starting point (x, y) for the new line segment.
|
281
|
+
direction (Tuple[float, float]): The direction vector in which to extend the line segment.
|
282
|
+
id (Optional[int]): Optional ID for the new line segment. If not provided, defaults to None.
|
283
|
+
box_size(optional[Int]]): The size of the box. Defaults to 1.
|
284
|
+
|
285
|
+
Returns:
|
286
|
+
LineSegment: A new line segment object with its neighbors based on intersections.
|
287
|
+
"""
|
288
|
+
|
289
|
+
# Create a temporary line segment extending from the location in both directions
|
290
|
+
s_temp = LineSegment(start=np.array(location) - 10*np.sqrt(2)*box_size * np.array(direction), end=np.array(location) + 10*np.sqrt(2)*box_size * np.array(direction))
|
291
|
+
|
292
|
+
intersection_points = []
|
293
|
+
|
294
|
+
# Check for intersections with existing line segments
|
295
|
+
for segment in line_segments_to_check:
|
296
|
+
intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment, box_size)
|
297
|
+
|
298
|
+
if intersect:
|
299
|
+
segment_length = math.sqrt(
|
300
|
+
(location[0] - intersect_x) ** 2
|
301
|
+
+ (location[1] - intersect_y) ** 2
|
302
|
+
)
|
303
|
+
intersection_points.append(
|
304
|
+
{"id": segment.id, "point": (intersect_x, intersect_y), "segment_length": segment_length}
|
305
|
+
)
|
306
|
+
|
307
|
+
# Divide intersections into ones behind and in front of the new line
|
308
|
+
intersections_b = [intersection for intersection in intersection_points if intersection["point"][0] < location[0]]
|
309
|
+
intersections_f = [intersection for intersection in intersection_points if intersection["point"][0] > location[0]]
|
310
|
+
|
311
|
+
if not intersections_b or not intersections_f:
|
312
|
+
intersections_b = [intersection for intersection in intersection_points if intersection["point"][1] < location[1]]
|
313
|
+
intersections_f = [intersection for intersection in intersection_points if intersection["point"][1] > location[1]]
|
314
|
+
|
315
|
+
# Determine the closest intersections for segment start and end
|
316
|
+
s_start = min(intersections_b, key=lambda x: x["segment_length"])
|
317
|
+
s_end = min(intersections_f, key=lambda x: x["segment_length"])
|
318
|
+
start, end = s_start['point'], s_end['point']
|
319
|
+
start_id, end_id = s_start['id'], s_end['id']
|
320
|
+
|
321
|
+
# Ensure the start comes before the end
|
322
|
+
if start[0] > end[0]:
|
323
|
+
start, end = end, start
|
324
|
+
start_id, end_id = end_id, start_id
|
325
|
+
|
326
|
+
# Create a new line segment and assign neighbors
|
327
|
+
neighbors_initial = {start_id: start, end_id: end}
|
328
|
+
segment_new = LineSegment(start=start, end=end, id=id, neighbors_initial=neighbors_initial, neighbors=neighbors_initial)
|
329
|
+
|
330
|
+
return segment_new
|
331
|
+
|
332
|
+
def update_data(
|
333
|
+
segments_dict: Dict[int, LineSegment],
|
334
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
335
|
+
polygon_id: str,
|
336
|
+
segment_thickness_dict: Dict[int, Polygon],
|
337
|
+
vertices0: List[Tuple[float, float]],
|
338
|
+
vertices1: List[Tuple[float, float]],
|
339
|
+
vertices2: List[Tuple[float, float]],
|
340
|
+
cycle0: List[int],
|
341
|
+
cycle1: List[int],
|
342
|
+
cycle2: List[int],
|
343
|
+
neighbor1_1: int,
|
344
|
+
neighbor1_2: int,
|
345
|
+
neighbor2_1: int,
|
346
|
+
neighbor2_2: int,
|
347
|
+
vertex_begin_1: Tuple[float, float],
|
348
|
+
vertex_end_1: Tuple[float, float],
|
349
|
+
vertex_begin_2: Tuple[float, float],
|
350
|
+
vertex_end_2: Tuple[float, float],
|
351
|
+
id_1: int,
|
352
|
+
id_2: int
|
353
|
+
) -> Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
354
|
+
"""
|
355
|
+
Updates the segments, polygons, and segment thickness dictionaries by adding new data derived
|
356
|
+
from provided vertices and neighbor information.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
segments_dict (Dict[int, LineSegment]): A dictionary of segments with segment ID as the key.
|
360
|
+
polygon_arr (Dict[str, Dict[str, object]]): A dictionary of polygons with polygon ID as the key.
|
361
|
+
polygon_id (str): The ID of the polygon being updated.
|
362
|
+
segment_thickness_dict (Dict[int, Polygon]): A dictionary mapping thickness information to polygon objects.
|
363
|
+
vertices0 (List[Tuple[float, float]]): Vertices of the base polygon.
|
364
|
+
vertices1 (List[Tuple[float, float]]): Vertices of the first new polygon.
|
365
|
+
vertices2 (List[Tuple[float, float]]): Vertices of the second new polygon.
|
366
|
+
cycle0 (List[int]): List of face indices for the base polygon.
|
367
|
+
cycle1 (List[int]): List of face indices for the first new polygon.
|
368
|
+
cycle2 (List[int]): List of face indices for the second new polygon.
|
369
|
+
neighbor1_1 (int): ID of the first neighbor of the first segment.
|
370
|
+
neighbor1_2 (int): ID of the second neighbor of the first segment.
|
371
|
+
neighbor2_1 (int): ID of the first neighbor of the second segment.
|
372
|
+
neighbor2_2 (int): ID of the second neighbor of the second segment.
|
373
|
+
vertex_begin_1 (Tuple[float, float]): Starting vertex of the first segment.
|
374
|
+
vertex_end_1 (Tuple[float, float]): Ending vertex of the first segment.
|
375
|
+
vertex_begin_2 (Tuple[float, float]): Starting vertex of the second segment.
|
376
|
+
vertex_end_2 (Tuple[float, float]): Ending vertex of the second segment.
|
377
|
+
id_1 (int): ID of the first new segment.
|
378
|
+
id_2 (int): ID of the second new segment.
|
379
|
+
|
380
|
+
Returns:
|
381
|
+
Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
382
|
+
- Updated dictionary of line segments.
|
383
|
+
- Updated dictionary of polygons.
|
384
|
+
- Updated dictionary of segment thickness.
|
385
|
+
"""
|
386
|
+
|
387
|
+
# Update polygon_arr (a dictionary of polygons)
|
388
|
+
polygon_new_1 = {
|
389
|
+
f'p{len(polygon_arr) + 1}': {
|
390
|
+
'vertices': vertices1,
|
391
|
+
'area': Polygon(vertices=vertices1).area(),
|
392
|
+
'faces': cycle1
|
393
|
+
}
|
394
|
+
}
|
395
|
+
polygon_new_2 = {
|
396
|
+
polygon_id: {
|
397
|
+
'vertices': vertices2,
|
398
|
+
'area': Polygon(vertices=vertices2).area(),
|
399
|
+
'faces': cycle2
|
400
|
+
}
|
401
|
+
}
|
402
|
+
polygon_arr.update(polygon_new_1)
|
403
|
+
polygon_arr.update(polygon_new_2)
|
404
|
+
|
405
|
+
# Update the segments_dict for the first segment
|
406
|
+
neighbors_initial_1 = {
|
407
|
+
neighbor1_1: vertex_begin_1,
|
408
|
+
neighbor1_2: vertex_end_1
|
409
|
+
}
|
410
|
+
segment_new_1 = LineSegment(
|
411
|
+
start=vertex_begin_1,
|
412
|
+
end=vertex_end_1,
|
413
|
+
id=id_1,
|
414
|
+
neighbors_initial=neighbors_initial_1,
|
415
|
+
neighbors=neighbors_initial_1
|
416
|
+
)
|
417
|
+
segments_dict[segment_new_1.id] = segment_new_1
|
418
|
+
segments_dict[neighbor1_1].neighbors[id_1] = vertex_begin_1
|
419
|
+
segments_dict[neighbor1_2].neighbors[id_1] = vertex_end_1
|
420
|
+
|
421
|
+
# Update the segments_dict for the second segment
|
422
|
+
neighbors_initial_2 = {
|
423
|
+
neighbor2_1: vertex_begin_2,
|
424
|
+
neighbor2_2: vertex_end_2
|
425
|
+
}
|
426
|
+
segment_new_2 = LineSegment(
|
427
|
+
start=vertex_begin_2,
|
428
|
+
end=vertex_end_2,
|
429
|
+
id=id_2,
|
430
|
+
neighbors_initial=neighbors_initial_2,
|
431
|
+
neighbors=neighbors_initial_2
|
432
|
+
)
|
433
|
+
segments_dict[segment_new_2.id] = segment_new_2
|
434
|
+
segments_dict[neighbor2_1].neighbors[id_2] = vertex_begin_2
|
435
|
+
segments_dict[neighbor2_2].neighbors[id_2] = vertex_end_2
|
436
|
+
|
437
|
+
# Update the segment_thickness_dict with the base polygon
|
438
|
+
neighbors = cycle0.copy()
|
439
|
+
neighbors.remove(f'{len(segment_thickness_dict)+1}_1')
|
440
|
+
neighbors.remove(f'{len(segment_thickness_dict)+1}_2')
|
441
|
+
neighbors = [ i[:-2] if i not in ['b1', 'b2', 'b3', 'b4'] else i for i in neighbors ]
|
442
|
+
neighbors = list(set(neighbors))
|
443
|
+
|
444
|
+
segment_thickness_dict[len(segment_thickness_dict) + 1] = Polygon(vertices=vertices0, neighbors=neighbors)
|
445
|
+
|
446
|
+
for n in neighbors:
|
447
|
+
if n in ['b1', 'b2', 'b3', 'b4']:
|
448
|
+
continue
|
449
|
+
|
450
|
+
else:
|
451
|
+
segment_thickness_dict[int(n)].neighbors.append(len(segment_thickness_dict) + 1)
|
452
|
+
|
453
|
+
return segments_dict, polygon_arr, segment_thickness_dict
|
454
|
+
|
455
|
+
def add_line_segment(
|
456
|
+
segments_dict: Dict[int, LineSegment],
|
457
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
458
|
+
segment_thickness_dict: Dict[int, Polygon],
|
459
|
+
angle: float,
|
460
|
+
thickness: float = 0,
|
461
|
+
nucleation_point: Tuple[float, float] = None,
|
462
|
+
min_distance: float = 0,
|
463
|
+
box_size: float = 1,
|
464
|
+
max_attempts: int = 1000
|
465
|
+
) -> Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], List[float], float], bool]:
|
466
|
+
"""
|
467
|
+
Adds a new line segment to the segments and polygon data structures, with a given thickness and angle distribution.
|
468
|
+
|
469
|
+
Args:
|
470
|
+
segments_dict (Dict[int, LineSegment]): A dictionary containing the current line segments.
|
471
|
+
polygon_arr (Dict[str, Dict[str, object]]): A dictionary containing the current polygons and their properties.
|
472
|
+
segment_thickness_dict (Dict[int, Polygon]): A dictionary storing the thickness information mapped to polygons.
|
473
|
+
thickness (float): The thickness of the new segment to be added. Defaults to 0.
|
474
|
+
angles (str): The angle distribution method. Defaults to 'uniform'.
|
475
|
+
nucleation_point (Tuple[float, float]): A predefined nucleation point for the new segment. Defaults to None.
|
476
|
+
min_distance (float): The minimum distance between two lines. Defaults to 0.
|
477
|
+
box_size (float): The size of the box. Defaults to 1.
|
478
|
+
max_attempts (int): The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
479
|
+
|
480
|
+
Returns:
|
481
|
+
Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]], List[float], float, bool]:
|
482
|
+
- A tuple containing the updated segments dictionary, polygon dictionary, and thickness dictionary,
|
483
|
+
or False if no valid location for the new segment is found.
|
484
|
+
-nucleation point in a list [x,y] and the angle of the segment in radians.
|
485
|
+
"""
|
486
|
+
|
487
|
+
# Get a valid location and direction, or return False if none is found
|
488
|
+
loc = get_location_and_direction(polygon_arr, angle=angle, thickness=thickness, nucleation_point=nucleation_point, min_distance=min_distance, max_attempts=max_attempts)
|
489
|
+
if loc:
|
490
|
+
polygon_id, polygon, location_new, direction_new, perpendicular, angle_new = loc
|
491
|
+
else:
|
492
|
+
print('No valid location found')
|
493
|
+
return False
|
494
|
+
|
495
|
+
# Get the borders of the new segment with the given thickness
|
496
|
+
line_segments_to_check = [segments_dict[segment] for segment in polygon['faces']]
|
497
|
+
middle_segment = get_new_segment(line_segments_to_check, location=location_new, direction=direction_new, box_size=box_size)
|
498
|
+
s1 = get_new_segment(line_segments_to_check, location=np.array(location_new) + thickness * perpendicular / 2, direction=direction_new,box_size=box_size)
|
499
|
+
s2 = get_new_segment(line_segments_to_check, location=np.array(location_new) - thickness * perpendicular / 2, direction=direction_new, box_size=box_size)
|
500
|
+
|
501
|
+
# Extract neighbor information and segment vertices
|
502
|
+
neighbor1_1, neighbor1_2 = list(s1.neighbors.keys())
|
503
|
+
vertex_begin_1, vertex_end_1 = list(s1.neighbors.values())
|
504
|
+
neighbor2_1, neighbor2_2 = list(s2.neighbors.keys())
|
505
|
+
vertex_begin_2, vertex_end_2 = list(s2.neighbors.values())
|
506
|
+
id_1 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_1'
|
507
|
+
id_2 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_2'
|
508
|
+
|
509
|
+
# Get the resulting polygons after splitting
|
510
|
+
cycle0, vertices0, cycle1, vertices1, cycle2, vertices2 = get_polygons(
|
511
|
+
polygon_id,
|
512
|
+
polygon_arr,
|
513
|
+
neighbor1_1,
|
514
|
+
neighbor1_2,
|
515
|
+
vertex_begin_1,
|
516
|
+
vertex_end_1,
|
517
|
+
neighbor2_1,
|
518
|
+
neighbor2_2,
|
519
|
+
vertex_begin_2=vertex_begin_2,
|
520
|
+
vertex_end_2=vertex_end_2,
|
521
|
+
segment_new_id_1=id_1,
|
522
|
+
segment_new_id_2=id_2
|
523
|
+
)
|
524
|
+
|
525
|
+
# Update all relevant data structures
|
526
|
+
segments_dict, polygon_arr, segment_thickness_dict = update_data(
|
527
|
+
segments_dict,
|
528
|
+
polygon_arr,
|
529
|
+
polygon_id,
|
530
|
+
segment_thickness_dict,
|
531
|
+
vertices0,
|
532
|
+
vertices1,
|
533
|
+
vertices2,
|
534
|
+
cycle0,
|
535
|
+
cycle1,
|
536
|
+
cycle2,
|
537
|
+
neighbor1_1,
|
538
|
+
neighbor1_2,
|
539
|
+
neighbor2_1,
|
540
|
+
neighbor2_2,
|
541
|
+
vertex_begin_1,
|
542
|
+
vertex_end_1,
|
543
|
+
vertex_begin_2,
|
544
|
+
vertex_end_2,
|
545
|
+
id_1,
|
546
|
+
id_2
|
547
|
+
)
|
548
|
+
|
549
|
+
# Associate the middle segment with the newly created thickness entry
|
550
|
+
segment_thickness_dict[list(segment_thickness_dict.keys())[-1]].middle_segment = middle_segment
|
551
|
+
|
552
|
+
return segments_dict, polygon_arr, segment_thickness_dict, location_new, angle_new
|
553
|
+
|
554
|
+
def generate_line_segments_thickness(
|
555
|
+
size: int,
|
556
|
+
thickness_arr: List[float],
|
557
|
+
angles: str = 'uniform',
|
558
|
+
config: List[List[float]] = None,
|
559
|
+
epsilon: float = 0,
|
560
|
+
box_size: float = 1
|
561
|
+
) -> Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], np.ndarray]:
|
562
|
+
"""
|
563
|
+
Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
|
564
|
+
|
565
|
+
Args:
|
566
|
+
size (int): The number of line segments to generate.
|
567
|
+
thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
|
568
|
+
angles (str): Angle used in the generation of the segments.
|
569
|
+
config (List[List[float]]): A list of configurations for the nucleation points and angles.
|
570
|
+
epsilon (float): the minimum distance between two line.
|
571
|
+
box_size (float): the size of the box.
|
572
|
+
|
573
|
+
Returns:
|
574
|
+
Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
575
|
+
- Updated dictionary of line segments.
|
576
|
+
- Updated dictionary of polygons.
|
577
|
+
- Updated dictionary of segment thicknesses.
|
578
|
+
- Array of the nucleation points and angles [x,y,theta].
|
579
|
+
"""
|
580
|
+
|
581
|
+
# Initialize border segments for a square and its polygon representation
|
582
|
+
borders = [
|
583
|
+
LineSegment((box_size, 0), (0, 0), id='b1', neighbors_initial={'b2': (0, 0), 'b4': (box_size, 0)}, neighbors={'b2': (0, 0), 'b4': (box_size, 0)}),
|
584
|
+
LineSegment((0, box_size), (0, 0), id='b2', neighbors_initial={'b1': (0, 0), 'b3': (0, box_size)}, neighbors={'b1': (0, 0), 'b3': (0, box_size)}),
|
585
|
+
LineSegment((0, box_size), (box_size, box_size), id='b3', neighbors_initial={'b2': (0, box_size), 'b4': (box_size, box_size)}, neighbors={'b2': (0, box_size), 'b4': (box_size, box_size)}),
|
586
|
+
LineSegment((box_size, box_size), (box_size, 0), id='b4', neighbors_initial={'b1': (box_size, 0), 'b3': (box_size, box_size)}, neighbors={'b1': (box_size, 0), 'b3': (box_size, box_size)})
|
587
|
+
]
|
588
|
+
|
589
|
+
polygon_arr = {
|
590
|
+
'p1': {
|
591
|
+
'vertices': [(0, 0), (0, box_size), (box_size, box_size), (box_size, 0)],
|
592
|
+
'area': box_size**2,
|
593
|
+
'faces': ['b1', 'b2', 'b3', 'b4']
|
594
|
+
}
|
595
|
+
}
|
596
|
+
|
597
|
+
segments = borders
|
598
|
+
segments_dict = {segment.id: segment for segment in segments}
|
599
|
+
segment_thickness_dict = {}
|
600
|
+
generated_config = []
|
601
|
+
|
602
|
+
if config is not None and size > len(config):
|
603
|
+
print("The size of the configuration is smaller than the size of the segments. Generated a network of the same size as the configuration.")
|
604
|
+
size = len(config)
|
605
|
+
|
606
|
+
jammed = False
|
607
|
+
for i in range(size):
|
608
|
+
if config:
|
609
|
+
nucleation_point = config[i]['location']
|
610
|
+
angles = [config[i]['angle']]
|
611
|
+
else:
|
612
|
+
nucleation_point = None
|
613
|
+
|
614
|
+
output = add_line_segment(segments_dict,
|
615
|
+
polygon_arr,
|
616
|
+
segment_thickness_dict,
|
617
|
+
thickness=thickness_arr[i],
|
618
|
+
min_distance = epsilon,
|
619
|
+
nucleation_point = nucleation_point,
|
620
|
+
angle=angles[i],
|
621
|
+
box_size=box_size)
|
622
|
+
if output:
|
623
|
+
segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
|
624
|
+
generated_config.append({ 'location': location, 'angle': angle, 'thickness': thickness_arr[i] })
|
625
|
+
|
626
|
+
else:
|
627
|
+
if config:
|
628
|
+
print('Configuration not possible. Point is skipped.')
|
629
|
+
else:
|
630
|
+
print(f"Stopped at iteration {len(segment_thickness_dict)}, could not find a valid segment position.")
|
631
|
+
jammed = True
|
632
|
+
break
|
633
|
+
|
634
|
+
# Uncomment the following line if you want progress feedback
|
635
|
+
percentage = np.round(i / size * 100, 3)
|
636
|
+
print(f'generate_segments: {percentage}% done', end='\r')
|
637
|
+
|
638
|
+
data_dict = {'segments_dict': segments_dict,
|
639
|
+
'polygon_arr': polygon_arr,
|
640
|
+
'segment_thickness_dict': segment_thickness_dict,
|
641
|
+
'jammed': jammed,
|
642
|
+
'generated_config': generated_config}
|
643
|
+
|
644
|
+
return data_dict
|
@@ -170,7 +170,7 @@ def rotate_network(
|
|
170
170
|
middle_segment_new = LineSegment(start=start, end=end)
|
171
171
|
|
172
172
|
# Store the rotated segment in the new dictionary
|
173
|
-
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
173
|
+
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
|
174
174
|
|
175
175
|
return segment_thickness_dict_new
|
176
176
|
|
@@ -237,7 +237,7 @@ def clip_network(
|
|
237
237
|
middle_segment_new = LineSegment(start=start, end=end)
|
238
238
|
|
239
239
|
# Create a new clipped polygon with updated vertices and middle segment
|
240
|
-
pol_new = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
240
|
+
pol_new = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
|
241
241
|
pol_new.sort_vertices() # Ensure vertices are sorted
|
242
242
|
segment_thickness_dict_new[id] = pol_new
|
243
243
|
|
@@ -285,7 +285,7 @@ def translate_network(
|
|
285
285
|
middle_segment_new = LineSegment(start=start, end=end)
|
286
286
|
|
287
287
|
# Store the translated segment in the new dictionary
|
288
|
-
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
288
|
+
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
|
289
289
|
|
290
290
|
return segment_thickness_dict_new
|
291
291
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|