topologicpy 0.5.9__py3-none-any.whl → 6.0.0__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/Aperture.py +72 -72
- topologicpy/Cell.py +2169 -2169
- topologicpy/CellComplex.py +1137 -1137
- topologicpy/Cluster.py +1288 -1280
- topologicpy/Color.py +423 -423
- topologicpy/Context.py +79 -79
- topologicpy/DGL.py +3213 -3240
- topologicpy/Dictionary.py +698 -698
- topologicpy/Edge.py +1187 -1187
- topologicpy/EnergyModel.py +1180 -1152
- topologicpy/Face.py +2141 -2141
- topologicpy/Graph.py +7768 -7768
- topologicpy/Grid.py +353 -353
- topologicpy/Helper.py +507 -507
- topologicpy/Honeybee.py +461 -461
- topologicpy/Matrix.py +271 -271
- topologicpy/Neo4j.py +521 -521
- topologicpy/Plotly.py +2 -2
- topologicpy/Polyskel.py +541 -541
- topologicpy/Shell.py +1768 -1768
- topologicpy/Speckle.py +508 -508
- topologicpy/Topology.py +7060 -7002
- topologicpy/Vector.py +905 -905
- topologicpy/Vertex.py +1585 -1585
- topologicpy/Wire.py +3050 -3050
- topologicpy/__init__.py +22 -38
- topologicpy/version.py +1 -0
- {topologicpy-0.5.9.dist-info → topologicpy-6.0.0.dist-info}/LICENSE +661 -704
- topologicpy-6.0.0.dist-info/METADATA +751 -0
- topologicpy-6.0.0.dist-info/RECORD +32 -0
- topologicpy/bin/linux/topologic/__init__.py +0 -2
- topologicpy/bin/linux/topologic/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic/libstdc++-672d7b41.so.6.0.30 +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
- topologicpy/bin/macos/topologic/__init__.py +0 -2
- topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
- topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
- topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
- topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
- topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
- topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
- topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
- topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
- topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
- topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
- topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
- topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
- topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
- topologicpy/bin/windows/topologic/__init__.py +0 -2
- topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
- topologicpy-0.5.9.dist-info/METADATA +0 -86
- topologicpy-0.5.9.dist-info/RECORD +0 -91
- {topologicpy-0.5.9.dist-info → topologicpy-6.0.0.dist-info}/WHEEL +0 -0
- {topologicpy-0.5.9.dist-info → topologicpy-6.0.0.dist-info}/top_level.txt +0 -0
topologicpy/Cluster.py
CHANGED
@@ -1,1281 +1,1289 @@
|
|
1
|
-
# Copyright (C) 2024
|
2
|
-
# Wassim Jabi <wassim.jabi@gmail.com>
|
3
|
-
#
|
4
|
-
# This program is free software: you can redistribute it and/or modify it under
|
5
|
-
# the terms of the GNU Affero General Public License as published by the Free Software
|
6
|
-
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
-
# version.
|
8
|
-
#
|
9
|
-
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
-
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
-
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
12
|
-
# details.
|
13
|
-
#
|
14
|
-
# You should have received a copy of the GNU Affero General Public License along with
|
15
|
-
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
from topologicpy.Topology import Topology
|
18
|
-
import topologic
|
19
|
-
import os
|
20
|
-
import warnings
|
21
|
-
|
22
|
-
try:
|
23
|
-
import numpy as np
|
24
|
-
except:
|
25
|
-
print("Cluster - Installing required numpy library.")
|
26
|
-
try:
|
27
|
-
os.system("pip install numpy")
|
28
|
-
except:
|
29
|
-
os.system("pip install numpy --user")
|
30
|
-
try:
|
31
|
-
import numpy as np
|
32
|
-
print("Cluster - numpy library installed correctly.")
|
33
|
-
except:
|
34
|
-
warnings.warn("Cluster - Error: Could not import numpy.")
|
35
|
-
|
36
|
-
try:
|
37
|
-
from scipy.spatial.distance import pdist, squareform
|
38
|
-
except:
|
39
|
-
print("Cluster - Installing required scipy library.")
|
40
|
-
try:
|
41
|
-
os.system("pip install scipy")
|
42
|
-
except:
|
43
|
-
os.system("pip install scipy --user")
|
44
|
-
try:
|
45
|
-
from scipy.spatial.distance import pdist, squareform
|
46
|
-
print("Cluster - scipy library installed correctly.")
|
47
|
-
except:
|
48
|
-
warnings.warn("Cluster - Error: Could not import scipy.")
|
49
|
-
|
50
|
-
class Cluster(Topology):
|
51
|
-
@staticmethod
|
52
|
-
def ByFormula(formula, xRange=None, yRange=None, xString="X", yString="Y"):
|
53
|
-
"""
|
54
|
-
Creates a cluster of vertices by
|
55
|
-
|
56
|
-
Parameters
|
57
|
-
----------
|
58
|
-
formula : str
|
59
|
-
A string representing the formula to be evaluated.
|
60
|
-
For 2D formulas
|
61
|
-
|
62
|
-
For
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
if not
|
103
|
-
|
104
|
-
|
105
|
-
while
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
y_return.append(
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
The
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
labels
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
if
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
if
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
if
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
The
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
for
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
if
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
if
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
if
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
return
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
return
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
for
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
The
|
1102
|
-
|
1103
|
-
The
|
1104
|
-
|
1105
|
-
The
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
_ = cluster.
|
1188
|
-
|
1189
|
-
_ = cluster.
|
1190
|
-
|
1191
|
-
_ = cluster.
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1
|
+
# Copyright (C) 2024
|
2
|
+
# Wassim Jabi <wassim.jabi@gmail.com>
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify it under
|
5
|
+
# the terms of the GNU Affero General Public License as published by the Free Software
|
6
|
+
# Foundation, either version 3 of the License, or (at your option) any later
|
7
|
+
# version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
12
|
+
# details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Affero General Public License along with
|
15
|
+
# this program. If not, see <https://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
from topologicpy.Topology import Topology
|
18
|
+
import topologic_core as topologic
|
19
|
+
import os
|
20
|
+
import warnings
|
21
|
+
|
22
|
+
try:
|
23
|
+
import numpy as np
|
24
|
+
except:
|
25
|
+
print("Cluster - Installing required numpy library.")
|
26
|
+
try:
|
27
|
+
os.system("pip install numpy")
|
28
|
+
except:
|
29
|
+
os.system("pip install numpy --user")
|
30
|
+
try:
|
31
|
+
import numpy as np
|
32
|
+
print("Cluster - numpy library installed correctly.")
|
33
|
+
except:
|
34
|
+
warnings.warn("Cluster - Error: Could not import numpy.")
|
35
|
+
|
36
|
+
try:
|
37
|
+
from scipy.spatial.distance import pdist, squareform
|
38
|
+
except:
|
39
|
+
print("Cluster - Installing required scipy library.")
|
40
|
+
try:
|
41
|
+
os.system("pip install scipy")
|
42
|
+
except:
|
43
|
+
os.system("pip install scipy --user")
|
44
|
+
try:
|
45
|
+
from scipy.spatial.distance import pdist, squareform
|
46
|
+
print("Cluster - scipy library installed correctly.")
|
47
|
+
except:
|
48
|
+
warnings.warn("Cluster - Error: Could not import scipy.")
|
49
|
+
|
50
|
+
class Cluster(Topology):
|
51
|
+
@staticmethod
|
52
|
+
def ByFormula(formula, xRange=None, yRange=None, xString="X", yString="Y"):
|
53
|
+
"""
|
54
|
+
Creates a cluster of vertices by evaluating the input formula for a range of x values and, optionally, a range of y values.
|
55
|
+
|
56
|
+
Parameters
|
57
|
+
----------
|
58
|
+
formula : str
|
59
|
+
A string representing the formula to be evaluated.
|
60
|
+
For 2D formulas (i.e. Z = 0), use either 'X' (uppercase) or 'Y' (uppercase) for the independent variable.
|
61
|
+
For 3D formulas, use 'X' and 'Y' (uppercase) for the independent variables. The Z value will be evaluated.
|
62
|
+
For 3D formulas, both xRange and yRange MUST be specified.
|
63
|
+
You can use standard math functions like 'sin', 'cos', 'tan', 'sqrt', etc.
|
64
|
+
For example, 'X**2 + 2*X - sqrt(X)' or 'cos(abs(X)+abs(Y))'
|
65
|
+
xRange : tuple , optional
|
66
|
+
A tuple (start, end, step) representing the range of X values for which the formula should be evaluated.
|
67
|
+
For example, to evaluate Y for X values from -5 to 5 with a step of 0.1, you should specify xRange=(-5, 5, 0.1).
|
68
|
+
If the xRange is set to None or not specified:
|
69
|
+
The method assumes that the formula uses the yString (e.g. 'Y' as in 'Y**2 + 2*Y - sqrt(Y)')
|
70
|
+
The method will attempt to evaluate X based on the specified yRange.
|
71
|
+
xRange and yRange CANNOT be None or unspecified at the same time. One or the other must be specified.
|
72
|
+
yRange : tuple , optional
|
73
|
+
A tuple (start, end, step) representing the range of Y values for which the formula should be evaluated.
|
74
|
+
For example, to evaluate X for Y values from -5 to 5 with a step of 0.1, you should specify yRange=(-5,5,0.1).
|
75
|
+
If the yRange is set to None or not specified:
|
76
|
+
The method assumes that the formula uses the xString (e.g. 'X' as in 'X**2 + 2*X - sqrt(X)')
|
77
|
+
The method will attempt to evaluate Y based on the specified xRange.
|
78
|
+
xRange and yRange CANNOT be None or unspecified at the same time. One or the other must be specified.
|
79
|
+
xString : str , optional
|
80
|
+
The string used to represent the X independent variable. The default is 'X' (uppercase).
|
81
|
+
yString : str , optional
|
82
|
+
The string used to represent the Y independent variable. The default is 'Y' (uppercase).
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
topologic.Cluster
|
86
|
+
The created cluster of vertices.
|
87
|
+
"""
|
88
|
+
from topologicpy.Vertex import Vertex
|
89
|
+
import math
|
90
|
+
if xRange == None and yRange == None:
|
91
|
+
print("Cluster.ByFormula - Error: Both ranges cannot be None at the same time. Returning None.")
|
92
|
+
return None
|
93
|
+
if xString.islower():
|
94
|
+
print("Cluster.ByFormula - Error: the input xString cannot lowercase. Please consider using uppercase (e.g. X). Returning None.")
|
95
|
+
return None
|
96
|
+
if yString == 'y':
|
97
|
+
print("Cluster.ByFormula - Error: the input yString cannot be lowercase. Please consider using uppercase (e.g. Y). Returning None.")
|
98
|
+
return None
|
99
|
+
|
100
|
+
x_values = []
|
101
|
+
y_values = []
|
102
|
+
if not xRange == None:
|
103
|
+
x_start, x_end, x_step = xRange
|
104
|
+
x = x_start
|
105
|
+
while x < x_end:
|
106
|
+
x_values.append(x)
|
107
|
+
x = x + x_step
|
108
|
+
x_values.append(x_end)
|
109
|
+
|
110
|
+
if not yRange == None:
|
111
|
+
y_start, y_end, y_step = yRange
|
112
|
+
y = y_start
|
113
|
+
while y < y_end:
|
114
|
+
y_values.append(y)
|
115
|
+
y = y + y_step
|
116
|
+
y_values.append(y_end)
|
117
|
+
|
118
|
+
# Evaluate the formula for each x and y value
|
119
|
+
x_return = []
|
120
|
+
y_return = []
|
121
|
+
z_return = []
|
122
|
+
if len(x_values) > 0 and len(y_values) > 0: # Both X and Y exist, compute Z.
|
123
|
+
for x in x_values:
|
124
|
+
for y in y_values:
|
125
|
+
x_return.append(x)
|
126
|
+
y_return.append(y)
|
127
|
+
formula1 = formula.replace(xString, str(x)).replace(yString, str(y)).replace('sqrt', 'math.sqrt').replace('sin', 'math.sin').replace('cos', 'math.cos').replace('tan', 'math.tan').replace('radians', 'math.radians').replace('pi', 'math.pi')
|
128
|
+
z_return.append(eval(formula1))
|
129
|
+
elif len(x_values) == 0 and len(y_values) > 0: # Only Y exists, compute X, Z is always 0.
|
130
|
+
for y in y_values:
|
131
|
+
y_return.append(y)
|
132
|
+
formula1 = formula.replace(xString, str(y)).replace('sqrt', 'math.sqrt').replace('sin', 'math.sin').replace('cos', 'math.cos').replace('tan', 'math.tan').replace('radians', 'math.radians').replace('pi', 'math.pi')
|
133
|
+
x_return.append(eval(formula1))
|
134
|
+
z_return.append(0)
|
135
|
+
else: # Only X exists, compute Y, Z is always 0.
|
136
|
+
for x in x_values:
|
137
|
+
x_return.append(x)
|
138
|
+
formula1 = formula.replace(xString, str(x)).replace('sqrt', 'math.sqrt').replace('sin', 'math.sin').replace('cos', 'math.cos').replace('tan', 'math.tan').replace('radians', 'math.radians').replace('pi', 'math.pi')
|
139
|
+
y_return.append(eval(formula1))
|
140
|
+
z_return.append(0)
|
141
|
+
vertices = []
|
142
|
+
for i in range(len(x_return)):
|
143
|
+
vertices.append(Vertex.ByCoordinates(x_return[i], y_return[i], z_return[i]))
|
144
|
+
return Cluster.ByTopologies(vertices)
|
145
|
+
|
146
|
+
@staticmethod
|
147
|
+
def ByTopologies(*args, transferDictionaries: bool = False) -> topologic.Cluster:
|
148
|
+
"""
|
149
|
+
Creates a topologic Cluster from the input list of topologies. The input can be individual topologies each as an input argument or a list of topologies stored in one input argument.
|
150
|
+
|
151
|
+
Parameters
|
152
|
+
----------
|
153
|
+
topologies : list
|
154
|
+
The list of topologies.
|
155
|
+
transferDictionaries : bool , optional
|
156
|
+
If set to True, the dictionaries from the input topologies are merged and transferred to the cluster. Otherwise they are not. The default is False.
|
157
|
+
|
158
|
+
Returns
|
159
|
+
-------
|
160
|
+
topologic.Cluster
|
161
|
+
The created topologic Cluster.
|
162
|
+
|
163
|
+
"""
|
164
|
+
from topologicpy.Dictionary import Dictionary
|
165
|
+
from topologicpy.Topology import Topology
|
166
|
+
from topologicpy.Helper import Helper
|
167
|
+
|
168
|
+
if len(args) == 0:
|
169
|
+
print("Cluster.ByTopologies - Error: The input topologies parameter is an empty list. Returning None.")
|
170
|
+
return None
|
171
|
+
if len(args) == 1:
|
172
|
+
topologies = args[0]
|
173
|
+
if isinstance(topologies, list):
|
174
|
+
if len(topologies) == 0:
|
175
|
+
print("Cluster.ByTopologies - Error: The input topologies parameter is an empty list. Returning None.")
|
176
|
+
return None
|
177
|
+
else:
|
178
|
+
topologyList = [x for x in topologies if isinstance(x, topologic.Topology)]
|
179
|
+
if len(topologies) == 0:
|
180
|
+
print("Cluster.ByTopologies - Error: The input topologies parameter does not contain any valid topologies. Returning None.")
|
181
|
+
return None
|
182
|
+
else:
|
183
|
+
print("Cluster.ByTopologies - Warning: The input topologies parameter contains only one topology. Returning the same topology.")
|
184
|
+
return topologies
|
185
|
+
else:
|
186
|
+
topologyList = Helper.Flatten(list(args))
|
187
|
+
topologyList = [x for x in topologyList if isinstance(x, topologic.Topology)]
|
188
|
+
if len(topologyList) == 0:
|
189
|
+
print("Cluster.ByTopologies - Error: The input parameters do not contain any valid topologies. Returning None.")
|
190
|
+
return None
|
191
|
+
cluster = topologic.Cluster.ByTopologies(topologyList, False)
|
192
|
+
dictionaries = []
|
193
|
+
for t in topologyList:
|
194
|
+
d = Topology.Dictionary(t)
|
195
|
+
keys = Dictionary.Keys(d)
|
196
|
+
if isinstance(keys, list):
|
197
|
+
if len(keys) > 0:
|
198
|
+
dictionaries.append(d)
|
199
|
+
if len(dictionaries) > 0:
|
200
|
+
if len(dictionaries) > 1:
|
201
|
+
d = Dictionary.ByMergedDictionaries(dictionaries)
|
202
|
+
else:
|
203
|
+
d = dictionaries[0]
|
204
|
+
cluster = Topology.SetDictionary(cluster, d)
|
205
|
+
return cluster
|
206
|
+
|
207
|
+
@staticmethod
|
208
|
+
def CellComplexes(cluster: topologic.Cluster) -> list:
|
209
|
+
"""
|
210
|
+
Returns the cellComplexes of the input cluster.
|
211
|
+
|
212
|
+
Parameters
|
213
|
+
----------
|
214
|
+
cluster : topologic.Cluster
|
215
|
+
The input cluster.
|
216
|
+
|
217
|
+
Returns
|
218
|
+
-------
|
219
|
+
list
|
220
|
+
The list of cellComplexes.
|
221
|
+
|
222
|
+
"""
|
223
|
+
if not isinstance(cluster, topologic.Cluster):
|
224
|
+
print("Cluster.CellComplexes - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
225
|
+
return None
|
226
|
+
cellComplexes = []
|
227
|
+
_ = cluster.CellComplexes(None, cellComplexes)
|
228
|
+
return cellComplexes
|
229
|
+
|
230
|
+
@staticmethod
|
231
|
+
def Cells(cluster: topologic.Cluster) -> list:
|
232
|
+
"""
|
233
|
+
Returns the cells of the input cluster.
|
234
|
+
|
235
|
+
Parameters
|
236
|
+
----------
|
237
|
+
cluster : topologic.Cluster
|
238
|
+
The input cluster.
|
239
|
+
|
240
|
+
Returns
|
241
|
+
-------
|
242
|
+
list
|
243
|
+
The list of cells.
|
244
|
+
|
245
|
+
"""
|
246
|
+
if not isinstance(cluster, topologic.Cluster):
|
247
|
+
print("Cluster.Cells - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
248
|
+
return None
|
249
|
+
cells = []
|
250
|
+
_ = cluster.Cells(None, cells)
|
251
|
+
return cells
|
252
|
+
|
253
|
+
|
254
|
+
@staticmethod
|
255
|
+
def DBSCAN(topologies, selectors=None, keys=["x", "y", "z"], epsilon: float = 0.5, minSamples: int = 2):
|
256
|
+
"""
|
257
|
+
Clusters the input vertices based on the Density-Based Spatial Clustering of Applications with Noise (DBSCAN) method. See https://en.wikipedia.org/wiki/DBSCAN
|
258
|
+
|
259
|
+
Parameters
|
260
|
+
----------
|
261
|
+
topologies : list
|
262
|
+
The input list of topologies to be clustered.
|
263
|
+
selectors : list , optional
|
264
|
+
If the list of topologies are not vertices then please provide a corresponding list of selectors (vertices) that represent the topologies for clustering. For example, these can be the centroids of the topologies.
|
265
|
+
If set to None, the list of topologies is expected to be a list of vertices. The default is None.
|
266
|
+
keys : list, optional
|
267
|
+
The keys in the embedded dictionaries in the topologies. If specified, the values at these keys will be added to the dimensions to be clustered. The values must be numeric. If you wish the x, y, z location to be included,
|
268
|
+
make sure the keys list includes "X", "Y", and/or "Z" (case insensitive). The default is ["x", "y", "z"]
|
269
|
+
epsilon : float , optional
|
270
|
+
The maximum radius around a data point within which other points are considered to be part of the same sense region (cluster). The default is 0.5.
|
271
|
+
minSamples : int , optional
|
272
|
+
The minimum number of points required to form a dense region (cluster). The default is 2.
|
273
|
+
|
274
|
+
Returns
|
275
|
+
-------
|
276
|
+
list, list
|
277
|
+
The list of clusters and the list of vertices considered to be noise if any (otherwise returns None).
|
278
|
+
|
279
|
+
"""
|
280
|
+
from topologicpy.Vertex import Vertex
|
281
|
+
from topologicpy.Topology import Topology
|
282
|
+
from topologicpy.Dictionary import Dictionary
|
283
|
+
|
284
|
+
def dbscan_3d_indices(data, eps, min_samples):
|
285
|
+
"""
|
286
|
+
DBSCAN clustering algorithm for 3D points.
|
287
|
+
|
288
|
+
Parameters:
|
289
|
+
- data: NumPy array, input data points with X, Y, and Z coordinates.
|
290
|
+
- eps: float, maximum distance between two samples for one to be considered as in the neighborhood of the other.
|
291
|
+
- min_samples: int, the number of samples (or total weight) in a neighborhood for a point to be considered as a core point.
|
292
|
+
|
293
|
+
Returns:
|
294
|
+
- clusters: List of lists, each list containing the indices of points in a cluster.
|
295
|
+
- noise: List of indices, indices of points labeled as noise.
|
296
|
+
"""
|
297
|
+
|
298
|
+
# Compute pairwise distances
|
299
|
+
dists = squareform(pdist(data))
|
300
|
+
|
301
|
+
# Initialize labels and cluster ID
|
302
|
+
labels = np.full(data.shape[0], -1)
|
303
|
+
cluster_id = 0
|
304
|
+
|
305
|
+
# Iterate through each point
|
306
|
+
for i in range(data.shape[0]):
|
307
|
+
if labels[i] != -1:
|
308
|
+
continue # Skip already processed points
|
309
|
+
|
310
|
+
# Find neighbors within epsilon distance
|
311
|
+
neighbors = np.where(dists[i] < eps)[0]
|
312
|
+
|
313
|
+
if len(neighbors) < min_samples:
|
314
|
+
# Label as noise
|
315
|
+
labels[i] = -1
|
316
|
+
else:
|
317
|
+
# Expand cluster
|
318
|
+
cluster_id += 1
|
319
|
+
expand_cluster_3d_indices(labels, dists, i, neighbors, cluster_id, eps, min_samples)
|
320
|
+
|
321
|
+
# Organize indices into clusters and noise
|
322
|
+
clusters = [list(np.where(labels == cid)[0]) for cid in range(1, cluster_id + 1)]
|
323
|
+
noise = list(np.where(labels == -1)[0])
|
324
|
+
|
325
|
+
return clusters, noise
|
326
|
+
|
327
|
+
def expand_cluster_3d_indices(labels, dists, point_index, neighbors, cluster_id, eps, min_samples):
|
328
|
+
"""
|
329
|
+
Expand the cluster around a core point for 3D points.
|
330
|
+
|
331
|
+
Parameters:
|
332
|
+
- labels: NumPy array, cluster labels for each data point.
|
333
|
+
- dists: NumPy array, pairwise distances between data points.
|
334
|
+
- point_index: int, index of the core point.
|
335
|
+
- neighbors: NumPy array, indices of neighbors.
|
336
|
+
- cluster_id: int, current cluster ID.
|
337
|
+
- eps: float, maximum distance between two samples for one to be considered as in the neighborhood of the other.
|
338
|
+
- min_samples: int, the number of samples (or total weight) in a neighborhood for a point to be considered as a core point.
|
339
|
+
"""
|
340
|
+
labels[point_index] = cluster_id
|
341
|
+
|
342
|
+
i = 0
|
343
|
+
while i < len(neighbors):
|
344
|
+
current_neighbor = neighbors[i]
|
345
|
+
|
346
|
+
if labels[current_neighbor] == -1:
|
347
|
+
labels[current_neighbor] = cluster_id
|
348
|
+
|
349
|
+
new_neighbors = np.where(dists[current_neighbor] < eps)[0]
|
350
|
+
if len(new_neighbors) >= min_samples:
|
351
|
+
neighbors = np.concatenate([neighbors, new_neighbors])
|
352
|
+
|
353
|
+
elif labels[current_neighbor] == 0:
|
354
|
+
labels[current_neighbor] = cluster_id
|
355
|
+
|
356
|
+
i += 1
|
357
|
+
|
358
|
+
if not isinstance(topologies, list):
|
359
|
+
print("Cluster.DBSCAN - Error: The input vertices parameter is not a valid list. Returning None.")
|
360
|
+
return None, None
|
361
|
+
topologyList = [t for t in topologies if isinstance(t, topologic.Topology)]
|
362
|
+
if len(topologyList) < 1:
|
363
|
+
print("Cluster.DBSCAN - Error: The input vertices parameter does not contain any valid vertices. Returning None.")
|
364
|
+
return None, None
|
365
|
+
if len(topologyList) < minSamples:
|
366
|
+
print("Cluster.DBSCAN - Error: The input minSamples parameter cannot be larger than the number of vertices. Returning None.")
|
367
|
+
return None, None
|
368
|
+
|
369
|
+
if not isinstance(selectors, list):
|
370
|
+
check_vertices = [t for t in topologyList if not isinstance(t, topologic.Vertex)]
|
371
|
+
if len(check_vertices) > 0:
|
372
|
+
print("Cluster.DBSCAN - Error: The input selectors parameter is not a valid list and this is needed since the list of topologies contains objects of type other than a topologic.Vertex. Returning None.")
|
373
|
+
return None, None
|
374
|
+
else:
|
375
|
+
selectors = [s for s in selectors if isinstance(s, topologic.Vertex)]
|
376
|
+
if len(selectors) < 1:
|
377
|
+
check_vertices = [t for t in topologyList if not isinstance(t, topologic.Vertex)]
|
378
|
+
if len(check_vertices) > 0:
|
379
|
+
print("Cluster.DBSCAN - Error: The input selectors parameter does not contain any valid vertices and this is needed since the list of topologies contains objects of type other than a topologic.Vertex. Returning None.")
|
380
|
+
return None, None
|
381
|
+
if not len(selectors) == len(topologyList):
|
382
|
+
print("Cluster.DBSCAN - Error: The input topologies and selectors parameters do not have the same length. Returning None.")
|
383
|
+
return None, None
|
384
|
+
if not isinstance(keys, list):
|
385
|
+
print("Cluster.DBSCAN - Error: The input keys parameter is not a valid list. Returning None.")
|
386
|
+
return None
|
387
|
+
|
388
|
+
|
389
|
+
data = []
|
390
|
+
if selectors == None:
|
391
|
+
for t in topologyList:
|
392
|
+
elements = []
|
393
|
+
if keys:
|
394
|
+
d = Topology.Dictionary(t)
|
395
|
+
for key in keys:
|
396
|
+
if key.lower() == "x":
|
397
|
+
value = Vertex.X(t)
|
398
|
+
elif key.lower() == "y":
|
399
|
+
value = Vertex.Y(t)
|
400
|
+
elif key.lower() == "z":
|
401
|
+
value = Vertex.Z(t)
|
402
|
+
else:
|
403
|
+
value = Dictionary.ValueAtKey(d, key)
|
404
|
+
if value != None:
|
405
|
+
elements.append(value)
|
406
|
+
data.append(elements)
|
407
|
+
else:
|
408
|
+
for i, s in enumerate(selectors):
|
409
|
+
elements = []
|
410
|
+
if keys:
|
411
|
+
d = Topology.Dictionary(topologyList[i])
|
412
|
+
for key in keys:
|
413
|
+
if key.lower() == "x":
|
414
|
+
value = Vertex.X(s)
|
415
|
+
elif key.lower() == "y":
|
416
|
+
value = Vertex.Y(s)
|
417
|
+
elif key.lower() == "z":
|
418
|
+
value = Vertex.Z(s)
|
419
|
+
else:
|
420
|
+
value = Dictionary.ValueAtKey(d, key)
|
421
|
+
if value != None:
|
422
|
+
elements.append(value)
|
423
|
+
data.append(elements)
|
424
|
+
#coords = [[Vertex.X(v), Vertex.Y(v), Vertex.Z(v)] for v in vertexList]
|
425
|
+
clusters, noise = dbscan_3d_indices(np.array(data), epsilon, minSamples)
|
426
|
+
tp_clusters = []
|
427
|
+
for cluster in clusters:
|
428
|
+
tp_clusters.append(Cluster.ByTopologies([topologyList[i] for i in cluster]))
|
429
|
+
vert_group = []
|
430
|
+
tp_noise = None
|
431
|
+
if len(noise) > 0:
|
432
|
+
tp_noise = Cluster.ByTopologies([topologyList[i] for i in noise])
|
433
|
+
return tp_clusters, tp_noise
|
434
|
+
|
435
|
+
@staticmethod
|
436
|
+
def Edges(cluster: topologic.Cluster) -> list:
|
437
|
+
"""
|
438
|
+
Returns the edges of the input cluster.
|
439
|
+
|
440
|
+
Parameters
|
441
|
+
----------
|
442
|
+
cluster : topologic.Cluster
|
443
|
+
The input cluster.
|
444
|
+
|
445
|
+
Returns
|
446
|
+
-------
|
447
|
+
list
|
448
|
+
The list of edges.
|
449
|
+
|
450
|
+
"""
|
451
|
+
if not isinstance(cluster, topologic.Cluster):
|
452
|
+
print("Cluster.Edges - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
453
|
+
return None
|
454
|
+
edges = []
|
455
|
+
_ = cluster.Edges(None, edges)
|
456
|
+
return edges
|
457
|
+
|
458
|
+
@staticmethod
|
459
|
+
def Faces(cluster: topologic.Cluster) -> list:
|
460
|
+
"""
|
461
|
+
Returns the faces of the input cluster.
|
462
|
+
|
463
|
+
Parameters
|
464
|
+
----------
|
465
|
+
cluster : topologic.Cluster
|
466
|
+
The input cluster.
|
467
|
+
|
468
|
+
Returns
|
469
|
+
-------
|
470
|
+
list
|
471
|
+
The list of faces.
|
472
|
+
|
473
|
+
"""
|
474
|
+
if not isinstance(cluster, topologic.Cluster):
|
475
|
+
print("Cluster.Faces - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
476
|
+
return None
|
477
|
+
faces = []
|
478
|
+
_ = cluster.Faces(None, faces)
|
479
|
+
return faces
|
480
|
+
|
481
|
+
@staticmethod
|
482
|
+
def FreeCells(cluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
483
|
+
"""
|
484
|
+
Returns the free cells of the input cluster that are not part of a higher topology.
|
485
|
+
|
486
|
+
Parameters
|
487
|
+
----------
|
488
|
+
cluster : topologic.Cluster
|
489
|
+
The input cluster.
|
490
|
+
tolerance : float , optional
|
491
|
+
The desired tolerance. The default is 0.0001.
|
492
|
+
|
493
|
+
Returns
|
494
|
+
-------
|
495
|
+
list
|
496
|
+
The list of free cells.
|
497
|
+
|
498
|
+
"""
|
499
|
+
from topologicpy.CellComplex import CellComplex
|
500
|
+
from topologicpy.Topology import Topology
|
501
|
+
|
502
|
+
if not isinstance(cluster, topologic.Cluster):
|
503
|
+
print("Cluster.FreeCells - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
504
|
+
return None
|
505
|
+
allCells = []
|
506
|
+
_ = cluster.Cells(None, allCells)
|
507
|
+
if len(allCells) < 1:
|
508
|
+
return []
|
509
|
+
allCellsCluster = Cluster.ByTopologies(allCells)
|
510
|
+
freeCells = []
|
511
|
+
cellComplexes = []
|
512
|
+
_ = cluster.CellComplexes(None, cellComplexes)
|
513
|
+
cellComplexesCells = []
|
514
|
+
for cellComplex in cellComplexes:
|
515
|
+
tempCells = CellComplex.Cells(cellComplex)
|
516
|
+
cellComplexesCells += tempCells
|
517
|
+
if len(cellComplexesCells) == 0:
|
518
|
+
return allCells
|
519
|
+
cellComplexesCluster = Cluster.ByTopologies(cellComplexesCells)
|
520
|
+
resultingCluster = Topology.Boolean(allCellsCluster, cellComplexesCluster, operation="difference", tolerance=tolerance)
|
521
|
+
if resultingCluster == None:
|
522
|
+
return []
|
523
|
+
if isinstance(resultingCluster, topologic.Cell):
|
524
|
+
return [resultingCluster]
|
525
|
+
result = Topology.SubTopologies(resultingCluster, subTopologyType="cell")
|
526
|
+
if result == None:
|
527
|
+
return [] #Make sure you return an empty list instead of None
|
528
|
+
return result
|
529
|
+
|
530
|
+
@staticmethod
|
531
|
+
def FreeShells(cluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
532
|
+
"""
|
533
|
+
Returns the free shells of the input cluster that are not part of a higher topology.
|
534
|
+
|
535
|
+
Parameters
|
536
|
+
----------
|
537
|
+
cluster : topologic.Cluster
|
538
|
+
The input cluster.
|
539
|
+
tolerance : float, optional
|
540
|
+
The desired tolerance. The default is 0.0001.
|
541
|
+
|
542
|
+
Returns
|
543
|
+
-------
|
544
|
+
list
|
545
|
+
The list of free shells.
|
546
|
+
|
547
|
+
"""
|
548
|
+
from topologicpy.Cell import Cell
|
549
|
+
from topologicpy.Topology import Topology
|
550
|
+
|
551
|
+
if not isinstance(cluster, topologic.Cluster):
|
552
|
+
print("Cluster.FreeShells - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
553
|
+
return None
|
554
|
+
allShells = []
|
555
|
+
_ = cluster.Shells(None, allShells)
|
556
|
+
if len(allShells) < 1:
|
557
|
+
return []
|
558
|
+
allShellsCluster = Cluster.ByTopologies(allShells)
|
559
|
+
cells = []
|
560
|
+
_ = cluster.Cells(None, cells)
|
561
|
+
cellsShells = []
|
562
|
+
for cell in cells:
|
563
|
+
tempShells = Cell.Shells(cell)
|
564
|
+
cellsShells += tempShells
|
565
|
+
if len(cellsShells) == 0:
|
566
|
+
return allShells
|
567
|
+
cellsCluster = Cluster.ByTopologies(cellsShells)
|
568
|
+
resultingCluster = Topology.Boolean(allShellsCluster, cellsCluster, operation="difference", tolerance=tolerance)
|
569
|
+
if resultingCluster == None:
|
570
|
+
return []
|
571
|
+
if isinstance(resultingCluster, topologic.Shell):
|
572
|
+
return [resultingCluster]
|
573
|
+
result = Topology.SubTopologies(resultingCluster, subTopologyType="shell")
|
574
|
+
if result == None:
|
575
|
+
return [] #Make sure you return an empty list instead of None
|
576
|
+
return result
|
577
|
+
|
578
|
+
@staticmethod
|
579
|
+
def FreeFaces(cluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
580
|
+
"""
|
581
|
+
Returns the free faces of the input cluster that are not part of a higher topology.
|
582
|
+
|
583
|
+
Parameters
|
584
|
+
----------
|
585
|
+
cluster : topologic.Cluster
|
586
|
+
The input cluster.
|
587
|
+
tolerance : float , optional
|
588
|
+
The desired tolerance. The default is 0.0001.
|
589
|
+
|
590
|
+
Returns
|
591
|
+
-------
|
592
|
+
list
|
593
|
+
The list of free faces.
|
594
|
+
|
595
|
+
"""
|
596
|
+
from topologicpy.Shell import Shell
|
597
|
+
from topologicpy.Topology import Topology
|
598
|
+
|
599
|
+
if not isinstance(cluster, topologic.Cluster):
|
600
|
+
print("Cluster.FreeFaces - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
601
|
+
return None
|
602
|
+
allFaces = []
|
603
|
+
_ = cluster.Faces(None, allFaces)
|
604
|
+
if len(allFaces) < 1:
|
605
|
+
return []
|
606
|
+
allFacesCluster = Cluster.ByTopologies(allFaces)
|
607
|
+
shells = []
|
608
|
+
_ = cluster.Shells(None, shells)
|
609
|
+
shellFaces = []
|
610
|
+
for shell in shells:
|
611
|
+
tempFaces = Shell.Faces(shell)
|
612
|
+
shellFaces += tempFaces
|
613
|
+
if len(shellFaces) == 0:
|
614
|
+
return allFaces
|
615
|
+
shellCluster = Cluster.ByTopologies(shellFaces)
|
616
|
+
resultingCluster = Topology.Boolean(allFacesCluster, shellCluster, operation="difference", tolerance=tolerance)
|
617
|
+
if resultingCluster == None:
|
618
|
+
return []
|
619
|
+
if isinstance(resultingCluster, topologic.Face):
|
620
|
+
return [resultingCluster]
|
621
|
+
result = Topology.SubTopologies(resultingCluster, subTopologyType="face")
|
622
|
+
if result == None:
|
623
|
+
return [] #Make sure you return an empty list instead of None
|
624
|
+
return result
|
625
|
+
|
626
|
+
@staticmethod
|
627
|
+
def FreeWires(cluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
628
|
+
"""
|
629
|
+
Returns the free wires of the input cluster that are not part of a higher topology.
|
630
|
+
|
631
|
+
Parameters
|
632
|
+
----------
|
633
|
+
cluster : topologic.Cluster
|
634
|
+
The input cluster.
|
635
|
+
tolerance : float , optional
|
636
|
+
The desired tolerance. The default is 0.0001.
|
637
|
+
|
638
|
+
Returns
|
639
|
+
-------
|
640
|
+
list
|
641
|
+
The list of free wires.
|
642
|
+
|
643
|
+
"""
|
644
|
+
from topologicpy.Face import Face
|
645
|
+
from topologicpy.Topology import Topology
|
646
|
+
|
647
|
+
if not isinstance(cluster, topologic.Cluster):
|
648
|
+
print("Cluster.FreeWires - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
649
|
+
return None
|
650
|
+
allWires = []
|
651
|
+
_ = cluster.Wires(None, allWires)
|
652
|
+
if len(allWires) < 1:
|
653
|
+
return []
|
654
|
+
allWiresCluster = Cluster.ByTopologies(allWires)
|
655
|
+
faces = []
|
656
|
+
_ = cluster.Faces(None, faces)
|
657
|
+
facesWires = []
|
658
|
+
for face in faces:
|
659
|
+
tempWires = Face.Wires(face)
|
660
|
+
facesWires += tempWires
|
661
|
+
if len(facesWires) == 0:
|
662
|
+
return allWires
|
663
|
+
facesCluster = Cluster.ByTopologies(facesWires)
|
664
|
+
resultingCluster = Topology.Boolean(allWiresCluster, facesCluster, operation="difference", tolerance=tolerance)
|
665
|
+
if resultingCluster == None:
|
666
|
+
return []
|
667
|
+
if isinstance(resultingCluster, topologic.Wire):
|
668
|
+
return [resultingCluster]
|
669
|
+
result = Topology.SubTopologies(resultingCluster, subTopologyType="wire")
|
670
|
+
if not result:
|
671
|
+
return [] #Make sure you return an empty list instead of None
|
672
|
+
return result
|
673
|
+
|
674
|
+
@staticmethod
|
675
|
+
def FreeEdges(cluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
676
|
+
"""
|
677
|
+
Returns the free edges of the input cluster that are not part of a higher topology.
|
678
|
+
|
679
|
+
Parameters
|
680
|
+
----------
|
681
|
+
cluster : topologic.Cluster
|
682
|
+
The input cluster.
|
683
|
+
tolerance : float, optional
|
684
|
+
The desired tolerance. The default is 0.0001.
|
685
|
+
|
686
|
+
Returns
|
687
|
+
-------
|
688
|
+
list
|
689
|
+
The list of free edges.
|
690
|
+
|
691
|
+
"""
|
692
|
+
from topologicpy.Wire import Wire
|
693
|
+
from topologicpy.Topology import Topology
|
694
|
+
|
695
|
+
if not isinstance(cluster, topologic.Cluster):
|
696
|
+
print("Cluster.FreeEdges - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
697
|
+
return None
|
698
|
+
allEdges = []
|
699
|
+
_ = cluster.Edges(None, allEdges)
|
700
|
+
if len(allEdges) < 1:
|
701
|
+
return []
|
702
|
+
allEdgesCluster = Cluster.ByTopologies(allEdges)
|
703
|
+
wires = []
|
704
|
+
_ = cluster.Wires(None, wires)
|
705
|
+
wireEdges = []
|
706
|
+
for wire in wires:
|
707
|
+
tempEdges = Wire.Edges(wire)
|
708
|
+
wireEdges += tempEdges
|
709
|
+
if len(wireEdges) == 0:
|
710
|
+
return allEdges
|
711
|
+
wireCluster = Cluster.ByTopologies(wireEdges)
|
712
|
+
resultingCluster = Topology.Boolean(allEdgesCluster, wireCluster, operation="difference", tolerance=tolerance)
|
713
|
+
if resultingCluster == None:
|
714
|
+
return []
|
715
|
+
if isinstance(resultingCluster, topologic.Edge):
|
716
|
+
return [resultingCluster]
|
717
|
+
result = Topology.SubTopologies(resultingCluster, subTopologyType="edge")
|
718
|
+
if result == None:
|
719
|
+
return [] #Make sure you return an empty list instead of None
|
720
|
+
return result
|
721
|
+
|
722
|
+
@staticmethod
|
723
|
+
def FreeVertices(cluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
724
|
+
"""
|
725
|
+
Returns the free vertices of the input cluster that are not part of a higher topology.
|
726
|
+
|
727
|
+
Parameters
|
728
|
+
----------
|
729
|
+
cluster : topologic.Cluster
|
730
|
+
The input cluster.
|
731
|
+
tolerance : float , optional
|
732
|
+
The desired tolerance. The default is 0.0001.
|
733
|
+
|
734
|
+
Returns
|
735
|
+
-------
|
736
|
+
list
|
737
|
+
The list of free vertices.
|
738
|
+
|
739
|
+
"""
|
740
|
+
from topologicpy.Edge import Edge
|
741
|
+
from topologicpy.Topology import Topology
|
742
|
+
|
743
|
+
if not isinstance(cluster, topologic.Cluster):
|
744
|
+
print("Cluster.FreeVertices - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
745
|
+
return None
|
746
|
+
allVertices = []
|
747
|
+
_ = cluster.Vertices(None, allVertices)
|
748
|
+
if len(allVertices) < 1:
|
749
|
+
return []
|
750
|
+
allVerticesCluster = Cluster.ByTopologies(allVertices)
|
751
|
+
edges = []
|
752
|
+
_ = cluster.Edges(None, edges)
|
753
|
+
edgesVertices = []
|
754
|
+
for edge in edges:
|
755
|
+
tempVertices = Edge.Vertices(edge)
|
756
|
+
edgesVertices += tempVertices
|
757
|
+
if len(edgesVertices) == 0:
|
758
|
+
return allVertices
|
759
|
+
edgesCluster = Cluster.ByTopologies(edgesVertices)
|
760
|
+
resultingCluster = Topology.Boolean(allVerticesCluster, edgesCluster, operation="difference", tolerance=tolerance)
|
761
|
+
if isinstance(resultingCluster, topologic.Vertex):
|
762
|
+
return [resultingCluster]
|
763
|
+
if resultingCluster == None:
|
764
|
+
return []
|
765
|
+
result = Topology.SubTopologies(resultingCluster, subTopologyType="vertex")
|
766
|
+
if result == None:
|
767
|
+
return [] #Make sure you return an empty list instead of None
|
768
|
+
return result
|
769
|
+
|
770
|
+
@staticmethod
|
771
|
+
def FreeTopologies(cluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
|
772
|
+
"""
|
773
|
+
Returns the free topologies of the input cluster that are not part of a higher topology.
|
774
|
+
|
775
|
+
Parameters
|
776
|
+
----------
|
777
|
+
cluster : topologic.Cluster
|
778
|
+
The input cluster.
|
779
|
+
tolerance : float , optional
|
780
|
+
The desired tolerance. The default is 0.0001.
|
781
|
+
|
782
|
+
Returns
|
783
|
+
-------
|
784
|
+
list
|
785
|
+
The list of free topologies.
|
786
|
+
|
787
|
+
"""
|
788
|
+
topologies = Cluster.FreeVertices(cluster, tolerance=tolerance)
|
789
|
+
topologies += Cluster.FreeEdges(cluster, tolerance=tolerance)
|
790
|
+
topologies += Cluster.FreeWires(cluster, tolerance=tolerance)
|
791
|
+
topologies += Cluster.FreeFaces(cluster, tolerance=tolerance)
|
792
|
+
topologies += Cluster.FreeShells(cluster, tolerance=tolerance)
|
793
|
+
topologies += Cluster.FreeCells(cluster, tolerance=tolerance)
|
794
|
+
topologies += Cluster.CellComplexes(cluster)
|
795
|
+
|
796
|
+
return topologies
|
797
|
+
|
798
|
+
@staticmethod
|
799
|
+
def HighestType(cluster: topologic.Cluster) -> int:
|
800
|
+
"""
|
801
|
+
Returns the type of the highest dimension subtopology found in the input cluster.
|
802
|
+
|
803
|
+
Parameters
|
804
|
+
----------
|
805
|
+
cluster : topologic.Cluster
|
806
|
+
The input cluster.
|
807
|
+
|
808
|
+
Returns
|
809
|
+
-------
|
810
|
+
int
|
811
|
+
The type of the highest dimension subtopology found in the input cluster.
|
812
|
+
|
813
|
+
"""
|
814
|
+
if not isinstance(cluster, topologic.Cluster):
|
815
|
+
print("Cluster.HighestType - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
816
|
+
return None
|
817
|
+
cellComplexes = Cluster.CellComplexes(cluster)
|
818
|
+
if len(cellComplexes) > 0:
|
819
|
+
return topologic.CellComplex.Type()
|
820
|
+
cells = Cluster.Cells(cluster)
|
821
|
+
if len(cells) > 0:
|
822
|
+
return topologic.Cell.Type()
|
823
|
+
shells = Cluster.Shells(cluster)
|
824
|
+
if len(shells) > 0:
|
825
|
+
return topologic.Shell.Type()
|
826
|
+
faces = Cluster.Faces(cluster)
|
827
|
+
if len(faces) > 0:
|
828
|
+
return topologic.Face.Type()
|
829
|
+
wires = Cluster.Wires(cluster)
|
830
|
+
if len(wires) > 0:
|
831
|
+
return topologic.Wire.Type()
|
832
|
+
edges = Cluster.Edges(cluster)
|
833
|
+
if len(edges) > 0:
|
834
|
+
return topologic.Edge.Type()
|
835
|
+
vertices = Cluster.Vertices(cluster)
|
836
|
+
if len(vertices) > 0:
|
837
|
+
return topologic.Vertex.Type()
|
838
|
+
|
839
|
+
@staticmethod
|
840
|
+
def K_Means(topologies, selectors=None, keys=["x", "y", "z"], k=4, maxIterations=100, centroidKey="k_centroid"):
|
841
|
+
"""
|
842
|
+
Clusters the input topologies using K-Means clustering. See https://en.wikipedia.org/wiki/K-means_clustering
|
843
|
+
|
844
|
+
Parameters
|
845
|
+
----------
|
846
|
+
topologies : list
|
847
|
+
The input list of topologies. If this is not a list of topologic vertices then please provide a list of selectors
|
848
|
+
selectors : list , optional
|
849
|
+
If the list of topologies are not vertices then please provide a corresponding list of selectors (vertices) that represent the topologies for clustering. For example, these can be the centroids of the topologies.
|
850
|
+
If set to None, the list of topologies is expected to be a list of vertices. The default is None.
|
851
|
+
keys : list, optional
|
852
|
+
The keys in the embedded dictionaries in the topologies. If specified, the values at these keys will be added to the dimensions to be clustered. The values must be numeric. If you wish the x, y, z location to be included,
|
853
|
+
make sure the keys list includes "X", "Y", and/or "Z" (case insensitive). The default is ["x", "y", "z"]
|
854
|
+
k : int , optional
|
855
|
+
The desired number of clusters. The default is 4.
|
856
|
+
maxIterations : int , optional
|
857
|
+
The desired maximum number of iterations for the clustering algorithm
|
858
|
+
centroidKey : str , optional
|
859
|
+
The desired dictionary key under which to store the cluster's centroid (this is not to be confused with the actual geometric centroid of the cluster). The default is "k_centroid"
|
860
|
+
|
861
|
+
Returns
|
862
|
+
-------
|
863
|
+
list
|
864
|
+
The created list of clusters.
|
865
|
+
|
866
|
+
"""
|
867
|
+
from topologicpy.Helper import Helper
|
868
|
+
from topologicpy.Vertex import Vertex
|
869
|
+
from topologicpy.Dictionary import Dictionary
|
870
|
+
from topologicpy.Topology import Topology
|
871
|
+
|
872
|
+
|
873
|
+
def k_means(data, vertices, k=4, maxIterations=100):
|
874
|
+
import random
|
875
|
+
def euclidean_distance(p, q):
|
876
|
+
return sum((pi - qi) ** 2 for pi, qi in zip(p, q)) ** 0.5
|
877
|
+
|
878
|
+
# Initialize k centroids randomly
|
879
|
+
centroids = random.sample(data, k)
|
880
|
+
|
881
|
+
for _ in range(maxIterations):
|
882
|
+
# Assign each data point to the nearest centroid
|
883
|
+
clusters = [[] for _ in range(k)]
|
884
|
+
clusters_v = [[] for _ in range(k)]
|
885
|
+
for i, point in enumerate(data):
|
886
|
+
distances = [euclidean_distance(point, centroid) for centroid in centroids]
|
887
|
+
nearest_centroid_index = distances.index(min(distances))
|
888
|
+
clusters[nearest_centroid_index].append(point)
|
889
|
+
clusters_v[nearest_centroid_index].append(vertices[i])
|
890
|
+
|
891
|
+
# Compute the new centroids as the mean of the points in each cluster
|
892
|
+
new_centroids = []
|
893
|
+
for cluster in clusters:
|
894
|
+
if not cluster:
|
895
|
+
# If a cluster is empty, keep the previous centroid
|
896
|
+
new_centroids.append(centroids[clusters.index(cluster)])
|
897
|
+
else:
|
898
|
+
new_centroids.append([sum(dim) / len(cluster) for dim in zip(*cluster)])
|
899
|
+
|
900
|
+
# Check if the centroids have converged
|
901
|
+
if new_centroids == centroids:
|
902
|
+
break
|
903
|
+
|
904
|
+
centroids = new_centroids
|
905
|
+
|
906
|
+
return {'clusters': clusters, 'clusters_v': clusters_v, 'centroids': centroids}
|
907
|
+
|
908
|
+
|
909
|
+
|
910
|
+
if not isinstance(topologies, list):
|
911
|
+
print("Cluster.K_Means - Error: The input topologies parameter is not a valid list. Returning None.")
|
912
|
+
return None
|
913
|
+
topologies = [t for t in topologies if isinstance(t, topologic.Topology)]
|
914
|
+
if len(topologies) < 1:
|
915
|
+
print("Cluster.K_Means - Error: The input topologies parameter does not contain any valid topologies. Returning None.")
|
916
|
+
return None
|
917
|
+
if not isinstance(selectors, list):
|
918
|
+
check_vertices = [v for v in topologies if not isinstance(v, topologic.Vertex)]
|
919
|
+
if len(check_vertices) > 0:
|
920
|
+
print("Cluster.K_Means - Error: The input selectors parameter is not a valid list and this is needed since the list of topologies contains objects of type other than a topologic.Vertex. Returning None.")
|
921
|
+
return None
|
922
|
+
else:
|
923
|
+
selectors = [s for s in selectors if isinstance(s, topologic.Vertex)]
|
924
|
+
if len(selectors) < 1:
|
925
|
+
check_vertices = [v for v in topologies if not isinstance(v, topologic.Vertex)]
|
926
|
+
if len(check_vertices) > 0:
|
927
|
+
print("Cluster.K_Means - Error: The input selectors parameter does not contain any valid vertices and this is needed since the list of topologies contains objects of type other than a topologic.Vertex. Returning None.")
|
928
|
+
return None
|
929
|
+
if not len(selectors) == len(topologies):
|
930
|
+
print("Cluster.K_Means - Error: The input topologies and selectors parameters do not have the same length. Returning None.")
|
931
|
+
return None
|
932
|
+
if not isinstance(keys, list):
|
933
|
+
print("Cluster.K_Means - Error: The input keys parameter is not a valid list. Returning None.")
|
934
|
+
return None
|
935
|
+
if not isinstance(k , int):
|
936
|
+
print("Cluster.K_Means - Error: The input k parameter is not a valid integer. Returning None.")
|
937
|
+
return None
|
938
|
+
if k < 1:
|
939
|
+
print("Cluster.K_Means - Error: The input k parameter is less than one. Returning None.")
|
940
|
+
return None
|
941
|
+
if len(topologies) < k:
|
942
|
+
print("Cluster.K_Means - Error: The input topologies parameter is less than the specified number of clusters. Returning None.")
|
943
|
+
return None
|
944
|
+
if len(topologies) == k:
|
945
|
+
t_clusters = []
|
946
|
+
for topology in topologies:
|
947
|
+
t_cluster = Cluster.ByTopologies([topology])
|
948
|
+
for key in keys:
|
949
|
+
if key.lower() == "x":
|
950
|
+
value = Vertex.X(t)
|
951
|
+
elif key.lower() == "y":
|
952
|
+
value = Vertex.Y(t)
|
953
|
+
elif key.lower() == "z":
|
954
|
+
value = Vertex.Z(t)
|
955
|
+
else:
|
956
|
+
value = Dictionary.ValueAtKey(d, key)
|
957
|
+
if value != None:
|
958
|
+
elements.append(value)
|
959
|
+
d = Dictionary.ByKeysValues([centroidKey], [elements])
|
960
|
+
t_cluster = Topology.SetDictionary(t_cluster, d)
|
961
|
+
t_clusters.append(t_cluster)
|
962
|
+
return t_clusters
|
963
|
+
|
964
|
+
data = []
|
965
|
+
if selectors == None:
|
966
|
+
for t in topologies:
|
967
|
+
elements = []
|
968
|
+
if keys:
|
969
|
+
d = Topology.Dictionary(t)
|
970
|
+
for key in keys:
|
971
|
+
if key.lower() == "x":
|
972
|
+
value = Vertex.X(t)
|
973
|
+
elif key.lower() == "y":
|
974
|
+
value = Vertex.Y(t)
|
975
|
+
elif key.lower() == "z":
|
976
|
+
value = Vertex.Z(t)
|
977
|
+
else:
|
978
|
+
value = Dictionary.ValueAtKey(d, key)
|
979
|
+
if value != None:
|
980
|
+
elements.append(value)
|
981
|
+
data.append(elements)
|
982
|
+
else:
|
983
|
+
for i, s in enumerate(selectors):
|
984
|
+
elements = []
|
985
|
+
if keys:
|
986
|
+
d = Topology.Dictionary(topologies[i])
|
987
|
+
for key in keys:
|
988
|
+
if key.lower() == "x":
|
989
|
+
value = Vertex.X(s)
|
990
|
+
elif key.lower() == "y":
|
991
|
+
value = Vertex.Y(s)
|
992
|
+
elif key.lower() == "z":
|
993
|
+
value = Vertex.Z(s)
|
994
|
+
else:
|
995
|
+
value = Dictionary.ValueAtKey(d, key)
|
996
|
+
if value != None:
|
997
|
+
elements.append(value)
|
998
|
+
data.append(elements)
|
999
|
+
if len(data) == 0:
|
1000
|
+
print("Cluster.K_Means - Error: Could not perform the operation. Returning None.")
|
1001
|
+
return None
|
1002
|
+
if selectors:
|
1003
|
+
dict = k_means(data, selectors, k=k, maxIterations=maxIterations)
|
1004
|
+
else:
|
1005
|
+
dict = k_means(data, topologies, k=k, maxIterations=maxIterations)
|
1006
|
+
clusters = dict['clusters_v']
|
1007
|
+
centroids = dict['centroids']
|
1008
|
+
t_clusters = []
|
1009
|
+
for i, cluster in enumerate(clusters):
|
1010
|
+
cluster_vertices = []
|
1011
|
+
for v in cluster:
|
1012
|
+
if selectors == None:
|
1013
|
+
cluster_vertices.append(v)
|
1014
|
+
else:
|
1015
|
+
index = selectors.index(v)
|
1016
|
+
cluster_vertices.append(topologies[index])
|
1017
|
+
cluster = Cluster.ByTopologies(cluster_vertices)
|
1018
|
+
d = Dictionary.ByKeysValues([centroidKey], [centroids[i]])
|
1019
|
+
cluster = Topology.SetDictionary(cluster, d)
|
1020
|
+
t_clusters.append(cluster)
|
1021
|
+
return t_clusters
|
1022
|
+
|
1023
|
+
@staticmethod
|
1024
|
+
def MergeCells(cells, tolerance=0.0001):
|
1025
|
+
"""
|
1026
|
+
Creates a cluster that contains cellComplexes where it can create them plus any additional free cells.
|
1027
|
+
|
1028
|
+
Parameters
|
1029
|
+
----------
|
1030
|
+
cells : list
|
1031
|
+
The input list of cells.
|
1032
|
+
tolerance : float , optional
|
1033
|
+
The desired tolerance. The default is 0.0001.
|
1034
|
+
|
1035
|
+
Returns
|
1036
|
+
-------
|
1037
|
+
topologic.Cluster
|
1038
|
+
The created cluster with merged cells as possible.
|
1039
|
+
|
1040
|
+
"""
|
1041
|
+
|
1042
|
+
from topologicpy.CellComplex import CellComplex
|
1043
|
+
from topologicpy.Topology import Topology
|
1044
|
+
|
1045
|
+
def find_cell_complexes(cells, adjacency_test, tolerance=0.0001):
|
1046
|
+
cell_complexes = []
|
1047
|
+
remaining_cells = set(cells)
|
1048
|
+
|
1049
|
+
def explore_complex(cell_complex, remaining, tolerance=0.0001):
|
1050
|
+
new_cells = set()
|
1051
|
+
for cell in remaining:
|
1052
|
+
if any(adjacency_test(cell, existing_cell, tolerance=tolerance) for existing_cell in cell_complex):
|
1053
|
+
new_cells.add(cell)
|
1054
|
+
return new_cells
|
1055
|
+
|
1056
|
+
while remaining_cells:
|
1057
|
+
current_cell = remaining_cells.pop()
|
1058
|
+
current_complex = {current_cell}
|
1059
|
+
current_complex.update(explore_complex(current_complex, remaining_cells, tolerance=tolerance))
|
1060
|
+
cell_complexes.append(current_complex)
|
1061
|
+
remaining_cells -= current_complex
|
1062
|
+
|
1063
|
+
return cell_complexes
|
1064
|
+
|
1065
|
+
# Example adjacency test function (replace this with your actual implementation)
|
1066
|
+
def adjacency_test(cell1, cell2, tolerance=0.0001):
|
1067
|
+
return isinstance(Topology.Merge(cell1, cell2, tolerance=tolerance), topologic.CellComplex)
|
1068
|
+
|
1069
|
+
if not isinstance(cells, list):
|
1070
|
+
print("Cluster.MergeCells - Error: The input cells parameter is not a valid list of cells. Returning None.")
|
1071
|
+
return None
|
1072
|
+
#cells = [cell for cell in cells if isinstance(cell, topologic.Cell)]
|
1073
|
+
if len(cells) < 1:
|
1074
|
+
print("Cluster.MergeCells - Error: The input cells parameter does not contain any valid cells. Returning None.")
|
1075
|
+
return None
|
1076
|
+
|
1077
|
+
complexes = find_cell_complexes(cells, adjacency_test)
|
1078
|
+
cellComplexes = []
|
1079
|
+
cells = []
|
1080
|
+
for aComplex in complexes:
|
1081
|
+
aComplex = list(aComplex)
|
1082
|
+
if len(aComplex) > 1:
|
1083
|
+
cc = CellComplex.ByCells(aComplex, silent=True)
|
1084
|
+
if isinstance(cc, topologic.CellComplex):
|
1085
|
+
cellComplexes.append(cc)
|
1086
|
+
elif len(aComplex) == 1:
|
1087
|
+
if isinstance(aComplex[0], topologic.Cell):
|
1088
|
+
cells.append(aComplex[0])
|
1089
|
+
return Cluster.ByTopologies(cellComplexes+cells)
|
1090
|
+
|
1091
|
+
@staticmethod
|
1092
|
+
def MysticRose(wire: topologic.Wire = None, origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, perimeter: bool = True, direction: list = [0, 0, 1], placement:str = "center", tolerance: float = 0.0001) -> topologic.Cluster:
|
1093
|
+
"""
|
1094
|
+
Creates a mystic rose.
|
1095
|
+
|
1096
|
+
Parameters
|
1097
|
+
----------
|
1098
|
+
wire : topologic.Wire , optional
|
1099
|
+
The input Wire. if set to None, a circle with the input parameters is created. Otherwise, the input parameters are ignored.
|
1100
|
+
origin : topologic.Vertex , optional
|
1101
|
+
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
|
1102
|
+
radius : float , optional
|
1103
|
+
The radius of the mystic rose. The default is 1.
|
1104
|
+
sides : int , optional
|
1105
|
+
The number of sides of the mystic rose. The default is 16.
|
1106
|
+
perimeter : bool , optional
|
1107
|
+
If True, the perimeter edges are included in the output. The default is True.
|
1108
|
+
direction : list , optional
|
1109
|
+
The vector representing the up direction of the mystic rose. The default is [0, 0, 1].
|
1110
|
+
placement : str , optional
|
1111
|
+
The description of the placement of the origin of the mystic rose. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
|
1112
|
+
tolerance : float , optional
|
1113
|
+
The desired tolerance. The default is 0.0001.
|
1114
|
+
|
1115
|
+
Returns
|
1116
|
+
-------
|
1117
|
+
topologic.cluster
|
1118
|
+
The created mystic rose (cluster of edges).
|
1119
|
+
|
1120
|
+
"""
|
1121
|
+
import topologicpy
|
1122
|
+
from topologicpy.Vertex import Vertex
|
1123
|
+
from topologicpy.Edge import Edge
|
1124
|
+
from topologicpy.Wire import Wire
|
1125
|
+
from topologicpy.Cluster import Cluster
|
1126
|
+
from itertools import combinations
|
1127
|
+
|
1128
|
+
if wire == None:
|
1129
|
+
wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=0, toAngle=360, close=True, direction=direction, placement=placement, tolerance=tolerance)
|
1130
|
+
if not Wire.IsClosed(wire):
|
1131
|
+
print("Cluster.MysticRose - Error: The input wire parameter is not a closed topologic wire. Returning None.")
|
1132
|
+
return None
|
1133
|
+
vertices = Wire.Vertices(wire)
|
1134
|
+
indices = list(range(len(vertices)))
|
1135
|
+
combs = [[comb[0],comb[1]] for comb in combinations(indices, 2) if not (abs(comb[0]-comb[1]) == 1) and not (abs(comb[0]-comb[1]) == len(indices)-1)]
|
1136
|
+
edges = []
|
1137
|
+
if perimeter:
|
1138
|
+
edges = Wire.Edges(wire)
|
1139
|
+
for comb in combs:
|
1140
|
+
edges.append(Edge.ByVertices([vertices[comb[0]], vertices[comb[1]]], tolerance=tolerance))
|
1141
|
+
return Cluster.ByTopologies(edges)
|
1142
|
+
|
1143
|
+
@staticmethod
|
1144
|
+
def Shells(cluster: topologic.Cluster) -> list:
|
1145
|
+
"""
|
1146
|
+
Returns the shells of the input cluster.
|
1147
|
+
|
1148
|
+
Parameters
|
1149
|
+
----------
|
1150
|
+
cluster : topologic.Cluster
|
1151
|
+
The input cluster.
|
1152
|
+
|
1153
|
+
Returns
|
1154
|
+
-------
|
1155
|
+
list
|
1156
|
+
The list of shells.
|
1157
|
+
|
1158
|
+
"""
|
1159
|
+
if not isinstance(cluster, topologic.Cluster):
|
1160
|
+
print("Cluster.Shells - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
1161
|
+
return None
|
1162
|
+
shells = []
|
1163
|
+
_ = cluster.Shells(None, shells)
|
1164
|
+
return shells
|
1165
|
+
|
1166
|
+
@staticmethod
|
1167
|
+
def Simplify(cluster: topologic.Cluster):
|
1168
|
+
"""
|
1169
|
+
Simplifies the input cluster if possible. For example, if the cluster contains only one cell, that cell is returned.
|
1170
|
+
|
1171
|
+
Parameters
|
1172
|
+
----------
|
1173
|
+
cluster : topologic.Cluster
|
1174
|
+
The input cluster.
|
1175
|
+
|
1176
|
+
Returns
|
1177
|
+
-------
|
1178
|
+
topologic.Topology or list
|
1179
|
+
The simplification of the cluster.
|
1180
|
+
|
1181
|
+
"""
|
1182
|
+
if not isinstance(cluster, topologic.Cluster):
|
1183
|
+
print("Cluster.Simplify - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
1184
|
+
return None
|
1185
|
+
resultingTopologies = []
|
1186
|
+
topCC = []
|
1187
|
+
_ = cluster.CellComplexes(None, topCC)
|
1188
|
+
topCells = []
|
1189
|
+
_ = cluster.Cells(None, topCells)
|
1190
|
+
topShells = []
|
1191
|
+
_ = cluster.Shells(None, topShells)
|
1192
|
+
topFaces = []
|
1193
|
+
_ = cluster.Faces(None, topFaces)
|
1194
|
+
topWires = []
|
1195
|
+
_ = cluster.Wires(None, topWires)
|
1196
|
+
topEdges = []
|
1197
|
+
_ = cluster.Edges(None, topEdges)
|
1198
|
+
topVertices = []
|
1199
|
+
_ = cluster.Vertices(None, topVertices)
|
1200
|
+
if len(topCC) == 1:
|
1201
|
+
cc = topCC[0]
|
1202
|
+
ccVertices = []
|
1203
|
+
_ = cc.Vertices(None, ccVertices)
|
1204
|
+
if len(topVertices) == len(ccVertices):
|
1205
|
+
resultingTopologies.append(cc)
|
1206
|
+
if len(topCC) == 0 and len(topCells) == 1:
|
1207
|
+
cell = topCells[0]
|
1208
|
+
ccVertices = []
|
1209
|
+
_ = cell.Vertices(None, ccVertices)
|
1210
|
+
if len(topVertices) == len(ccVertices):
|
1211
|
+
resultingTopologies.append(cell)
|
1212
|
+
if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 1:
|
1213
|
+
shell = topShells[0]
|
1214
|
+
ccVertices = []
|
1215
|
+
_ = shell.Vertices(None, ccVertices)
|
1216
|
+
if len(topVertices) == len(ccVertices):
|
1217
|
+
resultingTopologies.append(shell)
|
1218
|
+
if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 1:
|
1219
|
+
face = topFaces[0]
|
1220
|
+
ccVertices = []
|
1221
|
+
_ = face.Vertices(None, ccVertices)
|
1222
|
+
if len(topVertices) == len(ccVertices):
|
1223
|
+
resultingTopologies.append(face)
|
1224
|
+
if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 1:
|
1225
|
+
wire = topWires[0]
|
1226
|
+
ccVertices = []
|
1227
|
+
_ = wire.Vertices(None, ccVertices)
|
1228
|
+
if len(topVertices) == len(ccVertices):
|
1229
|
+
resultingTopologies.append(wire)
|
1230
|
+
if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 1:
|
1231
|
+
edge = topEdges[0]
|
1232
|
+
ccVertices = []
|
1233
|
+
_ = wire.Vertices(None, ccVertices)
|
1234
|
+
if len(topVertices) == len(ccVertices):
|
1235
|
+
resultingTopologies.append(edge)
|
1236
|
+
if len(topCC) == 0 and len(topCells) == 0 and len(topShells) == 0 and len(topFaces) == 0 and len(topWires) == 0 and len(topEdges) == 0 and len(topVertices) == 1:
|
1237
|
+
vertex = topVertices[0]
|
1238
|
+
resultingTopologies.append(vertex)
|
1239
|
+
if len(resultingTopologies) == 1:
|
1240
|
+
return resultingTopologies[0]
|
1241
|
+
return cluster
|
1242
|
+
|
1243
|
+
@staticmethod
|
1244
|
+
def Vertices(cluster: topologic.Cluster) -> list:
|
1245
|
+
"""
|
1246
|
+
Returns the vertices of the input cluster.
|
1247
|
+
|
1248
|
+
Parameters
|
1249
|
+
----------
|
1250
|
+
cluster : topologic.Cluster
|
1251
|
+
The input cluster.
|
1252
|
+
|
1253
|
+
Returns
|
1254
|
+
-------
|
1255
|
+
list
|
1256
|
+
The list of vertices.
|
1257
|
+
|
1258
|
+
"""
|
1259
|
+
if not isinstance(cluster, topologic.Cluster):
|
1260
|
+
print("Cluster.Vertices - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
1261
|
+
return None
|
1262
|
+
vertices = []
|
1263
|
+
_ = cluster.Vertices(None, vertices)
|
1264
|
+
return vertices
|
1265
|
+
|
1266
|
+
@staticmethod
|
1267
|
+
def Wires(cluster: topologic.Cluster) -> list:
|
1268
|
+
"""
|
1269
|
+
Returns the wires of the input cluster.
|
1270
|
+
|
1271
|
+
Parameters
|
1272
|
+
----------
|
1273
|
+
cluster : topologic.Cluster
|
1274
|
+
The input cluster.
|
1275
|
+
|
1276
|
+
Returns
|
1277
|
+
-------
|
1278
|
+
list
|
1279
|
+
The list of wires.
|
1280
|
+
|
1281
|
+
"""
|
1282
|
+
if not isinstance(cluster, topologic.Cluster):
|
1283
|
+
print("Cluster.Wires - Error: The input cluster parameter is not a valid topologic cluster. Returning None.")
|
1284
|
+
return None
|
1285
|
+
wires = []
|
1286
|
+
_ = cluster.Wires(None, wires)
|
1287
|
+
return wires
|
1288
|
+
|
1281
1289
|
|