pyedb 0.13.0__py3-none-any.whl → 0.14.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.

Potentially problematic release.


This version of pyedb might be problematic. Click here for more details.

Files changed (75) hide show
  1. pyedb/__init__.py +1 -1
  2. pyedb/configuration/cfg_common.py +0 -5
  3. pyedb/configuration/cfg_components.py +0 -2
  4. pyedb/configuration/cfg_operations.py +0 -2
  5. pyedb/configuration/cfg_package_definition.py +0 -2
  6. pyedb/configuration/cfg_ports_sources.py +14 -11
  7. pyedb/configuration/cfg_stackup.py +0 -7
  8. pyedb/configuration/configuration.py +0 -6
  9. pyedb/dotnet/application/Variables.py +4 -40
  10. pyedb/dotnet/edb.py +27 -82
  11. pyedb/dotnet/edb_core/{edb_data/components_data.py → cell/hierarchy/component.py} +13 -133
  12. pyedb/dotnet/edb_core/cell/hierarchy/model.py +0 -3
  13. pyedb/dotnet/edb_core/cell/hierarchy/netlist_model.py +30 -0
  14. pyedb/dotnet/edb_core/cell/hierarchy/pin_pair_model.py +105 -0
  15. pyedb/dotnet/edb_core/cell/hierarchy/s_parameter_model.py +34 -0
  16. pyedb/dotnet/edb_core/cell/hierarchy/spice_model.py +34 -0
  17. pyedb/dotnet/edb_core/cell/layout.py +137 -0
  18. pyedb/dotnet/edb_core/cell/layout_obj.py +2 -4
  19. pyedb/dotnet/edb_core/cell/primitive.py +199 -1
  20. pyedb/dotnet/edb_core/cell/terminal/bundle_terminal.py +52 -0
  21. pyedb/dotnet/edb_core/cell/terminal/edge_terminal.py +50 -0
  22. pyedb/dotnet/edb_core/cell/terminal/padstack_instance_terminal.py +88 -0
  23. pyedb/dotnet/edb_core/cell/terminal/pingroup_terminal.py +59 -0
  24. pyedb/dotnet/edb_core/cell/terminal/point_terminal.py +73 -0
  25. pyedb/dotnet/edb_core/{edb_data/terminals.py → cell/terminal/terminal.py} +33 -242
  26. pyedb/dotnet/edb_core/components.py +10 -56
  27. pyedb/dotnet/edb_core/definition/component_def.py +1 -8
  28. pyedb/dotnet/edb_core/definition/component_model.py +0 -2
  29. pyedb/dotnet/edb_core/definition/definitions.py +0 -2
  30. pyedb/dotnet/edb_core/definition/package_def.py +7 -5
  31. pyedb/dotnet/edb_core/edb_data/control_file.py +0 -3
  32. pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +0 -5
  33. pyedb/dotnet/edb_core/edb_data/hfss_pi_simulation_setup_data.py +4 -9
  34. pyedb/dotnet/edb_core/edb_data/layer_data.py +0 -7
  35. pyedb/dotnet/edb_core/edb_data/nets_data.py +2 -5
  36. pyedb/dotnet/edb_core/edb_data/padstacks_data.py +11 -29
  37. pyedb/dotnet/edb_core/edb_data/ports.py +4 -4
  38. pyedb/dotnet/edb_core/edb_data/primitives_data.py +3 -26
  39. pyedb/dotnet/edb_core/edb_data/raptor_x_simulation_setup_data.py +13 -20
  40. pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +3 -11
  41. pyedb/dotnet/edb_core/edb_data/sources.py +12 -17
  42. pyedb/dotnet/edb_core/general.py +1 -6
  43. pyedb/dotnet/edb_core/geometry/polygon_data.py +0 -3
  44. pyedb/dotnet/edb_core/hfss.py +1 -33
  45. pyedb/dotnet/edb_core/layout.py +0 -35
  46. pyedb/dotnet/edb_core/layout_validation.py +1 -3
  47. pyedb/dotnet/edb_core/materials.py +1 -22
  48. pyedb/dotnet/edb_core/net_class.py +0 -8
  49. pyedb/dotnet/edb_core/nets.py +4 -29
  50. pyedb/dotnet/edb_core/padstack.py +76 -30
  51. pyedb/dotnet/edb_core/sim_setup_data/data/adaptive_frequency_data.py +72 -0
  52. pyedb/dotnet/edb_core/sim_setup_data/data/mesh_operation.py +287 -0
  53. pyedb/dotnet/edb_core/{edb_data/hfss_simulation_setup_data.py → sim_setup_data/data/settings.py} +174 -878
  54. pyedb/dotnet/edb_core/sim_setup_data/data/sweep_data.py +509 -0
  55. pyedb/dotnet/edb_core/sim_setup_data/io/__init__.py +0 -0
  56. pyedb/dotnet/edb_core/{edb_data/siwave_simulation_setup_data.py → sim_setup_data/io/siwave.py} +0 -341
  57. pyedb/dotnet/edb_core/siwave.py +5 -33
  58. pyedb/dotnet/edb_core/stackup.py +4 -51
  59. pyedb/dotnet/edb_core/utilities/simulation_setup.py +612 -366
  60. pyedb/generic/data_handlers.py +1 -9
  61. pyedb/generic/general_methods.py +3 -53
  62. pyedb/generic/plot.py +1 -2
  63. pyedb/ipc2581/ecad/cad_data/layer_feature.py +1 -7
  64. pyedb/ipc2581/ecad/cad_data/package.py +1 -4
  65. pyedb/ipc2581/ecad/cad_data/path.py +1 -3
  66. pyedb/ipc2581/ecad/cad_data/polygon.py +1 -6
  67. pyedb/ipc2581/ecad/cad_data/step.py +1 -10
  68. pyedb/ipc2581/ipc2581.py +8 -15
  69. pyedb/modeler/geometry_operators.py +164 -67
  70. pyedb/siwave.py +1 -16
  71. {pyedb-0.13.0.dist-info → pyedb-0.14.0.dist-info}/METADATA +2 -2
  72. {pyedb-0.13.0.dist-info → pyedb-0.14.0.dist-info}/RECORD +75 -61
  73. /pyedb/dotnet/edb_core/cell/{__init__.py → terminal/__init__.py} +0 -0
  74. {pyedb-0.13.0.dist-info → pyedb-0.14.0.dist-info}/LICENSE +0 -0
  75. {pyedb-0.13.0.dist-info → pyedb-0.14.0.dist-info}/WHEEL +0 -0
@@ -1,17 +1,18 @@
1
1
  # -*- coding: utf-8 -*-
2
+ from collections import defaultdict
2
3
  import math
3
4
  import re
4
5
  import sys
5
6
 
7
+ import numpy as np
8
+
6
9
  from pyedb.generic.constants import AXIS, PLANE, SWEEPDRAFT, scale_units
7
- from pyedb.generic.general_methods import pyedb_function_handler
8
10
 
9
11
 
10
12
  class GeometryOperators(object):
11
13
  """Manages geometry operators."""
12
14
 
13
15
  @staticmethod
14
- @pyedb_function_handler()
15
16
  def List2list(input_list): # pragma: no cover
16
17
  """Convert a C# list object to a Python list.
17
18
 
@@ -37,7 +38,6 @@ class GeometryOperators(object):
37
38
  return output_list
38
39
 
39
40
  @staticmethod
40
- @pyedb_function_handler()
41
41
  def parse_dim_arg(string, scale_to_unit=None, variable_manager=None): # pragma: no cover
42
42
  """Convert a number and unit to a float.
43
43
  Angles are converted in radians.
@@ -106,7 +106,6 @@ class GeometryOperators(object):
106
106
  return value
107
107
 
108
108
  @staticmethod
109
- @pyedb_function_handler()
110
109
  def cs_plane_to_axis_str(val): # pragma: no cover
111
110
  """Retrieve a string for a coordinate system plane.
112
111
 
@@ -129,7 +128,6 @@ class GeometryOperators(object):
129
128
  return "Y"
130
129
 
131
130
  @staticmethod
132
- @pyedb_function_handler()
133
131
  def cs_plane_to_plane_str(val): # pragma: no cover
134
132
  """Retrieve a string for a coordinate system plane.
135
133
 
@@ -152,7 +150,6 @@ class GeometryOperators(object):
152
150
  return "ZX"
153
151
 
154
152
  @staticmethod
155
- @pyedb_function_handler()
156
153
  def cs_axis_str(val): # pragma: no cover
157
154
  """Retrieve a string for a coordinate system axis.
158
155
 
@@ -176,7 +173,6 @@ class GeometryOperators(object):
176
173
  return "Z"
177
174
 
178
175
  @staticmethod
179
- @pyedb_function_handler()
180
176
  def draft_type_str(val): # pragma: no cover
181
177
  """Retrieve the draft type.
182
178
 
@@ -199,7 +195,6 @@ class GeometryOperators(object):
199
195
  return "Natural"
200
196
 
201
197
  @staticmethod
202
- @pyedb_function_handler()
203
198
  def get_mid_point(v1, v2):
204
199
  """Evaluate the midpoint between two points.
205
200
 
@@ -220,7 +215,6 @@ class GeometryOperators(object):
220
215
  return m
221
216
 
222
217
  @staticmethod
223
- @pyedb_function_handler()
224
218
  def get_triangle_area(v1, v2, v3): # pragma: no cover
225
219
  """Evaluate the area of a triangle defined by its three vertices.
226
220
 
@@ -249,7 +243,6 @@ class GeometryOperators(object):
249
243
  return area
250
244
 
251
245
  @staticmethod
252
- @pyedb_function_handler()
253
246
  def v_cross(a, b): # pragma: no cover
254
247
  """Evaluate the cross product of two geometry vectors.
255
248
 
@@ -269,7 +262,6 @@ class GeometryOperators(object):
269
262
  return c
270
263
 
271
264
  @staticmethod
272
- @pyedb_function_handler()
273
265
  def _v_dot(a, b): # pragma: no cover
274
266
  """Evaluate the dot product between two geometry vectors.
275
267
 
@@ -295,7 +287,6 @@ class GeometryOperators(object):
295
287
  return False
296
288
 
297
289
  @staticmethod
298
- @pyedb_function_handler()
299
290
  def v_dot(a, b): # pragma: no cover
300
291
  """Evaluate the dot product between two geometry vectors.
301
292
 
@@ -315,7 +306,6 @@ class GeometryOperators(object):
315
306
  return GeometryOperators._v_dot(a, b)
316
307
 
317
308
  @staticmethod
318
- @pyedb_function_handler()
319
309
  def v_prod(s, v): # pragma: no cover
320
310
  """Evaluate the product between a scalar value and a vector.
321
311
 
@@ -338,7 +328,6 @@ class GeometryOperators(object):
338
328
  return r
339
329
 
340
330
  @staticmethod
341
- @pyedb_function_handler()
342
331
  def v_rotate_about_axis(vector, angle, radians=False, axis="z"): # pragma: no cover
343
332
  """Evaluate rotation of a vector around an axis.
344
333
 
@@ -380,7 +369,6 @@ class GeometryOperators(object):
380
369
  return rotated_x, rotated_y, rotated_z
381
370
 
382
371
  @staticmethod
383
- @pyedb_function_handler()
384
372
  def v_sub(a, b): # pragma: no cover
385
373
  """Evaluate two geometry vectors by subtracting them (a-b).
386
374
 
@@ -401,7 +389,6 @@ class GeometryOperators(object):
401
389
  return c
402
390
 
403
391
  @staticmethod
404
- @pyedb_function_handler()
405
392
  def v_sum(a, b): # pragma: no cover
406
393
  """Evaluate two geometry vectors by adding them (a+b).
407
394
 
@@ -422,7 +409,6 @@ class GeometryOperators(object):
422
409
  return c
423
410
 
424
411
  @staticmethod
425
- @pyedb_function_handler()
426
412
  def v_norm(a): # pragma: no cover
427
413
  """Evaluate the Euclidean norm of a geometry vector.
428
414
 
@@ -444,7 +430,6 @@ class GeometryOperators(object):
444
430
  return m
445
431
 
446
432
  @staticmethod
447
- @pyedb_function_handler()
448
433
  def normalize_vector(v): # pragma: no cover
449
434
  """Normalize a geometry vector.
450
435
 
@@ -465,7 +450,6 @@ class GeometryOperators(object):
465
450
  return vn
466
451
 
467
452
  @staticmethod
468
- @pyedb_function_handler()
469
453
  def v_points(p1, p2): # pragma: no cover
470
454
  """Vector from one point to another point.
471
455
 
@@ -484,7 +468,6 @@ class GeometryOperators(object):
484
468
  return GeometryOperators.v_sub(p2, p1)
485
469
 
486
470
  @staticmethod
487
- @pyedb_function_handler()
488
471
  def points_distance(p1, p2): # pragma: no cover
489
472
  """Evaluate the distance between two points expressed as their Cartesian coordinates.
490
473
 
@@ -510,7 +493,6 @@ class GeometryOperators(object):
510
493
  # fmt: on
511
494
 
512
495
  @staticmethod
513
- @pyedb_function_handler()
514
496
  def find_point_on_plane(pointlists, direction=0): # pragma: no cover
515
497
  """Find a point on a plane.
516
498
 
@@ -539,7 +521,6 @@ class GeometryOperators(object):
539
521
  return point
540
522
 
541
523
  @staticmethod
542
- @pyedb_function_handler()
543
524
  def distance_vector(p, a, b): # pragma: no cover
544
525
  """Evaluate the vector distance between point ``p`` and a line defined by two points, ``a`` and ``b``.
545
526
 
@@ -571,7 +552,6 @@ class GeometryOperators(object):
571
552
  return vd
572
553
 
573
554
  @staticmethod
574
- @pyedb_function_handler()
575
555
  def is_between_points(p, a, b, tol=1e-6): # pragma: no cover
576
556
  """Check if a point lies on the segment defined by two points.
577
557
 
@@ -604,7 +584,6 @@ class GeometryOperators(object):
604
584
  return True
605
585
 
606
586
  @staticmethod
607
- @pyedb_function_handler()
608
587
  def is_parallel(a1, a2, b1, b2, tol=1e-6): # pragma: no cover
609
588
  """Check if a segment defined by two points is parallel to a segment defined by two other points.
610
589
 
@@ -633,7 +612,6 @@ class GeometryOperators(object):
633
612
  return False
634
613
 
635
614
  @staticmethod
636
- @pyedb_function_handler()
637
615
  def parallel_coeff(a1, a2, b1, b2): # pragma: no cover
638
616
  """ADD DESCRIPTION.
639
617
 
@@ -661,7 +639,6 @@ class GeometryOperators(object):
661
639
  return abs(var)
662
640
 
663
641
  @staticmethod
664
- @pyedb_function_handler()
665
642
  def is_collinear(a, b, tol=1e-6): # pragma: no cover
666
643
  """Check if two vectors are collinear (parallel or anti-parallel).
667
644
 
@@ -689,7 +666,6 @@ class GeometryOperators(object):
689
666
  return False
690
667
 
691
668
  @staticmethod
692
- @pyedb_function_handler()
693
669
  def is_projection_inside(a1, a2, b1, b2): # pragma: no cover
694
670
  """Project a segment onto another segment and check if the projected segment is inside it.
695
671
 
@@ -722,7 +698,6 @@ class GeometryOperators(object):
722
698
  return True
723
699
 
724
700
  @staticmethod
725
- @pyedb_function_handler()
726
701
  def arrays_positions_sum(vertlist1, vertlist2): # pragma: no cover
727
702
  """Return the sum of two vertices lists.
728
703
 
@@ -744,7 +719,6 @@ class GeometryOperators(object):
744
719
  return s / (len(vertlist1) + len(vertlist2))
745
720
 
746
721
  @staticmethod
747
- @pyedb_function_handler()
748
722
  def v_angle(a, b): # pragma: no cover
749
723
  """Evaluate the angle between two geometry vectors.
750
724
 
@@ -770,7 +744,6 @@ class GeometryOperators(object):
770
744
  return math.acos(d / (an * bn))
771
745
 
772
746
  @staticmethod
773
- @pyedb_function_handler()
774
747
  def pointing_to_axis(x_pointing, y_pointing): # pragma: no cover
775
748
  """Retrieve the axes from the HFSS X axis and Y pointing axis as per
776
749
  the definition of the AEDT interface coordinate system.
@@ -798,7 +771,6 @@ class GeometryOperators(object):
798
771
  return xp, yp, zp
799
772
 
800
773
  @staticmethod
801
- @pyedb_function_handler()
802
774
  def axis_to_euler_zxz(x, y, z): # pragma: no cover
803
775
  """Retrieve Euler angles of a frame following the rotation sequence ZXZ.
804
776
 
@@ -842,7 +814,6 @@ class GeometryOperators(object):
842
814
  return phi, theta, psi
843
815
 
844
816
  @staticmethod
845
- @pyedb_function_handler()
846
817
  def axis_to_euler_zyz(x, y, z): # pragma: no cover
847
818
  """Retrieve Euler angles of a frame following the rotation sequence ZYZ.
848
819
 
@@ -886,7 +857,6 @@ class GeometryOperators(object):
886
857
  return phi, theta, psi
887
858
 
888
859
  @staticmethod
889
- @pyedb_function_handler()
890
860
  def quaternion_to_axis(q): # pragma: no cover
891
861
  """Convert a quaternion to a rotated frame defined by X, Y, and Z axes.
892
862
 
@@ -925,7 +895,6 @@ class GeometryOperators(object):
925
895
  return x, y, z
926
896
 
927
897
  @staticmethod
928
- @pyedb_function_handler()
929
898
  def quaternion_to_axis_angle(q): # pragma: no cover
930
899
  """Convert a quaternion to the axis angle rotation formulation.
931
900
 
@@ -951,7 +920,6 @@ class GeometryOperators(object):
951
920
  return u, theta
952
921
 
953
922
  @staticmethod
954
- @pyedb_function_handler()
955
923
  def axis_angle_to_quaternion(u, theta): # pragma: no cover
956
924
  """Convert the axis angle rotation formulation to a quaternion.
957
925
 
@@ -978,7 +946,6 @@ class GeometryOperators(object):
978
946
  return [q1, q2, q3, q4]
979
947
 
980
948
  @staticmethod
981
- @pyedb_function_handler()
982
949
  def quaternion_to_euler_zxz(q): # pragma: no cover
983
950
  """Convert a quaternion to Euler angles following rotation sequence ZXZ.
984
951
 
@@ -1008,7 +975,6 @@ class GeometryOperators(object):
1008
975
  return phi, theta, psi
1009
976
 
1010
977
  @staticmethod
1011
- @pyedb_function_handler()
1012
978
  def euler_zxz_to_quaternion(phi, theta, psi): # pragma: no cover
1013
979
  """Convert the Euler angles following rotation sequence ZXZ to a quaternion.
1014
980
 
@@ -1039,7 +1005,6 @@ class GeometryOperators(object):
1039
1005
  return [q1, q2, q3, q4]
1040
1006
 
1041
1007
  @staticmethod
1042
- @pyedb_function_handler()
1043
1008
  def quaternion_to_euler_zyz(q): # pragma: no cover
1044
1009
  """Convert a quaternion to Euler angles following rotation sequence ZYZ.
1045
1010
 
@@ -1069,7 +1034,6 @@ class GeometryOperators(object):
1069
1034
  return phi, theta, psi
1070
1035
 
1071
1036
  @staticmethod
1072
- @pyedb_function_handler()
1073
1037
  def euler_zyz_to_quaternion(phi, theta, psi): # pragma: no cover
1074
1038
  """Convert the Euler angles following rotation sequence ZYZ to a quaternion.
1075
1039
 
@@ -1100,7 +1064,6 @@ class GeometryOperators(object):
1100
1064
  return [q1, q2, q3, q4]
1101
1065
 
1102
1066
  @staticmethod
1103
- @pyedb_function_handler()
1104
1067
  def deg2rad(angle):
1105
1068
  """Convert the angle from degrees to radians.
1106
1069
 
@@ -1119,7 +1082,6 @@ class GeometryOperators(object):
1119
1082
  return angle / 180.0 * pi
1120
1083
 
1121
1084
  @staticmethod
1122
- @pyedb_function_handler()
1123
1085
  def rad2deg(angle):
1124
1086
  """Convert the angle from radians to degrees.
1125
1087
 
@@ -1138,7 +1100,6 @@ class GeometryOperators(object):
1138
1100
  return angle * 180.0 / pi
1139
1101
 
1140
1102
  @staticmethod
1141
- @pyedb_function_handler()
1142
1103
  def atan2(y, x): # pragma: no cover
1143
1104
  """Implementation of atan2 that does not suffer from the following issues:
1144
1105
  math.atan2(0.0, 0.0) = 0.0
@@ -1168,7 +1129,6 @@ class GeometryOperators(object):
1168
1129
  return math.atan2(y, x)
1169
1130
 
1170
1131
  @staticmethod
1171
- @pyedb_function_handler()
1172
1132
  def q_prod(p, q): # pragma: no cover
1173
1133
  """Evaluate the product of two quaternions, ``p`` and ``q``, defined as:
1174
1134
  p = p0 + p' = p0 + ip1 + jp2 + kp3.
@@ -1204,7 +1164,6 @@ class GeometryOperators(object):
1204
1164
  return [r0, rv[0], rv[1], rv[2]]
1205
1165
 
1206
1166
  @staticmethod
1207
- @pyedb_function_handler()
1208
1167
  def q_rotation(v, q): # pragma: no cover
1209
1168
  """Evaluate the rotation of a vector, defined by a quaternion.
1210
1169
  Evaluated as:
@@ -1240,7 +1199,6 @@ class GeometryOperators(object):
1240
1199
  return w
1241
1200
 
1242
1201
  @staticmethod
1243
- @pyedb_function_handler()
1244
1202
  def q_rotation_inv(v, q):
1245
1203
  """Evaluate the inverse rotation of a vector that is defined by a quaternion.
1246
1204
 
@@ -1268,7 +1226,6 @@ class GeometryOperators(object):
1268
1226
  return GeometryOperators.q_rotation(v, q1)
1269
1227
 
1270
1228
  @staticmethod
1271
- @pyedb_function_handler()
1272
1229
  def get_polygon_centroid(pts): # pragma: no cover
1273
1230
  """Evaluate the centroid of a polygon defined by its points.
1274
1231
 
@@ -1304,7 +1261,6 @@ class GeometryOperators(object):
1304
1261
  return [xc, yc, zc]
1305
1262
 
1306
1263
  @staticmethod
1307
- @pyedb_function_handler()
1308
1264
  def cs_xy_pointing_expression(yaw, pitch, roll): # pragma: no cover
1309
1265
  """Return x_pointing and y_pointing vectors as expressions from
1310
1266
  the yaw, ptich, and roll input (as strings).
@@ -1343,7 +1299,6 @@ class GeometryOperators(object):
1343
1299
  return [x_pointing, y_pointing]
1344
1300
 
1345
1301
  @staticmethod
1346
- @pyedb_function_handler()
1347
1302
  def get_numeric(s):
1348
1303
  """Convert a string to a numeric value. Discard the suffix."""
1349
1304
  if type(s) == str:
@@ -1357,7 +1312,6 @@ class GeometryOperators(object):
1357
1312
  return float(s)
1358
1313
 
1359
1314
  @staticmethod
1360
- @pyedb_function_handler()
1361
1315
  def is_small(s):
1362
1316
  """Return ``True`` if the number represented by s is zero (i.e very small).
1363
1317
 
@@ -1375,7 +1329,6 @@ class GeometryOperators(object):
1375
1329
  return True if math.fabs(n) < 2.0 * abs(sys.float_info.epsilon) else False
1376
1330
 
1377
1331
  @staticmethod
1378
- @pyedb_function_handler()
1379
1332
  def numeric_cs(cs_in): # pragma: no cover
1380
1333
  """Return a list of [x,y,z] numeric values given a coordinate system as input.
1381
1334
 
@@ -1396,7 +1349,6 @@ class GeometryOperators(object):
1396
1349
  return [0, 0, 0]
1397
1350
 
1398
1351
  @staticmethod
1399
- @pyedb_function_handler()
1400
1352
  def orient_polygon(x, y, clockwise=True):
1401
1353
  """
1402
1354
  Orient a polygon clockwise or counterclockwise. The vertices should be already ordered either way.
@@ -1472,7 +1424,6 @@ class GeometryOperators(object):
1472
1424
  return x_ret, y_ret
1473
1425
 
1474
1426
  @staticmethod
1475
- @pyedb_function_handler()
1476
1427
  def v_angle_sign(va, vb, vn, right_handed=True): # pragma: no cover
1477
1428
  """Evaluate the signed angle between two geometry vectors.
1478
1429
  The sign is evaluated respect to the normal to the plane containing the two vectors as per the following rule.
@@ -1517,7 +1468,6 @@ class GeometryOperators(object):
1517
1468
  return math.atan2(GeometryOperators.v_dot(mcross, vnn), GeometryOperators.v_dot(va, vb))
1518
1469
 
1519
1470
  @staticmethod
1520
- @pyedb_function_handler()
1521
1471
  def v_angle_sign_2D(va, vb, right_handed=True):
1522
1472
  """Evaluate the signed angle between two 2D geometry vectors.
1523
1473
  Iit the 2D version of the ``GeometryOperators.v_angle_sign`` considering vn = [0,0,1].
@@ -1547,7 +1497,6 @@ class GeometryOperators(object):
1547
1497
  return math.atan2(-c, GeometryOperators.v_dot(va, vb))
1548
1498
 
1549
1499
  @staticmethod
1550
- @pyedb_function_handler()
1551
1500
  def point_in_polygon(point, polygon, tolerance=1e-8):
1552
1501
  """Determine if a point is inside, outside the polygon or at exactly at the border.
1553
1502
 
@@ -1595,7 +1544,6 @@ class GeometryOperators(object):
1595
1544
  # fmt: on
1596
1545
 
1597
1546
  @staticmethod
1598
- @pyedb_function_handler()
1599
1547
  def is_point_in_polygon(point, polygon):
1600
1548
  """Determine if a point is inside or outside a polygon, both located on the same plane.
1601
1549
 
@@ -1619,7 +1567,6 @@ class GeometryOperators(object):
1619
1567
  return True
1620
1568
 
1621
1569
  @staticmethod
1622
- @pyedb_function_handler()
1623
1570
  def are_segments_intersecting(a1, a2, b1, b2, include_collinear=True):
1624
1571
  """
1625
1572
  Determine if the two segments a and b are intersecting.
@@ -1708,7 +1655,6 @@ class GeometryOperators(object):
1708
1655
  # fmt: on
1709
1656
 
1710
1657
  @staticmethod
1711
- @pyedb_function_handler()
1712
1658
  def is_segment_intersecting_polygon(a, b, polygon):
1713
1659
  """
1714
1660
  Determine if a segment defined by two points ``a`` and ``b`` intersects a polygon.
@@ -1743,7 +1689,6 @@ class GeometryOperators(object):
1743
1689
  return False
1744
1690
 
1745
1691
  @staticmethod
1746
- @pyedb_function_handler()
1747
1692
  def is_perpendicular(a, b, tol=1e-6):
1748
1693
  """Check if two vectors are perpendicular.
1749
1694
 
@@ -1769,7 +1714,6 @@ class GeometryOperators(object):
1769
1714
  return False
1770
1715
 
1771
1716
  @staticmethod
1772
- @pyedb_function_handler()
1773
1717
  def is_point_projection_in_segment(p, a, b):
1774
1718
  """Check if a point projection lies on the segment defined by two points.
1775
1719
 
@@ -1796,7 +1740,6 @@ class GeometryOperators(object):
1796
1740
  # fmt: on
1797
1741
 
1798
1742
  @staticmethod
1799
- @pyedb_function_handler()
1800
1743
  def point_segment_distance(p, a, b): # pragma: no cover
1801
1744
  """Calculate the distance between a point ``p`` and a segment defined by two points ``a`` and ``b``.
1802
1745
 
@@ -1822,7 +1765,6 @@ class GeometryOperators(object):
1822
1765
  # fmt: on
1823
1766
 
1824
1767
  @staticmethod
1825
- @pyedb_function_handler()
1826
1768
  def find_largest_rectangle_inside_polygon(polygon, partition_max_order=16):
1827
1769
  """Find the largest area rectangles of arbitrary orientation in a polygon.
1828
1770
 
@@ -1931,7 +1873,6 @@ class GeometryOperators(object):
1931
1873
  # fmt: on
1932
1874
 
1933
1875
  @staticmethod
1934
- @pyedb_function_handler()
1935
1876
  def degrees_over_rounded(angle, digits):
1936
1877
  """Ceil of angle.
1937
1878
 
@@ -1950,7 +1891,6 @@ class GeometryOperators(object):
1950
1891
  return math.ceil(math.degrees(angle) * 10**digits) / (10**digits)
1951
1892
 
1952
1893
  @staticmethod
1953
- @pyedb_function_handler()
1954
1894
  def radians_over_rounded(angle, digits):
1955
1895
  """Radian angle ceiling.
1956
1896
 
@@ -1969,7 +1909,6 @@ class GeometryOperators(object):
1969
1909
  return math.ceil(math.radians(angle) * 10**digits) / (10**digits)
1970
1910
 
1971
1911
  @staticmethod
1972
- @pyedb_function_handler()
1973
1912
  def degrees_default_rounded(angle, digits):
1974
1913
  """Convert angle to degree with given digits rounding.
1975
1914
 
@@ -1988,7 +1927,6 @@ class GeometryOperators(object):
1988
1927
  return math.floor(math.degrees(angle) * 10**digits) / (10**digits)
1989
1928
 
1990
1929
  @staticmethod
1991
- @pyedb_function_handler()
1992
1930
  def radians_default_rounded(angle, digits):
1993
1931
  """Convert to radians with given round.
1994
1932
 
@@ -2007,7 +1945,6 @@ class GeometryOperators(object):
2007
1945
  return math.floor(math.radians(angle) * 10**digits) / (10**digits)
2008
1946
 
2009
1947
  @staticmethod
2010
- @pyedb_function_handler()
2011
1948
  def find_closest_points(points_list, reference_point, tol=1e-6): # pragma: no cover
2012
1949
  """Given a list of points, finds the closest points to a reference point.
2013
1950
  It returns a list of points because more than one can be found.
@@ -2055,7 +1992,6 @@ class GeometryOperators(object):
2055
1992
  # fmt: on
2056
1993
 
2057
1994
  @staticmethod
2058
- @pyedb_function_handler()
2059
1995
  def mirror_point(start, reference, vector): # pragma: no cover
2060
1996
  """Mirror point about a plane defining by a point on the plane and a normal point.
2061
1997
 
@@ -2080,3 +2016,164 @@ class GeometryOperators(object):
2080
2016
  dot_product = sum([distance[i] * vector[i] for i in range(3)])
2081
2017
  reflection = [-dot_product * vector[i] * 2 + start[i] for i in range(3)]
2082
2018
  return reflection
2019
+
2020
+ @staticmethod
2021
+ def find_points_along_lines(
2022
+ points, minimum_number_of_points=3, distance_threshold=None, return_additional_info=False
2023
+ ):
2024
+ """Detect all points that are placed along lines.
2025
+
2026
+ The method takes as input a list of 2D points and detects all lines that contain at least 3 points.
2027
+ Optionally, the minimum number of points contained in a line can be specified by setting the
2028
+ argument ``minimum_number_of_points``.
2029
+ As default, all points along the lines are returned, regardless of their relative distance.
2030
+ Optionally, a `distance_threshold` can be set. If two points in a line are separated by a distance larger than
2031
+ ``distance_threshold``, the line is divided in two parts. If one of those parts does not satisfy the
2032
+ ``minimum_number_of_points`` requirement, it is discarded.
2033
+ If `distance_threshold` is set (not ``None``), the computational time increases.
2034
+
2035
+ points : List, numpy.ndarray
2036
+ The points to process. Can be a list of lists where each sublist
2037
+ represents a 2D point ``[x, y]`` coordinates, or a numpy array of shape (n, 2).
2038
+ minimum_number_of_points : int, optional
2039
+ The minimum number of points that a line must contain. Default is ``3``.
2040
+ distance_threshold : float, None, optional
2041
+ If two points in a line are separated by a distance larger than `distance_threshold`,
2042
+ the line is divided in two parts. Default is ``None``, in which case the control is not performed.
2043
+ return_additional_info : bool, optional
2044
+ Whether to return additional information about the number of elements processed.
2045
+ The default is ``True``.
2046
+
2047
+ Returns
2048
+ -------
2049
+ tuple
2050
+ The tuple contains:
2051
+ - lines: a list of lists where each sublist represents a 2D point ``[x, y]`` coordinates in each line.
2052
+ - lines indexes: a list of lists where each sublist represents the index of the point in each line.
2053
+ The index is referring to the point position in the input point list.
2054
+ - number of processed points: optional, returned if ``return_additional_info`` is ``True``
2055
+ - number of processed lines: optional, returned if ``return_additional_info`` is ``True``
2056
+ - number of detected lines after ``minimum_number_of_points`` is applied: optional,
2057
+ returned if ``return_additional_info`` is ``True``
2058
+ - number of detected lines after ``distance_threshold`` is applied: optional,
2059
+ returned if ``return_additional_info`` is ``True``
2060
+ """
2061
+
2062
+ # Parameters
2063
+ min_num_points = max(3, int(minimum_number_of_points))
2064
+ cluster_lines_flag = False if distance_threshold is None else True
2065
+ tol_rad = 1e-10
2066
+
2067
+ # Converts the input
2068
+ points = np.array(points)
2069
+ # Number of points
2070
+ num_points = len(points)
2071
+
2072
+ angles = []
2073
+ # Evaluate the angle with x-axis for every possible line defined by 2 points
2074
+ # Nested for loops to iterate over each point
2075
+ for i in range(num_points - 1):
2076
+ angles.append([])
2077
+ for j in range(i + 1, num_points):
2078
+ p1 = points[i]
2079
+ p2 = points[j]
2080
+ x1 = p1[0]
2081
+ y1 = p1[1]
2082
+ x2 = p2[0]
2083
+ y2 = p2[1]
2084
+
2085
+ dx = x2 - x1
2086
+ dy = y2 - y1
2087
+
2088
+ # Calculate the angle in radians with the x-axis
2089
+ angle_rad = np.arctan2(dy, dx)
2090
+ if angle_rad < 0:
2091
+ angles[i].append(angle_rad + np.pi)
2092
+ else:
2093
+ angles[i].append(angle_rad)
2094
+
2095
+ # rounding the angles float number
2096
+ def bin_float(value, bin_size):
2097
+ return round(value / bin_size) * bin_size
2098
+
2099
+ for col in angles:
2100
+ for i, a in enumerate(col):
2101
+ col[i] = bin_float(a, tol_rad)
2102
+
2103
+ # Group the lines based on angles
2104
+ detected_lines_idx = []
2105
+ for i in range(num_points - 1):
2106
+ column = angles[i]
2107
+ new_lines = defaultdict(set) # sets are more efficient than lists for inclusion check
2108
+ lines_to_check = [line for line in detected_lines_idx if i in line]
2109
+ for k in range(len(column)):
2110
+ j = k + i + 1
2111
+ angle = column[k]
2112
+
2113
+ # Check if both indexes (points) are in any of the sets (lines)
2114
+ # Note that lines containing `i` are pre-selected outside this loop.
2115
+ # This makes the check O(n^2) instead of O(n^3)
2116
+ found = any(j in l for l in lines_to_check)
2117
+ if found:
2118
+ # this two points already belong to a line, no need to store them again
2119
+ continue
2120
+ new_lines[angle].update((i, j))
2121
+ # considering only lines with at least 3 points
2122
+ lines_to_add = [l for l in new_lines.values() if len(l) >= 3]
2123
+ detected_lines_idx.extend(lines_to_add)
2124
+
2125
+ # Discard the lines with less than min_num_points
2126
+ selected_lines_idx = [line for line in detected_lines_idx if len(line) >= min_num_points]
2127
+
2128
+ # Convert the lines' indexes in ndarrays
2129
+ selected_lines_idx = [np.array(list(line)) for line in selected_lines_idx]
2130
+
2131
+ # First sort the points in the detected lines
2132
+ lines_with_sorted_points_idx = []
2133
+ for line_idx in selected_lines_idx:
2134
+ pts_in_line = points[list(line_idx)]
2135
+ min_x = pts_in_line[:, 0].min()
2136
+ max_x = pts_in_line[:, 0].max()
2137
+ if max_x - min_x < 1e-7:
2138
+ # cluster on y because the line is vertical
2139
+ sort_idx = pts_in_line[:, 1].argsort()
2140
+ else:
2141
+ # cluster on x on the other cases
2142
+ sort_idx = pts_in_line[:, 0].argsort()
2143
+ sorted_points_idx = line_idx[sort_idx]
2144
+ lines_with_sorted_points_idx.append(sorted_points_idx)
2145
+ lines_idx = lines_with_sorted_points_idx
2146
+
2147
+ def cluster_line_points(points_idx, threshold):
2148
+ # Requires sorted points, points must be in numpy array format
2149
+ # Initialize clusters
2150
+ clusters = []
2151
+ current_cluster = [points_idx[0]]
2152
+ # Iterate through the sorted points
2153
+ for i in range(1, len(points_idx)):
2154
+ # Check if the current point is within the distance threshold from the last point in the current cluster
2155
+ if np.linalg.norm(points[points_idx[i]] - points[points_idx[i - 1]]) <= threshold:
2156
+ current_cluster.append(points_idx[i])
2157
+ else:
2158
+ # The current point is too far from the last point, finalize the current cluster and start a new one
2159
+ clusters.append(current_cluster)
2160
+ current_cluster = [points_idx[i]]
2161
+ # Append the last cluster
2162
+ clusters.append(current_cluster)
2163
+ return clusters
2164
+
2165
+ # separate lines based on required minimum distance btw points
2166
+ # It requires the points of the line to be sorted
2167
+ if cluster_lines_flag:
2168
+ clustered_lines = []
2169
+ for p_idx in lines_with_sorted_points_idx:
2170
+ clusters_in_line = cluster_line_points(p_idx, distance_threshold)
2171
+ clustered_lines.extend([c for c in clusters_in_line if len(c) >= min_num_points])
2172
+ lines_idx = clustered_lines
2173
+
2174
+ # Convert indexes to points coordinates
2175
+ lines = [points[list(line)].tolist() for line in lines_idx]
2176
+
2177
+ if return_additional_info:
2178
+ return lines, lines_idx, num_points, len(detected_lines_idx), len(selected_lines_idx), len(lines_idx)
2179
+ return lines, lines_idx