RDG-Networks 0.3.5__py3-none-any.whl → 0.3.6__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {RDG_Networks-0.3.5.dist-info → RDG_Networks-0.3.6.dist-info}/METADATA +1 -1
- {RDG_Networks-0.3.5.dist-info → RDG_Networks-0.3.6.dist-info}/RECORD +12 -11
- {RDG_Networks-0.3.5.dist-info → RDG_Networks-0.3.6.dist-info}/entry_points.txt +5 -1
- RDG_networks/__init__.py +8 -0
- RDG_networks/thickness/Classes.py +3 -2
- RDG_networks/thickness/__init__.py +9 -1
- RDG_networks/thickness/{ggenerate_line_segments_thickness.py → generate_line_segments_thickness.py} +9 -6
- RDG_networks/thickness/generate_line_segments_thickness_orientation backup.py +202 -0
- RDG_networks/thickness/generate_line_segments_thickness_orientation.py +327 -113
- {RDG_Networks-0.3.5.dist-info → RDG_Networks-0.3.6.dist-info}/LICENSE.txt +0 -0
- {RDG_Networks-0.3.5.dist-info → RDG_Networks-0.3.6.dist-info}/WHEEL +0 -0
- {RDG_Networks-0.3.5.dist-info → RDG_Networks-0.3.6.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
|
|
1
1
|
RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
|
2
|
-
RDG_networks/__init__.py,sha256=
|
2
|
+
RDG_networks/__init__.py,sha256=SmpD26lMaZ3wPz2Hs2Z-24AJiEU5DbNldhQt1fZxI0U,1607
|
3
3
|
RDG_networks/draw_segments.py,sha256=U53N5GXmQHWKdM1Q1faP_EGKjc6enOu2mcsunzSFpP0,984
|
4
4
|
RDG_networks/generate_line_network.py,sha256=lJ4rhObim3WcEQoebomewRQKWNJC5phFyFYRW7qjXIg,1127
|
5
5
|
RDG_networks/generate_line_segments.py,sha256=QV8_k7q6TD5c7Hcb2Ms_apEdWYw4XdLr7rdJgh49v4Q,9004
|
@@ -8,14 +8,15 @@ RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ
|
|
8
8
|
RDG_networks/get_intersection_segments.py,sha256=mXB5qCy1oOps4Vu1mX6flW6v_4Xxc71YK41yOWjJX8o,2797
|
9
9
|
RDG_networks/sample_in_polygon.py,sha256=qpPpW-Da1vK8ZkVWMJ0zBsE8IgyMB619gCdybSkzKSQ,1605
|
10
10
|
RDG_networks/save_to_stl.py,sha256=xHwuoG39cbemggoIjT44DlsMlhjlV3uxTWo6gcHEePA,3414
|
11
|
-
RDG_networks/thickness/Classes.py,sha256=
|
12
|
-
RDG_networks/thickness/__init__.py,sha256=
|
13
|
-
RDG_networks/thickness/
|
14
|
-
RDG_networks/thickness/
|
11
|
+
RDG_networks/thickness/Classes.py,sha256=gVe_q5Rh_1DBiJoZ8H0FyJ4xG-IAcespjUpUirxFfAA,8125
|
12
|
+
RDG_networks/thickness/__init__.py,sha256=87hNJsP1c-4y5QqZVM0bDcVn2cWoNd23Da3_Dvy7Fno,745
|
13
|
+
RDG_networks/thickness/generate_line_segments_thickness.py,sha256=4SJn2_ScMKNSbHWFCSQCInCWRTo08AUiobAFxasFEKI,29190
|
14
|
+
RDG_networks/thickness/generate_line_segments_thickness_orientation backup.py,sha256=5gcStrs4SNLp17tFb9XsN4TLWpOaeu0cHteEedISs5E,8204
|
15
|
+
RDG_networks/thickness/generate_line_segments_thickness_orientation.py,sha256=oTEwQkXRBuoHvEdIGU30p21e2QHW1UlmApzRO1s5c64,16821
|
15
16
|
RDG_networks/thickness/sample_in_polygon.py,sha256=nJ-yqfoCCGfC6_EpGL3L1t1LOYdqWZd-7v5bxy6th34,1849
|
16
|
-
RDG_Networks-0.3.
|
17
|
-
RDG_Networks-0.3.
|
18
|
-
RDG_Networks-0.3.
|
19
|
-
RDG_Networks-0.3.
|
20
|
-
RDG_Networks-0.3.
|
21
|
-
RDG_Networks-0.3.
|
17
|
+
RDG_Networks-0.3.6.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
|
18
|
+
RDG_Networks-0.3.6.dist-info/METADATA,sha256=wGZdZ1ZiKAB4gHxXv3WosChSNoaZ2FFKjbfAIog92Dw,2422
|
19
|
+
RDG_Networks-0.3.6.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
20
|
+
RDG_Networks-0.3.6.dist-info/entry_points.txt,sha256=g8LC0VSpouLfaeL08Wqn31Pm3K74h4DfkD39C9Fr120,938
|
21
|
+
RDG_Networks-0.3.6.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
|
22
|
+
RDG_Networks-0.3.6.dist-info/RECORD,,
|
@@ -1,10 +1,14 @@
|
|
1
1
|
[console_scripts]
|
2
|
+
clip_network = RDG_networks.thickness.clip_network:main
|
2
3
|
draw_segments = RDG_networks.draw_segments:main
|
3
4
|
generate_line_network = RDG_networks.generate_line_network:main
|
4
5
|
generate_line_segments = RDG_networks.generate_line_segments:main
|
5
6
|
generate_line_segments_dynamic = RDG_networks.generate_line_segments_dynamic:main
|
6
|
-
generate_line_segments_dynamic_orientation = RDG_networks.generate_line_segments_dynamic_orientation:main
|
7
7
|
generate_line_segments_static = RDG_networks.generate_line_segments_static:main
|
8
8
|
generate_line_segments_thickness = RDG_networks.thickness.generate_line_segments_thickness:main
|
9
|
+
generate_line_segments_thickness_orientation = RDG_networks.thickness.generate_line_segments_dynamic_orientation:main
|
10
|
+
get_alignment_mean = RDG_networks.thickness.get_alignment_mean:main
|
9
11
|
get_intersection_segments = RDG_networks.get_intersection_segments:main
|
12
|
+
rotate_network = RDG_networks.thickness.rotate_network:main
|
10
13
|
save_to_stl = RDG_networks.save_to_stl:main
|
14
|
+
translate_network = RDG_networks.thickness.translate_network:main
|
RDG_networks/__init__.py
CHANGED
@@ -9,11 +9,19 @@ from .generate_line_segments_static import generate_line_segments_static
|
|
9
9
|
from .draw_segments import draw_segments
|
10
10
|
from .thickness.generate_line_segments_thickness import generate_line_segments_thickness
|
11
11
|
from .thickness.generate_line_segments_thickness_orientation import generate_line_segments_thickness_orientation
|
12
|
+
from .thickness.generate_line_segments_thickness_orientation import translate_network
|
13
|
+
from .thickness.generate_line_segments_thickness_orientation import clip_network
|
14
|
+
from .thickness.generate_line_segments_thickness_orientation import rotate_network
|
15
|
+
from .thickness.generate_line_segments_thickness_orientation import get_alignment_mean
|
12
16
|
from .save_to_stl import save_to_stl
|
13
17
|
|
14
18
|
__all__ = ['generate_line_segments',
|
15
19
|
'generate_line_segments_thickness',
|
16
20
|
'generate_line_segments_thickness_orientation',
|
21
|
+
'translate_network',
|
22
|
+
'clip_network',
|
23
|
+
'rotate_network',
|
24
|
+
'get_alignment_mean',
|
17
25
|
'generate_line_segments_dynamic',
|
18
26
|
'generate_line_segments_static',
|
19
27
|
'generate_line_network',
|
@@ -103,7 +103,7 @@ class Polygon:
|
|
103
103
|
vertices (List[Tuple[float, float]]): A list of (x, y) coordinates representing the vertices of the polygon.
|
104
104
|
"""
|
105
105
|
self.vertices = vertices
|
106
|
-
self.
|
106
|
+
self.middle_segment = middle_segment
|
107
107
|
|
108
108
|
def area(self) -> float:
|
109
109
|
"""
|
@@ -151,7 +151,8 @@ class Polygon:
|
|
151
151
|
dy = point[1] - reference_point[1]
|
152
152
|
return np.arctan2(dy, dx)
|
153
153
|
|
154
|
-
reference_point = min(self.vertices, key=lambda point: point[1])
|
154
|
+
# reference_point = min(self.vertices, key=lambda point: point[1])
|
155
|
+
reference_point = np.mean(self.vertices, axis=0)
|
155
156
|
return sorted(self.vertices, key=lambda point: polar_angle(point, reference_point))
|
156
157
|
|
157
158
|
def draw(self, ax: axes.Axes, color='purple', alpha=0.8):
|
@@ -2,8 +2,16 @@
|
|
2
2
|
|
3
3
|
from .generate_line_segments_thickness import generate_line_segments_thickness
|
4
4
|
from .generate_line_segments_thickness_orientation import generate_line_segments_thickness_orientation
|
5
|
+
from .generate_line_segments_thickness_orientation import translate_network
|
6
|
+
from .generate_line_segments_thickness_orientation import clip_network
|
7
|
+
from .generate_line_segments_thickness_orientation import rotate_network
|
8
|
+
from .generate_line_segments_thickness_orientation import get_alignment_mean
|
5
9
|
|
6
10
|
__all__ = [
|
7
11
|
'generate_line_segments_thickness',
|
8
|
-
'generate_line_segments_thickness_orientation'
|
12
|
+
'generate_line_segments_thickness_orientation',
|
13
|
+
'translate_network',
|
14
|
+
'clip_network',
|
15
|
+
'rotate_network',
|
16
|
+
'get_alignment_mean'
|
9
17
|
]
|
RDG_networks/thickness/{ggenerate_line_segments_thickness.py → generate_line_segments_thickness.py}
RENAMED
@@ -196,7 +196,7 @@ def get_location_and_direction(
|
|
196
196
|
|
197
197
|
# Sample a location within the polygon
|
198
198
|
#check if nucleation point is given
|
199
|
-
if nucleation_point:
|
199
|
+
if nucleation_point is not None:
|
200
200
|
location_new = nucleation_point
|
201
201
|
else:
|
202
202
|
location_new = sample_in_polygon(polygon['vertices'])
|
@@ -619,13 +619,16 @@ def generate_line_segments_thickness(
|
|
619
619
|
box_size=box_size)
|
620
620
|
if output:
|
621
621
|
segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
|
622
|
-
# generated_config += [[n_pts[0], n_pts[1], ang]]
|
623
622
|
generated_config.append({ 'location': location, 'angle': angle, 'thickness': thickness_arr[i] })
|
624
623
|
|
625
|
-
else:
|
626
|
-
|
627
|
-
|
628
|
-
|
624
|
+
else:
|
625
|
+
if config:
|
626
|
+
print('Configuration not possible. Point is skipped.')
|
627
|
+
else:
|
628
|
+
print(f"Stopped at iteration {len(segment_thickness_dict)}, could not find a valid segment position.")
|
629
|
+
jammed = True
|
630
|
+
break
|
631
|
+
|
629
632
|
# Uncomment the following line if you want progress feedback
|
630
633
|
percentage = np.round(i / size * 100, 3)
|
631
634
|
print(f'generate_segments: {percentage}% done', end='\r')
|
@@ -0,0 +1,202 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from typing import List, Dict, Tuple, Union, Optional
|
3
|
+
from shapely.geometry import Polygon as Polygon_Shapely, LineString
|
4
|
+
from shapely.geometry import Point
|
5
|
+
import matplotlib.pyplot as plt
|
6
|
+
from concurrent.futures import ProcessPoolExecutor
|
7
|
+
|
8
|
+
from .Classes import Line, LineSegment, Polygon
|
9
|
+
from .generate_line_segments_thickness import generate_line_segments_thickness
|
10
|
+
|
11
|
+
def rotate(point, center, rotation_matrix):
|
12
|
+
"""
|
13
|
+
Rotates a point around the center using the given rotation matrix.
|
14
|
+
point: numpy array representing the point to rotate
|
15
|
+
center: numpy array representing the center of rotation
|
16
|
+
rotation_matrix: 2x2 numpy array representing the rotation matrix
|
17
|
+
"""
|
18
|
+
translated_point = point - center
|
19
|
+
rotated_point = np.dot(rotation_matrix, translated_point)
|
20
|
+
final_point = rotated_point + center
|
21
|
+
|
22
|
+
return final_point
|
23
|
+
|
24
|
+
def unit_vector(v):
|
25
|
+
""" Returns the unit vector of the vector. """
|
26
|
+
return v / np.linalg.norm(v)
|
27
|
+
|
28
|
+
def angle_between(v1, v2):
|
29
|
+
""" Returns the angle in radians between vectors 'v1' and 'v2'."""
|
30
|
+
v1_u = unit_vector(v1)
|
31
|
+
v2_u = unit_vector(v2)
|
32
|
+
return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
|
33
|
+
|
34
|
+
def get_alignment_mean(line_vector_arr, director):
|
35
|
+
"""Get the mean alignment."""
|
36
|
+
S_all = []
|
37
|
+
for item in line_vector_arr:
|
38
|
+
line_vector = item['line_vector']
|
39
|
+
area = item['area']
|
40
|
+
P2 = 0.5*(3*(np.cos(angle_between(line_vector, director)))**2-1)
|
41
|
+
S_all.append(P2*area)
|
42
|
+
|
43
|
+
return float(np.mean(S_all))
|
44
|
+
|
45
|
+
def compute_alignment_for_angle(
|
46
|
+
angle: float,
|
47
|
+
segment_thickness_dict: dict,
|
48
|
+
director: np.ndarray,
|
49
|
+
box_size: float
|
50
|
+
) -> tuple[float, float]:
|
51
|
+
"""Compute the alignment for a given angle."""
|
52
|
+
rotation_matrix = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
|
53
|
+
center = np.array([box_size / 2, box_size / 2])
|
54
|
+
line_vectors = [
|
55
|
+
{'line_vector': np.diff(rotate(np.array([seg.middle_segment.start, seg.middle_segment.end]), center, rotation_matrix), axis=0)[0],
|
56
|
+
'area': seg.area()}
|
57
|
+
for seg in segment_thickness_dict.values()
|
58
|
+
]
|
59
|
+
|
60
|
+
alignment = get_alignment_mean(line_vectors, director)
|
61
|
+
return angle, alignment
|
62
|
+
|
63
|
+
|
64
|
+
def get_max_alignment_angle(
|
65
|
+
segment_thickness_dict: dict,
|
66
|
+
director: np.ndarray,
|
67
|
+
box_size: float,
|
68
|
+
grid_points: int = 360
|
69
|
+
) -> float:
|
70
|
+
"""Find the angle with the maximum alignment."""
|
71
|
+
angles = np.linspace(0, 2 * np.pi, grid_points)
|
72
|
+
|
73
|
+
with ProcessPoolExecutor() as executor:
|
74
|
+
results = executor.map(
|
75
|
+
compute_alignment_for_angle,
|
76
|
+
angles,
|
77
|
+
[segment_thickness_dict] * grid_points,
|
78
|
+
[director] * grid_points,
|
79
|
+
[box_size] * grid_points
|
80
|
+
)
|
81
|
+
|
82
|
+
return max(results, key=lambda x: x[1])[0]
|
83
|
+
|
84
|
+
def orientate_network(
|
85
|
+
segment_thickness_dict: dict,
|
86
|
+
config: list[dict],
|
87
|
+
rotate_angle: float,
|
88
|
+
box_size: float
|
89
|
+
) -> list[dict]:
|
90
|
+
"""
|
91
|
+
Rotates and clips a network of line segments within a bounding box.
|
92
|
+
|
93
|
+
Parameters:
|
94
|
+
segment_thickness_dict (dict): Segment data with start and end points.
|
95
|
+
config (list[dict]): Segment configuration with angle and thickness.
|
96
|
+
rotate_angle (float): Rotation angle in radians.
|
97
|
+
box_size (float): Size of the bounding box.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
list[dict]: New segment positions, angles, and thicknesses.
|
101
|
+
"""
|
102
|
+
center = np.array([box_size / 2, box_size / 2])
|
103
|
+
rotation_matrix = np.array([[np.cos(rotate_angle), -np.sin(rotate_angle)], [np.sin(rotate_angle), np.cos(rotate_angle)]])
|
104
|
+
box = Polygon_Shapely([(0, 0), (box_size, 0), (box_size, box_size), (0, box_size)])
|
105
|
+
|
106
|
+
segment_thickness_dict_new = {}
|
107
|
+
# orientated_config = []
|
108
|
+
for i, segment in enumerate(segment_thickness_dict.values()):
|
109
|
+
for vertex in segment.vertices:
|
110
|
+
angle_rotated = config[i]['angle'] - rotate_angle
|
111
|
+
start_rotated = rotate(np.array(v.middle_segment.start), center, rotation_matrix)
|
112
|
+
end_rotated = rotate(np.array(v.middle_segment.end), center, rotation_matrix)
|
113
|
+
|
114
|
+
# Find the intersection between the rotated line and the square
|
115
|
+
line_middle_point = LineString([start_rotated, end_rotated])
|
116
|
+
|
117
|
+
# Calculate the intersection between the box and the line
|
118
|
+
intersection = box.intersection(line_middle_point)
|
119
|
+
|
120
|
+
# Check if the line intersects the polygon
|
121
|
+
if intersection.is_empty:
|
122
|
+
continue
|
123
|
+
else:
|
124
|
+
length = intersection.length
|
125
|
+
# midpoint = intersection.interpolate(1/2, normalized=True)
|
126
|
+
midpoint = intersection.interpolate(length/2)
|
127
|
+
|
128
|
+
x = midpoint.xy[0][0]
|
129
|
+
y = midpoint.xy[1][0]
|
130
|
+
|
131
|
+
# orientated_config.append({ 'location': (x,y), 'angle': angle_rotated, 'thickness': config[i]['thickness'] })
|
132
|
+
|
133
|
+
# return orientated_config
|
134
|
+
|
135
|
+
return segment_thickness_dict_new
|
136
|
+
|
137
|
+
def generate_line_segments_thickness_orientation(
|
138
|
+
size: int,
|
139
|
+
thickness_arr: List[float],
|
140
|
+
orientation: List[int],
|
141
|
+
angles: List[float],
|
142
|
+
config: List[List[float]] = None,
|
143
|
+
epsilon: float = 0,
|
144
|
+
box_size: float = 1,
|
145
|
+
grid_points: int = 360
|
146
|
+
) -> List[Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], np.ndarray]]:
|
147
|
+
"""
|
148
|
+
Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
size (int): The number of line segments to generate.
|
152
|
+
thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
|
153
|
+
angles (str): The angle distribution method for generating segments. Defaults to 'uniform'.
|
154
|
+
List[float]: list of angles in radians.
|
155
|
+
orientation (List[int]): the orientation of the model.
|
156
|
+
config (List[List[float]]): A list of configurations for the nucleation points and angles.
|
157
|
+
epsilon (float): the minimum distance between two line.
|
158
|
+
box_size (float): the size of the system.
|
159
|
+
grid_points (int): the number of points to test for the alignment.
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
- an array of dictionaries for each orientation containing:
|
163
|
+
Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
164
|
+
- Updated dictionary of line segments.
|
165
|
+
- Updated dictionary of polygons.
|
166
|
+
- Updated dictionary of segment thicknesses.
|
167
|
+
- Array of the nucleation points and angles [x,y,theta].
|
168
|
+
"""
|
169
|
+
# Size of the box
|
170
|
+
box_size_0 = box_size*np.sqrt(2)
|
171
|
+
|
172
|
+
# Initial structure
|
173
|
+
data_dict = generate_line_segments_thickness(size = size,
|
174
|
+
thickness_arr = thickness_arr,
|
175
|
+
epsilon= epsilon,
|
176
|
+
config = config,
|
177
|
+
angles = angles,
|
178
|
+
box_size= box_size_0)
|
179
|
+
|
180
|
+
segment_thickness_dict = data_dict['segment_thickness_dict']
|
181
|
+
generated_config = data_dict['generated_config']
|
182
|
+
|
183
|
+
# Calculate alignment with the y axis
|
184
|
+
director = (0,1)
|
185
|
+
max_angle = get_max_alignment_angle(segment_thickness_dict, director, box_size, grid_points)
|
186
|
+
|
187
|
+
# Regenerate network for each orientation
|
188
|
+
output = [{'orientation': 'original', 'data_dict': data_dict}]
|
189
|
+
for o in orientation:
|
190
|
+
rotate_angle = o-max_angle
|
191
|
+
orientated_config = orientate_network(segment_thickness_dict, generated_config, rotate_angle, box_size)
|
192
|
+
|
193
|
+
data_dict_new = generate_line_segments_thickness(size=size,
|
194
|
+
thickness_arr=thickness_arr,
|
195
|
+
epsilon=epsilon,
|
196
|
+
config=orientated_config,
|
197
|
+
angles=angles,
|
198
|
+
box_size=box_size)
|
199
|
+
|
200
|
+
output.append({'orientation': o, 'data_dict': data_dict_new})
|
201
|
+
|
202
|
+
return output
|
@@ -1,13 +1,11 @@
|
|
1
1
|
import numpy as np
|
2
|
-
from typing import List, Dict, Tuple
|
3
|
-
from shapely.geometry import Polygon as Polygon_Shapely
|
4
|
-
|
2
|
+
from typing import List, Dict, Tuple
|
3
|
+
from shapely.geometry import Polygon as Polygon_Shapely
|
4
|
+
from shapely.geometry import LineString, box
|
5
5
|
from concurrent.futures import ProcessPoolExecutor
|
6
|
+
from .Classes import LineSegment, Polygon
|
6
7
|
|
7
|
-
|
8
|
-
from .generate_line_segments_dynamic_thickness import generate_line_segments_dynamic_thickness
|
9
|
-
|
10
|
-
def rot(point, center, rotation_matrix):
|
8
|
+
def rotate(point, center, rotation_matrix):
|
11
9
|
"""
|
12
10
|
Rotates a point around the center using the given rotation matrix.
|
13
11
|
point: numpy array representing the point to rotate
|
@@ -41,140 +39,356 @@ def get_alignment_mean(line_vector_arr, director):
|
|
41
39
|
|
42
40
|
return float(np.mean(S_all))
|
43
41
|
|
44
|
-
def compute_alignment_for_angle(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
for
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
42
|
+
def compute_alignment_for_angle(
|
43
|
+
angle: float,
|
44
|
+
segment_thickness_dict: dict[str, Polygon],
|
45
|
+
director: np.ndarray,
|
46
|
+
box_measurements: list[list[float]]
|
47
|
+
) -> tuple[float, float]:
|
48
|
+
"""
|
49
|
+
Computes the alignment of a network of segments for a given rotation angle.
|
50
|
+
|
51
|
+
This function rotates a network of segments based on the specified angle, clips the network to fit within the
|
52
|
+
specified bounding box, and then calculates the alignment of the network relative to a director vector.
|
53
|
+
|
54
|
+
Parameters:
|
55
|
+
-----------
|
56
|
+
angle : float
|
57
|
+
The angle (in radians or degrees, depending on your implementation) by which to rotate the network of segments.
|
58
|
+
segment_thickness_dict : dict[str, object]
|
59
|
+
A dictionary where the keys are segment IDs (as strings) and the values are objects representing segments
|
60
|
+
(should include properties like `middle_segment` and `area()`).
|
61
|
+
director : np.ndarray
|
62
|
+
A numpy array representing the director vector, typically a unit vector used for calculating the alignment.
|
63
|
+
box_measurements : list[list[float]]
|
64
|
+
A list containing the measurements of the bounding box. It typically contains four corner points as
|
65
|
+
sublists, with each sublist representing [x, y] coordinates of a corner.
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
--------
|
69
|
+
tuple[float, float]
|
70
|
+
A tuple where the first element is the input angle and the second element is the computed alignment value.
|
71
|
+
"""
|
72
|
+
box_center = (np.array(box_measurements[0]) + np.array(box_measurements[2])) / 2
|
73
|
+
|
74
|
+
# Rotate network
|
75
|
+
segment_thickness_dict_new = rotate_network(segment_thickness_dict, rotate_angle=angle, box_center=box_center)
|
76
|
+
|
77
|
+
# Clip network
|
78
|
+
segment_thickness_dict_new = clip_network(segment_thickness_dict_new, box_measurements=box_measurements)
|
79
|
+
|
80
|
+
line_vectors = [
|
81
|
+
{'line_vector': [seg.middle_segment.start, seg.middle_segment.end], 'area': seg.area()}
|
82
|
+
for seg in segment_thickness_dict_new.values()
|
83
|
+
]
|
84
|
+
|
85
|
+
alignment = get_alignment_mean(line_vectors, director)
|
86
|
+
|
59
87
|
return angle, alignment
|
60
88
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
89
|
+
def rotate_network(
|
90
|
+
segment_thickness_dict: dict[str, Polygon],
|
91
|
+
rotate_angle: float,
|
92
|
+
box_center: Tuple[float, float]
|
93
|
+
) -> dict[str, object]:
|
94
|
+
"""
|
95
|
+
Rotates a network of line segments around a given center point.
|
65
96
|
|
66
|
-
|
67
|
-
|
68
|
-
results = list(executor.map(
|
69
|
-
compute_alignment_for_angle,
|
70
|
-
angles,
|
71
|
-
[segment_thickness_dict] * len(angles),
|
72
|
-
[director] * len(angles),
|
73
|
-
[box_size] * len(angles)
|
74
|
-
))
|
97
|
+
This function rotates each segment in the provided network by a specified angle around the center of a bounding box.
|
98
|
+
The segments are represented by their vertices and a middle segment, and both are transformed using a rotation matrix.
|
75
99
|
|
76
|
-
|
77
|
-
|
100
|
+
Parameters:
|
101
|
+
-----------
|
102
|
+
segment_thickness_dict : dict[str, object]
|
103
|
+
A dictionary where the keys are segment IDs (as strings) and the values are segment objects. Each segment
|
104
|
+
object must have a `vertices` attribute (list of vertex coordinates) and a `middle_segment` attribute.
|
105
|
+
rotate_angle : float
|
106
|
+
The angle in radians by which to rotate the network of segments.
|
107
|
+
box_center : Tuple[float, float]
|
108
|
+
The (x, y) coordinates representing the center point around which to rotate the network.
|
78
109
|
|
79
|
-
|
110
|
+
Returns:
|
111
|
+
--------
|
112
|
+
dict[str, object]
|
113
|
+
A new dictionary with rotated segments, where the keys are the same segment IDs and the values are the
|
114
|
+
transformed segment objects with updated vertices and middle segments.
|
115
|
+
"""
|
116
|
+
# Define the center and rotation matrix for rotation
|
117
|
+
center = np.array([box_center[0], box_center[1]])
|
118
|
+
rotation_matrix = np.array([[np.cos(rotate_angle), -np.sin(rotate_angle)],
|
119
|
+
[np.sin(rotate_angle), np.cos(rotate_angle)]])
|
120
|
+
|
121
|
+
# Create a new dictionary to store the rotated segments
|
122
|
+
segment_thickness_dict_new = {}
|
123
|
+
|
124
|
+
# Iterate over each segment and apply the rotation
|
125
|
+
for id, segment in segment_thickness_dict.items():
|
126
|
+
vertices_new = []
|
127
|
+
# Rotate each vertex of the segment
|
128
|
+
for v in segment.vertices:
|
129
|
+
v_rotate = rotate(v, center, rotation_matrix)
|
130
|
+
vertices_new.append(v_rotate)
|
131
|
+
|
132
|
+
# Rotate the start and end points of the middle segment
|
133
|
+
start = rotate(segment.middle_segment.start, center, rotation_matrix)
|
134
|
+
end = rotate(segment.middle_segment.end, center, rotation_matrix)
|
135
|
+
|
136
|
+
# Create a new middle segment with rotated coordinates
|
137
|
+
middle_segment_new = LineSegment(start=start, end=end)
|
138
|
+
|
139
|
+
# Store the rotated segment in the new dictionary
|
140
|
+
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
141
|
+
|
142
|
+
return segment_thickness_dict_new
|
143
|
+
|
144
|
+
def clip_network(
|
145
|
+
segment_thickness_dict: dict[str, Polygon],
|
146
|
+
box_measurements: list[list[float]]
|
147
|
+
) -> dict[str, object]:
|
148
|
+
"""
|
149
|
+
Clips the segments in the network to fit within a bounding box.
|
150
|
+
|
151
|
+
This function clips each segment in the network so that only the portions that lie inside the bounding box are retained.
|
152
|
+
The bounding box is represented as a polygon, and any segment that intersects the box is clipped to the intersection area.
|
153
|
+
|
154
|
+
Parameters:
|
155
|
+
-----------
|
156
|
+
segment_thickness_dict : dict[str, object]
|
157
|
+
A dictionary where the keys are segment IDs (as strings) and the values are segment objects. Each segment
|
158
|
+
object must have a `vertices` attribute (list of vertex coordinates) and a `middle_segment` attribute.
|
159
|
+
box_measurements : list[list[float]]
|
160
|
+
A list of 4 sublists, each representing the [x, y] coordinates of a corner of the bounding box.
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
--------
|
164
|
+
dict[str, object]
|
165
|
+
A dictionary containing the clipped segments, where the keys are the same segment IDs and the values are the
|
166
|
+
clipped segment objects with updated vertices and middle segments.
|
167
|
+
"""
|
168
|
+
# Create a Shapely Polygon from the bounding box measurements
|
169
|
+
box_new = Polygon_Shapely([
|
170
|
+
(box_measurements[0][0], box_measurements[0][1]),
|
171
|
+
(box_measurements[1][0], box_measurements[1][1]),
|
172
|
+
(box_measurements[2][0], box_measurements[2][1]),
|
173
|
+
(box_measurements[3][0], box_measurements[3][1])
|
174
|
+
])
|
175
|
+
|
176
|
+
# Dictionary to store the clipped segments
|
177
|
+
segment_thickness_dict_new = {}
|
178
|
+
|
179
|
+
# Iterate over each segment
|
180
|
+
for id, segment in enumerate(segment_thickness_dict.values()):
|
181
|
+
vertices_new = []
|
182
|
+
vertices = segment.vertices
|
183
|
+
|
184
|
+
# Create a Shapely polygon for the segment's vertices
|
185
|
+
pol = Polygon_Shapely(vertices)
|
186
|
+
|
187
|
+
# Find the intersection between the segment's polygon and the bounding box
|
188
|
+
intersection = box_new.intersection(pol)
|
189
|
+
if not intersection.is_empty:
|
190
|
+
# If there is an intersection, retrieve the clipped vertices
|
191
|
+
vertices_new = list(intersection.exterior.coords)
|
192
|
+
|
193
|
+
if vertices_new:
|
194
|
+
# If new vertices exist, clip the middle segment as well
|
195
|
+
start = segment.middle_segment.start
|
196
|
+
end = segment.middle_segment.end
|
80
197
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
198
|
+
middle_segment_new = None
|
199
|
+
# Find the intersection between the middle segment and the bounding box
|
200
|
+
intersection = box_new.intersection(LineString([start, end]))
|
201
|
+
if not intersection.is_empty:
|
202
|
+
start = list(intersection.coords)[0]
|
203
|
+
end = list(intersection.coords)[-1]
|
204
|
+
middle_segment_new = LineSegment(start=start, end=end)
|
85
205
|
|
86
|
-
|
87
|
-
|
206
|
+
# Create a new clipped polygon with updated vertices and middle segment
|
207
|
+
pol_new = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
208
|
+
pol_new.sort_vertices() # Ensure vertices are sorted
|
209
|
+
segment_thickness_dict_new[id] = pol_new
|
88
210
|
|
89
|
-
|
90
|
-
for i, v in enumerate(segment_thickness_dict.values()):
|
91
|
-
angle_rotated = config_angles[i] - rotate_angle
|
92
|
-
n_p_0 = rot(np.array(v.middle_segment.start), center, rotation_matrix)
|
93
|
-
n_p_1 = rot(np.array(v.middle_segment.end), center, rotation_matrix)
|
211
|
+
return segment_thickness_dict_new
|
94
212
|
|
95
|
-
|
96
|
-
|
97
|
-
|
213
|
+
def translate_network(
|
214
|
+
segment_thickness_dict: dict[str, Polygon],
|
215
|
+
translation_vector: np.ndarray
|
216
|
+
) -> dict[str, object]:
|
217
|
+
"""
|
218
|
+
Translates a network of line segments by a given translation vector.
|
219
|
+
|
220
|
+
This function moves each segment in the network by applying the translation vector to the coordinates of the vertices
|
221
|
+
and the start and end points of the middle segment (if it exists).
|
222
|
+
|
223
|
+
Parameters:
|
224
|
+
-----------
|
225
|
+
segment_thickness_dict : dict[str, object]
|
226
|
+
A dictionary where the keys are segment IDs (as strings) and the values are segment objects. Each segment
|
227
|
+
object must have `vertices` (list of vertex coordinates) and `middle_segment` attributes.
|
228
|
+
translation_vector : np.ndarray
|
229
|
+
A 2D numpy array representing the translation vector [x, y] that will be applied to all the vertices and
|
230
|
+
middle segments of each segment.
|
98
231
|
|
99
|
-
|
100
|
-
|
232
|
+
Returns:
|
233
|
+
--------
|
234
|
+
dict[str, object]
|
235
|
+
A new dictionary with the translated segments, where the keys are the same segment IDs and the values are
|
236
|
+
the translated segment objects.
|
237
|
+
"""
|
238
|
+
# Create a new dictionary to store the translated segments
|
239
|
+
segment_thickness_dict_new = {}
|
101
240
|
|
102
|
-
|
103
|
-
|
104
|
-
|
241
|
+
# Iterate over each segment and apply the translation
|
242
|
+
for id, segment in segment_thickness_dict.items():
|
243
|
+
# Translate the vertices by adding the translation vector to each vertex
|
244
|
+
vertices_new = [np.array(v) + translation_vector for v in segment.vertices]
|
245
|
+
|
246
|
+
# Check if the segment has a middle segment to translate
|
247
|
+
if segment.middle_segment is None:
|
248
|
+
middle_segment_new = None
|
105
249
|
else:
|
106
|
-
|
107
|
-
|
250
|
+
start = segment.middle_segment.start + translation_vector
|
251
|
+
end = segment.middle_segment.end + translation_vector
|
252
|
+
middle_segment_new = LineSegment(start=start, end=end)
|
253
|
+
|
254
|
+
# Store the translated segment in the new dictionary
|
255
|
+
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
108
256
|
|
109
|
-
|
110
|
-
y = midpoint.xy[1][0]
|
257
|
+
return segment_thickness_dict_new
|
111
258
|
|
112
|
-
|
259
|
+
def get_alignment_mean(line_vector_arr, director):
|
260
|
+
"""Get the mean alignment."""
|
261
|
+
S_all = []
|
262
|
+
for item in line_vector_arr:
|
263
|
+
line_vector = item['line_vector']
|
264
|
+
area = item['area']
|
265
|
+
P2 = 0.5*(3*(np.cos(angle_between(line_vector, director)))**2-1)
|
266
|
+
S_all.append(P2*area)
|
113
267
|
|
114
|
-
return
|
268
|
+
return float(np.mean(S_all))
|
115
269
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
270
|
+
def compute_alignment_for_angle(
|
271
|
+
segment_thickness_dict: dict,
|
272
|
+
angle: float,
|
273
|
+
box_center,
|
274
|
+
director: np.ndarray,
|
275
|
+
) -> tuple[float, float]:
|
276
|
+
"""Compute the alignment for a given angle."""
|
277
|
+
|
278
|
+
# Rotate the segment network for the given angle
|
279
|
+
segment_thickness_dict_rotated = rotate_network(segment_thickness_dict, rotate_angle=angle, box_center=box_center)
|
280
|
+
|
281
|
+
# Create line vectors from the rotated segments
|
282
|
+
line_vectors = []
|
283
|
+
for s in segment_thickness_dict_rotated.values():
|
284
|
+
line_vectors.append({'line_vector': np.array([s.middle_segment.start, s.middle_segment.end]), 'area': s.area()})
|
285
|
+
|
286
|
+
# Compute the alignment for the current angle
|
287
|
+
alignment = get_alignment_mean(line_vectors, director)
|
288
|
+
return angle, alignment
|
289
|
+
|
290
|
+
def get_max_alignment_angle(
|
291
|
+
segment_thickness_dict: dict,
|
292
|
+
director: np.ndarray,
|
293
|
+
box_measurements: list[float],
|
124
294
|
grid_points: int = 360
|
125
|
-
|
295
|
+
) -> float:
|
296
|
+
"""Find the angle with the maximum alignment using parallel processing."""
|
297
|
+
|
298
|
+
# Create a list of angles to evaluate
|
299
|
+
angles = np.linspace(0, 2 * np.pi, grid_points)
|
300
|
+
|
301
|
+
# Use ProcessPoolExecutor for parallel computation of alignment
|
302
|
+
with ProcessPoolExecutor() as executor:
|
303
|
+
# Submit tasks to the pool for each angle
|
304
|
+
results = list(executor.map(
|
305
|
+
compute_alignment_for_angle,
|
306
|
+
[segment_thickness_dict] * len(angles), # Same segment dictionary for all angles
|
307
|
+
angles, # Different angles
|
308
|
+
[box_measurements] * len(angles), # Same box measurements for all angles
|
309
|
+
[director] * len(angles) # Same director for all angles
|
310
|
+
))
|
311
|
+
|
312
|
+
# Find the angle with the maximum alignment
|
313
|
+
max_alignment = 0
|
314
|
+
max_angle = 0
|
315
|
+
for angle, alignment in results:
|
316
|
+
if alignment > max_alignment:
|
317
|
+
max_alignment = alignment
|
318
|
+
max_angle = angle
|
319
|
+
|
320
|
+
return max_angle
|
321
|
+
|
322
|
+
def generate_line_segments_thickness_orientation(
|
323
|
+
data_dict: Dict[str, dict],
|
324
|
+
orientation: List[int],
|
325
|
+
grid_points: int = 360,
|
326
|
+
box_measurements: List[Tuple[float, float]] = [(0, 0), (0, 1), (1, 1), (1, 0)]
|
327
|
+
) -> List[Dict[str, dict]]:
|
126
328
|
"""
|
127
|
-
Generates a
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
329
|
+
Generates a set of networks of line segments with different thicknesses and orientations, and clips them to fit
|
330
|
+
within a bounding box. The function also aligns the network to the maximum alignment angle with respect to the y-axis.
|
331
|
+
|
332
|
+
Parameters:
|
333
|
+
-----------
|
334
|
+
data_dict : Dict[str, dict]
|
335
|
+
A dictionary containing the initial network data. Must include the key 'segment_thickness_dict', which holds
|
336
|
+
the segment information.
|
337
|
+
orientation : List[int]
|
338
|
+
A list of orientations (angles in degrees or radians) to rotate the network. For each orientation, the network
|
339
|
+
is regenerated and rotated.
|
340
|
+
grid_points : int, optional
|
341
|
+
The number of grid points for calculating the maximum alignment angle (default is 360).
|
342
|
+
box_measurements : List[Tuple[float, float]], optional
|
343
|
+
A list of tuples representing the corner points of the bounding box (default is a unit square).
|
138
344
|
|
139
345
|
Returns:
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
- Updated dictionary of segment thicknesses.
|
145
|
-
- Array of the nucleation points and angles [x,y,theta].
|
346
|
+
--------
|
347
|
+
List[Dict[str, dict]]
|
348
|
+
A list of dictionaries. Each dictionary contains the 'orientation' of the network and the updated 'data_dict'
|
349
|
+
with the rotated and clipped segment information.
|
146
350
|
"""
|
147
|
-
# Size of the box
|
148
|
-
box_size_0 = box_size*np.sqrt(2)
|
149
|
-
|
150
|
-
# Initial structure
|
151
|
-
data_dict = generate_line_segments_dynamic_thickness(size = size,
|
152
|
-
thickness_arr = thickness_arr,
|
153
|
-
epsilon= epsilon,
|
154
|
-
config = config,
|
155
|
-
angles = angles,
|
156
|
-
box_size= box_size_0)
|
157
351
|
|
352
|
+
# Compute the center of the box
|
353
|
+
box_center = (np.array(box_measurements[0]) + np.array(box_measurements[2])) / 2
|
354
|
+
|
355
|
+
# Extract the segment thickness dictionary from the input data
|
158
356
|
segment_thickness_dict = data_dict['segment_thickness_dict']
|
159
|
-
generated_config = data_dict['generated_config']
|
160
357
|
|
161
|
-
#
|
162
|
-
director = (0,1)
|
163
|
-
|
358
|
+
# Define the director vector along the y-axis
|
359
|
+
director = np.array([0, 1])
|
360
|
+
|
361
|
+
# Find the angle that aligns the network most with the y-axis
|
362
|
+
max_angle = get_max_alignment_angle(segment_thickness_dict, director, box_measurements, grid_points)
|
363
|
+
|
364
|
+
# Store the initial unmodified configuration
|
365
|
+
output = [{'orientation': 'original', 'data_dict': data_dict}]
|
164
366
|
|
165
|
-
#
|
166
|
-
output = []
|
367
|
+
# Loop through each given orientation, rotate, clip, and translate the network
|
167
368
|
for o in orientation:
|
168
|
-
|
169
|
-
|
369
|
+
# Compute the rotation angle for the current orientation relative to max alignment
|
370
|
+
rotate_angle = o - max_angle
|
371
|
+
|
372
|
+
# Rotate the network by the computed angle
|
373
|
+
segment_thickness_dict_new = rotate_network(segment_thickness_dict, rotate_angle=rotate_angle, box_center=box_center)
|
170
374
|
|
171
|
-
|
172
|
-
|
173
|
-
epsilon=epsilon,
|
174
|
-
config=orientated_config,
|
175
|
-
angles=angles,
|
176
|
-
box_size=box_size)
|
375
|
+
# Clip the rotated network to fit within the bounding box
|
376
|
+
segment_thickness_dict_new = clip_network(segment_thickness_dict_new, box_measurements=box_measurements)
|
177
377
|
|
378
|
+
# Translate the clipped network to start at the origin (0,0)
|
379
|
+
translation_vector = -np.array(box_measurements[0])
|
380
|
+
segment_thickness_dict_new = translate_network(segment_thickness_dict_new, translation_vector)
|
381
|
+
|
382
|
+
# Prepare a new data dictionary with the transformed segment information
|
383
|
+
data_dict_new = {
|
384
|
+
'segments_dict': None,
|
385
|
+
'polygon_arr': None,
|
386
|
+
'segment_thickness_dict': segment_thickness_dict_new,
|
387
|
+
'jammed': None,
|
388
|
+
'generated_config': None
|
389
|
+
}
|
390
|
+
|
391
|
+
# Append the result for this orientation
|
178
392
|
output.append({'orientation': o, 'data_dict': data_dict_new})
|
179
393
|
|
180
394
|
return output
|
File without changes
|
File without changes
|
File without changes
|