RDG-Networks 0.3.2__tar.gz → 0.3.5__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/PKG-INFO +1 -1
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_Networks.egg-info/PKG-INFO +1 -1
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_Networks.egg-info/SOURCES.txt +3 -2
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_Networks.egg-info/entry_points.txt +3 -1
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/__init__.py +9 -5
- rdg_networks-0.3.5/RDG_networks/save_to_stl.py +105 -0
- rdg_networks-0.3.5/RDG_networks/thickness/__init__.py +9 -0
- rdg_networks-0.3.5/RDG_networks/thickness/generate_line_segments_thickness_orientation.py +180 -0
- rdg_networks-0.3.2/RDG_networks/thickness/generate_line_segments_dynamic_thickness.py → rdg_networks-0.3.5/RDG_networks/thickness/ggenerate_line_segments_thickness.py +117 -50
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/setup.py +7 -5
- rdg_networks-0.3.2/RDG_networks/thickness/__init__.py +0 -7
- rdg_networks-0.3.2/RDG_networks/thickness/functions.py +0 -255
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/LICENSE.txt +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_Networks.egg-info/dependency_links.txt +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_Networks.egg-info/requires.txt +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_Networks.egg-info/top_level.txt +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/Classes.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/draw_segments.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/generate_line_network.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/generate_line_segments.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/generate_line_segments_dynamic.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/generate_line_segments_static.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/get_intersection_segments.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/sample_in_polygon.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/thickness/Classes.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/RDG_networks/thickness/sample_in_polygon.py +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/README.md +0 -0
- {rdg_networks-0.3.2 → rdg_networks-0.3.5}/setup.cfg +0 -0
@@ -16,8 +16,9 @@ RDG_networks/generate_line_segments_dynamic.py
|
|
16
16
|
RDG_networks/generate_line_segments_static.py
|
17
17
|
RDG_networks/get_intersection_segments.py
|
18
18
|
RDG_networks/sample_in_polygon.py
|
19
|
+
RDG_networks/save_to_stl.py
|
19
20
|
RDG_networks/thickness/Classes.py
|
20
21
|
RDG_networks/thickness/__init__.py
|
21
|
-
RDG_networks/thickness/
|
22
|
-
RDG_networks/thickness/
|
22
|
+
RDG_networks/thickness/generate_line_segments_thickness_orientation.py
|
23
|
+
RDG_networks/thickness/ggenerate_line_segments_thickness.py
|
23
24
|
RDG_networks/thickness/sample_in_polygon.py
|
@@ -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
|
-
|
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
|
@@ -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.
|
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
|
-
'
|
14
|
-
'
|
15
|
+
'generate_line_segments_thickness',
|
16
|
+
'generate_line_segments_thickness_orientation',
|
15
17
|
'generate_line_segments_dynamic',
|
16
18
|
'generate_line_segments_static',
|
17
|
-
'
|
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)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# __init__.py
|
2
|
+
|
3
|
+
from .generate_line_segments_thickness import generate_line_segments_thickness
|
4
|
+
from .generate_line_segments_thickness_orientation import generate_line_segments_thickness_orientation
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
'generate_line_segments_thickness',
|
8
|
+
'generate_line_segments_thickness_orientation'
|
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
|
-
|
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:
|
44
|
-
segment2:
|
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
|
-
|
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
|
-
|
175
|
-
direction =
|
176
|
-
direction = np.array(direction) / np.linalg.norm(direction)
|
188
|
+
angle_new = random.choice(angles)
|
189
|
+
direction = (np.cos(angle_new), np.sin(angle_new))
|
190
|
+
direction = np.array(direction) / np.linalg.norm(direction)
|
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
|
-
|
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
|
196
|
-
p2 = np.array(location_new) - thickness
|
213
|
+
p1 = np.array(location_new) + (thickness/2 + min_distance) * perpendicular
|
214
|
+
p2 = np.array(location_new) - (thickness/2 + min_distance) * perpendicular
|
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[
|
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
|
-
|
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
|
-
|
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,
|
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,
|
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,
|
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,
|
451
|
+
segments_dict: Dict[int, LineSegment],
|
431
452
|
polygon_arr: Dict[str, Dict[str, object]],
|
432
|
-
segment_thickness_dict: Dict[int,
|
433
|
-
|
434
|
-
|
435
|
-
|
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=
|
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
|
549
|
+
def generate_line_segments_thickness(
|
520
550
|
size: int,
|
521
551
|
thickness_arr: List[float],
|
522
|
-
angles: str = 'uniform'
|
523
|
-
|
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((
|
542
|
-
LineSegment((0,
|
543
|
-
LineSegment((0,
|
544
|
-
LineSegment((
|
579
|
+
LineSegment((box_size, 0), (0, 0), id='b1', neighbors_initial={'b2': (0, 0), 'b4': (box_size, 0)}, neighbors={'b2': (0, 0), 'b4': (box_size, 0)}),
|
580
|
+
LineSegment((0, box_size), (0, 0), id='b2', neighbors_initial={'b1': (0, 0), 'b3': (0, box_size)}, neighbors={'b1': (0, 0), 'b3': (0, box_size)}),
|
581
|
+
LineSegment((0, box_size), (box_size, box_size), id='b3', neighbors_initial={'b2': (0, box_size), 'b4': (box_size, box_size)}, neighbors={'b2': (0, box_size), 'b4': (box_size, box_size)}),
|
582
|
+
LineSegment((box_size, box_size), (box_size, 0), id='b4', neighbors_initial={'b1': (box_size, 0), 'b3': (box_size, box_size)}, neighbors={'b1': (box_size, 0), 'b3': (box_size, box_size)})
|
545
583
|
]
|
546
584
|
|
547
585
|
polygon_arr = {
|
548
586
|
'p1': {
|
549
|
-
'vertices': [(0, 0), (0,
|
550
|
-
'area':
|
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
|
-
|
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
|
-
|
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 {
|
566
|
-
|
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
|
-
|
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
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='RDG-Networks',
|
5
|
-
version='0.3.
|
5
|
+
version='0.3.5',
|
6
6
|
author='Niek Mooij',
|
7
7
|
author_email='mooij.niek@gmail.com',
|
8
8
|
description='Most of the code from the RDG Networks project',
|
@@ -24,12 +24,14 @@ setup(
|
|
24
24
|
entry_points={
|
25
25
|
'console_scripts': [
|
26
26
|
'generate_line_segments=RDG_networks.generate_line_segments:main',
|
27
|
-
'
|
28
|
-
'
|
27
|
+
'generate_line_segments_thickness=RDG_networks.thickness.generate_line_segments_thickness:main',
|
28
|
+
'generate_line_segments_dynamic_orientation=RDG_networks.generate_line_segments_dynamic_orientation:main',
|
29
29
|
'generate_line_segments_dynamic=RDG_networks.generate_line_segments_dynamic:main',
|
30
30
|
'generate_line_segments_static=RDG_networks.generate_line_segments_static:main',
|
31
|
-
'
|
32
|
-
'
|
31
|
+
'generate_line_network=RDG_networks.generate_line_network:main',
|
32
|
+
'get_intersection_segments=RDG_networks.get_intersection_segments:main',
|
33
|
+
'draw_segments=RDG_networks.draw_segments:main',
|
34
|
+
'save_to_stl=RDG_networks.save_to_stl:main'
|
33
35
|
],
|
34
36
|
},
|
35
37
|
)
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|