RDG-Networks 0.3.7__py3-none-any.whl → 0.3.9__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {RDG_Networks-0.3.7.dist-info → RDG_Networks-0.3.9.dist-info}/METADATA +10 -2
- {RDG_Networks-0.3.7.dist-info → RDG_Networks-0.3.9.dist-info}/RECORD +13 -12
- {RDG_Networks-0.3.7.dist-info → RDG_Networks-0.3.9.dist-info}/WHEEL +1 -1
- {RDG_Networks-0.3.7.dist-info → RDG_Networks-0.3.9.dist-info}/entry_points.txt +1 -1
- RDG_networks/__init__.py +6 -6
- RDG_networks/thickness/Classes.py +2 -1
- RDG_networks/thickness/__init__.py +6 -6
- RDG_networks/thickness/generate_line_segments_thickness.py +27 -25
- RDG_networks/thickness/generate_line_segments_thickness_correct.py +633 -0
- RDG_networks/thickness/generate_line_segments_thickness_static.py +1 -1
- RDG_networks/thickness/{generate_line_segments_thickness_orientation.py → orientate_network.py} +56 -88
- {RDG_Networks-0.3.7.dist-info → RDG_Networks-0.3.9.dist-info}/LICENSE.txt +0 -0
- {RDG_Networks-0.3.7.dist-info → RDG_Networks-0.3.9.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: RDG-Networks
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.9
|
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
|
@@ -14,6 +14,14 @@ Requires-Dist: numpy
|
|
14
14
|
Requires-Dist: scipy
|
15
15
|
Requires-Dist: shapely
|
16
16
|
Requires-Dist: typing
|
17
|
+
Dynamic: author
|
18
|
+
Dynamic: author-email
|
19
|
+
Dynamic: classifier
|
20
|
+
Dynamic: description
|
21
|
+
Dynamic: home-page
|
22
|
+
Dynamic: license
|
23
|
+
Dynamic: requires-dist
|
24
|
+
Dynamic: summary
|
17
25
|
|
18
26
|
## Overview
|
19
27
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
RDG_networks/Classes.py,sha256=_9X3JPHFAYYlaC8IZ_H9__sfz99G5l9UfPl65lL60_4,7977
|
2
|
-
RDG_networks/__init__.py,sha256=
|
2
|
+
RDG_networks/__init__.py,sha256=8cc-h5ifS7RP1-N4b1Ow-4bA6MxgFETaS6SD0of54Fk,1575
|
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
|
@@ -8,15 +8,16 @@ RDG_networks/generate_line_segments_static.py,sha256=7KvHZi3krv-tAGydJR_gbMMmHKZ
|
|
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
10
|
RDG_networks/save_to_stl.py,sha256=St8kGw6wl8uOGx8KhrZhBfe89-mOfp5JKhz0dEDBvc0,3894
|
11
|
-
RDG_networks/thickness/Classes.py,sha256=
|
12
|
-
RDG_networks/thickness/__init__.py,sha256=
|
13
|
-
RDG_networks/thickness/generate_line_segments_thickness.py,sha256=
|
14
|
-
RDG_networks/thickness/
|
15
|
-
RDG_networks/thickness/generate_line_segments_thickness_static.py,sha256=
|
11
|
+
RDG_networks/thickness/Classes.py,sha256=zRhi2TFDwK1oARvAd1HlxnMWeHSj7KKO-u79_r23tXc,8176
|
12
|
+
RDG_networks/thickness/__init__.py,sha256=DzH-mmdrk5e1LL7oq5kg0xaDjtWR7DiCeKKchArHSIs,703
|
13
|
+
RDG_networks/thickness/generate_line_segments_thickness.py,sha256=q3MVKEC8w1MUpiRGF6o4MKh6VHBIs1nuIibsqsewDcQ,29037
|
14
|
+
RDG_networks/thickness/generate_line_segments_thickness_correct.py,sha256=rvvqEMGYX7imosKedyXHyTkBV4C2_24K8UxthXDTe7Q,28627
|
15
|
+
RDG_networks/thickness/generate_line_segments_thickness_static.py,sha256=6-2p4GOQFU-dP5Q9nYuaVqeb7wvxk2Fqld3A7cV2pY8,8964
|
16
|
+
RDG_networks/thickness/orientate_network.py,sha256=4jii3y89Jlw1tPmTWoUyrKE7MlHq21JuoiuwnBa1Mkk,15484
|
16
17
|
RDG_networks/thickness/sample_in_polygon.py,sha256=nJ-yqfoCCGfC6_EpGL3L1t1LOYdqWZd-7v5bxy6th34,1849
|
17
|
-
RDG_Networks-0.3.
|
18
|
-
RDG_Networks-0.3.
|
19
|
-
RDG_Networks-0.3.
|
20
|
-
RDG_Networks-0.3.
|
21
|
-
RDG_Networks-0.3.
|
22
|
-
RDG_Networks-0.3.
|
18
|
+
RDG_Networks-0.3.9.dist-info/LICENSE.txt,sha256=BHUkX2GsdTr30sKmVZ1MLGR1njnx17EX_oUuuSVZZPE,598
|
19
|
+
RDG_Networks-0.3.9.dist-info/METADATA,sha256=hlDd-R21n_eTohn-xz3OU7-ZvLragLhxSHwNWTq0u4M,2577
|
20
|
+
RDG_Networks-0.3.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
21
|
+
RDG_Networks-0.3.9.dist-info/entry_points.txt,sha256=coqOWe9rYYuz9VQvJGFomTzvEP7JY5T9V9gauMhSb_0,986
|
22
|
+
RDG_Networks-0.3.9.dist-info/top_level.txt,sha256=4gUUYafD5Al9V8ZSiViVGYHpRMMCsCBcGgCNodk9Syg,13
|
23
|
+
RDG_Networks-0.3.9.dist-info/RECORD,,
|
@@ -6,10 +6,10 @@ generate_line_segments = RDG_networks.generate_line_segments:main
|
|
6
6
|
generate_line_segments_dynamic = RDG_networks.generate_line_segments_dynamic:main
|
7
7
|
generate_line_segments_static = RDG_networks.generate_line_segments_static:main
|
8
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
9
|
generate_line_segments_thickness_static = RDG_networks.generate_line_segments_thickness_static:main
|
11
10
|
get_alignment_mean = RDG_networks.thickness.get_alignment_mean:main
|
12
11
|
get_intersection_segments = RDG_networks.get_intersection_segments:main
|
12
|
+
orientate_network = RDG_networks.thickness.orientate_network:main
|
13
13
|
rotate_network = RDG_networks.thickness.rotate_network:main
|
14
14
|
save_to_stl = RDG_networks.save_to_stl:main
|
15
15
|
translate_network = RDG_networks.thickness.translate_network:main
|
RDG_networks/__init__.py
CHANGED
@@ -8,17 +8,17 @@ 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
10
|
from .thickness.generate_line_segments_thickness import generate_line_segments_thickness
|
11
|
-
from .thickness.
|
11
|
+
from .thickness.orientate_network import orientate_network
|
12
12
|
from .thickness.generate_line_segments_thickness_static import generate_line_segments_thickness_static
|
13
|
-
from .thickness.
|
14
|
-
from .thickness.
|
15
|
-
from .thickness.
|
16
|
-
from .thickness.
|
13
|
+
from .thickness.orientate_network import translate_network
|
14
|
+
from .thickness.orientate_network import clip_network
|
15
|
+
from .thickness.orientate_network import rotate_network
|
16
|
+
from .thickness.orientate_network import get_alignment_mean
|
17
17
|
from .save_to_stl import save_to_stl
|
18
18
|
|
19
19
|
__all__ = ['generate_line_segments',
|
20
20
|
'generate_line_segments_thickness',
|
21
|
-
'
|
21
|
+
'orientate_network',
|
22
22
|
'translate_network',
|
23
23
|
'clip_network',
|
24
24
|
'rotate_network',
|
@@ -95,7 +95,7 @@ class Polygon:
|
|
95
95
|
vertices (List[Tuple[float, float]]): A list of (x, y) coordinates representing the vertices of the polygon.
|
96
96
|
"""
|
97
97
|
|
98
|
-
def __init__(self, vertices: List[tuple], middle_segment=None):
|
98
|
+
def __init__(self, vertices: List[tuple], middle_segment=None, neighbors=None):
|
99
99
|
"""
|
100
100
|
Initializes a Polygon instance with the provided vertices.
|
101
101
|
|
@@ -104,6 +104,7 @@ class Polygon:
|
|
104
104
|
"""
|
105
105
|
self.vertices = vertices
|
106
106
|
self.middle_segment = middle_segment
|
107
|
+
self.neighbors = neighbors
|
107
108
|
|
108
109
|
def area(self) -> float:
|
109
110
|
"""
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# __init__.py
|
2
2
|
|
3
3
|
from .generate_line_segments_thickness import generate_line_segments_thickness
|
4
|
-
from .
|
4
|
+
from .orientate_network import orientate_network
|
5
5
|
from .generate_line_segments_thickness_static import generate_line_segments_thickness_static
|
6
|
-
from .
|
7
|
-
from .
|
8
|
-
from .
|
9
|
-
from .
|
6
|
+
from .orientate_network import translate_network
|
7
|
+
from .orientate_network import clip_network
|
8
|
+
from .orientate_network import rotate_network
|
9
|
+
from .orientate_network import get_alignment_mean
|
10
10
|
|
11
11
|
__all__ = [
|
12
12
|
'generate_line_segments_thickness',
|
13
|
-
'
|
13
|
+
'orientate_network',
|
14
14
|
'generate_line_segments_thickness_static',
|
15
15
|
'translate_network',
|
16
16
|
'clip_network',
|
@@ -142,10 +142,10 @@ def pick_item_with_probability(
|
|
142
142
|
def get_location_and_direction(
|
143
143
|
polygon_arr: Dict[str, Dict[str, object]],
|
144
144
|
thickness: float,
|
145
|
+
angle: float,
|
145
146
|
nucleation_point: Tuple[float, float] = None,
|
146
147
|
min_distance: float = 0,
|
147
|
-
max_attempts: int = 1000
|
148
|
-
angles: Union[str, List[float]] = 'uniform'
|
148
|
+
max_attempts: int = 1000
|
149
149
|
) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
150
150
|
"""
|
151
151
|
Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
|
@@ -159,8 +159,8 @@ def get_location_and_direction(
|
|
159
159
|
The thickness of the segment that needs to fit inside the polygon.
|
160
160
|
max_attempts (int, optional):
|
161
161
|
The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
162
|
-
|
163
|
-
A
|
162
|
+
angle (float):
|
163
|
+
A float indicating the angle of the new segment.
|
164
164
|
nucleation_point (Tuple[float, float], optional):
|
165
165
|
predified nucleation point for the segment. Defaults to None.
|
166
166
|
min_distance (float, optional):
|
@@ -178,16 +178,8 @@ def get_location_and_direction(
|
|
178
178
|
- The angle of the segment.
|
179
179
|
- Returns `False` if no valid location and direction are found after the maximum attempts.
|
180
180
|
"""
|
181
|
-
|
182
|
-
|
183
|
-
if angles == 'uniform':
|
184
|
-
angle_new = random.uniform(-np.pi, np.pi)
|
185
|
-
direction = (np.cos(angle_new), np.sin(angle_new))
|
186
|
-
direction = direction / np.linalg.norm(direction) # Normalize the direction vector
|
187
|
-
else:
|
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)
|
181
|
+
direction = (np.cos(angle), np.sin(angle))
|
182
|
+
direction = np.array(direction) / np.linalg.norm(direction)
|
191
183
|
|
192
184
|
# Try to find a valid location and direction up to max_attempts
|
193
185
|
attempt = 0
|
@@ -215,7 +207,7 @@ def get_location_and_direction(
|
|
215
207
|
|
216
208
|
# Check if both endpoints of the segment are inside the polygon
|
217
209
|
if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
|
218
|
-
return polygon_id, polygon, location_new, direction, perpendicular,
|
210
|
+
return polygon_id, polygon, location_new, direction, perpendicular, angle
|
219
211
|
|
220
212
|
attempt += 1
|
221
213
|
|
@@ -425,7 +417,7 @@ def update_data(
|
|
425
417
|
segments_dict[segment_new_1.id] = segment_new_1
|
426
418
|
segments_dict[neighbor1_1].neighbors[id_1] = vertex_begin_1
|
427
419
|
segments_dict[neighbor1_2].neighbors[id_1] = vertex_end_1
|
428
|
-
|
420
|
+
|
429
421
|
# Update the segments_dict for the second segment
|
430
422
|
neighbors_initial_2 = {
|
431
423
|
neighbor2_1: vertex_begin_2,
|
@@ -443,7 +435,20 @@ def update_data(
|
|
443
435
|
segments_dict[neighbor2_2].neighbors[id_2] = vertex_end_2
|
444
436
|
|
445
437
|
# Update the segment_thickness_dict with the base polygon
|
446
|
-
|
438
|
+
neighbors = cycle0.copy()
|
439
|
+
neighbors.remove(f'{len(segment_thickness_dict)+1}_1')
|
440
|
+
neighbors.remove(f'{len(segment_thickness_dict)+1}_2')
|
441
|
+
neighbors = [ i[:-2] if i not in ['b1', 'b2', 'b3', 'b4'] else i for i in neighbors ]
|
442
|
+
neighbors = list(set(neighbors))
|
443
|
+
|
444
|
+
segment_thickness_dict[len(segment_thickness_dict) + 1] = Polygon(vertices=vertices0, neighbors=neighbors)
|
445
|
+
|
446
|
+
for n in neighbors:
|
447
|
+
if n in ['b1', 'b2', 'b3', 'b4']:
|
448
|
+
continue
|
449
|
+
|
450
|
+
else:
|
451
|
+
segment_thickness_dict[int(n)].neighbors.append(len(segment_thickness_dict) + 1)
|
447
452
|
|
448
453
|
return segments_dict, polygon_arr, segment_thickness_dict
|
449
454
|
|
@@ -451,7 +456,7 @@ def add_line_segment(
|
|
451
456
|
segments_dict: Dict[int, LineSegment],
|
452
457
|
polygon_arr: Dict[str, Dict[str, object]],
|
453
458
|
segment_thickness_dict: Dict[int, Polygon],
|
454
|
-
|
459
|
+
angle: float,
|
455
460
|
thickness: float = 0,
|
456
461
|
nucleation_point: Tuple[float, float] = None,
|
457
462
|
min_distance: float = 0,
|
@@ -480,7 +485,7 @@ def add_line_segment(
|
|
480
485
|
"""
|
481
486
|
|
482
487
|
# Get a valid location and direction, or return False if none is found
|
483
|
-
loc = get_location_and_direction(polygon_arr, thickness, nucleation_point, min_distance, max_attempts=max_attempts
|
488
|
+
loc = get_location_and_direction(polygon_arr, angle=angle, thickness=thickness, nucleation_point=nucleation_point, min_distance=min_distance, max_attempts=max_attempts)
|
484
489
|
if loc:
|
485
490
|
polygon_id, polygon, location_new, direction_new, perpendicular, angle_new = loc
|
486
491
|
else:
|
@@ -560,8 +565,7 @@ def generate_line_segments_thickness(
|
|
560
565
|
Args:
|
561
566
|
size (int): The number of line segments to generate.
|
562
567
|
thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
|
563
|
-
angles (str):
|
564
|
-
List[float]: list of angles in radians.
|
568
|
+
angles (str): Angle used in the generation of the segments.
|
565
569
|
config (List[List[float]]): A list of configurations for the nucleation points and angles.
|
566
570
|
epsilon (float): the minimum distance between two line.
|
567
571
|
box_size (float): the size of the box.
|
@@ -606,8 +610,6 @@ def generate_line_segments_thickness(
|
|
606
610
|
angles = [config[i]['angle']]
|
607
611
|
else:
|
608
612
|
nucleation_point = None
|
609
|
-
# if angles != 'uniform':
|
610
|
-
# angles=[angles[i]]
|
611
613
|
|
612
614
|
output = add_line_segment(segments_dict,
|
613
615
|
polygon_arr,
|
@@ -615,7 +617,7 @@ def generate_line_segments_thickness(
|
|
615
617
|
thickness=thickness_arr[i],
|
616
618
|
min_distance = epsilon,
|
617
619
|
nucleation_point = nucleation_point,
|
618
|
-
|
620
|
+
angle=angles[i],
|
619
621
|
box_size=box_size)
|
620
622
|
if output:
|
621
623
|
segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
|
@@ -624,7 +626,7 @@ def generate_line_segments_thickness(
|
|
624
626
|
else:
|
625
627
|
if config:
|
626
628
|
print('Configuration not possible. Point is skipped.')
|
627
|
-
else:
|
629
|
+
else:
|
628
630
|
print(f"Stopped at iteration {len(segment_thickness_dict)}, could not find a valid segment position.")
|
629
631
|
jammed = True
|
630
632
|
break
|
@@ -0,0 +1,633 @@
|
|
1
|
+
import math
|
2
|
+
import numpy as np
|
3
|
+
import random
|
4
|
+
from typing import List, Dict, Tuple, Union, Optional
|
5
|
+
from shapely.geometry import Polygon as Polygon_Shapely, LineString
|
6
|
+
|
7
|
+
from .Classes import Line, LineSegment, Polygon
|
8
|
+
from .sample_in_polygon import sample_in_polygon, is_inside_polygon
|
9
|
+
|
10
|
+
def doLinesIntersect(line1: Line, line2: Line, box_size = 1) -> Tuple[bool, Union[Tuple[float, float], None]]:
|
11
|
+
"""
|
12
|
+
Check if two lines intersect and return the intersection point.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
- line1 (Line): The first line segment.
|
16
|
+
- line2 (Line): The second line segment.
|
17
|
+
- box_size (float): The size of the bounding box. Defaults to 1.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
- intersect (bool): True if the lines intersect, False otherwise.
|
21
|
+
- intersection_point (tuple or None): The intersection point (x, y) if lines intersect, None otherwise.
|
22
|
+
"""
|
23
|
+
x1, y1 = line1.location
|
24
|
+
v1, w1 = line1.direction
|
25
|
+
|
26
|
+
x2, y2 = line2.location
|
27
|
+
v2, w2 = line2.direction
|
28
|
+
|
29
|
+
determinant = v1 * w2 - v2 * w1
|
30
|
+
|
31
|
+
if determinant == 0:
|
32
|
+
return False, (None, None)
|
33
|
+
|
34
|
+
t1 = ((x2 - x1) * w2 - (y2 - y1) * v2) / determinant
|
35
|
+
t2 = ((x2 - x1) * w1 - (y2 - y1) * v1) / determinant
|
36
|
+
|
37
|
+
intersect_x = x1 + v1 * t1
|
38
|
+
intersect_y = y2 + w2 * t2
|
39
|
+
|
40
|
+
|
41
|
+
if -1e-6 < intersect_x < box_size + 1e-6 and -1e-6 < intersect_y < box_size + 1e-6:
|
42
|
+
return True, (intersect_x, intersect_y)
|
43
|
+
else:
|
44
|
+
return False, (None, None)
|
45
|
+
|
46
|
+
def doSegmentsIntersect(
|
47
|
+
segment1: LineSegment,
|
48
|
+
segment2: LineSegment,
|
49
|
+
box_size = 1
|
50
|
+
) -> Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
51
|
+
"""
|
52
|
+
Determines if two line segments intersect and returns the intersection point if they do.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
segment1 (LineSegment): The first line segment.
|
56
|
+
segment2 (LineSegment): The second line segment.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
Tuple[bool, Tuple[Optional[float], Optional[float]]]:
|
60
|
+
- A boolean indicating whether the segments intersect.
|
61
|
+
- A tuple of the x and y coordinates of the intersection point if they intersect,
|
62
|
+
otherwise (None, None).
|
63
|
+
"""
|
64
|
+
|
65
|
+
# Create line equations based on the segments' start and end points
|
66
|
+
line1 = Line(location=segment1.start, direction=np.array(segment1.end) - np.array(segment1.start))
|
67
|
+
line2 = Line(location=segment2.start, direction=np.array(segment2.end) - np.array(segment2.start))
|
68
|
+
|
69
|
+
# Check if the infinite extensions of the two lines intersect
|
70
|
+
intersect, (intersect_x, intersect_y) = doLinesIntersect(line1, line2, box_size)
|
71
|
+
|
72
|
+
# If no intersection, return False
|
73
|
+
if not intersect:
|
74
|
+
return False, (None, None)
|
75
|
+
|
76
|
+
# Check if the intersection point is within the bounds of both segments in the x-direction
|
77
|
+
xcheck = (
|
78
|
+
(segment1.end[0] <= intersect_x <= segment1.start[0]
|
79
|
+
or segment1.start[0] <= intersect_x <= segment1.end[0]
|
80
|
+
or abs(intersect_x - segment1.end[0]) < 1e-6
|
81
|
+
or abs(intersect_x - segment1.start[0]) < 1e-6)
|
82
|
+
and
|
83
|
+
(segment2.end[0] <= intersect_x <= segment2.start[0]
|
84
|
+
or segment2.start[0] <= intersect_x <= segment2.end[0]
|
85
|
+
or abs(intersect_x - segment2.end[0]) < 1e-6
|
86
|
+
or abs(intersect_x - segment2.start[0]) < 1e-6)
|
87
|
+
)
|
88
|
+
|
89
|
+
# Check if the intersection point is within the bounds of both segments in the y-direction
|
90
|
+
ycheck = (
|
91
|
+
(segment1.end[1] <= intersect_y <= segment1.start[1]
|
92
|
+
or segment1.start[1] <= intersect_y <= segment1.end[1]
|
93
|
+
or abs(intersect_y - segment1.end[1]) < 1e-6
|
94
|
+
or abs(intersect_y - segment1.start[1]) < 1e-6)
|
95
|
+
and
|
96
|
+
(segment2.end[1] <= intersect_y <= segment2.start[1]
|
97
|
+
or segment2.start[1] <= intersect_y <= segment2.end[1]
|
98
|
+
or abs(intersect_y - segment2.end[1]) < 1e-6
|
99
|
+
or abs(intersect_y - segment2.start[1]) < 1e-6)
|
100
|
+
)
|
101
|
+
|
102
|
+
# If the intersection point lies within the bounds of both segments, return True with the intersection point
|
103
|
+
if xcheck and ycheck:
|
104
|
+
return True, (intersect_x, intersect_y)
|
105
|
+
|
106
|
+
# Otherwise, return False and no intersection point
|
107
|
+
return False, (None, None)
|
108
|
+
|
109
|
+
def pick_item_with_probability(
|
110
|
+
polygon_arr: Dict[str, Dict[str, object]]
|
111
|
+
) -> Tuple[str, Dict[str, object]]:
|
112
|
+
"""
|
113
|
+
Randomly selects an item from the polygon array with a probability proportional to the area of the polygons.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
117
|
+
A dictionary where keys are polygon identifiers (e.g., 'p1', 'p2') and values are dictionaries containing polygon properties,
|
118
|
+
including an 'area' key that stores the area of the polygon.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
Tuple[str, Dict[str, object]]:
|
122
|
+
- The identifier of the selected polygon.
|
123
|
+
- The corresponding polygon data (dictionary) containing its properties.
|
124
|
+
"""
|
125
|
+
|
126
|
+
# Calculate the total weight (sum of areas of all polygons)
|
127
|
+
max_weight = sum(pol['area'] for pol in polygon_arr.values())
|
128
|
+
|
129
|
+
# Generate a random threshold between 0 and the total weight
|
130
|
+
threshold = random.uniform(0, max_weight)
|
131
|
+
cumulative_weight = 0
|
132
|
+
|
133
|
+
# Iterate through the polygons, accumulating weights
|
134
|
+
for item, pol in polygon_arr.items():
|
135
|
+
weight = pol['area']
|
136
|
+
cumulative_weight += weight
|
137
|
+
|
138
|
+
# Return the polygon when the cumulative weight surpasses the threshold
|
139
|
+
if cumulative_weight >= threshold:
|
140
|
+
return item, pol
|
141
|
+
|
142
|
+
def get_location_and_direction(
|
143
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
144
|
+
thickness: float,
|
145
|
+
angle: float,
|
146
|
+
nucleation_point: Tuple[float, float] = None,
|
147
|
+
min_distance: float = 0,
|
148
|
+
max_attempts: int = 1000
|
149
|
+
) -> Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
150
|
+
"""
|
151
|
+
Attempts to find a valid location and direction within a polygon for placing a new segment. The direction can either be randomly
|
152
|
+
chosen (uniformly) or from a specified list of angles. It ensures that the segment lies within the polygon's bounds given the
|
153
|
+
specified thickness.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
polygon_arr (Dict[str, Dict[str, object]]):
|
157
|
+
A dictionary where the keys are polygon identifiers and the values are dictionaries containing polygon properties, including 'vertices'.
|
158
|
+
thickness (float):
|
159
|
+
The thickness of the segment that needs to fit inside the polygon.
|
160
|
+
max_attempts (int, optional):
|
161
|
+
The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
162
|
+
angle (float):
|
163
|
+
A float indicating the angle of the new segment.
|
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.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
Union[Tuple[str, Dict[str, object], Tuple[float, float], np.ndarray, np.ndarray], bool]:
|
171
|
+
- If a valid location and direction are found, returns a tuple containing:
|
172
|
+
- The polygon ID (`str`).
|
173
|
+
- The polygon data (`Dict[str, object]`).
|
174
|
+
- The new location as a tuple of floats (`Tuple[float, float]`).
|
175
|
+
- The direction vector as a numpy array (`np.ndarray`).
|
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.
|
179
|
+
- Returns `False` if no valid location and direction are found after the maximum attempts.
|
180
|
+
"""
|
181
|
+
direction = (np.cos(angle), np.sin(angle))
|
182
|
+
direction = np.array(direction) / np.linalg.norm(direction)
|
183
|
+
|
184
|
+
# Try to find a valid location and direction up to max_attempts
|
185
|
+
attempt = 0
|
186
|
+
while attempt < max_attempts:
|
187
|
+
polygon_id, polygon = pick_item_with_probability(polygon_arr)
|
188
|
+
|
189
|
+
# Sample a location within the polygon
|
190
|
+
#check if nucleation point is given
|
191
|
+
if nucleation_point is not None:
|
192
|
+
location_new = nucleation_point
|
193
|
+
else:
|
194
|
+
location_new = sample_in_polygon(polygon['vertices'])
|
195
|
+
|
196
|
+
# Compute the perpendicular vector to the direction
|
197
|
+
perpendicular = np.array([direction[1], -direction[0]])
|
198
|
+
perpendicular = perpendicular / np.linalg.norm(perpendicular)
|
199
|
+
|
200
|
+
# Ensure the perpendicular vector is oriented consistently (y-component is non-negative)
|
201
|
+
if perpendicular[1] < 0:
|
202
|
+
perpendicular = -perpendicular
|
203
|
+
|
204
|
+
# Compute the positions for the segment with thickness, shifted by half-thickness along the perpendicular direction
|
205
|
+
p1 = np.array(location_new) + (thickness/2 + min_distance) * perpendicular
|
206
|
+
p2 = np.array(location_new) - (thickness/2 + min_distance) * perpendicular
|
207
|
+
|
208
|
+
# Check if both endpoints of the segment are inside the polygon
|
209
|
+
if is_inside_polygon(polygon['vertices'], p1) and is_inside_polygon(polygon['vertices'], p2):
|
210
|
+
return polygon_id, polygon, location_new, direction, perpendicular, angle
|
211
|
+
|
212
|
+
attempt += 1
|
213
|
+
|
214
|
+
# If no valid location and direction is found, return False
|
215
|
+
return False
|
216
|
+
|
217
|
+
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):
|
218
|
+
# Extract vertices and cycle (faces) of the original polygon
|
219
|
+
vertices = polygon_arr[polygon_id]['vertices']
|
220
|
+
cycle = polygon_arr[polygon_id]['faces']
|
221
|
+
|
222
|
+
# Get first cycle and vertices
|
223
|
+
index_start_1, index_end_1 = (cycle.index(neighbor1_1), cycle.index(neighbor1_2))
|
224
|
+
if index_start_1 < index_end_1:
|
225
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:index_end_1+1]
|
226
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:index_end_1] + [vertex_end_1]
|
227
|
+
else:
|
228
|
+
cycle1 = [segment_new_id_1] + cycle[index_start_1:] + cycle[:index_end_1+1]
|
229
|
+
vertices1 = [vertex_begin_1] + vertices[index_start_1:] + vertices[:index_end_1] + [vertex_end_1]
|
230
|
+
|
231
|
+
# Get second cycle and vertices
|
232
|
+
index_start_2, index_end_2 = (cycle.index(neighbor2_2), cycle.index(neighbor2_1))
|
233
|
+
if index_start_2 < index_end_2:
|
234
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:index_end_2+1]
|
235
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:index_end_2] + [vertex_begin_2]
|
236
|
+
else:
|
237
|
+
cycle2 = [segment_new_id_2] + cycle[index_start_2:] + cycle[:index_end_2+1]
|
238
|
+
vertices2 = [vertex_end_2] + vertices[index_start_2:] + vertices[:index_end_2] + [vertex_begin_2]
|
239
|
+
|
240
|
+
# Get middle cycle and vertices
|
241
|
+
cycle0 = [neighbor1_1, segment_new_id_1, neighbor1_2]
|
242
|
+
vertices0 = [vertex_begin_1, vertex_end_1]
|
243
|
+
|
244
|
+
index_start_0, index_end_0 = (cycle.index(neighbor1_2), cycle.index(neighbor2_2))
|
245
|
+
if index_start_0 < index_end_0:
|
246
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
247
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
248
|
+
|
249
|
+
elif index_start_0 > index_end_0:
|
250
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
251
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
252
|
+
|
253
|
+
cycle0 = cycle0 + [segment_new_id_2]
|
254
|
+
vertices0 = vertices0 + [vertex_end_2] + [vertex_begin_2]
|
255
|
+
|
256
|
+
index_start_0, index_end_0 = (cycle.index(neighbor2_1), cycle.index(neighbor1_1))
|
257
|
+
if index_start_0 < index_end_0:
|
258
|
+
cycle0 = cycle0 + cycle[index_start_0:index_end_0+1]
|
259
|
+
vertices0 = vertices0 + vertices[index_start_0:index_end_0]
|
260
|
+
|
261
|
+
elif index_start_0 > index_end_0:
|
262
|
+
cycle0 = cycle0 + cycle[index_start_0:] + cycle[:index_end_0+1]
|
263
|
+
vertices0 = vertices0 + vertices[index_start_0:] + vertices[:index_end_0]
|
264
|
+
|
265
|
+
return cycle0, vertices0, cycle1, vertices1, cycle2, vertices2
|
266
|
+
|
267
|
+
def get_new_segment(
|
268
|
+
line_segments_to_check: List[LineSegment],
|
269
|
+
location: Tuple[float, float],
|
270
|
+
direction: Tuple[float, float],
|
271
|
+
id: Optional[int] = None,
|
272
|
+
box_size: float = 1
|
273
|
+
) -> LineSegment:
|
274
|
+
"""
|
275
|
+
Creates a new line segment by extending a given location in a specified direction and
|
276
|
+
determines its neighbors by checking intersections with other line segments.
|
277
|
+
|
278
|
+
Args:
|
279
|
+
line_segments_to_check (List[LineSegment]): List of existing line segments to check for intersections.
|
280
|
+
location (Tuple[float, float]): The starting point (x, y) for the new line segment.
|
281
|
+
direction (Tuple[float, float]): The direction vector in which to extend the line segment.
|
282
|
+
id (Optional[int]): Optional ID for the new line segment. If not provided, defaults to None.
|
283
|
+
box_size(optional[Int]]): The size of the box. Defaults to 1.
|
284
|
+
|
285
|
+
Returns:
|
286
|
+
LineSegment: A new line segment object with its neighbors based on intersections.
|
287
|
+
"""
|
288
|
+
|
289
|
+
# Create a temporary line segment extending from the location in both directions
|
290
|
+
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))
|
291
|
+
|
292
|
+
intersection_points = []
|
293
|
+
|
294
|
+
# Check for intersections with existing line segments
|
295
|
+
for segment in line_segments_to_check:
|
296
|
+
intersect, (intersect_x, intersect_y) = doSegmentsIntersect(s_temp, segment, box_size)
|
297
|
+
|
298
|
+
if intersect:
|
299
|
+
segment_length = math.sqrt(
|
300
|
+
(location[0] - intersect_x) ** 2
|
301
|
+
+ (location[1] - intersect_y) ** 2
|
302
|
+
)
|
303
|
+
intersection_points.append(
|
304
|
+
{"id": segment.id, "point": (intersect_x, intersect_y), "segment_length": segment_length}
|
305
|
+
)
|
306
|
+
|
307
|
+
# Divide intersections into ones behind and in front of the new line
|
308
|
+
intersections_b = [intersection for intersection in intersection_points if intersection["point"][0] < location[0]]
|
309
|
+
intersections_f = [intersection for intersection in intersection_points if intersection["point"][0] > location[0]]
|
310
|
+
|
311
|
+
if not intersections_b or not intersections_f:
|
312
|
+
intersections_b = [intersection for intersection in intersection_points if intersection["point"][1] < location[1]]
|
313
|
+
intersections_f = [intersection for intersection in intersection_points if intersection["point"][1] > location[1]]
|
314
|
+
|
315
|
+
# Determine the closest intersections for segment start and end
|
316
|
+
s_start = min(intersections_b, key=lambda x: x["segment_length"])
|
317
|
+
s_end = min(intersections_f, key=lambda x: x["segment_length"])
|
318
|
+
start, end = s_start['point'], s_end['point']
|
319
|
+
start_id, end_id = s_start['id'], s_end['id']
|
320
|
+
|
321
|
+
# Ensure the start comes before the end
|
322
|
+
if start[0] > end[0]:
|
323
|
+
start, end = end, start
|
324
|
+
start_id, end_id = end_id, start_id
|
325
|
+
|
326
|
+
# Create a new line segment and assign neighbors
|
327
|
+
neighbors_initial = {start_id: start, end_id: end}
|
328
|
+
segment_new = LineSegment(start=start, end=end, id=id, neighbors_initial=neighbors_initial, neighbors=neighbors_initial)
|
329
|
+
|
330
|
+
return segment_new
|
331
|
+
|
332
|
+
def update_data(
|
333
|
+
segments_dict: Dict[int, LineSegment],
|
334
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
335
|
+
polygon_id: str,
|
336
|
+
segment_thickness_dict: Dict[int, Polygon],
|
337
|
+
vertices0: List[Tuple[float, float]],
|
338
|
+
vertices1: List[Tuple[float, float]],
|
339
|
+
vertices2: List[Tuple[float, float]],
|
340
|
+
cycle0: List[int],
|
341
|
+
cycle1: List[int],
|
342
|
+
cycle2: List[int],
|
343
|
+
neighbor1_1: int,
|
344
|
+
neighbor1_2: int,
|
345
|
+
neighbor2_1: int,
|
346
|
+
neighbor2_2: int,
|
347
|
+
vertex_begin_1: Tuple[float, float],
|
348
|
+
vertex_end_1: Tuple[float, float],
|
349
|
+
vertex_begin_2: Tuple[float, float],
|
350
|
+
vertex_end_2: Tuple[float, float],
|
351
|
+
id_1: int,
|
352
|
+
id_2: int
|
353
|
+
) -> Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
354
|
+
"""
|
355
|
+
Updates the segments, polygons, and segment thickness dictionaries by adding new data derived
|
356
|
+
from provided vertices and neighbor information.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
segments_dict (Dict[int, LineSegment]): A dictionary of segments with segment ID as the key.
|
360
|
+
polygon_arr (Dict[str, Dict[str, object]]): A dictionary of polygons with polygon ID as the key.
|
361
|
+
polygon_id (str): The ID of the polygon being updated.
|
362
|
+
segment_thickness_dict (Dict[int, Polygon]): A dictionary mapping thickness information to polygon objects.
|
363
|
+
vertices0 (List[Tuple[float, float]]): Vertices of the base polygon.
|
364
|
+
vertices1 (List[Tuple[float, float]]): Vertices of the first new polygon.
|
365
|
+
vertices2 (List[Tuple[float, float]]): Vertices of the second new polygon.
|
366
|
+
cycle0 (List[int]): List of face indices for the base polygon.
|
367
|
+
cycle1 (List[int]): List of face indices for the first new polygon.
|
368
|
+
cycle2 (List[int]): List of face indices for the second new polygon.
|
369
|
+
neighbor1_1 (int): ID of the first neighbor of the first segment.
|
370
|
+
neighbor1_2 (int): ID of the second neighbor of the first segment.
|
371
|
+
neighbor2_1 (int): ID of the first neighbor of the second segment.
|
372
|
+
neighbor2_2 (int): ID of the second neighbor of the second segment.
|
373
|
+
vertex_begin_1 (Tuple[float, float]): Starting vertex of the first segment.
|
374
|
+
vertex_end_1 (Tuple[float, float]): Ending vertex of the first segment.
|
375
|
+
vertex_begin_2 (Tuple[float, float]): Starting vertex of the second segment.
|
376
|
+
vertex_end_2 (Tuple[float, float]): Ending vertex of the second segment.
|
377
|
+
id_1 (int): ID of the first new segment.
|
378
|
+
id_2 (int): ID of the second new segment.
|
379
|
+
|
380
|
+
Returns:
|
381
|
+
Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
382
|
+
- Updated dictionary of line segments.
|
383
|
+
- Updated dictionary of polygons.
|
384
|
+
- Updated dictionary of segment thickness.
|
385
|
+
"""
|
386
|
+
|
387
|
+
# Update polygon_arr (a dictionary of polygons)
|
388
|
+
polygon_new_1 = {
|
389
|
+
f'p{len(polygon_arr) + 1}': {
|
390
|
+
'vertices': vertices1,
|
391
|
+
'area': Polygon(vertices=vertices1).area(),
|
392
|
+
'faces': cycle1
|
393
|
+
}
|
394
|
+
}
|
395
|
+
polygon_new_2 = {
|
396
|
+
polygon_id: {
|
397
|
+
'vertices': vertices2,
|
398
|
+
'area': Polygon(vertices=vertices2).area(),
|
399
|
+
'faces': cycle2
|
400
|
+
}
|
401
|
+
}
|
402
|
+
polygon_arr.update(polygon_new_1)
|
403
|
+
polygon_arr.update(polygon_new_2)
|
404
|
+
|
405
|
+
# Update the segments_dict for the first segment
|
406
|
+
neighbors_initial_1 = {
|
407
|
+
neighbor1_1: vertex_begin_1,
|
408
|
+
neighbor1_2: vertex_end_1
|
409
|
+
}
|
410
|
+
segment_new_1 = LineSegment(
|
411
|
+
start=vertex_begin_1,
|
412
|
+
end=vertex_end_1,
|
413
|
+
id=id_1,
|
414
|
+
neighbors_initial=neighbors_initial_1,
|
415
|
+
neighbors=neighbors_initial_1
|
416
|
+
)
|
417
|
+
segments_dict[segment_new_1.id] = segment_new_1
|
418
|
+
segments_dict[neighbor1_1].neighbors[id_1] = vertex_begin_1
|
419
|
+
segments_dict[neighbor1_2].neighbors[id_1] = vertex_end_1
|
420
|
+
|
421
|
+
# Update the segments_dict for the second segment
|
422
|
+
neighbors_initial_2 = {
|
423
|
+
neighbor2_1: vertex_begin_2,
|
424
|
+
neighbor2_2: vertex_end_2
|
425
|
+
}
|
426
|
+
segment_new_2 = LineSegment(
|
427
|
+
start=vertex_begin_2,
|
428
|
+
end=vertex_end_2,
|
429
|
+
id=id_2,
|
430
|
+
neighbors_initial=neighbors_initial_2,
|
431
|
+
neighbors=neighbors_initial_2
|
432
|
+
)
|
433
|
+
segments_dict[segment_new_2.id] = segment_new_2
|
434
|
+
segments_dict[neighbor2_1].neighbors[id_2] = vertex_begin_2
|
435
|
+
segments_dict[neighbor2_2].neighbors[id_2] = vertex_end_2
|
436
|
+
|
437
|
+
# Update the segment_thickness_dict with the base polygon
|
438
|
+
segment_thickness_dict[len(segment_thickness_dict) + 1] = Polygon(vertices=vertices0)
|
439
|
+
|
440
|
+
return segments_dict, polygon_arr, segment_thickness_dict
|
441
|
+
|
442
|
+
def add_line_segment(
|
443
|
+
segments_dict: Dict[int, LineSegment],
|
444
|
+
polygon_arr: Dict[str, Dict[str, object]],
|
445
|
+
segment_thickness_dict: Dict[int, Polygon],
|
446
|
+
angle: float,
|
447
|
+
thickness: float = 0,
|
448
|
+
nucleation_point: Tuple[float, float] = None,
|
449
|
+
min_distance: float = 0,
|
450
|
+
box_size: float = 1,
|
451
|
+
max_attempts: int = 1000
|
452
|
+
) -> Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], List[float], float], bool]:
|
453
|
+
"""
|
454
|
+
Adds a new line segment to the segments and polygon data structures, with a given thickness and angle distribution.
|
455
|
+
|
456
|
+
Args:
|
457
|
+
segments_dict (Dict[int, LineSegment]): A dictionary containing the current line segments.
|
458
|
+
polygon_arr (Dict[str, Dict[str, object]]): A dictionary containing the current polygons and their properties.
|
459
|
+
segment_thickness_dict (Dict[int, Polygon]): A dictionary storing the thickness information mapped to polygons.
|
460
|
+
thickness (float): The thickness of the new segment to be added. Defaults to 0.
|
461
|
+
angles (str): The angle distribution method. Defaults to 'uniform'.
|
462
|
+
nucleation_point (Tuple[float, float]): A predefined nucleation point for the new segment. Defaults to None.
|
463
|
+
min_distance (float): The minimum distance between two lines. Defaults to 0.
|
464
|
+
box_size (float): The size of the box. Defaults to 1.
|
465
|
+
max_attempts (int): The maximum number of attempts to find a valid location and direction. Defaults to 1000.
|
466
|
+
|
467
|
+
Returns:
|
468
|
+
Union[Tuple[Dict[int, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]], List[float], float, bool]:
|
469
|
+
- A tuple containing the updated segments dictionary, polygon dictionary, and thickness dictionary,
|
470
|
+
or False if no valid location for the new segment is found.
|
471
|
+
-nucleation point in a list [x,y] and the angle of the segment in radians.
|
472
|
+
"""
|
473
|
+
|
474
|
+
# Get a valid location and direction, or return False if none is found
|
475
|
+
loc = get_location_and_direction(polygon_arr, angle=angle, thickness=thickness, nucleation_point=nucleation_point, min_distance=min_distance, max_attempts=max_attempts)
|
476
|
+
if loc:
|
477
|
+
polygon_id, polygon, location_new, direction_new, perpendicular, angle_new = loc
|
478
|
+
else:
|
479
|
+
print('No valid location found')
|
480
|
+
return False
|
481
|
+
|
482
|
+
# Get the borders of the new segment with the given thickness
|
483
|
+
line_segments_to_check = [segments_dict[segment] for segment in polygon['faces']]
|
484
|
+
middle_segment = get_new_segment(line_segments_to_check, location=location_new, direction=direction_new, box_size=box_size)
|
485
|
+
s1 = get_new_segment(line_segments_to_check, location=np.array(location_new) + thickness * perpendicular / 2, direction=direction_new,box_size=box_size)
|
486
|
+
s2 = get_new_segment(line_segments_to_check, location=np.array(location_new) - thickness * perpendicular / 2, direction=direction_new, box_size=box_size)
|
487
|
+
|
488
|
+
# Extract neighbor information and segment vertices
|
489
|
+
neighbor1_1, neighbor1_2 = list(s1.neighbors.keys())
|
490
|
+
vertex_begin_1, vertex_end_1 = list(s1.neighbors.values())
|
491
|
+
neighbor2_1, neighbor2_2 = list(s2.neighbors.keys())
|
492
|
+
vertex_begin_2, vertex_end_2 = list(s2.neighbors.values())
|
493
|
+
id_1 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_1'
|
494
|
+
id_2 = str(int((len(segments_dict.keys()) - 2) / 2)) + '_2'
|
495
|
+
|
496
|
+
# Get the resulting polygons after splitting
|
497
|
+
cycle0, vertices0, cycle1, vertices1, cycle2, vertices2 = get_polygons(
|
498
|
+
polygon_id,
|
499
|
+
polygon_arr,
|
500
|
+
neighbor1_1,
|
501
|
+
neighbor1_2,
|
502
|
+
vertex_begin_1,
|
503
|
+
vertex_end_1,
|
504
|
+
neighbor2_1,
|
505
|
+
neighbor2_2,
|
506
|
+
vertex_begin_2=vertex_begin_2,
|
507
|
+
vertex_end_2=vertex_end_2,
|
508
|
+
segment_new_id_1=id_1,
|
509
|
+
segment_new_id_2=id_2
|
510
|
+
)
|
511
|
+
|
512
|
+
# Update all relevant data structures
|
513
|
+
segments_dict, polygon_arr, segment_thickness_dict = update_data(
|
514
|
+
segments_dict,
|
515
|
+
polygon_arr,
|
516
|
+
polygon_id,
|
517
|
+
segment_thickness_dict,
|
518
|
+
vertices0,
|
519
|
+
vertices1,
|
520
|
+
vertices2,
|
521
|
+
cycle0,
|
522
|
+
cycle1,
|
523
|
+
cycle2,
|
524
|
+
neighbor1_1,
|
525
|
+
neighbor1_2,
|
526
|
+
neighbor2_1,
|
527
|
+
neighbor2_2,
|
528
|
+
vertex_begin_1,
|
529
|
+
vertex_end_1,
|
530
|
+
vertex_begin_2,
|
531
|
+
vertex_end_2,
|
532
|
+
id_1,
|
533
|
+
id_2
|
534
|
+
)
|
535
|
+
|
536
|
+
# Associate the middle segment with the newly created thickness entry
|
537
|
+
segment_thickness_dict[list(segment_thickness_dict.keys())[-1]].middle_segment = middle_segment
|
538
|
+
|
539
|
+
return segments_dict, polygon_arr, segment_thickness_dict, location_new, angle_new
|
540
|
+
|
541
|
+
def generate_line_segments_thickness(
|
542
|
+
size: int,
|
543
|
+
thickness_arr: List[float],
|
544
|
+
angles: str = 'uniform',
|
545
|
+
config: List[List[float]] = None,
|
546
|
+
epsilon: float = 0,
|
547
|
+
box_size: float = 1
|
548
|
+
) -> Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon], np.ndarray]:
|
549
|
+
"""
|
550
|
+
Generates a specified number of line segments and updates the polygon and segment thickness dictionaries.
|
551
|
+
|
552
|
+
Args:
|
553
|
+
size (int): The number of line segments to generate.
|
554
|
+
thickness_arr (List[float]): A list containing the thickness values for each segment to be generated.
|
555
|
+
angles (str): Angle used in the generation of the segments.
|
556
|
+
config (List[List[float]]): A list of configurations for the nucleation points and angles.
|
557
|
+
epsilon (float): the minimum distance between two line.
|
558
|
+
box_size (float): the size of the box.
|
559
|
+
|
560
|
+
Returns:
|
561
|
+
Tuple[Dict[str, LineSegment], Dict[str, Dict[str, object]], Dict[int, Polygon]]:
|
562
|
+
- Updated dictionary of line segments.
|
563
|
+
- Updated dictionary of polygons.
|
564
|
+
- Updated dictionary of segment thicknesses.
|
565
|
+
- Array of the nucleation points and angles [x,y,theta].
|
566
|
+
"""
|
567
|
+
|
568
|
+
# Initialize border segments for a square and its polygon representation
|
569
|
+
borders = [
|
570
|
+
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)}),
|
571
|
+
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)}),
|
572
|
+
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)}),
|
573
|
+
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)})
|
574
|
+
]
|
575
|
+
|
576
|
+
polygon_arr = {
|
577
|
+
'p1': {
|
578
|
+
'vertices': [(0, 0), (0, box_size), (box_size, box_size), (box_size, 0)],
|
579
|
+
'area': box_size**2,
|
580
|
+
'faces': ['b1', 'b2', 'b3', 'b4']
|
581
|
+
}
|
582
|
+
}
|
583
|
+
|
584
|
+
segments = borders
|
585
|
+
segments_dict = {segment.id: segment for segment in segments}
|
586
|
+
segment_thickness_dict = {}
|
587
|
+
generated_config = []
|
588
|
+
|
589
|
+
if config is not None and size > len(config):
|
590
|
+
print("The size of the configuration is smaller than the size of the segments. Generated a network of the same size as the configuration.")
|
591
|
+
size = len(config)
|
592
|
+
|
593
|
+
jammed = False
|
594
|
+
for i in range(size):
|
595
|
+
if config:
|
596
|
+
nucleation_point = config[i]['location']
|
597
|
+
angles = [config[i]['angle']]
|
598
|
+
else:
|
599
|
+
nucleation_point = None
|
600
|
+
# if angles != 'uniform':
|
601
|
+
# angles=[angles[i]]
|
602
|
+
|
603
|
+
output = add_line_segment(segments_dict,
|
604
|
+
polygon_arr,
|
605
|
+
segment_thickness_dict,
|
606
|
+
thickness=thickness_arr[i],
|
607
|
+
min_distance = epsilon,
|
608
|
+
nucleation_point = nucleation_point,
|
609
|
+
angle=angles[i],
|
610
|
+
box_size=box_size)
|
611
|
+
if output:
|
612
|
+
segments_dict, polygon_arr, segment_thickness_dict, location, angle = output
|
613
|
+
generated_config.append({ 'location': location, 'angle': angle, 'thickness': thickness_arr[i] })
|
614
|
+
|
615
|
+
else:
|
616
|
+
if config:
|
617
|
+
print('Configuration not possible. Point is skipped.')
|
618
|
+
else:
|
619
|
+
print(f"Stopped at iteration {len(segment_thickness_dict)}, could not find a valid segment position.")
|
620
|
+
jammed = True
|
621
|
+
break
|
622
|
+
|
623
|
+
# Uncomment the following line if you want progress feedback
|
624
|
+
percentage = np.round(i / size * 100, 3)
|
625
|
+
print(f'generate_segments: {percentage}% done', end='\r')
|
626
|
+
|
627
|
+
data_dict = {'segments_dict': segments_dict,
|
628
|
+
'polygon_arr': polygon_arr,
|
629
|
+
'segment_thickness_dict': segment_thickness_dict,
|
630
|
+
'jammed': jammed,
|
631
|
+
'generated_config': generated_config}
|
632
|
+
|
633
|
+
return data_dict
|
@@ -105,7 +105,7 @@ def seeds(number_of_lines, radius = 0.015, number_of_trials = 10000):
|
|
105
105
|
if trial <= number_of_trials:
|
106
106
|
nucleation_points += [new_points]
|
107
107
|
angles = [0, np.pi/4, np.pi/2, 3*np.pi/4]
|
108
|
-
angle_new = random.uniform(0, 2*np.pi)
|
108
|
+
angle_new = random.uniform(0, 2*np.pi)
|
109
109
|
angle += [angle_new]
|
110
110
|
Line[line_id] = [ new_points ,angle_new]
|
111
111
|
line_id += 1
|
RDG_networks/thickness/{generate_line_segments_thickness_orientation.py → orientate_network.py}
RENAMED
@@ -1,8 +1,8 @@
|
|
1
1
|
import numpy as np
|
2
|
+
import math
|
2
3
|
from typing import List, Dict, Tuple
|
3
4
|
from shapely.geometry import Polygon as Polygon_Shapely
|
4
5
|
from shapely.geometry import LineString, box
|
5
|
-
from concurrent.futures import ProcessPoolExecutor
|
6
6
|
from .Classes import LineSegment, Polygon
|
7
7
|
|
8
8
|
def rotate(point, center, rotation_matrix):
|
@@ -13,7 +13,9 @@ def rotate(point, center, rotation_matrix):
|
|
13
13
|
rotation_matrix: 2x2 numpy array representing the rotation matrix
|
14
14
|
"""
|
15
15
|
translated_point = point - center
|
16
|
-
|
16
|
+
|
17
|
+
# rotated_point = np.dot(rotation_matrix, translated_point)
|
18
|
+
rotated_point = rotation_matrix@translated_point
|
17
19
|
final_point = rotated_point + center
|
18
20
|
|
19
21
|
return final_point
|
@@ -30,16 +32,22 @@ def angle_between(v1, v2):
|
|
30
32
|
|
31
33
|
def get_alignment_mean(line_vector_arr, director):
|
32
34
|
"""Get the mean alignment."""
|
33
|
-
S_all =
|
35
|
+
S_all = 0
|
36
|
+
total_mass = 0
|
34
37
|
for item in line_vector_arr:
|
35
38
|
line_vector = item['line_vector']
|
39
|
+
vector_diff = np.array(line_vector[1]) - np.array(line_vector[0])
|
40
|
+
|
36
41
|
area = item['area']
|
37
|
-
|
38
|
-
S_all
|
42
|
+
align = math.cos(angle_between(vector_diff, director))**2
|
43
|
+
S_all += align*area
|
44
|
+
total_mass += area
|
39
45
|
|
40
|
-
|
46
|
+
output = S_all / total_mass
|
41
47
|
|
42
|
-
|
48
|
+
return output
|
49
|
+
|
50
|
+
def compute_alignment(
|
43
51
|
angle: float,
|
44
52
|
segment_thickness_dict: dict[str, Polygon],
|
45
53
|
director: np.ndarray,
|
@@ -69,7 +77,7 @@ def compute_alignment_for_angle(
|
|
69
77
|
tuple[float, float]
|
70
78
|
A tuple where the first element is the input angle and the second element is the computed alignment value.
|
71
79
|
"""
|
72
|
-
box_center =
|
80
|
+
box_center = np.array((box_measurements[0]) + np.array(box_measurements[2])) / 2
|
73
81
|
|
74
82
|
# Rotate network
|
75
83
|
segment_thickness_dict_new = rotate_network(segment_thickness_dict, rotate_angle=angle, box_center=box_center)
|
@@ -79,13 +87,38 @@ def compute_alignment_for_angle(
|
|
79
87
|
|
80
88
|
line_vectors = [
|
81
89
|
{'line_vector': [seg.middle_segment.start, seg.middle_segment.end], 'area': seg.area()}
|
82
|
-
for seg in segment_thickness_dict_new.values()
|
90
|
+
for seg in segment_thickness_dict_new.values() if seg.middle_segment is not None
|
83
91
|
]
|
84
92
|
|
85
93
|
alignment = get_alignment_mean(line_vectors, director)
|
86
|
-
|
94
|
+
|
87
95
|
return angle, alignment
|
88
96
|
|
97
|
+
def get_max_alignment(
|
98
|
+
segment_thickness_dict: dict,
|
99
|
+
director: np.ndarray,
|
100
|
+
box_measurements: list[float],
|
101
|
+
grid_points: int = 360
|
102
|
+
) -> float:
|
103
|
+
"""Find the angle with the maximum alignment using parallel processing."""
|
104
|
+
# Create a list of angles to evaluate
|
105
|
+
angles = np.linspace(0, np.pi, grid_points)
|
106
|
+
|
107
|
+
results = []
|
108
|
+
for a in angles:
|
109
|
+
result = compute_alignment(a, segment_thickness_dict, director, box_measurements)
|
110
|
+
results.append(result)
|
111
|
+
|
112
|
+
# Find the angle with the maximum alignment
|
113
|
+
max_alignment = 0
|
114
|
+
max_angle = None
|
115
|
+
for angle, alignment in results:
|
116
|
+
if alignment > max_alignment:
|
117
|
+
max_alignment = alignment
|
118
|
+
max_angle = angle
|
119
|
+
|
120
|
+
return max_angle
|
121
|
+
|
89
122
|
def rotate_network(
|
90
123
|
segment_thickness_dict: dict[str, Polygon],
|
91
124
|
rotate_angle: float,
|
@@ -137,7 +170,7 @@ def rotate_network(
|
|
137
170
|
middle_segment_new = LineSegment(start=start, end=end)
|
138
171
|
|
139
172
|
# Store the rotated segment in the new dictionary
|
140
|
-
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
173
|
+
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
|
141
174
|
|
142
175
|
return segment_thickness_dict_new
|
143
176
|
|
@@ -204,7 +237,7 @@ def clip_network(
|
|
204
237
|
middle_segment_new = LineSegment(start=start, end=end)
|
205
238
|
|
206
239
|
# Create a new clipped polygon with updated vertices and middle segment
|
207
|
-
pol_new = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
240
|
+
pol_new = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
|
208
241
|
pol_new.sort_vertices() # Ensure vertices are sorted
|
209
242
|
segment_thickness_dict_new[id] = pol_new
|
210
243
|
|
@@ -252,78 +285,16 @@ def translate_network(
|
|
252
285
|
middle_segment_new = LineSegment(start=start, end=end)
|
253
286
|
|
254
287
|
# Store the translated segment in the new dictionary
|
255
|
-
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new)
|
288
|
+
segment_thickness_dict_new[id] = Polygon(vertices=vertices_new, middle_segment=middle_segment_new, neighbors=segment.neighbors)
|
256
289
|
|
257
290
|
return segment_thickness_dict_new
|
258
291
|
|
259
|
-
def
|
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(
|
292
|
+
def orientate_network(
|
323
293
|
data_dict: Dict[str, dict],
|
324
294
|
orientation: List[int],
|
325
295
|
grid_points: int = 360,
|
326
|
-
box_measurements: List[Tuple[float, float]] = [(0, 0), (0, 1), (1, 1), (1, 0)]
|
296
|
+
box_measurements: List[Tuple[float, float]] = [(0, 0), (0, 1), (1, 1), (1, 0)],
|
297
|
+
director: np.ndarray = np.array([0, 1])
|
327
298
|
) -> List[Dict[str, dict]]:
|
328
299
|
"""
|
329
300
|
Generates a set of networks of line segments with different thicknesses and orientations, and clips them to fit
|
@@ -354,36 +325,33 @@ def generate_line_segments_thickness_orientation(
|
|
354
325
|
|
355
326
|
# Extract the segment thickness dictionary from the input data
|
356
327
|
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
328
|
|
361
329
|
# Find the angle that aligns the network most with the y-axis
|
362
|
-
max_angle =
|
363
|
-
|
330
|
+
max_angle = get_max_alignment(segment_thickness_dict, director, box_measurements, grid_points)
|
331
|
+
|
364
332
|
# Store the initial unmodified configuration
|
365
333
|
output = [{'orientation': 'original', 'data_dict': data_dict}]
|
366
334
|
|
367
335
|
# Loop through each given orientation, rotate, clip, and translate the network
|
368
336
|
for o in orientation:
|
369
337
|
# Compute the rotation angle for the current orientation relative to max alignment
|
370
|
-
rotate_angle =
|
338
|
+
rotate_angle = -max_angle + o
|
371
339
|
|
372
340
|
# Rotate the network by the computed angle
|
373
|
-
|
341
|
+
segment_thickness_dict_rotated = rotate_network(segment_thickness_dict, rotate_angle=rotate_angle, box_center=box_center)
|
374
342
|
|
375
343
|
# Clip the rotated network to fit within the bounding box
|
376
|
-
|
344
|
+
segment_thickness_dict_clipped = clip_network(segment_thickness_dict_rotated, box_measurements=box_measurements)
|
377
345
|
|
378
346
|
# Translate the clipped network to start at the origin (0,0)
|
379
347
|
translation_vector = -np.array(box_measurements[0])
|
380
|
-
|
348
|
+
segment_thickness_dict_translated = translate_network(segment_thickness_dict_clipped, translation_vector)
|
381
349
|
|
382
350
|
# Prepare a new data dictionary with the transformed segment information
|
383
351
|
data_dict_new = {
|
384
352
|
'segments_dict': None,
|
385
353
|
'polygon_arr': None,
|
386
|
-
'segment_thickness_dict':
|
354
|
+
'segment_thickness_dict': segment_thickness_dict_translated,
|
387
355
|
'jammed': None,
|
388
356
|
'generated_config': None
|
389
357
|
}
|
File without changes
|
File without changes
|