RDG-Networks 0.3.4__py3-none-any.whl → 0.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {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
|