RDG-Networks 0.3.4__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.4.dist-info → RDG_Networks-0.3.6.dist-info}/METADATA +1 -1
- RDG_Networks-0.3.6.dist-info/RECORD +22 -0
- {RDG_Networks-0.3.4.dist-info → RDG_Networks-0.3.6.dist-info}/WHEEL +1 -1
- RDG_Networks-0.3.6.dist-info/entry_points.txt +14 -0
- RDG_networks/__init__.py +17 -5
- RDG_networks/save_to_stl.py +105 -0
- RDG_networks/thickness/Classes.py +3 -2
- RDG_networks/thickness/__init__.py +12 -2
- RDG_networks/thickness/{generate_line_segments_dynamic_thickness.py → generate_line_segments_thickness.py} +115 -46
- RDG_networks/thickness/generate_line_segments_thickness_orientation backup.py +202 -0
- RDG_networks/thickness/generate_line_segments_thickness_orientation.py +394 -0
- RDG_Networks-0.3.4.dist-info/RECORD +0 -19
- RDG_Networks-0.3.4.dist-info/entry_points.txt +0 -8
- {RDG_Networks-0.3.4.dist-info → RDG_Networks-0.3.6.dist-info}/LICENSE.txt +0 -0
- {RDG_Networks-0.3.4.dist-info → RDG_Networks-0.3.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
|
2
|
+
RDG_networks/__init__.py,sha256=SmpD26lMaZ3wPz2Hs2Z-24AJiEU5DbNldhQt1fZxI0U,1607
|
3
|
+
RDG_networks/draw_segments.py,sha256=U53N5GXmQHWKdM1Q1faP_EGKjc6enOu2mcsunzSFpP0,984
|
4
|
+
RDG_networks/generate_line_network.py,sha256=lJ4rhObim3WcEQoebomewRQKWNJC5phFyFYRW7qjXIg,1127
|
5
|
+
RDG_networks/generate_line_segments.py,sha256=QV8_k7q6TD5c7Hcb2Ms_apEdWYw4XdLr7rdJgh49v4Q,9004
|
6
|
+
RDG_networks/generate_line_segments_dynamic.py,sha256=GoIhGXYbcvjqR5BJCnkvAGp8QBpzsE1ZSbl2k9XAOGI,7531
|
7
|
+
RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ5azzrKcQe3fuWzCE,9265
|
8
|
+
RDG_networks/get_intersection_segments.py,sha256=mXB5qCy1oOps4Vu1mX6flW6v_4Xxc71YK41yOWjJX8o,2797
|
9
|
+
RDG_networks/sample_in_polygon.py,sha256=qpPpW-Da1vK8ZkVWMJ0zBsE8IgyMB619gCdybSkzKSQ,1605
|
10
|
+
RDG_networks/save_to_stl.py,sha256=xHwuoG39cbemggoIjT44DlsMlhjlV3uxTWo6gcHEePA,3414
|
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
|
16
|
+
RDG_networks/thickness/sample_in_polygon.py,sha256=nJ-yqfoCCGfC6_EpGL3L1t1LOYdqWZd-7v5bxy6th34,1849
|
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,,
|
@@ -0,0 +1,14 @@
|
|
1
|
+
[console_scripts]
|
2
|
+
clip_network = RDG_networks.thickness.clip_network:main
|
3
|
+
draw_segments = RDG_networks.draw_segments:main
|
4
|
+
generate_line_network = RDG_networks.generate_line_network:main
|
5
|
+
generate_line_segments = RDG_networks.generate_line_segments:main
|
6
|
+
generate_line_segments_dynamic = RDG_networks.generate_line_segments_dynamic:main
|
7
|
+
generate_line_segments_static = RDG_networks.generate_line_segments_static:main
|
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
|
11
|
+
get_intersection_segments = RDG_networks.get_intersection_segments:main
|
12
|
+
rotate_network = RDG_networks.thickness.rotate_network:main
|
13
|
+
save_to_stl = RDG_networks.save_to_stl:main
|
14
|
+
translate_network = RDG_networks.thickness.translate_network:main
|
RDG_networks/__init__.py
CHANGED
@@ -7,17 +7,29 @@ from .get_intersection_segments import get_intersection_segments
|
|
7
7
|
from .generate_line_segments_dynamic import generate_line_segments_dynamic
|
8
8
|
from .generate_line_segments_static import generate_line_segments_static
|
9
9
|
from .draw_segments import draw_segments
|
10
|
-
from .thickness.
|
10
|
+
from .thickness.generate_line_segments_thickness import generate_line_segments_thickness
|
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
|
16
|
+
from .save_to_stl import save_to_stl
|
11
17
|
|
12
18
|
__all__ = ['generate_line_segments',
|
13
|
-
'
|
14
|
-
'
|
19
|
+
'generate_line_segments_thickness',
|
20
|
+
'generate_line_segments_thickness_orientation',
|
21
|
+
'translate_network',
|
22
|
+
'clip_network',
|
23
|
+
'rotate_network',
|
24
|
+
'get_alignment_mean',
|
15
25
|
'generate_line_segments_dynamic',
|
16
26
|
'generate_line_segments_static',
|
17
|
-
'
|
27
|
+
'generate_line_network',
|
28
|
+
'get_intersection_segments',
|
18
29
|
'draw_segments',
|
19
30
|
'sample_in_polygon',
|
20
31
|
'Line',
|
21
32
|
'LineSegment',
|
22
|
-
'Polygon'
|
33
|
+
'Polygon',
|
34
|
+
'save_to_stl'
|
23
35
|
]
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import json
|
3
|
+
import pandas as pd
|
4
|
+
import networkx as nx
|
5
|
+
import random
|
6
|
+
import matplotlib.pyplot as plt
|
7
|
+
import numpy as np
|
8
|
+
import math
|
9
|
+
import pickle
|
10
|
+
|
11
|
+
import numpy as np
|
12
|
+
from stl import mesh
|
13
|
+
from shapely.geometry import Polygon
|
14
|
+
from shapely.ops import unary_union
|
15
|
+
|
16
|
+
# Function to convert 2D polygon to 3D mesh by adding thickness
|
17
|
+
def polygon_to_3d_mesh(polygon, thickness=1.0):
|
18
|
+
# Get the exterior coordinates of the 2D polygon
|
19
|
+
exterior_coords = list(polygon.exterior.coords)
|
20
|
+
|
21
|
+
# Create vertices for the top and bottom of the 3D shape
|
22
|
+
top_vertices = [(x, y, thickness) for x, y in exterior_coords]
|
23
|
+
bottom_vertices = [(x, y, 0) for x, y in exterior_coords]
|
24
|
+
|
25
|
+
# Vertices array: two sets of vertices (top and bottom)
|
26
|
+
vertices = np.array(top_vertices + bottom_vertices)
|
27
|
+
n = len(exterior_coords)
|
28
|
+
|
29
|
+
# Create faces (triangles) connecting the top and bottom surfaces
|
30
|
+
faces = []
|
31
|
+
|
32
|
+
# Create side walls
|
33
|
+
for i in range(n):
|
34
|
+
next_i = (i + 1) % n
|
35
|
+
faces.append([i, next_i, n + next_i]) # Top to bottom
|
36
|
+
faces.append([i, n + next_i, n + i]) # Bottom to top
|
37
|
+
|
38
|
+
# Create top and bottom surfaces
|
39
|
+
for i in range(1, n - 1):
|
40
|
+
faces.append([0, i+1, i ]) # Top face
|
41
|
+
faces.append([n, n + i, n + i+1]) # Bottom face
|
42
|
+
|
43
|
+
# Convert faces to NumPy array
|
44
|
+
faces = np.array(faces)
|
45
|
+
|
46
|
+
# Create mesh object
|
47
|
+
polygon_mesh = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
|
48
|
+
|
49
|
+
for i, face in enumerate(faces):
|
50
|
+
for j in range(3):
|
51
|
+
polygon_mesh.vectors[i][j] = vertices[face[j], :]
|
52
|
+
|
53
|
+
return polygon_mesh
|
54
|
+
|
55
|
+
def merge_3d_meshes(mesh_list):
|
56
|
+
# List to hold the vertices and faces of the merged mesh
|
57
|
+
vertices = []
|
58
|
+
faces = []
|
59
|
+
|
60
|
+
# Variable to track the current offset for the face indices
|
61
|
+
vertex_offset = 0
|
62
|
+
|
63
|
+
# Iterate over each mesh and extract its vertices and faces
|
64
|
+
for m in mesh_list:
|
65
|
+
# Extract the vertices and faces of the current mesh
|
66
|
+
current_vertices = m.vectors.reshape(-1, 3) # Each face is a set of 3 vertices
|
67
|
+
current_faces = np.arange(len(current_vertices)).reshape(-1, 3)
|
68
|
+
|
69
|
+
# Append the vertices, and adjust the face indices by the current offset
|
70
|
+
vertices.append(current_vertices)
|
71
|
+
faces.append(current_faces + vertex_offset)
|
72
|
+
|
73
|
+
# Update the vertex offset for the next mesh
|
74
|
+
vertex_offset += len(current_vertices)
|
75
|
+
|
76
|
+
# Concatenate all the vertices and faces into a single array
|
77
|
+
vertices = np.vstack(vertices)
|
78
|
+
faces = np.vstack(faces)
|
79
|
+
|
80
|
+
# Create a new merged mesh
|
81
|
+
merged_mesh = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
|
82
|
+
|
83
|
+
# Fill the new mesh with the vertices and faces
|
84
|
+
for i, face in enumerate(faces):
|
85
|
+
for j in range(3):
|
86
|
+
merged_mesh.vectors[i][j] = vertices[face[j], :]
|
87
|
+
|
88
|
+
return merged_mesh
|
89
|
+
|
90
|
+
def save_to_stl(seg_thick_dict, thickness, name):
|
91
|
+
mesh_list = []
|
92
|
+
for k,v in seg_thick_dict.items():
|
93
|
+
p = []
|
94
|
+
for j in v.vertices:
|
95
|
+
try:
|
96
|
+
p.append((float(j[0]), float(j[1])))
|
97
|
+
except:
|
98
|
+
None
|
99
|
+
|
100
|
+
mesh_list.append(polygon_to_3d_mesh(Polygon(p), thickness=thickness))
|
101
|
+
|
102
|
+
merged_mesh = merge_3d_meshes(mesh_list)
|
103
|
+
|
104
|
+
# Save the merged mesh as an STL file
|
105
|
+
merged_mesh.save(name)
|
@@ -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):
|
@@ -1,7 +1,17 @@
|
|
1
1
|
# __init__.py
|
2
2
|
|
3
|
-
from .
|
3
|
+
from .generate_line_segments_thickness import generate_line_segments_thickness
|
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
|
4
9
|
|
5
10
|
__all__ = [
|
6
|
-
'
|
11
|
+
'generate_line_segments_thickness',
|
12
|
+
'generate_line_segments_thickness_orientation',
|
13
|
+
'translate_network',
|
14
|
+
'clip_network',
|
15
|
+
'rotate_network',
|
16
|
+
'get_alignment_mean'
|
7
17
|
]
|
@@ -2,17 +2,19 @@ import math
|
|
2
2
|
import numpy as np
|
3
3
|
import random
|
4
4
|
from typing import List, Dict, Tuple, Union, Optional
|
5
|
+
from shapely.geometry import Polygon as Polygon_Shapely, LineString
|
5
6
|
|
6
7
|
from .Classes import Line, LineSegment, Polygon
|
7
8
|
from .sample_in_polygon import sample_in_polygon, is_inside_polygon
|
8
9
|
|
9
|
-
def doLinesIntersect(line1: Line, line2: Line) -> Tuple[bool, Union[Tuple[float, float], None]]:
|
10
|
+
def doLinesIntersect(line1: Line, line2: Line, box_size = 1) -> Tuple[bool, Union[Tuple[float, float], None]]:
|
10
11
|
"""
|
11
12
|
Check if two lines intersect and return the intersection point.
|
12
13
|
|
13
14
|
Args:
|
14
15
|
- line1 (Line): The first line segment.
|
15
16
|
- line2 (Line): The second line segment.
|
17
|
+
- box_size (float): The size of the bounding box. Defaults to 1.
|
16
18
|
|
17
19
|
Returns:
|
18
20
|
- intersect (bool): True if the lines intersect, False otherwise.
|
@@ -35,14 +37,16 @@ def doLinesIntersect(line1: Line, line2: Line) -> Tuple[bool, Union[Tuple[float,
|
|
35
37
|
intersect_x = x1 + v1 * t1
|
36
38
|
intersect_y = y2 + w2 * t2
|
37
39
|
|
38
|
-
|
40
|
+
|
41
|
+
if -1e-6 < intersect_x < box_size + 1e-6 and -1e-6 < intersect_y < box_size + 1e-6:
|
39
42
|
return True, (intersect_x, intersect_y)
|
40
43
|
else:
|
41
44
|
return False, (None, None)
|
42
45
|
|
43
46
|
def doSegmentsIntersect(
|
44
47
|
segment1: LineSegment,
|
45
|
-
segment2: LineSegment
|
48
|
+
segment2: LineSegment,
|
49
|
+
box_size = 1
|
46
50
|
) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
47
51
|
"""
|
48
52
|
Determines if two line segments intersect and returns the intersection point if they do.
|
@@ -63,7 +67,7 @@ def doSegmentsIntersect(
|
|
63
67
|
line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
|
64
68
|
|
65
69
|
# Check if the infinite extensions of the two lines intersect
|
66
|
-
intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2)
|
70
|
+
intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2, box_size)
|
67
71
|
|
68
72
|
# If no intersection, return False
|
69
73
|
if not intersect:
|
@@ -138,6 +142,8 @@ def pick_item_with_probability(
|
|
138
142
|
def get_location_and_direction(
|
139
143
|
polygon_arr: Dict[str, Dict[str, object]],
|
140
144
|
thickness: float,
|
145
|
+
nucleation_point: Tuple[float, float] = None,
|
146
|
+
min_distance: float = 0,
|
141
147
|
max_attempts: int = 1000,
|
142
148
|
angles: Union[str, List[float]] = 'uniform'
|
143
149
|
) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
@@ -155,6 +161,10 @@ def get_location_and_direction(
|
|
155
161
|
The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
156
162
|
angles (Union[str, List[float]], optional):
|
157
163
|
A string ('uniform' for random directions) or a list of angles (in radians) to choose the direction from. Defaults to 'uniform'.
|
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.
|
158
168
|
|
159
169
|
Returns:
|
160
170
|
Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
@@ -164,17 +174,20 @@ def get_location_and_direction(
|
|
164
174
|
- The new location as a tuple of floats (`Tuple[float, float]`).
|
165
175
|
- The direction vector as a numpy array (`np.ndarray`).
|
166
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.
|
167
179
|
- Returns `False` if no valid location and direction are found after the maximum attempts.
|
168
180
|
"""
|
169
181
|
|
170
182
|
# Generate a new direction based on the angles parameter
|
171
183
|
if angles == 'uniform':
|
172
|
-
|
184
|
+
angle_new = random.uniform(-np.pi, np.pi)
|
185
|
+
direction = (np.cos(angle_new), np.sin(angle_new))
|
173
186
|
direction = direction / np.linalg.norm(direction) # Normalize the direction vector
|
174
187
|
else:
|
175
|
-
|
176
|
-
direction =
|
177
|
-
direction = np.array(direction) / np.linalg.norm(direction)
|
188
|
+
angle_new = random.choice(angles)
|
189
|
+
direction = (np.cos(angle_new), np.sin(angle_new))
|
190
|
+
direction = np.array(direction) / np.linalg.norm(direction)
|
178
191
|
|
179
192
|
# Try to find a valid location and direction up to max_attempts
|
180
193
|
attempt = 0
|
@@ -182,7 +195,11 @@ def get_location_and_direction(
|
|
182
195
|
polygon_id, polygon = pick_item_with_probability(polygon_arr)
|
183
196
|
|
184
197
|
# Sample a location within the polygon
|
185
|
-
|
198
|
+
#check if nucleation point is given
|
199
|
+
if nucleation_point is not None:
|
200
|
+
location_new = nucleation_point
|
201
|
+
else:
|
202
|
+
location_new = sample_in_polygon(polygon['vertices'])
|
186
203
|
|
187
204
|
# Compute the perpendicular vector to the direction
|
188
205
|
perpendicular = np.array([direction[1], -direction[0]])
|
@@ -193,12 +210,12 @@ def get_location_and_direction(
|
|
193
210
|
perpendicular = -perpendicular
|
194
211
|
|
195
212
|
# Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
|
196
|
-
p1 = np.array(location_new) + thickness
|
197
|
-
p2 = np.array(location_new) - thickness
|
213
|
+
p1 = np.array(location_new) + (thickness/2 + min_distance) * perpendicular
|
214
|
+
p2 = np.array(location_new) - (thickness/2 + min_distance) * perpendicular
|
198
215
|
|
199
216
|
# Check if both endpoints of the segment are inside the polygon
|
200
217
|
if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
|
201
|
-
return polygon_id, polygon, location_new, direction, perpendicular
|
218
|
+
return polygon_id, polygon, location_new, direction, perpendicular, angle_new
|
202
219
|
|
203
220
|
attempt += 1
|
204
221
|
|
@@ -259,8 +276,9 @@ def get_new_segment(
|
|
259
276
|
line_segments_to_check: List[LineSegment],
|
260
277
|
location: Tuple[float, float],
|
261
278
|
direction: Tuple[float, float],
|
262
|
-
id: Optional[int] = None
|
263
|
-
|
279
|
+
id: Optional[int] = None,
|
280
|
+
box_size: float = 1
|
281
|
+
) -> LineSegment:
|
264
282
|
"""
|
265
283
|
Creates a new line segment by extending a given location in a specified direction and
|
266
284
|
determines its neighbors by checking intersections with other line segments.
|
@@ -270,18 +288,20 @@ def get_new_segment(
|
|
270
288
|
location (Tuple[float, float]): The starting point (x, y) for the new line segment.
|
271
289
|
direction (Tuple[float, float]): The direction vector in which to extend the line segment.
|
272
290
|
id (Optional[int]): Optional ID for the new line segment. If not provided, defaults to None.
|
291
|
+
box_size(optional[Int]]): The size of the box. Defaults to 1.
|
273
292
|
|
274
293
|
Returns:
|
275
294
|
LineSegment: A new line segment object with its neighbors based on intersections.
|
276
295
|
"""
|
277
296
|
|
278
297
|
# Create a temporary line segment extending from the location in both directions
|
279
|
-
s_temp = LineSegment(start=np.array(location) - 10 * np.array(direction), end=np.array(location) + 10 * np.array(direction))
|
280
|
-
|
298
|
+
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))
|
299
|
+
|
300
|
+
intersection_points = []
|
281
301
|
|
282
302
|
# Check for intersections with existing line segments
|
283
303
|
for segment in line_segments_to_check:
|
284
|
-
intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment)
|
304
|
+
intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment, box_size)
|
285
305
|
|
286
306
|
if intersect:
|
287
307
|
segment_length = math.sqrt(
|
@@ -318,10 +338,10 @@ def get_new_segment(
|
|
318
338
|
return segment_new
|
319
339
|
|
320
340
|
def update_data(
|
321
|
-
segments_dict: Dict[int,
|
341
|
+
segments_dict: Dict[int, LineSegment],
|
322
342
|
polygon_arr: Dict[str, Dict[str, object]],
|
323
343
|
polygon_id: str,
|
324
|
-
segment_thickness_dict: Dict[int,
|
344
|
+
segment_thickness_dict: Dict[int, Polygon],
|
325
345
|
vertices0: List[Tuple[float, float]],
|
326
346
|
vertices1: List[Tuple[float, float]],
|
327
347
|
vertices2: List[Tuple[float, float]],
|
@@ -431,9 +451,13 @@ def add_line_segment(
|
|
431
451
|
segments_dict: Dict[int, LineSegment],
|
432
452
|
polygon_arr: Dict[str, Dict[str, object]],
|
433
453
|
segment_thickness_dict: Dict[int, Polygon],
|
434
|
-
|
435
|
-
|
436
|
-
|
454
|
+
angles: str = 'uniform',
|
455
|
+
thickness: float = 0,
|
456
|
+
nucleation_point: Tuple[float, float] = None,
|
457
|
+
min_distance: float = 0,
|
458
|
+
box_size: float = 1,
|
459
|
+
max_attempts: int = 1000
|
460
|
+
) -> Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], List[float], float], bool]:
|
437
461
|
"""
|
438
462
|
Adds a new line segment to the segments and polygon data structures, with a given thickness and angle distribution.
|
439
463
|
|
@@ -443,26 +467,31 @@ def add_line_segment(
|
|
443
467
|
segment_thickness_dict (Dict[int, Polygon]): A dictionary storing the thickness information mapped to polygons.
|
444
468
|
thickness (float): The thickness of the new segment to be added. Defaults to 0.
|
445
469
|
angles (str): The angle distribution method. Defaults to 'uniform'.
|
470
|
+
nucleation_point (Tuple[float, float]): A predefined nucleation point for the new segment. Defaults to None.
|
471
|
+
min_distance (float): The minimum distance between two lines. Defaults to 0.
|
472
|
+
box_size (float): The size of the box. Defaults to 1.
|
473
|
+
max_attempts (int): The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
446
474
|
|
447
475
|
Returns:
|
448
|
-
Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]], bool]:
|
476
|
+
Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]], List[float], float, bool]:
|
449
477
|
- A tuple containing the updated segments dictionary, polygon dictionary, and thickness dictionary,
|
450
478
|
or False if no valid location for the new segment is found.
|
479
|
+
-nucleation point in a list [x,y] and the angle of the segment in radians.
|
451
480
|
"""
|
452
481
|
|
453
482
|
# Get a valid location and direction, or return False if none is found
|
454
|
-
loc = get_location_and_direction(polygon_arr, thickness, max_attempts=
|
483
|
+
loc = get_location_and_direction(polygon_arr, thickness, nucleation_point, min_distance, max_attempts=max_attempts, angles=angles)
|
455
484
|
if loc:
|
456
|
-
polygon_id, polygon, location_new, direction_new, perpendicular = loc
|
485
|
+
polygon_id, polygon, location_new, direction_new, perpendicular, angle_new = loc
|
457
486
|
else:
|
458
487
|
print('No valid location found')
|
459
488
|
return False
|
460
489
|
|
461
490
|
# Get the borders of the new segment with the given thickness
|
462
491
|
line_segments_to_check = [segments_dict[segment] for segment in polygon['faces']]
|
463
|
-
middle_segment = get_new_segment(line_segments_to_check, location=location_new, direction=direction_new)
|
464
|
-
s1 = get_new_segment(line_segments_to_check, location=np.array(location_new) + thickness * perpendicular / 2, direction=direction_new)
|
465
|
-
s2 = get_new_segment(line_segments_to_check, location=np.array(location_new) - thickness * perpendicular / 2, direction=direction_new)
|
492
|
+
middle_segment = get_new_segment(line_segments_to_check, location=location_new, direction=direction_new, box_size=box_size)
|
493
|
+
s1 = get_new_segment(line_segments_to_check, location=np.array(location_new) + thickness * perpendicular / 2, direction=direction_new,box_size=box_size)
|
494
|
+
s2 = get_new_segment(line_segments_to_check, location=np.array(location_new) - thickness * perpendicular / 2, direction=direction_new, box_size=box_size)
|
466
495
|
|
467
496
|
# Extract neighbor information and segment vertices
|
468
497
|
neighbor1_1, neighbor1_2 = list(s1.neighbors.keys())
|
@@ -515,13 +544,16 @@ def add_line_segment(
|
|
515
544
|
# Associate the middle segment with the newly created thickness entry
|
516
545
|
segment_thickness_dict[list(segment_thickness_dict.keys())[-1]].middle_segment = middle_segment
|
517
546
|
|
518
|
-
return segments_dict, polygon_arr, segment_thickness_dict
|
547
|
+
return segments_dict, polygon_arr, segment_thickness_dict, location_new, angle_new
|
519
548
|
|
520
|
-
def
|
549
|
+
def generate_line_segments_thickness(
|
521
550
|
size: int,
|
522
551
|
thickness_arr: List[float],
|
523
|
-
angles: str = 'uniform'
|
524
|
-
|
552
|
+
angles: str = 'uniform',
|
553
|
+
config: List[List[float]] = None,
|
554
|
+
epsilon: float = 0,
|
555
|
+
box_size: float = 1
|
556
|
+
) -> Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], np.ndarray]:
|
525
557
|
"""
|
526
558
|
Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
|
527
559
|
|
@@ -529,26 +561,31 @@ def generate_line_segments_dynamic_thickness(
|
|
529
561
|
size (int): The number of line segments to generate.
|
530
562
|
thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
|
531
563
|
angles (str): The angle distribution method for generating segments. Defaults to 'uniform'.
|
564
|
+
List[float]: list of angles in radians.
|
565
|
+
config (List[List[float]]): A list of configurations for the nucleation points and angles.
|
566
|
+
epsilon (float): the minimum distance between two line.
|
567
|
+
box_size (float): the size of the box.
|
532
568
|
|
533
569
|
Returns:
|
534
570
|
Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
535
571
|
- Updated dictionary of line segments.
|
536
572
|
- Updated dictionary of polygons.
|
537
573
|
- Updated dictionary of segment thicknesses.
|
574
|
+
- Array of the nucleation points and angles [x,y,theta].
|
538
575
|
"""
|
539
576
|
|
540
577
|
# Initialize border segments for a square and its polygon representation
|
541
578
|
borders = [
|
542
|
-
LineSegment((
|
543
|
-
LineSegment((0,
|
544
|
-
LineSegment((0,
|
545
|
-
LineSegment((
|
579
|
+
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)}),
|
580
|
+
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)}),
|
581
|
+
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)}),
|
582
|
+
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)})
|
546
583
|
]
|
547
584
|
|
548
585
|
polygon_arr = {
|
549
586
|
'p1': {
|
550
|
-
'vertices': [(0, 0), (0,
|
551
|
-
'area':
|
587
|
+
'vertices': [(0, 0), (0, box_size), (box_size, box_size), (box_size, 0)],
|
588
|
+
'area': box_size**2,
|
552
589
|
'faces': ['b1', 'b2', 'b3', 'b4']
|
553
590
|
}
|
554
591
|
}
|
@@ -556,18 +593,50 @@ def generate_line_segments_dynamic_thickness(
|
|
556
593
|
segments = borders
|
557
594
|
segments_dict = {segment.id: segment for segment in segments}
|
558
595
|
segment_thickness_dict = {}
|
596
|
+
generated_config = []
|
597
|
+
|
598
|
+
if config is not None and size > len(config):
|
599
|
+
print("The size of the configuration is smaller than the size of the segments. Generated a network of the same size as the configuration.")
|
600
|
+
size = len(config)
|
559
601
|
|
560
|
-
|
602
|
+
jammed = False
|
561
603
|
for i in range(size):
|
562
|
-
|
604
|
+
if config:
|
605
|
+
nucleation_point = config[i]['location']
|
606
|
+
angles = [config[i]['angle']]
|
607
|
+
else:
|
608
|
+
nucleation_point = None
|
609
|
+
if angles != 'uniform':
|
610
|
+
angles=[angles[i]]
|
611
|
+
|
612
|
+
output = add_line_segment(segments_dict,
|
613
|
+
polygon_arr,
|
614
|
+
segment_thickness_dict,
|
615
|
+
thickness=thickness_arr[i],
|
616
|
+
min_distance = epsilon,
|
617
|
+
nucleation_point = nucleation_point,
|
618
|
+
angles=angles,
|
619
|
+
box_size=box_size)
|
563
620
|
if output:
|
564
|
-
segments_dict, polygon_arr, segment_thickness_dict = output
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
621
|
+
segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
|
622
|
+
generated_config.append({ 'location': location, 'angle': angle, 'thickness': thickness_arr[i] })
|
623
|
+
|
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
|
+
|
569
632
|
# Uncomment the following line if you want progress feedback
|
570
633
|
percentage = np.round(i / size * 100, 3)
|
571
634
|
print(f'generate_segments: {percentage}% done', end='\r')
|
572
635
|
|
573
|
-
|
636
|
+
data_dict = {'segments_dict': segments_dict,
|
637
|
+
'polygon_arr': polygon_arr,
|
638
|
+
'segment_thickness_dict': segment_thickness_dict,
|
639
|
+
'jammed': jammed,
|
640
|
+
'generated_config': generated_config}
|
641
|
+
|
642
|
+
return data_dict
|
@@ -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
|
@@ -0,0 +1,394 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from typing import List, Dict, Tuple
|
3
|
+
from shapely.geometry import Polygon as Polygon_Shapely
|
4
|
+
from shapely.geometry import LineString, box
|
5
|
+
from concurrent.futures import ProcessPoolExecutor
|
6
|
+
from .Classes import LineSegment, Polygon
|
7
|
+
|
8
|
+
def rotate(point, center, rotation_matrix):
|
9
|
+
"""
|
10
|
+
Rotates a point around the center using the given rotation matrix.
|
11
|
+
point: numpy array representing the point to rotate
|
12
|
+
center: numpy array representing the center of rotation
|
13
|
+
rotation_matrix: 2x2 numpy array representing the rotation matrix
|
14
|
+
"""
|
15
|
+
translated_point = point - center
|
16
|
+
rotated_point = np.dot(rotation_matrix, translated_point)
|
17
|
+
final_point = rotated_point + center
|
18
|
+
|
19
|
+
return final_point
|
20
|
+
|
21
|
+
def unit_vector(v):
|
22
|
+
""" Returns the unit vector of the vector. """
|
23
|
+
return v / np.linalg.norm(v)
|
24
|
+
|
25
|
+
def angle_between(v1, v2):
|
26
|
+
""" Returns the angle in radians between vectors 'v1' and 'v2'."""
|
27
|
+
v1_u = unit_vector(v1)
|
28
|
+
v2_u = unit_vector(v2)
|
29
|
+
return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
|
30
|
+
|
31
|
+
def get_alignment_mean(line_vector_arr, director):
|
32
|
+
"""Get the mean alignment."""
|
33
|
+
S_all = []
|
34
|
+
for item in line_vector_arr:
|
35
|
+
line_vector = item['line_vector']
|
36
|
+
area = item['area']
|
37
|
+
P2 = 0.5*(3*(np.cos(angle_between(line_vector, director)))**2-1)
|
38
|
+
S_all.append(P2*area)
|
39
|
+
|
40
|
+
return float(np.mean(S_all))
|
41
|
+
|
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
|
+
|
87
|
+
return angle, alignment
|
88
|
+
|
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.
|
96
|
+
|
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.
|
99
|
+
|
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.
|
109
|
+
|
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
|
197
|
+
|
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)
|
205
|
+
|
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
|
210
|
+
|
211
|
+
return segment_thickness_dict_new
|
212
|
+
|
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.
|
231
|
+
|
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 = {}
|
240
|
+
|
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
|
249
|
+
else:
|
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)
|
256
|
+
|
257
|
+
return segment_thickness_dict_new
|
258
|
+
|
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)
|
267
|
+
|
268
|
+
return float(np.mean(S_all))
|
269
|
+
|
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],
|
294
|
+
grid_points: int = 360
|
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]]:
|
328
|
+
"""
|
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).
|
344
|
+
|
345
|
+
Returns:
|
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.
|
350
|
+
"""
|
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
|
356
|
+
segment_thickness_dict = data_dict['segment_thickness_dict']
|
357
|
+
|
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}]
|
366
|
+
|
367
|
+
# Loop through each given orientation, rotate, clip, and translate the network
|
368
|
+
for o in orientation:
|
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)
|
374
|
+
|
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)
|
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
|
392
|
+
output.append({'orientation': o, 'data_dict': data_dict_new})
|
393
|
+
|
394
|
+
return output
|
@@ -1,19 +0,0 @@
|
|
1
|
-
RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
|
2
|
-
RDG_networks/__init__.py,sha256=juPsGdSg0t8rIjaELwsQPUKYXVVu1GrdbBI2ncd7lZ0,938
|
3
|
-
RDG_networks/draw_segments.py,sha256=U53N5GXmQHWKdM1Q1faP_EGKjc6enOu2mcsunzSFpP0,984
|
4
|
-
RDG_networks/generate_line_network.py,sha256=lJ4rhObim3WcEQoebomewRQKWNJC5phFyFYRW7qjXIg,1127
|
5
|
-
RDG_networks/generate_line_segments.py,sha256=QV8_k7q6TD5c7Hcb2Ms_apEdWYw4XdLr7rdJgh49v4Q,9004
|
6
|
-
RDG_networks/generate_line_segments_dynamic.py,sha256=GoIhGXYbcvjqR5BJCnkvAGp8QBpzsE1ZSbl2k9XAOGI,7531
|
7
|
-
RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ5azzrKcQe3fuWzCE,9265
|
8
|
-
RDG_networks/get_intersection_segments.py,sha256=mXB5qCy1oOps4Vu1mX6flW6v_4Xxc71YK41yOWjJX8o,2797
|
9
|
-
RDG_networks/sample_in_polygon.py,sha256=qpPpW-Da1vK8ZkVWMJ0zBsE8IgyMB619gCdybSkzKSQ,1605
|
10
|
-
RDG_networks/thickness/Classes.py,sha256=kgWNP5NCc11dTVAfujk30r_MsY2Xf4dAOmoalLRHa5E,8063
|
11
|
-
RDG_networks/thickness/__init__.py,sha256=ixi8dBaMi-M_kCdxGfx8V0o_vOTv_anpAgLnPaBwysc,190
|
12
|
-
RDG_networks/thickness/generate_line_segments_dynamic_thickness.py,sha256=i10rjKTB9PoFyMaUxVS99YQqegUXfeVfB1qZWryvxRk,25598
|
13
|
-
RDG_networks/thickness/sample_in_polygon.py,sha256=nJ-yqfoCCGfC6_EpGL3L1t1LOYdqWZd-7v5bxy6th34,1849
|
14
|
-
RDG_Networks-0.3.4.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
|
15
|
-
RDG_Networks-0.3.4.dist-info/METADATA,sha256=NrwTO4MslrgfxoBqUxHctABU8Ibht3LxDJZHtdb3k9w,2422
|
16
|
-
RDG_Networks-0.3.4.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
17
|
-
RDG_Networks-0.3.4.dist-info/entry_points.txt,sha256=6AGpOd8ecHGEsoUhRj0j6VvN_Fg0QNCY8h5rInAJom0,542
|
18
|
-
RDG_Networks-0.3.4.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
|
19
|
-
RDG_Networks-0.3.4.dist-info/RECORD,,
|
@@ -1,8 +0,0 @@
|
|
1
|
-
[console_scripts]
|
2
|
-
draw_segments = RDG_networks.draw_segments:main
|
3
|
-
generate_line_network = RDG_networks.generate_line_network:main
|
4
|
-
generate_line_segments = RDG_networks.generate_line_segments:main
|
5
|
-
generate_line_segments_dynamic = RDG_networks.generate_line_segments_dynamic:main
|
6
|
-
generate_line_segments_dynamic_thickness = RDG_networks.thickness.generate_line_segments_dynamic_thickness:main
|
7
|
-
generate_line_segments_static = RDG_networks.generate_line_segments_static:main
|
8
|
-
get_intersection_segments = RDG_networks.get_intersection_segments:main
|
File without changes
|
File without changes
|