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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RDG-Networks
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Most of the code from the RDG Networks project
5
5
  Home-page: https://github.com/NiekMooij/RDG_networks
6
6
  Author: Niek Mooij
@@ -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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.2)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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.generate_line_segments_dynamic_thickness import generate_line_segments_dynamic_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
- 'generate_line_network',
14
- 'get_intersection_segments',
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
- 'generate_line_segments_dynamic_thickness',
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.middle_line = middle_segment
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 .generate_line_segments_dynamic_thickness import generate_line_segments_dynamic_thickness
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
- 'generate_line_segments_dynamic_thickness',
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
- if -1e-6 < intersect_x < 1 + 1e-6 and -1e-6 < intersect_y < 1 + 1e-6:
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
- direction = np.array([random.uniform(-1, 1), random.uniform(-1, 1)])
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
- directions = [ (np.cos(angle), np.sin(angle)) for angle in angles ]
176
- direction = random.choice(directions)
177
- direction = np.array(direction) / np.linalg.norm(direction) # Normalize the chosen 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
- location_new = sample_in_polygon(polygon['vertices'])
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 / 2 * perpendicular
197
- p2 = np.array(location_new) - thickness / 2 * perpendicular
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
- ) -> 'LineSegment':
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
- intersection_points: List[Dict[str, Tuple[float, float]]] = []
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, 'LineSegment'],
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, 'Polygon'],
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
- thickness: float = 0,
435
- angles: str = 'uniform'
436
- ) -> Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]], bool]:
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=1000, angles=angles)
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 generate_line_segments_dynamic_thickness(
549
+ def generate_line_segments_thickness(
521
550
  size: int,
522
551
  thickness_arr: List[float],
523
- angles: str = 'uniform'
524
- ) -> Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
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((1, 0), (0, 0), id='b1', neighbors_initial={'b2': (0, 0), 'b4': (1, 0)}, neighbors={'b2': (0, 0), 'b4': (1, 0)}),
543
- LineSegment((0, 1), (0, 0), id='b2', neighbors_initial={'b1': (0, 0), 'b3': (0, 1)}, neighbors={'b1': (0, 0), 'b3': (0, 1)}),
544
- LineSegment((0, 1), (1, 1), id='b3', neighbors_initial={'b2': (0, 1), 'b4': (1, 1)}, neighbors={'b2': (0, 1), 'b4': (1, 1)}),
545
- LineSegment((1, 1), (1, 0), id='b4', neighbors_initial={'b1': (1, 0), 'b3': (1, 1)}, neighbors={'b1': (1, 0), 'b3': (1, 1)})
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, 1), (1, 1), (1, 0)],
551
- 'area': 1,
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
- # Generate new line segments based on the given size and thickness array
602
+ jammed = False
561
603
  for i in range(size):
562
- output = add_line_segment(segments_dict, polygon_arr, segment_thickness_dict, thickness=thickness_arr[i], angles=angles)
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
- else:
566
- print(f"Stopped at iteration {i}, could not find a valid segment position.")
567
- break
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
- return segments_dict, polygon_arr, segment_thickness_dict
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