topologicpy 0.7.69__py3-none-any.whl → 0.7.71__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- topologicpy/Wire.py +219 -2
- topologicpy/version.py +1 -1
- {topologicpy-0.7.69.dist-info → topologicpy-0.7.71.dist-info}/METADATA +1 -1
- {topologicpy-0.7.69.dist-info → topologicpy-0.7.71.dist-info}/RECORD +7 -7
- {topologicpy-0.7.69.dist-info → topologicpy-0.7.71.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.69.dist-info → topologicpy-0.7.71.dist-info}/WHEEL +0 -0
- {topologicpy-0.7.69.dist-info → topologicpy-0.7.71.dist-info}/top_level.txt +0 -0
topologicpy/Wire.py
CHANGED
@@ -1076,6 +1076,223 @@ class Wire():
|
|
1076
1076
|
new_wire = Topology.SelfMerge(Topology.ByGeometry(vertices=g_vertices, edges=g_edges, faces=[]))
|
1077
1077
|
return new_wire
|
1078
1078
|
|
1079
|
+
|
1080
|
+
|
1081
|
+
@staticmethod
|
1082
|
+
def ConcaveHull(topology, k: int = 3, mantissa: int = 6, tolerance: float = 0.0001):
|
1083
|
+
"""
|
1084
|
+
Returns a wire representing the 2D concave hull of the input topology. The vertices of the topology are assumed to be coplanar.
|
1085
|
+
Code based on Moreira, A and Santos, M Y, "CONCAVE HULL: A K-NEAREST NEIGHBOURS APPROACH FOR THE COMPUTATION OF THE REGION OCCUPIED BY A SET OF POINTS"
|
1086
|
+
GRAPP 2007 - International Conference on Computer Graphics Theory and Applications.
|
1087
|
+
|
1088
|
+
Parameters
|
1089
|
+
----------
|
1090
|
+
topology : topologic_core.Topology
|
1091
|
+
The input topology.
|
1092
|
+
k : int, optional
|
1093
|
+
The number of nearest neighbors to consider for each point when building the hull.
|
1094
|
+
Must be at least 3 for the algorithm to function correctly. Increasing `k` will produce a smoother,
|
1095
|
+
less concave hull, while decreasing `k` may yield a more detailed, concave shape. The default is 3.
|
1096
|
+
mantissa : int , optional
|
1097
|
+
The desired length of the mantissa. The default is 6.
|
1098
|
+
tolerance : float , optional
|
1099
|
+
The desired tolerance. The default is 0.0001.
|
1100
|
+
|
1101
|
+
Returns
|
1102
|
+
-------
|
1103
|
+
topologic_core.Wire
|
1104
|
+
The concave hull of the input topology.
|
1105
|
+
"""
|
1106
|
+
|
1107
|
+
from topologicpy.Vertex import Vertex
|
1108
|
+
from topologicpy.Face import Face
|
1109
|
+
from topologicpy.Topology import Topology
|
1110
|
+
from math import atan2, sqrt, pi
|
1111
|
+
from random import sample
|
1112
|
+
|
1113
|
+
# Helper function to clean the list by removing duplicate points
|
1114
|
+
def clean_list(points_list):
|
1115
|
+
return list(set(points_list))
|
1116
|
+
|
1117
|
+
# Helper function to find the point with the minimum Y-coordinate
|
1118
|
+
def find_min_y_point(points):
|
1119
|
+
return min(points, key=lambda p: [p[1], p[0]])
|
1120
|
+
|
1121
|
+
# Helper function to find the k-nearest neighbors to a given point
|
1122
|
+
def nearest_points(points, reference_point, k):
|
1123
|
+
# Sort points by distance from the reference point and select the first k points
|
1124
|
+
sorted_points = sorted(points, key=lambda p: sqrt((p[0] - reference_point[0]) ** 2 + (p[1] - reference_point[1]) ** 2))
|
1125
|
+
return sorted_points[:k]
|
1126
|
+
|
1127
|
+
# Helper function to sort points by the angle relative to the previous direction
|
1128
|
+
def sort_by_angle(points, current_point, prev_angle):
|
1129
|
+
def angle_to(p):
|
1130
|
+
angle = atan2(p[1] - current_point[1], p[0] - current_point[0])
|
1131
|
+
angle_diff = (angle - prev_angle + 2 * pi) % (2 * pi)
|
1132
|
+
return angle_diff
|
1133
|
+
return sorted(points, key=angle_to)
|
1134
|
+
|
1135
|
+
# Helper function to check if two line segments intersect
|
1136
|
+
def intersects_q(line1, line2):
|
1137
|
+
def orientation(p, q, r):
|
1138
|
+
val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1])
|
1139
|
+
if val == 0: return 0
|
1140
|
+
return 1 if val > 0 else 2
|
1141
|
+
|
1142
|
+
p1, q1 = line1
|
1143
|
+
p2, q2 = line2
|
1144
|
+
o1 = orientation(p1, q1, p2)
|
1145
|
+
o2 = orientation(p1, q1, q2)
|
1146
|
+
o3 = orientation(p2, q2, p1)
|
1147
|
+
o4 = orientation(p2, q2, q1)
|
1148
|
+
|
1149
|
+
if o1 != o2 and o3 != o4:
|
1150
|
+
return True
|
1151
|
+
if o1 == 0 and on_segment(p1, p2, q1): return True
|
1152
|
+
if o2 == 0 and on_segment(p1, q2, q1): return True
|
1153
|
+
if o3 == 0 and on_segment(p2, p1, q2): return True
|
1154
|
+
if o4 == 0 and on_segment(p2, q1, q2): return True
|
1155
|
+
return False
|
1156
|
+
|
1157
|
+
# Helper function to check if point q lies on segment pr
|
1158
|
+
def on_segment(p, q, r):
|
1159
|
+
return (q[0] <= max(p[0], r[0]) and q[0] >= min(p[0], r[0]) and
|
1160
|
+
q[1] <= max(p[1], r[1]) and q[1] >= min(p[1], r[1]))
|
1161
|
+
|
1162
|
+
# Helper function to calculate the angle between two points
|
1163
|
+
def angle(p1, p2):
|
1164
|
+
return atan2(p2[1] - p1[1], p2[0] - p1[0])
|
1165
|
+
|
1166
|
+
# Helper function to determine if a point is inside a polygon (Ray Casting method)
|
1167
|
+
def point_in_polygon_q(point, polygon):
|
1168
|
+
x, y = point
|
1169
|
+
inside = False
|
1170
|
+
n = len(polygon)
|
1171
|
+
p1x, p1y = polygon[0]
|
1172
|
+
for i in range(1, n + 1):
|
1173
|
+
p2x, p2y = polygon[i % n]
|
1174
|
+
if min(p1y, p2y) < y <= max(p1y, p2y) and x <= max(p1x, p2x):
|
1175
|
+
if p1y != p2y:
|
1176
|
+
xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
1177
|
+
if p1x == p2x or x <= xinters:
|
1178
|
+
inside = not inside
|
1179
|
+
p1x, p1y = p2x, p2y
|
1180
|
+
return inside
|
1181
|
+
|
1182
|
+
def concave_hull(points_list, k: int = 3):
|
1183
|
+
# Ensure k >= 3
|
1184
|
+
kk = max(k, 3)
|
1185
|
+
|
1186
|
+
# Remove duplicate points
|
1187
|
+
dataset = clean_list(points_list)
|
1188
|
+
|
1189
|
+
# If there are fewer than 3 unique points, no polygon can be formed
|
1190
|
+
if len(dataset) < 3:
|
1191
|
+
return None
|
1192
|
+
elif len(dataset) == 3:
|
1193
|
+
return dataset # If exactly 3 points, they form the polygon
|
1194
|
+
|
1195
|
+
# Ensure we have enough neighbors
|
1196
|
+
kk = min(kk, len(dataset) - 1)
|
1197
|
+
|
1198
|
+
# Find starting point (minimum Y value) and initialize hull
|
1199
|
+
first_point = find_min_y_point(dataset)
|
1200
|
+
hull = [first_point]
|
1201
|
+
current_point = first_point
|
1202
|
+
dataset.remove(first_point)
|
1203
|
+
prev_angle = 0
|
1204
|
+
step = 2
|
1205
|
+
|
1206
|
+
# Original code logic, with an update to calculate prev_angle
|
1207
|
+
while (current_point != first_point or step == 2) and len(dataset) > 0:
|
1208
|
+
# After 4 steps, re-add the starting point to check for closure
|
1209
|
+
if step == 5:
|
1210
|
+
dataset.append(first_point)
|
1211
|
+
|
1212
|
+
# Find the k-nearest points
|
1213
|
+
k_nearest_points = nearest_points(dataset, current_point, kk)
|
1214
|
+
|
1215
|
+
# Sort candidates based on angle
|
1216
|
+
c_points = sort_by_angle(k_nearest_points, current_point, prev_angle)
|
1217
|
+
|
1218
|
+
intersection_found = True
|
1219
|
+
i = 0
|
1220
|
+
|
1221
|
+
# Select the first candidate that does not intersect any polygon edges
|
1222
|
+
while intersection_found and i < len(c_points):
|
1223
|
+
candidate_point = c_points[i]
|
1224
|
+
i += 1
|
1225
|
+
|
1226
|
+
if candidate_point == first_point:
|
1227
|
+
last_point_check = 1
|
1228
|
+
else:
|
1229
|
+
last_point_check = 0
|
1230
|
+
|
1231
|
+
# Check for intersections with the existing edges
|
1232
|
+
j = 2
|
1233
|
+
intersection_found = False
|
1234
|
+
while not intersection_found and j < len(hull) - last_point_check:
|
1235
|
+
# Using hull[-1] and hull[-2] for last and second-to-last points
|
1236
|
+
intersection_found = intersects_q(
|
1237
|
+
(hull[-1], candidate_point),
|
1238
|
+
(hull[-1 - j], hull[-j])
|
1239
|
+
)
|
1240
|
+
j += 1
|
1241
|
+
|
1242
|
+
# If all candidates intersect, retry with a higher number of neighbors
|
1243
|
+
if intersection_found:
|
1244
|
+
return concave_hull(points_list, kk + 1)
|
1245
|
+
|
1246
|
+
# Update the hull with the selected candidate point
|
1247
|
+
current_point = candidate_point
|
1248
|
+
hull.append(current_point)
|
1249
|
+
|
1250
|
+
# Calculate the angle between the last two points in the hull to set `prev_angle`
|
1251
|
+
if len(hull) > 1:
|
1252
|
+
prev_angle = angle(hull[-1], hull[-2])
|
1253
|
+
|
1254
|
+
dataset.remove(current_point)
|
1255
|
+
step += 1
|
1256
|
+
|
1257
|
+
|
1258
|
+
# Check if all points are inside the constructed hull
|
1259
|
+
all_inside = True
|
1260
|
+
i = len(dataset) - 1
|
1261
|
+
while all_inside and i >= 0:
|
1262
|
+
all_inside = point_in_polygon_q(dataset[i], hull)
|
1263
|
+
i -= 1
|
1264
|
+
|
1265
|
+
# If any points are outside the hull, retry with a higher number of neighbors
|
1266
|
+
if not all_inside:
|
1267
|
+
return concave_hull(points_list, kk + 1)
|
1268
|
+
|
1269
|
+
# Return the completed hull if all points are inside
|
1270
|
+
return hull
|
1271
|
+
|
1272
|
+
f = None
|
1273
|
+
# Create a sample face and flatten
|
1274
|
+
while not Topology.IsInstance(f, "Face"):
|
1275
|
+
vertices = Topology.SubTopologies(topology=topology, subTopologyType="vertex")
|
1276
|
+
v = sample(vertices, 3)
|
1277
|
+
w = Wire.ByVertices(v)
|
1278
|
+
f = Face.ByWire(w, tolerance=tolerance, silent=True)
|
1279
|
+
if not f == None:
|
1280
|
+
origin = Topology.Centroid(f)
|
1281
|
+
normal = Face.Normal(f, mantissa=mantissa)
|
1282
|
+
f = Topology.Flatten(f, origin=origin, direction=normal)
|
1283
|
+
flat_topology = Topology.Flatten(topology, origin=origin, direction=normal)
|
1284
|
+
vertices = Topology.Vertices(flat_topology)
|
1285
|
+
points = []
|
1286
|
+
for v in vertices:
|
1287
|
+
points.append((Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa)))
|
1288
|
+
hull = concave_hull(points, k=k)
|
1289
|
+
hull_vertices = []
|
1290
|
+
for p in hull:
|
1291
|
+
hull_vertices.append(Vertex.ByCoordinates(p[0], p[1], 0))
|
1292
|
+
ch = Wire.ByVertices(hull_vertices, close=True)
|
1293
|
+
ch = Topology.Unflatten(ch, origin=origin, direction=normal)
|
1294
|
+
return ch
|
1295
|
+
|
1079
1296
|
@staticmethod
|
1080
1297
|
def ConvexHull(topology, mantissa: int = 6, tolerance: float = 0.0001):
|
1081
1298
|
"""
|
@@ -1197,8 +1414,8 @@ class Wire():
|
|
1197
1414
|
origin = Topology.Centroid(f)
|
1198
1415
|
normal = Face.Normal(f, mantissa=mantissa)
|
1199
1416
|
f = Topology.Flatten(f, origin=origin, direction=normal)
|
1200
|
-
|
1201
|
-
vertices = Topology.Vertices(
|
1417
|
+
flat_topology = Topology.Flatten(topology, origin=origin, direction=normal)
|
1418
|
+
vertices = Topology.Vertices(flat_topology)
|
1202
1419
|
points = []
|
1203
1420
|
for v in vertices:
|
1204
1421
|
points.append((Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa)))
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.7.
|
1
|
+
__version__ = '0.7.71'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.71
|
4
4
|
Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
|
5
5
|
Author-email: Wassim Jabi <wassim.jabi@gmail.com>
|
6
6
|
License: AGPL v3 License
|
@@ -26,11 +26,11 @@ topologicpy/Sun.py,sha256=42tDWMYpwRG7Z2Qjtp94eRgBuqySq7k8TgNUZDK7QxQ,36837
|
|
26
26
|
topologicpy/Topology.py,sha256=9DPTumt9kr9Su5b3RE8l_gLQSRalYHqf79tVHiy_aQM,400055
|
27
27
|
topologicpy/Vector.py,sha256=A1g83zDHep58iVPY8WQ8iHNrSOfGWFEzvVeDuMnjDNY,33078
|
28
28
|
topologicpy/Vertex.py,sha256=ZS6xK89JKokBKc0W8frdRhhuzR8c-dI1TTLt7pTf1iA,71032
|
29
|
-
topologicpy/Wire.py,sha256=
|
29
|
+
topologicpy/Wire.py,sha256=Ci2RSYCL4JPEBDKjP9B-yFfxI73TnPVxY_1eDzy1Iog,182073
|
30
30
|
topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
|
31
|
-
topologicpy/version.py,sha256=
|
32
|
-
topologicpy-0.7.
|
33
|
-
topologicpy-0.7.
|
34
|
-
topologicpy-0.7.
|
35
|
-
topologicpy-0.7.
|
36
|
-
topologicpy-0.7.
|
31
|
+
topologicpy/version.py,sha256=szrQeCdVCeZvANqYLZIpNzGjG1Hr4s6VgJvFSRkblPk,23
|
32
|
+
topologicpy-0.7.71.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
33
|
+
topologicpy-0.7.71.dist-info/METADATA,sha256=X-lNLlqlnoe8kwm3_KNuE6y5MzmnSDTxTHcKplvOlTw,10493
|
34
|
+
topologicpy-0.7.71.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
35
|
+
topologicpy-0.7.71.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
36
|
+
topologicpy-0.7.71.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|