RDG-Networks 0.3.2__py3-none-any.whl → 0.3.5__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.2
3
+ Version: 0.3.5
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
@@ -1,5 +1,5 @@
1
1
  RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
2
- RDG_networks/__init__.py,sha256=juPsGdSg0t8rIjaELwsQPUKYXVVu1GrdbBI2ncd7lZ0,938
2
+ RDG_networks/__init__.py,sha256=PnXsJcqHJ-TlwMBEV4v46jP5VE654UYeU094yP0rdh8,1149
3
3
  RDG_networks/draw_segments.py,sha256=U53N5GXmQHWKdM1Q1faP_EGKjc6enOu2mcsunzSFpP0,984
4
4
  RDG_networks/generate_line_network.py,sha256=lJ4rhObim3WcEQoebomewRQKWNJC5phFyFYRW7qjXIg,1127
5
5
  RDG_networks/generate_line_segments.py,sha256=QV8_k7q6TD5c7Hcb2Ms_apEdWYw4XdLr7rdJgh49v4Q,9004
@@ -7,14 +7,15 @@ RDG_networks/generate_line_segments_dynamic.py,sha256=GoIhGXYbcvjqR5BJCnkvAGp8QB
7
7
  RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ5azzrKcQe3fuWzCE,9265
8
8
  RDG_networks/get_intersection_segments.py,sha256=mXB5qCy1oOps4Vu1mX6flW6v_4Xxc71YK41yOWjJX8o,2797
9
9
  RDG_networks/sample_in_polygon.py,sha256=qpPpW-Da1vK8ZkVWMJ0zBsE8IgyMB619gCdybSkzKSQ,1605
10
+ RDG_networks/save_to_stl.py,sha256=xHwuoG39cbemggoIjT44DlsMlhjlV3uxTWo6gcHEePA,3414
10
11
  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/functions.py,sha256=cEUd858EZtf7ZUjq-MS1dctlWHPBxQzAY-B2Vko4DzU,11596
13
- RDG_networks/thickness/generate_line_segments_dynamic_thickness.py,sha256=M8jOad-Jg_J14Y263k9RTNoLd0VblOTKTumqwOlCM_I,25598
12
+ RDG_networks/thickness/__init__.py,sha256=iKwquAaxqyCZ6qi1YSkgm2oIEuu_FjhHsqJmwUNScyc,327
13
+ RDG_networks/thickness/generate_line_segments_thickness_orientation.py,sha256=7sEZX8ISTBm5Fy-jqKsvRpZkteaRxfTVPNTB3lNEUYk,7797
14
+ RDG_networks/thickness/ggenerate_line_segments_thickness.py,sha256=Eho-HAFVJdYSv1xqNSE2pCR--ncLrv1ZWahTkOlCI0A,29091
14
15
  RDG_networks/thickness/sample_in_polygon.py,sha256=nJ-yqfoCCGfC6_EpGL3L1t1LOYdqWZd-7v5bxy6th34,1849
15
- RDG_Networks-0.3.2.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
16
- RDG_Networks-0.3.2.dist-info/METADATA,sha256=WvRQZQwkb6he7bqjqJcRxcrr_xTfnSRNJDKL9Z-JI94,2422
17
- RDG_Networks-0.3.2.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
18
- RDG_Networks-0.3.2.dist-info/entry_points.txt,sha256=6AGpOd8ecHGEsoUhRj0j6VvN_Fg0QNCY8h5rInAJom0,542
19
- RDG_Networks-0.3.2.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
20
- RDG_Networks-0.3.2.dist-info/RECORD,,
16
+ RDG_Networks-0.3.5.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
17
+ RDG_Networks-0.3.5.dist-info/METADATA,sha256=dxEztE7a_LbwlRI-Os1SbdRPRIc7gg1-kTDVPOIZv2M,2422
18
+ RDG_Networks-0.3.5.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
19
+ RDG_Networks-0.3.5.dist-info/entry_points.txt,sha256=f6AkERofpy7bp4KBUJo3y_ey6lZtvXxBVAMtPZZHqiI,676
20
+ RDG_Networks-0.3.5.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
21
+ RDG_Networks-0.3.5.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
 
@@ -3,6 +3,8 @@ draw_segments = RDG_networks.draw_segments:main
3
3
  generate_line_network = RDG_networks.generate_line_network:main
4
4
  generate_line_segments = RDG_networks.generate_line_segments:main
5
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
6
+ generate_line_segments_dynamic_orientation = RDG_networks.generate_line_segments_dynamic_orientation:main
7
7
  generate_line_segments_static = RDG_networks.generate_line_segments_static:main
8
+ generate_line_segments_thickness = RDG_networks.thickness.generate_line_segments_thickness:main
8
9
  get_intersection_segments = RDG_networks.get_intersection_segments:main
10
+ save_to_stl = RDG_networks.save_to_stl:main
RDG_networks/__init__.py CHANGED
@@ -7,17 +7,21 @@ 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 .save_to_stl import save_to_stl
11
13
 
12
14
  __all__ = ['generate_line_segments',
13
- 'generate_line_network',
14
- 'get_intersection_segments',
15
+ 'generate_line_segments_thickness',
16
+ 'generate_line_segments_thickness_orientation',
15
17
  'generate_line_segments_dynamic',
16
18
  'generate_line_segments_static',
17
- 'generate_line_segments_dynamic_thickness',
19
+ 'generate_line_network',
20
+ 'get_intersection_segments',
18
21
  'draw_segments',
19
22
  'sample_in_polygon',
20
23
  'Line',
21
24
  'LineSegment',
22
- 'Polygon'
25
+ 'Polygon',
26
+ 'save_to_stl'
23
27
  ]
@@ -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)
@@ -1,7 +1,9 @@
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
4
5
 
5
6
  __all__ = [
6
- 'generate_line_segments_dynamic_thickness',
7
+ 'generate_line_segments_thickness',
8
+ 'generate_line_segments_thickness_orientation'
7
9
  ]
@@ -0,0 +1,180 @@
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
+ import matplotlib.pyplot as plt
5
+ from concurrent.futures import ProcessPoolExecutor
6
+
7
+ from .Classes import Line, LineSegment, Polygon
8
+ from .generate_line_segments_dynamic_thickness import generate_line_segments_dynamic_thickness
9
+
10
+ def rot(point, center, rotation_matrix):
11
+ """
12
+ Rotates a point around the center using the given rotation matrix.
13
+ point: numpy array representing the point to rotate
14
+ center: numpy array representing the center of rotation
15
+ rotation_matrix: 2x2 numpy array representing the rotation matrix
16
+ """
17
+ translated_point = point - center
18
+ rotated_point = np.dot(rotation_matrix, translated_point)
19
+ final_point = rotated_point + center
20
+
21
+ return final_point
22
+
23
+ def unit_vector(v):
24
+ """ Returns the unit vector of the vector. """
25
+ return v / np.linalg.norm(v)
26
+
27
+ def angle_between(v1, v2):
28
+ """ Returns the angle in radians between vectors 'v1' and 'v2'."""
29
+ v1_u = unit_vector(v1)
30
+ v2_u = unit_vector(v2)
31
+ return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))
32
+
33
+ def get_alignment_mean(line_vector_arr, director):
34
+ """Get the mean alignment."""
35
+ S_all = []
36
+ for item in line_vector_arr:
37
+ line_vector = item['line_vector']
38
+ area = item['area']
39
+ P2 = 0.5*(3*(np.cos(angle_between(line_vector, director)))**2-1)
40
+ S_all.append(P2*area)
41
+
42
+ return float(np.mean(S_all))
43
+
44
+ def compute_alignment_for_angle(angle, segment_thickness_dict, director, box_size):
45
+ """Helper function to compute alignment for a given angle."""
46
+ rotation_matrix = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
47
+ center = np.array([box_size / 2, box_size / 2])
48
+ line_vector_arr = []
49
+
50
+ # Get all line vectors and areas
51
+ for segment in segment_thickness_dict.values():
52
+ m_pts = np.array([segment.middle_segment.start, segment.middle_segment.end])
53
+ (start_rotated, end_rotated) = rot(m_pts, center, rotation_matrix)
54
+ line_vector = np.array(end_rotated) - np.array(start_rotated)
55
+ line_vector_arr.append({'line_vector': line_vector, 'area': segment.area()})
56
+
57
+ # Calculate the alignment for this angle
58
+ alignment = get_alignment_mean(line_vector_arr, director)
59
+ return angle, alignment
60
+
61
+ def get_max_alignment_angle(segment_thickness_dict, director, box_size, grid_points=360):
62
+ """Get the angle with the maximum alignment."""
63
+ # Define the angles we will test
64
+ angles = np.linspace(0, 2 * np.pi, grid_points)
65
+
66
+ with ProcessPoolExecutor() as executor:
67
+ # Submit tasks for each angle in parallel, passing necessary arguments
68
+ results = list(executor.map(
69
+ compute_alignment_for_angle,
70
+ angles,
71
+ [segment_thickness_dict] * len(angles),
72
+ [director] * len(angles),
73
+ [box_size] * len(angles)
74
+ ))
75
+
76
+ # Find the angle with the maximum alignment
77
+ max_angle, _ = max(results, key=lambda x: x[1])
78
+
79
+ return max_angle
80
+
81
+ def orientate_network(segment_thickness_dict, config, rotate_angle, box_size):
82
+ # Rotate the orginal network
83
+ rotation_matrix = np.array([[np.cos(rotate_angle), -np.sin(rotate_angle)],[np.sin(rotate_angle), np.cos(rotate_angle)]])
84
+ center = np.array([box_size/2,box_size/2])
85
+
86
+ config_angles = [ item['angle'] for item in config]
87
+ orientated_config = []
88
+
89
+ # Get all oriented lines
90
+ for i, v in enumerate(segment_thickness_dict.values()):
91
+ angle_rotated = config_angles[i] - rotate_angle
92
+ n_p_0 = rot(np.array(v.middle_segment.start), center, rotation_matrix)
93
+ n_p_1 = rot(np.array(v.middle_segment.end), center, rotation_matrix)
94
+
95
+ # Find the intersection between the rotated line and the square
96
+ polygon_box = Polygon_Shapely([(0, 0), (box_size, 0), (box_size, box_size), (0, box_size)])
97
+ line_middle_point = LineString([(n_p_0[0], n_p_0[1]), (n_p_1[0], n_p_1[1])])
98
+
99
+ # Calculate the intersection between the box and the line
100
+ intersection = polygon_box.intersection(line_middle_point)
101
+
102
+ # Check if the line intersects the polygon
103
+ if intersection.is_empty:
104
+ continue
105
+ else:
106
+ length = intersection.length
107
+ midpoint = intersection.interpolate(length/2)
108
+
109
+ x = midpoint.xy[0][0]
110
+ y = midpoint.xy[1][0]
111
+
112
+ orientated_config.append({ 'location': (x,y), 'angle': angle_rotated, 'thickness': config[i]['thickness'] })
113
+
114
+ return orientated_config
115
+
116
+ def generate_line_segments_thickness_orientation(
117
+ size: int,
118
+ thickness_arr: List[float],
119
+ orientation: List[int],
120
+ angles: List[float],
121
+ config: List[List[float]] = None,
122
+ epsilon: float = 0,
123
+ box_size: float = 1,
124
+ grid_points: int = 360
125
+ ) -> List[Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], np.ndarray]]:
126
+ """
127
+ Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
128
+
129
+ Args:
130
+ size (int): The number of line segments to generate.
131
+ thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
132
+ angles (str): The angle distribution method for generating segments. Defaults to 'uniform'.
133
+ List[float]: list of angles in radians.
134
+ orientation (List[int]): the orientation of the model.
135
+ config (List[List[float]]): A list of configurations for the nucleation points and angles.
136
+ epsilon (float): the minimum distance between two line.
137
+ box_size (float): the size of the system.
138
+
139
+ Returns:
140
+ - an array of dictionaries for each orientation containing:
141
+ Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
142
+ - Updated dictionary of line segments.
143
+ - Updated dictionary of polygons.
144
+ - Updated dictionary of segment thicknesses.
145
+ - Array of the nucleation points and angles [x,y,theta].
146
+ """
147
+ # Size of the box
148
+ box_size_0 = box_size*np.sqrt(2)
149
+
150
+ # Initial structure
151
+ data_dict = generate_line_segments_dynamic_thickness(size = size,
152
+ thickness_arr = thickness_arr,
153
+ epsilon= epsilon,
154
+ config = config,
155
+ angles = angles,
156
+ box_size= box_size_0)
157
+
158
+ segment_thickness_dict = data_dict['segment_thickness_dict']
159
+ generated_config = data_dict['generated_config']
160
+
161
+ # Calculate alignment with the y axis
162
+ director = (0,1)
163
+ max_angle = get_max_alignment_angle(segment_thickness_dict, director, box_size, grid_points)
164
+
165
+ # Regenerate network for each orientation
166
+ output = []
167
+ for o in orientation:
168
+ rotate_angle = o-max_angle
169
+ orientated_config = orientate_network(segment_thickness_dict, generated_config, rotate_angle, box_size)
170
+
171
+ data_dict_new = generate_line_segments_dynamic_thickness(size=size,
172
+ thickness_arr=thickness_arr,
173
+ epsilon=epsilon,
174
+ config=orientated_config,
175
+ angles=angles,
176
+ box_size=box_size)
177
+
178
+ output.append({'orientation': o, 'data_dict': data_dict_new})
179
+
180
+ return output
@@ -1,17 +1,20 @@
1
1
  import math
2
2
  import numpy as np
3
+ import random
3
4
  from typing import List, Dict, Tuple, Union, Optional
5
+ from shapely.geometry import Polygon as Polygon_Shapely, LineString
4
6
 
5
- from .Classes import Line, LineSegment
7
+ from .Classes import Line, LineSegment, Polygon
6
8
  from .sample_in_polygon import sample_in_polygon, is_inside_polygon
7
9
 
8
- 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]]:
9
11
  """
10
12
  Check if two lines intersect and return the intersection point.
11
13
 
12
14
  Args:
13
15
  - line1 (Line): The first line segment.
14
16
  - line2 (Line): The second line segment.
17
+ - box_size (float): The size of the bounding box. Defaults to 1.
15
18
 
16
19
  Returns:
17
20
  - intersect (bool): True if the lines intersect, False otherwise.
@@ -34,14 +37,16 @@ def doLinesIntersect(line1: Line, line2: Line) -> Tuple[bool, Union[Tuple[float,
34
37
  intersect_x = x1 + v1 * t1
35
38
  intersect_y = y2 + w2 * t2
36
39
 
37
- 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:
38
42
  return True, (intersect_x, intersect_y)
39
43
  else:
40
44
  return False, (None, None)
41
45
 
42
46
  def doSegmentsIntersect(
43
- segment1: 'LineSegment',
44
- segment2: 'LineSegment'
47
+ segment1: LineSegment,
48
+ segment2: LineSegment,
49
+ box_size = 1
45
50
  ) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
46
51
  """
47
52
  Determines if two line segments intersect and returns the intersection point if they do.
@@ -62,7 +67,7 @@ def doSegmentsIntersect(
62
67
  line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
63
68
 
64
69
  # Check if the infinite extensions of the two lines intersect
65
- intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2)
70
+ intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2, box_size)
66
71
 
67
72
  # If no intersection, return False
68
73
  if not intersect:
@@ -137,6 +142,8 @@ def pick_item_with_probability(
137
142
  def get_location_and_direction(
138
143
  polygon_arr: Dict[str, Dict[str, object]],
139
144
  thickness: float,
145
+ nucleation_point: Tuple[float, float] = None,
146
+ min_distance: float = 0,
140
147
  max_attempts: int = 1000,
141
148
  angles: Union[str, List[float]] = 'uniform'
142
149
  ) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
@@ -154,6 +161,10 @@ def get_location_and_direction(
154
161
  The maximum number of attempts to find a valid location and direction. Defaults to 1000.
155
162
  angles (Union[str, List[float]], optional):
156
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.
157
168
 
158
169
  Returns:
159
170
  Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
@@ -163,17 +174,20 @@ def get_location_and_direction(
163
174
  - The new location as a tuple of floats (`Tuple[float, float]`).
164
175
  - The direction vector as a numpy array (`np.ndarray`).
165
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.
166
179
  - Returns `False` if no valid location and direction are found after the maximum attempts.
167
180
  """
168
181
 
169
182
  # Generate a new direction based on the angles parameter
170
183
  if angles == 'uniform':
171
- 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))
172
186
  direction = direction / np.linalg.norm(direction) # Normalize the direction vector
173
187
  else:
174
- directions = [ (np.cos(angle), np.sin(angle)) for angle in angles ]
175
- direction = random.choice(directions)
176
- 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)
177
191
 
178
192
  # Try to find a valid location and direction up to max_attempts
179
193
  attempt = 0
@@ -181,7 +195,11 @@ def get_location_and_direction(
181
195
  polygon_id, polygon = pick_item_with_probability(polygon_arr)
182
196
 
183
197
  # Sample a location within the polygon
184
- location_new = sample_in_polygon(polygon['vertices'])
198
+ #check if nucleation point is given
199
+ if nucleation_point:
200
+ location_new = nucleation_point
201
+ else:
202
+ location_new = sample_in_polygon(polygon['vertices'])
185
203
 
186
204
  # Compute the perpendicular vector to the direction
187
205
  perpendicular = np.array([direction[1], -direction[0]])
@@ -192,12 +210,12 @@ def get_location_and_direction(
192
210
  perpendicular = -perpendicular
193
211
 
194
212
  # Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
195
- p1 = np.array(location_new) + thickness / 2 * perpendicular
196
- 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
197
215
 
198
216
  # Check if both endpoints of the segment are inside the polygon
199
217
  if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
200
- return polygon_id, polygon, location_new, direction, perpendicular
218
+ return polygon_id, polygon, location_new, direction, perpendicular, angle_new
201
219
 
202
220
  attempt += 1
203
221
 
@@ -255,11 +273,12 @@ def get_polygons(polygon_id, polygon_arr, neighbor1_1, neighbor1_2, vertex_begin
255
273
  return cycle0, vertices0, cycle1, vertices1, cycle2, vertices2
256
274
 
257
275
  def get_new_segment(
258
- line_segments_to_check: List['LineSegment'],
276
+ line_segments_to_check: List[LineSegment],
259
277
  location: Tuple[float, float],
260
278
  direction: Tuple[float, float],
261
- id: Optional[int] = None
262
- ) -> 'LineSegment':
279
+ id: Optional[int] = None,
280
+ box_size: float = 1
281
+ ) -> LineSegment:
263
282
  """
264
283
  Creates a new line segment by extending a given location in a specified direction and
265
284
  determines its neighbors by checking intersections with other line segments.
@@ -269,18 +288,20 @@ def get_new_segment(
269
288
  location (Tuple[float, float]): The starting point (x, y) for the new line segment.
270
289
  direction (Tuple[float, float]): The direction vector in which to extend the line segment.
271
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.
272
292
 
273
293
  Returns:
274
294
  LineSegment: A new line segment object with its neighbors based on intersections.
275
295
  """
276
296
 
277
297
  # Create a temporary line segment extending from the location in both directions
278
- s_temp = LineSegment(start=np.array(location) - 10 * np.array(direction), end=np.array(location) + 10 * np.array(direction))
279
- 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 = []
280
301
 
281
302
  # Check for intersections with existing line segments
282
303
  for segment in line_segments_to_check:
283
- intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment)
304
+ intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment, box_size)
284
305
 
285
306
  if intersect:
286
307
  segment_length = math.sqrt(
@@ -317,10 +338,10 @@ def get_new_segment(
317
338
  return segment_new
318
339
 
319
340
  def update_data(
320
- segments_dict: Dict[int, 'LineSegment'],
341
+ segments_dict: Dict[int, LineSegment],
321
342
  polygon_arr: Dict[str, Dict[str, object]],
322
343
  polygon_id: str,
323
- segment_thickness_dict: Dict[int, 'Polygon'],
344
+ segment_thickness_dict: Dict[int, Polygon],
324
345
  vertices0: List[Tuple[float, float]],
325
346
  vertices1: List[Tuple[float, float]],
326
347
  vertices2: List[Tuple[float, float]],
@@ -337,7 +358,7 @@ def update_data(
337
358
  vertex_end_2: Tuple[float, float],
338
359
  id_1: int,
339
360
  id_2: int
340
- ) -> Tuple[Dict[int, 'LineSegment'], Dict[str, Dict[str, object]], Dict[int, 'Polygon']]:
361
+ ) -> Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
341
362
  """
342
363
  Updates the segments, polygons, and segment thickness dictionaries by adding new data derived
343
364
  from provided vertices and neighbor information.
@@ -427,12 +448,16 @@ def update_data(
427
448
  return segments_dict, polygon_arr, segment_thickness_dict
428
449
 
429
450
  def add_line_segment(
430
- segments_dict: Dict[int, 'LineSegment'],
451
+ segments_dict: Dict[int, LineSegment],
431
452
  polygon_arr: Dict[str, Dict[str, object]],
432
- segment_thickness_dict: Dict[int, 'Polygon'],
433
- thickness: float = 0,
434
- angles: str = 'uniform'
435
- ) -> Union[Tuple[Dict[int, 'LineSegment'], Dict[str, Dict[str, object]], Dict[int, 'Polygon']], bool]:
453
+ segment_thickness_dict: Dict[int, Polygon],
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]:
436
461
  """
437
462
  Adds a new line segment to the segments and polygon data structures, with a given thickness and angle distribution.
438
463
 
@@ -442,26 +467,31 @@ def add_line_segment(
442
467
  segment_thickness_dict (Dict[int, Polygon]): A dictionary storing the thickness information mapped to polygons.
443
468
  thickness (float): The thickness of the new segment to be added. Defaults to 0.
444
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.
445
474
 
446
475
  Returns:
447
- 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]:
448
477
  - A tuple containing the updated segments dictionary, polygon dictionary, and thickness dictionary,
449
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.
450
480
  """
451
481
 
452
482
  # Get a valid location and direction, or return False if none is found
453
- 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)
454
484
  if loc:
455
- polygon_id, polygon, location_new, direction_new, perpendicular = loc
485
+ polygon_id, polygon, location_new, direction_new, perpendicular, angle_new = loc
456
486
  else:
457
487
  print('No valid location found')
458
488
  return False
459
489
 
460
490
  # Get the borders of the new segment with the given thickness
461
491
  line_segments_to_check = [segments_dict[segment] for segment in polygon['faces']]
462
- middle_segment = get_new_segment(line_segments_to_check, location=location_new, direction=direction_new)
463
- s1 = get_new_segment(line_segments_to_check, location=np.array(location_new) + thickness * perpendicular / 2, direction=direction_new)
464
- 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)
465
495
 
466
496
  # Extract neighbor information and segment vertices
467
497
  neighbor1_1, neighbor1_2 = list(s1.neighbors.keys())
@@ -514,13 +544,16 @@ def add_line_segment(
514
544
  # Associate the middle segment with the newly created thickness entry
515
545
  segment_thickness_dict[list(segment_thickness_dict.keys())[-1]].middle_segment = middle_segment
516
546
 
517
- return segments_dict, polygon_arr, segment_thickness_dict
547
+ return segments_dict, polygon_arr, segment_thickness_dict, location_new, angle_new
518
548
 
519
- def generate_line_segments_dynamic_thickness(
549
+ def generate_line_segments_thickness(
520
550
  size: int,
521
551
  thickness_arr: List[float],
522
- angles: str = 'uniform'
523
- ) -> 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]:
524
557
  """
525
558
  Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
526
559
 
@@ -528,26 +561,31 @@ def generate_line_segments_dynamic_thickness(
528
561
  size (int): The number of line segments to generate.
529
562
  thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
530
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.
531
568
 
532
569
  Returns:
533
570
  Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
534
571
  - Updated dictionary of line segments.
535
572
  - Updated dictionary of polygons.
536
573
  - Updated dictionary of segment thicknesses.
574
+ - Array of the nucleation points and angles [x,y,theta].
537
575
  """
538
576
 
539
577
  # Initialize border segments for a square and its polygon representation
540
578
  borders = [
541
- LineSegment((1, 0), (0, 0), id='b1', neighbors_initial={'b2': (0, 0), 'b4': (1, 0)}, neighbors={'b2': (0, 0), 'b4': (1, 0)}),
542
- LineSegment((0, 1), (0, 0), id='b2', neighbors_initial={'b1': (0, 0), 'b3': (0, 1)}, neighbors={'b1': (0, 0), 'b3': (0, 1)}),
543
- LineSegment((0, 1), (1, 1), id='b3', neighbors_initial={'b2': (0, 1), 'b4': (1, 1)}, neighbors={'b2': (0, 1), 'b4': (1, 1)}),
544
- 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)})
545
583
  ]
546
584
 
547
585
  polygon_arr = {
548
586
  'p1': {
549
- 'vertices': [(0, 0), (0, 1), (1, 1), (1, 0)],
550
- 'area': 1,
587
+ 'vertices': [(0, 0), (0, box_size), (box_size, box_size), (box_size, 0)],
588
+ 'area': box_size**2,
551
589
  'faces': ['b1', 'b2', 'b3', 'b4']
552
590
  }
553
591
  }
@@ -555,18 +593,47 @@ def generate_line_segments_dynamic_thickness(
555
593
  segments = borders
556
594
  segments_dict = {segment.id: segment for segment in segments}
557
595
  segment_thickness_dict = {}
596
+ generated_config = []
558
597
 
559
- # Generate new line segments based on the given size and thickness array
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)
601
+
602
+ jammed = False
560
603
  for i in range(size):
561
- 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)
562
620
  if output:
563
- segments_dict, polygon_arr, segment_thickness_dict = output
621
+ segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
622
+ # generated_config += [[n_pts[0], n_pts[1], ang]]
623
+ generated_config.append({ 'location': location, 'angle': angle, 'thickness': thickness_arr[i] })
624
+
564
625
  else:
565
- print(f"Stopped at iteration {i}, could not find a valid segment position.")
566
- break
626
+ print(f"Stopped at iteration {len(segment_thickness_dict)}, could not find a valid segment position.")
627
+ jammed = True
567
628
 
568
629
  # Uncomment the following line if you want progress feedback
569
630
  percentage = np.round(i / size * 100, 3)
570
631
  print(f'generate_segments: {percentage}% done', end='\r')
571
632
 
572
- return segments_dict, polygon_arr, segment_thickness_dict
633
+ data_dict = {'segments_dict': segments_dict,
634
+ 'polygon_arr': polygon_arr,
635
+ 'segment_thickness_dict': segment_thickness_dict,
636
+ 'jammed': jammed,
637
+ 'generated_config': generated_config}
638
+
639
+ return data_dict
@@ -1,255 +0,0 @@
1
- import numpy as np
2
- import random
3
- from typing import List, Dict, Tuple, Union, Optional
4
-
5
- from .Classes import Line, LineSegment
6
- from .sample_in_polygon import sample_in_polygon, is_inside_polygon
7
-
8
- def doLinesIntersect(line1: Line, line2: Line) -> Tuple[bool, Union[Tuple[float, float], None]]:
9
- """
10
- Check if two lines intersect and return the intersection point.
11
-
12
- Args:
13
- - line1 (Line): The first line segment.
14
- - line2 (Line): The second line segment.
15
-
16
- Returns:
17
- - intersect (bool): True if the lines intersect, False otherwise.
18
- - intersection_point (tuple or None): The intersection point (x, y) if lines intersect, None otherwise.
19
- """
20
- x1, y1 = line1.location
21
- v1, w1 = line1.direction
22
-
23
- x2, y2 = line2.location
24
- v2, w2 = line2.direction
25
-
26
- determinant = v1 * w2 - v2 * w1
27
-
28
- if determinant == 0:
29
- return False, (None, None)
30
-
31
- t1 = ((x2 - x1) * w2 - (y2 - y1) * v2) / determinant
32
- t2 = ((x2 - x1) * w1 - (y2 - y1) * v1) / determinant
33
-
34
- intersect_x = x1 + v1 * t1
35
- intersect_y = y2 + w2 * t2
36
-
37
- if -1e-6 < intersect_x < 1 + 1e-6 and -1e-6 < intersect_y < 1 + 1e-6:
38
- return True, (intersect_x, intersect_y)
39
- else:
40
- return False, (None, None)
41
-
42
- def doSegmentsIntersect(
43
- segment1: 'LineSegment',
44
- segment2: 'LineSegment'
45
- ) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
46
- """
47
- Determines if two line segments intersect and returns the intersection point if they do.
48
-
49
- Args:
50
- segment1 (LineSegment): The first line segment.
51
- segment2 (LineSegment): The second line segment.
52
-
53
- Returns:
54
- Tuple[bool, Tuple[Optional[float], Optional[float]]]:
55
- - A boolean indicating whether the segments intersect.
56
- - A tuple of the x and y coordinates of the intersection point if they intersect,
57
- otherwise (None, None).
58
- """
59
-
60
- # Create line equations based on the segments' start and end points
61
- line1 = Line(location=segment1.start, direction=np.array(segment1.end) - np.array(segment1.start))
62
- line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
63
-
64
- # Check if the infinite extensions of the two lines intersect
65
- intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2)
66
-
67
- # If no intersection, return False
68
- if not intersect:
69
- return False, (None, None)
70
-
71
- # Check if the intersection point is within the bounds of both segments in the x-direction
72
- xcheck = (
73
- (segment1.end[0] <= intersect_x <= segment1.start[0]
74
- or segment1.start[0] <= intersect_x <= segment1.end[0]
75
- or abs(intersect_x - segment1.end[0]) < 1e-6
76
- or abs(intersect_x - segment1.start[0]) < 1e-6)
77
- and
78
- (segment2.end[0] <= intersect_x <= segment2.start[0]
79
- or segment2.start[0] <= intersect_x <= segment2.end[0]
80
- or abs(intersect_x - segment2.end[0]) < 1e-6
81
- or abs(intersect_x - segment2.start[0]) < 1e-6)
82
- )
83
-
84
- # Check if the intersection point is within the bounds of both segments in the y-direction
85
- ycheck = (
86
- (segment1.end[1] <= intersect_y <= segment1.start[1]
87
- or segment1.start[1] <= intersect_y <= segment1.end[1]
88
- or abs(intersect_y - segment1.end[1]) < 1e-6
89
- or abs(intersect_y - segment1.start[1]) < 1e-6)
90
- and
91
- (segment2.end[1] <= intersect_y <= segment2.start[1]
92
- or segment2.start[1] <= intersect_y <= segment2.end[1]
93
- or abs(intersect_y - segment2.end[1]) < 1e-6
94
- or abs(intersect_y - segment2.start[1]) < 1e-6)
95
- )
96
-
97
- # If the intersection point lies within the bounds of both segments, return True with the intersection point
98
- if xcheck and ycheck:
99
- return True, (intersect_x, intersect_y)
100
-
101
- # Otherwise, return False and no intersection point
102
- return False, (None, None)
103
-
104
- def pick_item_with_probability(
105
- polygon_arr: Dict[str, Dict[str, object]]
106
- ) -> Tuple[str, Dict[str, object]]:
107
- """
108
- Randomly selects an item from the polygon array with a probability proportional to the area of the polygons.
109
-
110
- Args:
111
- polygon_arr (Dict[str, Dict[str, object]]):
112
- A dictionary where keys are polygon identifiers (e.g., 'p1', 'p2') and values are dictionaries containing polygon properties,
113
- including an 'area' key that stores the area of the polygon.
114
-
115
- Returns:
116
- Tuple[str, Dict[str, object]]:
117
- - The identifier of the selected polygon.
118
- - The corresponding polygon data (dictionary) containing its properties.
119
- """
120
-
121
- # Calculate the total weight (sum of areas of all polygons)
122
- max_weight = sum(pol['area'] for pol in polygon_arr.values())
123
-
124
- # Generate a random threshold between 0 and the total weight
125
- threshold = random.uniform(0, max_weight)
126
- cumulative_weight = 0
127
-
128
- # Iterate through the polygons, accumulating weights
129
- for item, pol in polygon_arr.items():
130
- weight = pol['area']
131
- cumulative_weight += weight
132
-
133
- # Return the polygon when the cumulative weight surpasses the threshold
134
- if cumulative_weight >= threshold:
135
- return item, pol
136
-
137
- def get_location_and_direction(
138
- polygon_arr: Dict[str, Dict[str, object]],
139
- thickness: float,
140
- max_attempts: int = 1000,
141
- angles: Union[str, List[float]] = 'uniform'
142
- ) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
143
- """
144
- Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
145
- chosen (uniformly) or from a specified list of angles. It ensures that the segment lies within the polygon's bounds given the
146
- specified thickness.
147
-
148
- Args:
149
- polygon_arr (Dict[str, Dict[str, object]]):
150
- A dictionary where the keys are polygon identifiers and the values are dictionaries containing polygon properties, including 'vertices'.
151
- thickness (float):
152
- The thickness of the segment that needs to fit inside the polygon.
153
- max_attempts (int, optional):
154
- The maximum number of attempts to find a valid location and direction. Defaults to 1000.
155
- angles (Union[str, List[float]], optional):
156
- A string ('uniform' for random directions) or a list of angles (in radians) to choose the direction from. Defaults to 'uniform'.
157
-
158
- Returns:
159
- Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
160
- - If a valid location and direction are found, returns a tuple containing:
161
- - The polygon ID (`str`).
162
- - The polygon data (`Dict[str, object]`).
163
- - The new location as a tuple of floats (`Tuple[float, float]`).
164
- - The direction vector as a numpy array (`np.ndarray`).
165
- - The perpendicular vector to the direction as a numpy array (`np.ndarray`).
166
- - Returns `False` if no valid location and direction are found after the maximum attempts.
167
- """
168
-
169
- # Generate a new direction based on the angles parameter
170
- if angles == 'uniform':
171
- direction = np.array([random.uniform(-1, 1), random.uniform(-1, 1)])
172
- direction = direction / np.linalg.norm(direction) # Normalize the direction vector
173
- else:
174
- directions = [ (np.cos(angle), np.sin(angle)) for angle in angles ]
175
- direction = random.choice(directions)
176
- direction = np.array(direction) / np.linalg.norm(direction) # Normalize the chosen direction
177
-
178
- # Try to find a valid location and direction up to max_attempts
179
- attempt = 0
180
- while attempt < max_attempts:
181
- polygon_id, polygon = pick_item_with_probability(polygon_arr)
182
-
183
- # Sample a location within the polygon
184
- location_new = sample_in_polygon(polygon['vertices'])
185
-
186
- # Compute the perpendicular vector to the direction
187
- perpendicular = np.array([direction[1], -direction[0]])
188
- perpendicular = perpendicular / np.linalg.norm(perpendicular)
189
-
190
- # Ensure the perpendicular vector is oriented consistently (y-component is non-negative)
191
- if perpendicular[1] < 0:
192
- perpendicular = -perpendicular
193
-
194
- # Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
195
- p1 = np.array(location_new) + thickness / 2 * perpendicular
196
- p2 = np.array(location_new) - thickness / 2 * perpendicular
197
-
198
- # Check if both endpoints of the segment are inside the polygon
199
- if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
200
- return polygon_id, polygon, location_new, direction, perpendicular
201
-
202
- attempt += 1
203
-
204
- # If no valid location and direction is found, return False
205
- return False
206
-
207
- def get_polygons(polygon_id, polygon_arr, neighbor1_1, neighbor1_2, vertex_begin_1, vertex_end_1, neighbor2_1, neighbor2_2, vertex_begin_2, vertex_end_2, segment_new_id_1, segment_new_id_2):
208
- # Extract vertices and cycle (faces) of the original polygon
209
- vertices = polygon_arr[polygon_id]['vertices']
210
- cycle = polygon_arr[polygon_id]['faces']
211
-
212
- # Get first cycle and vertices
213
- index_start_1, index_end_1 = (cycle.index(neighbor1_1), cycle.index(neighbor1_2))
214
- if index_start_1 < index_end_1:
215
- cycle1 = [segment_new_id_1] + cycle[index_start_1:index_end_1+1]
216
- vertices1 = [vertex_begin_1] + vertices[index_start_1:index_end_1] + [vertex_end_1]
217
- else:
218
- cycle1 = [segment_new_id_1] + cycle[index_start_1:] + cycle[:index_end_1+1]
219
- vertices1 = [vertex_begin_1] + vertices[index_start_1:] + vertices[:index_end_1] + [vertex_end_1]
220
-
221
- # Get second cycle and vertices
222
- index_start_2, index_end_2 = (cycle.index(neighbor2_2), cycle.index(neighbor2_1))
223
- if index_start_2 < index_end_2:
224
- cycle2 = [segment_new_id_2] + cycle[index_start_2:index_end_2+1]
225
- vertices2 = [vertex_end_2] + vertices[index_start_2:index_end_2] + [vertex_begin_2]
226
- else:
227
- cycle2 = [segment_new_id_2] + cycle[index_start_2:] + cycle[:index_end_2+1]
228
- vertices2 = [vertex_end_2] + vertices[index_start_2:] + vertices[:index_end_2] + [vertex_begin_2]
229
-
230
- # Get middle cycle and vertices
231
- cycle0 = [neighbor1_1, segment_new_id_1, neighbor1_2]
232
- vertices0 = [vertex_begin_1, vertex_end_1]
233
-
234
- index_start_0, index_end_0 = (cycle.index(neighbor1_2), cycle.index(neighbor2_2))
235
- if index_start_0 < index_end_0:
236
- cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
237
- vertices0 = vertices0 + vertices[index_start_0:index_end_0]
238
-
239
- elif index_start_0 > index_end_0:
240
- cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
241
- vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
242
-
243
- cycle0 = cycle0 + [segment_new_id_2]
244
- vertices0 = vertices0 + [vertex_end_2] + [vertex_begin_2]
245
-
246
- index_start_0, index_end_0 = (cycle.index(neighbor2_1), cycle.index(neighbor1_1))
247
- if index_start_0 < index_end_0:
248
- cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
249
- vertices0 = vertices0 + vertices[index_start_0:index_end_0]
250
-
251
- elif index_start_0 > index_end_0:
252
- cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
253
- vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
254
-
255
- return cycle0, vertices0, cycle1, vertices1, cycle2, vertices2