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.
@@ -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