pyedb 0.2.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 (128) hide show
  1. pyedb/__init__.py +17 -0
  2. pyedb/dotnet/__init__.py +0 -0
  3. pyedb/dotnet/application/Variables.py +2261 -0
  4. pyedb/dotnet/application/__init__.py +0 -0
  5. pyedb/dotnet/clr_module.py +103 -0
  6. pyedb/dotnet/edb.py +4237 -0
  7. pyedb/dotnet/edb_core/__init__.py +1 -0
  8. pyedb/dotnet/edb_core/cell/__init__.py +0 -0
  9. pyedb/dotnet/edb_core/cell/hierarchy/__init__.py +0 -0
  10. pyedb/dotnet/edb_core/cell/hierarchy/model.py +66 -0
  11. pyedb/dotnet/edb_core/components.py +2669 -0
  12. pyedb/dotnet/edb_core/configuration.py +423 -0
  13. pyedb/dotnet/edb_core/definition/__init__.py +0 -0
  14. pyedb/dotnet/edb_core/definition/component_def.py +166 -0
  15. pyedb/dotnet/edb_core/definition/component_model.py +30 -0
  16. pyedb/dotnet/edb_core/definition/definition_obj.py +18 -0
  17. pyedb/dotnet/edb_core/definition/definitions.py +12 -0
  18. pyedb/dotnet/edb_core/dotnet/__init__.py +0 -0
  19. pyedb/dotnet/edb_core/dotnet/database.py +1218 -0
  20. pyedb/dotnet/edb_core/dotnet/layout.py +238 -0
  21. pyedb/dotnet/edb_core/dotnet/primitive.py +1517 -0
  22. pyedb/dotnet/edb_core/edb_data/__init__.py +0 -0
  23. pyedb/dotnet/edb_core/edb_data/components_data.py +938 -0
  24. pyedb/dotnet/edb_core/edb_data/connectable.py +113 -0
  25. pyedb/dotnet/edb_core/edb_data/control_file.py +1268 -0
  26. pyedb/dotnet/edb_core/edb_data/design_options.py +35 -0
  27. pyedb/dotnet/edb_core/edb_data/edbvalue.py +45 -0
  28. pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +330 -0
  29. pyedb/dotnet/edb_core/edb_data/hfss_simulation_setup_data.py +1607 -0
  30. pyedb/dotnet/edb_core/edb_data/layer_data.py +576 -0
  31. pyedb/dotnet/edb_core/edb_data/nets_data.py +281 -0
  32. pyedb/dotnet/edb_core/edb_data/obj_base.py +19 -0
  33. pyedb/dotnet/edb_core/edb_data/padstacks_data.py +2080 -0
  34. pyedb/dotnet/edb_core/edb_data/ports.py +287 -0
  35. pyedb/dotnet/edb_core/edb_data/primitives_data.py +1397 -0
  36. pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +2914 -0
  37. pyedb/dotnet/edb_core/edb_data/simulation_setup.py +716 -0
  38. pyedb/dotnet/edb_core/edb_data/siwave_simulation_setup_data.py +1205 -0
  39. pyedb/dotnet/edb_core/edb_data/sources.py +514 -0
  40. pyedb/dotnet/edb_core/edb_data/terminals.py +632 -0
  41. pyedb/dotnet/edb_core/edb_data/utilities.py +148 -0
  42. pyedb/dotnet/edb_core/edb_data/variables.py +91 -0
  43. pyedb/dotnet/edb_core/general.py +181 -0
  44. pyedb/dotnet/edb_core/hfss.py +1646 -0
  45. pyedb/dotnet/edb_core/layout.py +1244 -0
  46. pyedb/dotnet/edb_core/layout_validation.py +272 -0
  47. pyedb/dotnet/edb_core/materials.py +939 -0
  48. pyedb/dotnet/edb_core/net_class.py +335 -0
  49. pyedb/dotnet/edb_core/nets.py +1215 -0
  50. pyedb/dotnet/edb_core/padstack.py +1389 -0
  51. pyedb/dotnet/edb_core/siwave.py +1427 -0
  52. pyedb/dotnet/edb_core/stackup.py +2703 -0
  53. pyedb/edb_logger.py +396 -0
  54. pyedb/generic/__init__.py +0 -0
  55. pyedb/generic/constants.py +1063 -0
  56. pyedb/generic/data_handlers.py +320 -0
  57. pyedb/generic/design_types.py +104 -0
  58. pyedb/generic/filesystem.py +150 -0
  59. pyedb/generic/general_methods.py +1535 -0
  60. pyedb/generic/plot.py +1840 -0
  61. pyedb/generic/process.py +285 -0
  62. pyedb/generic/settings.py +224 -0
  63. pyedb/ipc2581/__init__.py +0 -0
  64. pyedb/ipc2581/bom/__init__.py +0 -0
  65. pyedb/ipc2581/bom/bom.py +21 -0
  66. pyedb/ipc2581/bom/bom_item.py +32 -0
  67. pyedb/ipc2581/bom/characteristics.py +37 -0
  68. pyedb/ipc2581/bom/refdes.py +16 -0
  69. pyedb/ipc2581/content/__init__.py +0 -0
  70. pyedb/ipc2581/content/color.py +38 -0
  71. pyedb/ipc2581/content/content.py +55 -0
  72. pyedb/ipc2581/content/dictionary_color.py +29 -0
  73. pyedb/ipc2581/content/dictionary_fill.py +28 -0
  74. pyedb/ipc2581/content/dictionary_line.py +30 -0
  75. pyedb/ipc2581/content/entry_color.py +13 -0
  76. pyedb/ipc2581/content/entry_line.py +14 -0
  77. pyedb/ipc2581/content/fill.py +15 -0
  78. pyedb/ipc2581/content/layer_ref.py +10 -0
  79. pyedb/ipc2581/content/standard_geometries_dictionary.py +72 -0
  80. pyedb/ipc2581/ecad/__init__.py +0 -0
  81. pyedb/ipc2581/ecad/cad_data/__init__.py +0 -0
  82. pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +26 -0
  83. pyedb/ipc2581/ecad/cad_data/cad_data.py +37 -0
  84. pyedb/ipc2581/ecad/cad_data/component.py +41 -0
  85. pyedb/ipc2581/ecad/cad_data/drill.py +30 -0
  86. pyedb/ipc2581/ecad/cad_data/feature.py +54 -0
  87. pyedb/ipc2581/ecad/cad_data/layer.py +41 -0
  88. pyedb/ipc2581/ecad/cad_data/layer_feature.py +151 -0
  89. pyedb/ipc2581/ecad/cad_data/logical_net.py +32 -0
  90. pyedb/ipc2581/ecad/cad_data/outline.py +25 -0
  91. pyedb/ipc2581/ecad/cad_data/package.py +104 -0
  92. pyedb/ipc2581/ecad/cad_data/padstack_def.py +38 -0
  93. pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +24 -0
  94. pyedb/ipc2581/ecad/cad_data/padstack_instance.py +62 -0
  95. pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +26 -0
  96. pyedb/ipc2581/ecad/cad_data/path.py +89 -0
  97. pyedb/ipc2581/ecad/cad_data/phy_net.py +80 -0
  98. pyedb/ipc2581/ecad/cad_data/pin.py +31 -0
  99. pyedb/ipc2581/ecad/cad_data/polygon.py +169 -0
  100. pyedb/ipc2581/ecad/cad_data/profile.py +40 -0
  101. pyedb/ipc2581/ecad/cad_data/stackup.py +31 -0
  102. pyedb/ipc2581/ecad/cad_data/stackup_group.py +42 -0
  103. pyedb/ipc2581/ecad/cad_data/stackup_layer.py +21 -0
  104. pyedb/ipc2581/ecad/cad_data/step.py +275 -0
  105. pyedb/ipc2581/ecad/cad_header.py +33 -0
  106. pyedb/ipc2581/ecad/ecad.py +19 -0
  107. pyedb/ipc2581/ecad/spec.py +46 -0
  108. pyedb/ipc2581/history_record.py +37 -0
  109. pyedb/ipc2581/ipc2581.py +387 -0
  110. pyedb/ipc2581/logistic_header.py +25 -0
  111. pyedb/misc/__init__.py +0 -0
  112. pyedb/misc/aedtlib_personalib_install.py +14 -0
  113. pyedb/misc/downloads.py +322 -0
  114. pyedb/misc/misc.py +67 -0
  115. pyedb/misc/pyedb.runtimeconfig.json +13 -0
  116. pyedb/misc/siw_feature_config/__init__.py +0 -0
  117. pyedb/misc/siw_feature_config/emc/__init__.py +0 -0
  118. pyedb/misc/siw_feature_config/emc/component_tags.py +46 -0
  119. pyedb/misc/siw_feature_config/emc/net_tags.py +37 -0
  120. pyedb/misc/siw_feature_config/emc/tag_library.py +62 -0
  121. pyedb/misc/siw_feature_config/emc/xml_generic.py +78 -0
  122. pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +179 -0
  123. pyedb/misc/utilities.py +27 -0
  124. pyedb/modeler/geometry_operators.py +2082 -0
  125. pyedb-0.2.0.dist-info/LICENSE +21 -0
  126. pyedb-0.2.0.dist-info/METADATA +208 -0
  127. pyedb-0.2.0.dist-info/RECORD +128 -0
  128. pyedb-0.2.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,2082 @@
1
+ # -*- coding: utf-8 -*-
2
+ import math
3
+ import re
4
+ import sys
5
+
6
+ from pyedb.generic.constants import AXIS, PLANE, SWEEPDRAFT, scale_units
7
+ from pyedb.generic.general_methods import pyedb_function_handler
8
+
9
+
10
+ class GeometryOperators(object):
11
+ """Manages geometry operators."""
12
+
13
+ @staticmethod
14
+ @pyedb_function_handler()
15
+ def List2list(input_list): # pragma: no cover
16
+ """Convert a C# list object to a Python list.
17
+
18
+ This function performs a deep conversion.
19
+
20
+ Parameters
21
+ ----------
22
+ input_list : List
23
+ C# list to convert to a Python list.
24
+
25
+ Returns
26
+ -------
27
+ List
28
+ Converted Python list.
29
+
30
+ """
31
+ output_list = []
32
+ for i in input_list:
33
+ if "List" in str(type(i)):
34
+ output_list.append(GeometryOperators.List2list(list(i)))
35
+ else:
36
+ output_list.append(i)
37
+ return output_list
38
+
39
+ @staticmethod
40
+ @pyedb_function_handler()
41
+ def parse_dim_arg(string, scale_to_unit=None, variable_manager=None): # pragma: no cover
42
+ """Convert a number and unit to a float.
43
+ Angles are converted in radians.
44
+
45
+ Parameters
46
+ ----------
47
+ string : str, optional
48
+ String to convert. For example, ``"2mm"``. The default is ``None``.
49
+ scale_to_unit : str, optional
50
+ Units for the value to convert. For example, ``"mm"``.
51
+ variable_manager : :class:`pyedb.dotnet.application.Variables.VariableManager`, optional
52
+ Try to parse formula and returns numeric value.
53
+ The default is ``None``.
54
+
55
+ Returns
56
+ -------
57
+ float
58
+ Value for the converted value and units. For example, ``0.002``.
59
+
60
+ Examples
61
+ --------
62
+ Parse `'"2mm"'`.
63
+
64
+ >>> from pyedb.modeler.geometry_operators import GeometryOperators as go
65
+ >>> go.parse_dim_arg('2mm')
66
+ >>> 0.002
67
+
68
+ Use the optional argument ``scale_to_unit`` to specify the destination unit.
69
+
70
+ >>> go.parse_dim_arg('2mm', scale_to_unit='mm')
71
+ >>> 2.0
72
+
73
+ """
74
+ if type(string) is not str:
75
+ try:
76
+ return float(string)
77
+ except ValueError: # pragma: no cover
78
+ raise TypeError("Input argument is not string nor number")
79
+ sunit = 1.0
80
+ if scale_to_unit:
81
+ sunit = scale_units(scale_to_unit)
82
+
83
+ pattern = r"(?P<number>[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?)\s*(?P<unit>[a-z_A-Z]*)"
84
+ m = re.search(pattern, string)
85
+ if m:
86
+ if m.group(0) != string:
87
+ if variable_manager:
88
+ variable_manager["temp_var"] = string
89
+ value = variable_manager["temp_var"].numeric_value
90
+ del variable_manager["temp_var"]
91
+ return value
92
+ else:
93
+ return string
94
+ elif not m.group("unit"):
95
+ return float(m.group("number"))
96
+ else:
97
+ scaling_factor = scale_units(m.group("unit"))
98
+ return float(m.group("number")) * scaling_factor / sunit
99
+ else:
100
+ if variable_manager:
101
+ if not variable_manager.set_variable("temp_var", string):
102
+ if not variable_manager.set_variable("temp_var", string, postprocessing=True):
103
+ return string
104
+ value = variable_manager["temp_var"].value / sunit
105
+ del variable_manager["temp_var"]
106
+ return value
107
+
108
+ @staticmethod
109
+ @pyedb_function_handler()
110
+ def cs_plane_to_axis_str(val): # pragma: no cover
111
+ """Retrieve a string for a coordinate system plane.
112
+
113
+ Parameters
114
+ ----------
115
+ val : int
116
+ ``PLANE`` enum vélo.
117
+
118
+ Returns
119
+ -------
120
+ str
121
+ String for the coordinate system plane.
122
+
123
+ """
124
+ if val == PLANE.XY or val == "XY":
125
+ return "Z"
126
+ elif val == PLANE.YZ or val == "YZ":
127
+ return "X"
128
+ else:
129
+ return "Y"
130
+
131
+ @staticmethod
132
+ @pyedb_function_handler()
133
+ def cs_plane_to_plane_str(val): # pragma: no cover
134
+ """Retrieve a string for a coordinate system plane.
135
+
136
+ Parameters
137
+ ----------
138
+ val :
139
+
140
+
141
+ Returns
142
+ -------
143
+ str
144
+ String for the coordinate system plane.
145
+
146
+ """
147
+ if val == PLANE.XY or val == "XY":
148
+ return "XY"
149
+ elif val == PLANE.YZ or val == "YZ":
150
+ return "YZ"
151
+ else:
152
+ return "ZX"
153
+
154
+ @staticmethod
155
+ @pyedb_function_handler()
156
+ def cs_axis_str(val): # pragma: no cover
157
+ """Retrieve a string for a coordinate system axis.
158
+
159
+ Parameters
160
+ ----------
161
+ val : int
162
+ ``AXIS`` enum value.
163
+
164
+
165
+ Returns
166
+ -------
167
+ str
168
+ String for the coordinate system axis.
169
+
170
+ """
171
+ if val == AXIS.X or val == "X":
172
+ return "X"
173
+ elif val == AXIS.Y or val == "Y":
174
+ return "Y"
175
+ else:
176
+ return "Z"
177
+
178
+ @staticmethod
179
+ @pyedb_function_handler()
180
+ def draft_type_str(val): # pragma: no cover
181
+ """Retrieve the draft type.
182
+
183
+ Parameters
184
+ ----------
185
+ val : int
186
+ ``SWEEPDRAFT`` enum value.
187
+
188
+ Returns
189
+ -------
190
+ str
191
+ Type of the draft.
192
+
193
+ """
194
+ if val == SWEEPDRAFT.Extended:
195
+ return "Extended"
196
+ elif val == SWEEPDRAFT.Round:
197
+ return "Round"
198
+ else:
199
+ return "Natural"
200
+
201
+ @staticmethod
202
+ @pyedb_function_handler()
203
+ def get_mid_point(v1, v2):
204
+ """Evaluate the midpoint between two points.
205
+
206
+ Parameters
207
+ ----------
208
+ v1 : List
209
+ List of ``[x, y, z]`` coordinates for the first point.
210
+ v2 : List
211
+ List of ``[x, y, z]`` coordinates for the second point.
212
+
213
+ Returns
214
+ -------
215
+ List
216
+ List of ``[x, y, z]`` coordinates for the midpoint.
217
+
218
+ """
219
+ m = [((i + j) / 2.0) for i, j in zip(v1, v2)]
220
+ return m
221
+
222
+ @staticmethod
223
+ @pyedb_function_handler()
224
+ def get_triangle_area(v1, v2, v3): # pragma: no cover
225
+ """Evaluate the area of a triangle defined by its three vertices.
226
+
227
+ Parameters
228
+ ----------
229
+ v1 : List
230
+ List of ``[x, y, z]`` coordinates for the first vertex.
231
+ v2 : List
232
+ List of ``[x, y, z]`` coordinates for the second vertex.
233
+ v3 : List
234
+ List of ``[x, y, z]`` coordinates for the third vertex.
235
+
236
+ Returns
237
+ -------
238
+ float
239
+ Area of the triangle.
240
+
241
+ """
242
+ a = ((v1[0] - v2[0]) ** 2 + (v1[1] - v2[1]) ** 2 + (v1[2] - v2[2]) ** 2) ** 0.5
243
+ b = ((v2[0] - v3[0]) ** 2 + (v2[1] - v3[1]) ** 2 + (v2[2] - v3[2]) ** 2) ** 0.5
244
+ c = ((v3[0] - v1[0]) ** 2 + (v3[1] - v1[1]) ** 2 + (v3[2] - v1[2]) ** 2) ** 0.5
245
+ s = 0.5 * (a + b + c)
246
+ area = (s * (s - a) * (s - b) * (s - c)) ** 0.5
247
+ if isinstance(area, complex):
248
+ area = area.real
249
+ return area
250
+
251
+ @staticmethod
252
+ @pyedb_function_handler()
253
+ def v_cross(a, b): # pragma: no cover
254
+ """Evaluate the cross product of two geometry vectors.
255
+
256
+ Parameters
257
+ ----------
258
+ a : List
259
+ List of ``[x, y, z]`` coordinates for the first vector.
260
+ b : List
261
+ List of ``[x, y, z]`` coordinates for the second vector.
262
+
263
+ Returns
264
+ -------
265
+ List
266
+ List of ``[x, y, z]`` coordinates for the result vector.
267
+ """
268
+ c = [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
269
+ return c
270
+
271
+ @staticmethod
272
+ @pyedb_function_handler()
273
+ def _v_dot(a, b): # pragma: no cover
274
+ """Evaluate the dot product between two geometry vectors.
275
+
276
+ Parameters
277
+ ----------
278
+ a : List
279
+ List of ``[x, y, z]`` coordinates for the first vector.
280
+ b : List
281
+ List of ``[x, y, z]`` coordinates for the second vector.
282
+
283
+ Returns
284
+ -------
285
+ float
286
+ Result of the dot product.
287
+
288
+ """
289
+ if len(a) == 3:
290
+ c = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
291
+ return c
292
+ elif len(a) == 2:
293
+ c = a[0] * b[0] + a[1] * b[1]
294
+ return c
295
+ return False
296
+
297
+ @staticmethod
298
+ @pyedb_function_handler()
299
+ def v_dot(a, b): # pragma: no cover
300
+ """Evaluate the dot product between two geometry vectors.
301
+
302
+ Parameters
303
+ ----------
304
+ a : List
305
+ List of ``[x, y, z]`` coordinates for the first vector.
306
+ b : List
307
+ List of ``[x, y, z]`` coordinates for the second vector.
308
+
309
+ Returns
310
+ -------
311
+ float
312
+ Result of the dot product.
313
+
314
+ """
315
+ return GeometryOperators._v_dot(a, b)
316
+
317
+ @staticmethod
318
+ @pyedb_function_handler()
319
+ def v_prod(s, v): # pragma: no cover
320
+ """Evaluate the product between a scalar value and a vector.
321
+
322
+ Parameters
323
+ ----------
324
+ s : float
325
+ Scalar value.
326
+ v : List
327
+ List of values for the vector in the format ``[v1, v2,..., vn]``.
328
+ The vector can be any length.
329
+
330
+ Returns
331
+ -------
332
+ List
333
+ List of values for the result vector. This list is the
334
+ same length as the list for the input vector.
335
+
336
+ """
337
+ r = [s * i for i in v]
338
+ return r
339
+
340
+ @staticmethod
341
+ @pyedb_function_handler()
342
+ def v_rotate_about_axis(vector, angle, radians=False, axis="z"): # pragma: no cover
343
+ """Evaluate rotation of a vector around an axis.
344
+
345
+ Parameters
346
+ ----------
347
+ vector : list
348
+ List of the three component of the vector.
349
+ angle : float
350
+ Angle by which the vector is to be rotated (radians or degree).
351
+ radians : bool, optional
352
+ Whether the angle is expressed in radians. Default is ``False``.
353
+ axis : str, optional
354
+ Axis about which to rotate the vector. Default is ``"z"``.
355
+
356
+ Returns
357
+ -------
358
+ list
359
+ List of values for the result vector.
360
+
361
+ """
362
+ if not radians:
363
+ angle = math.radians(angle)
364
+ x, y, z = vector
365
+ axis = axis.lower()
366
+ if axis == "z":
367
+ rotated_x = x * math.cos(angle) - y * math.sin(angle)
368
+ rotated_y = x * math.sin(angle) + y * math.cos(angle)
369
+ rotated_z = z
370
+ elif axis == "y":
371
+ rotated_x = x * math.cos(angle) + z * math.sin(angle)
372
+ rotated_y = y
373
+ rotated_z = -x * math.sin(angle) + z * math.cos(angle)
374
+ elif axis == "x":
375
+ rotated_x = x
376
+ rotated_y = y * math.cos(angle) - z * math.sin(angle)
377
+ rotated_z = y * math.sin(angle) + z * math.cos(angle)
378
+ else: # pragma: no cover
379
+ raise ValueError("Invalid axis. Choose 'x', 'y', or 'z'.")
380
+ return rotated_x, rotated_y, rotated_z
381
+
382
+ @staticmethod
383
+ @pyedb_function_handler()
384
+ def v_sub(a, b): # pragma: no cover
385
+ """Evaluate two geometry vectors by subtracting them (a-b).
386
+
387
+ Parameters
388
+ ----------
389
+ a : List
390
+ List of ``[x, y, z]`` coordinates for the first vector.
391
+ b : List
392
+ List of ``[x, y, z]`` coordinates for the second vector.
393
+
394
+ Returns
395
+ -------
396
+ List
397
+ List of ``[x, y, z]`` coordinates for the result vector.
398
+
399
+ """
400
+ c = [i - j for i, j in zip(a, b)]
401
+ return c
402
+
403
+ @staticmethod
404
+ @pyedb_function_handler()
405
+ def v_sum(a, b): # pragma: no cover
406
+ """Evaluate two geometry vectors by adding them (a+b).
407
+
408
+ Parameters
409
+ ----------
410
+ a : List
411
+ List of ``[x, y, z]`` coordinates for the first vector.
412
+ b : List
413
+ List of ``[x, y, z]`` coordinates for the second vector.
414
+
415
+ Returns
416
+ -------
417
+ List
418
+ List of ``[x, y, z]`` coordinates for the result vector.
419
+
420
+ """
421
+ c = [i + j for i, j in zip(a, b)]
422
+ return c
423
+
424
+ @staticmethod
425
+ @pyedb_function_handler()
426
+ def v_norm(a): # pragma: no cover
427
+ """Evaluate the Euclidean norm of a geometry vector.
428
+
429
+ Parameters
430
+ ----------
431
+ a : List
432
+ List of ``[x, y, z]`` coordinates for the vector.
433
+
434
+ Returns
435
+ -------
436
+ float
437
+ Evaluated norm in the same unit as the coordinates for the input vector.
438
+
439
+ """
440
+ t = 0
441
+ for i in a:
442
+ t += i**2
443
+ m = t**0.5
444
+ return m
445
+
446
+ @staticmethod
447
+ @pyedb_function_handler()
448
+ def normalize_vector(v): # pragma: no cover
449
+ """Normalize a geometry vector.
450
+
451
+ Parameters
452
+ ----------
453
+ v : List
454
+ List of ``[x, y, z]`` coordinates for vector.
455
+
456
+ Returns
457
+ -------
458
+ List
459
+ List of ``[x, y, z]`` coordinates for the normalized vector.
460
+
461
+ """
462
+ # normalize a vector to its norm
463
+ norm = GeometryOperators.v_norm(v)
464
+ vn = [i / norm for i in v]
465
+ return vn
466
+
467
+ @staticmethod
468
+ @pyedb_function_handler()
469
+ def v_points(p1, p2): # pragma: no cover
470
+ """Vector from one point to another point.
471
+
472
+ Parameters
473
+ ----------
474
+ p1 : List
475
+ Coordinates ``[x1,y1,z1]`` for the first point.
476
+ p2 : List
477
+ Coordinates ``[x2,y2,z2]`` for second point.
478
+
479
+ Returns
480
+ -------
481
+ List
482
+ Coordinates ``[vx, vy, vz]`` for the vector from the first point to the second point.
483
+ """
484
+ return GeometryOperators.v_sub(p2, p1)
485
+
486
+ @staticmethod
487
+ @pyedb_function_handler()
488
+ def points_distance(p1, p2): # pragma: no cover
489
+ """Evaluate the distance between two points expressed as their Cartesian coordinates.
490
+
491
+ Parameters
492
+ ----------
493
+ p1 : List
494
+ List of ``[x1,y1,z1]`` coordinates for the first point.
495
+ p2 : List
496
+ List of ``[x2,y2,z2]`` coordinates for the second ppint.
497
+
498
+ Returns
499
+ -------
500
+ float
501
+ Distance between the two points in the same unit as the coordinates for the points.
502
+
503
+ """
504
+ # fmt: off
505
+ if len(p1) == 3:
506
+ return math.sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2 + (p2[2]-p1[2])**2)
507
+ elif len(p1) == 2:
508
+ return math.sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
509
+ return False
510
+ # fmt: on
511
+
512
+ @staticmethod
513
+ @pyedb_function_handler()
514
+ def find_point_on_plane(pointlists, direction=0): # pragma: no cover
515
+ """Find a point on a plane.
516
+
517
+ Parameters
518
+ ----------
519
+ pointlists : List
520
+ List of points.
521
+ direction : int, optional
522
+ The default is ``0``.
523
+
524
+ Returns
525
+ -------
526
+ List
527
+
528
+ """
529
+ if direction <= 2:
530
+ point = 1e6
531
+ for p in pointlists:
532
+ if p[direction] < point:
533
+ point = p[direction]
534
+ else:
535
+ point = -1e6
536
+ for p in pointlists:
537
+ if p[direction - 3] > point:
538
+ point = p[direction - 3]
539
+ return point
540
+
541
+ @staticmethod
542
+ @pyedb_function_handler()
543
+ def distance_vector(p, a, b): # pragma: no cover
544
+ """Evaluate the vector distance between point ``p`` and a line defined by two points, ``a`` and ``b``.
545
+
546
+ .. note::
547
+ he formula is ``d = (a-p)-((a-p)dot p)n``, where ``a`` is a point of the line (either ``a`` or ``b``)
548
+ and ``n`` is the unit vector in the direction of the line.
549
+
550
+ Parameters
551
+ ----------
552
+ p : List
553
+ List of ``[x, y, z]`` coordinates for the reference point.
554
+ a : List
555
+ List of ``[x, y, z]`` coordinates for the first point of the segment.
556
+ b : List
557
+ List of ``[x, y, z]`` coordinates for the second point of the segment.
558
+
559
+ Returns
560
+ -------
561
+ List
562
+ List of ``[x, y, z]`` coordinates for the distance vector.
563
+
564
+ """
565
+ v1 = GeometryOperators.v_points(a, b)
566
+ n = [i / GeometryOperators.v_norm(v1) for i in v1]
567
+ v2 = GeometryOperators.v_sub(a, p)
568
+ s1 = GeometryOperators._v_dot(v2, n)
569
+ v3 = [i * s1 for i in n]
570
+ vd = GeometryOperators.v_sub(v2, v3)
571
+ return vd
572
+
573
+ @staticmethod
574
+ @pyedb_function_handler()
575
+ def is_between_points(p, a, b, tol=1e-6): # pragma: no cover
576
+ """Check if a point lies on the segment defined by two points.
577
+
578
+ Parameters
579
+ ----------
580
+ p : List
581
+ List of ``[x, y, z]`` coordinates for the reference point ``p``.
582
+ a : List
583
+ List of ``[x, y, z]`` coordinates for the first point of the segment.
584
+ b : List
585
+ List of ``[x, y, z]`` coordinates for the second point of the segment.
586
+ tol : float
587
+ Linear tolerance. The default value is ``1e-6``.
588
+
589
+ Returns
590
+ -------
591
+ bool
592
+ ``True`` when the point lies on the segment defined by the two points, ``False`` otherwise.
593
+
594
+ """
595
+ v1 = GeometryOperators.v_points(a, b)
596
+ v2 = GeometryOperators.v_points(a, p)
597
+ if abs(GeometryOperators.v_norm(GeometryOperators.v_cross(v1, v2))) > tol:
598
+ return False # not collinear
599
+ t1 = GeometryOperators._v_dot(v1, v2)
600
+ t2 = GeometryOperators._v_dot(v1, v1)
601
+ if t1 < 0 or t1 > t2:
602
+ return False
603
+ else:
604
+ return True
605
+
606
+ @staticmethod
607
+ @pyedb_function_handler()
608
+ def is_parallel(a1, a2, b1, b2, tol=1e-6): # pragma: no cover
609
+ """Check if a segment defined by two points is parallel to a segment defined by two other points.
610
+
611
+ Parameters
612
+ ----------
613
+ a1 : List
614
+ List of ``[x, y, z]`` coordinates for the first point of the fiirst segment.
615
+ a2 : List
616
+ List of ``[x, y, z]`` coordinates for the second point of the first segment.
617
+ b1 : List
618
+ List of ``[x, y, z]`` coordinates for the first point of the second segment.
619
+ b2 : List
620
+ List of ``[x, y, z]`` coordinates for the second point of the second segment.
621
+ tol : float
622
+ Linear tolerance. The default value is ``1e-6``.
623
+
624
+ Returns
625
+ -------
626
+ bool
627
+ ``True`` when successful, ``False`` when failed.
628
+
629
+ """
630
+ if 1.0 - GeometryOperators.parallel_coeff(a1, a2, b1, b2) < tol * tol:
631
+ return True
632
+ else:
633
+ return False
634
+
635
+ @staticmethod
636
+ @pyedb_function_handler()
637
+ def parallel_coeff(a1, a2, b1, b2): # pragma: no cover
638
+ """ADD DESCRIPTION.
639
+
640
+ Parameters
641
+ ----------
642
+ a1 : List
643
+ List of ``[x, y, z]`` coordinates for the first point of the first segment.
644
+ a2 : List
645
+ List of ``[x, y, z]`` coordinates for the second point of the first segment.
646
+ b1 : List
647
+ List of ``[x, y, z]`` coordinates for the first point of the second segment.
648
+ b2 : List
649
+ List of ``[x, y, z]`` coordinates for the second point of the second segment.
650
+
651
+ Returns
652
+ -------
653
+ float
654
+ _vdot of 4 vertices of 2 segments.
655
+ """
656
+ va = GeometryOperators.v_points(a1, a2)
657
+ vb = GeometryOperators.v_points(b1, b2)
658
+ an = GeometryOperators.v_norm(va)
659
+ bn = GeometryOperators.v_norm(vb)
660
+ var = GeometryOperators._v_dot(va, vb) / (an * bn)
661
+ return abs(var)
662
+
663
+ @staticmethod
664
+ @pyedb_function_handler()
665
+ def is_collinear(a, b, tol=1e-6): # pragma: no cover
666
+ """Check if two vectors are collinear (parallel or anti-parallel).
667
+
668
+ Parameters
669
+ ----------
670
+ a : List
671
+ List of ``[x, y, z]`` coordinates for the first vector.
672
+ b : List
673
+ List of ``[x, y, z]`` coordinates for the second vector.
674
+ tol : float
675
+ Linear tolerance. The default value is ``1e-6``.
676
+
677
+ Returns
678
+ -------
679
+ bool
680
+ ``True`` if vectors are collinear, ``False`` otherwise.
681
+
682
+ """
683
+ an = GeometryOperators.v_norm(a)
684
+ bn = GeometryOperators.v_norm(b)
685
+ var = GeometryOperators._v_dot(a, b) / (an * bn)
686
+ if 1.0 - abs(var) < tol * tol:
687
+ return True
688
+ else:
689
+ return False
690
+
691
+ @staticmethod
692
+ @pyedb_function_handler()
693
+ def is_projection_inside(a1, a2, b1, b2): # pragma: no cover
694
+ """Project a segment onto another segment and check if the projected segment is inside it.
695
+
696
+ Parameters
697
+ ----------
698
+ a1 : List
699
+ List of ``[x, y, z]`` coordinates for the first point of the projected segment.
700
+ a2 : List
701
+ List of ``[x, y, z]`` coordinates for the second point of the projected segment.
702
+ b1 : List
703
+ List of ``[x, y, z]`` coordinates for the first point of the other segment.
704
+ b2 : List
705
+ List of ``[x, y, z]`` coordinates for the second point of the other segment.
706
+
707
+ Returns
708
+ -------
709
+ bool
710
+ ``True`` when the projected segment is inside the other segmennt, ``False`` otherwise.
711
+
712
+ """
713
+ if not GeometryOperators.is_parallel(a1, a2, b1, b2):
714
+ return False
715
+ d = GeometryOperators.distance_vector(a1, b1, b2)
716
+ a1n = GeometryOperators.v_sum(a1, d)
717
+ a2n = GeometryOperators.v_sum(a2, d)
718
+ if not GeometryOperators.is_between_points(a1n, b1, b2):
719
+ return False
720
+ if not GeometryOperators.is_between_points(a2n, b1, b2):
721
+ return False
722
+ return True
723
+
724
+ @staticmethod
725
+ @pyedb_function_handler()
726
+ def arrays_positions_sum(vertlist1, vertlist2): # pragma: no cover
727
+ """Return the sum of two vertices lists.
728
+
729
+ Parameters
730
+ ----------
731
+ vertlist1 : List
732
+
733
+ vertlist2 : List
734
+
735
+ Returns
736
+ -------
737
+ float
738
+
739
+ """
740
+ s = 0
741
+ for el in vertlist1:
742
+ for el1 in vertlist2:
743
+ s += GeometryOperators.points_distance(el, el1)
744
+ return s / (len(vertlist1) + len(vertlist2))
745
+
746
+ @staticmethod
747
+ @pyedb_function_handler()
748
+ def v_angle(a, b): # pragma: no cover
749
+ """Evaluate the angle between two geometry vectors.
750
+
751
+ Parameters
752
+ ----------
753
+ a : List
754
+ List of ``[x, y, z]`` coordinates for the first vector.
755
+ b : List
756
+ List of ``[x, y, z]`` coordinates for the second vector.
757
+
758
+ Returns
759
+ -------
760
+ float
761
+ Angle in radians.
762
+
763
+ """
764
+ d = GeometryOperators.v_dot(a, b)
765
+ an = GeometryOperators.v_norm(a)
766
+ bn = GeometryOperators.v_norm(b)
767
+ if (an * bn) == 0.0:
768
+ return 0.0
769
+ else:
770
+ return math.acos(d / (an * bn))
771
+
772
+ @staticmethod
773
+ @pyedb_function_handler()
774
+ def pointing_to_axis(x_pointing, y_pointing): # pragma: no cover
775
+ """Retrieve the axes from the HFSS X axis and Y pointing axis as per
776
+ the definition of the AEDT interface coordinate system.
777
+
778
+ Parameters
779
+ ----------
780
+ x_pointing : List
781
+ List of ``[x, y, z]`` coordinates for the X axis.
782
+
783
+ y_pointing : List
784
+ List of ``[x, y, z]`` coordinates for the Y pointing axis.
785
+
786
+ Returns
787
+ -------
788
+ tuple
789
+ ``[Xx, Xy, Xz], [Yx, Yy, Yz], [Zx, Zy, Zz]`` of the three axes (normalized).
790
+ """
791
+ zpt = GeometryOperators.v_cross(x_pointing, y_pointing)
792
+ ypt = GeometryOperators.v_cross(zpt, x_pointing)
793
+
794
+ xp = GeometryOperators.normalize_vector(x_pointing)
795
+ zp = GeometryOperators.normalize_vector(zpt)
796
+ yp = GeometryOperators.normalize_vector(ypt)
797
+
798
+ return xp, yp, zp
799
+
800
+ @staticmethod
801
+ @pyedb_function_handler()
802
+ def axis_to_euler_zxz(x, y, z): # pragma: no cover
803
+ """Retrieve Euler angles of a frame following the rotation sequence ZXZ.
804
+
805
+ Provides assumption for the gimbal lock problem.
806
+
807
+ Parameters
808
+ ----------
809
+ x : List
810
+ List of ``[Xx, Xy, Xz]`` coordinates for the X axis.
811
+ y : List
812
+ List of ``[Yx, Yy, Yz]`` coordinates for the Y axis.
813
+ z : List
814
+ List of ``[Zx, Zy, Zz]`` coordinates for the Z axis.
815
+
816
+ Returns
817
+ -------
818
+ tuple
819
+ (phi, theta, psi) containing the Euler angles in radians.
820
+
821
+ """
822
+ tol = 1e-16
823
+ x1 = x[0]
824
+ x2 = x[1]
825
+ x3 = x[2]
826
+ y3 = y[2]
827
+ z1 = z[0]
828
+ z2 = z[1]
829
+ z3 = z[2]
830
+ if GeometryOperators.v_norm(GeometryOperators.v_sub(z, [0, 0, 1])) < tol:
831
+ phi = GeometryOperators.atan2(x2, x1)
832
+ theta = 0.0
833
+ psi = 0.0
834
+ elif GeometryOperators.v_norm(GeometryOperators.v_sub(z, [0, 0, -1])) < tol:
835
+ phi = GeometryOperators.atan2(x2, x1)
836
+ theta = math.pi
837
+ psi = 0.0
838
+ else:
839
+ phi = GeometryOperators.atan2(z1, -z2)
840
+ theta = math.acos(z3)
841
+ psi = GeometryOperators.atan2(x3, y3)
842
+ return phi, theta, psi
843
+
844
+ @staticmethod
845
+ @pyedb_function_handler()
846
+ def axis_to_euler_zyz(x, y, z): # pragma: no cover
847
+ """Retrieve Euler angles of a frame following the rotation sequence ZYZ.
848
+
849
+ Provides assumption for the gimbal lock problem.
850
+
851
+ Parameters
852
+ ----------
853
+ x : List
854
+ List of ``[Xx, Xy, Xz]`` coordinates for the X axis.
855
+ y : List
856
+ List of ``[Yx, Yy, Yz]`` coordinates for the Y axis.
857
+ z : List
858
+ List of ``[Zx, Zy, Zz]`` coordinates for the Z axis.
859
+
860
+ Returns
861
+ -------
862
+ tuple
863
+ (phi, theta, psi) containing the Euler angles in radians.
864
+
865
+ """
866
+ tol = 1e-16
867
+ x1 = x[0]
868
+ x2 = x[1]
869
+ x3 = x[2]
870
+ y3 = y[2]
871
+ z1 = z[0]
872
+ z2 = z[1]
873
+ z3 = z[2]
874
+ if GeometryOperators.v_norm(GeometryOperators.v_sub(z, [0, 0, 1])) < tol:
875
+ phi = GeometryOperators.atan2(-x1, x2)
876
+ theta = 0.0
877
+ psi = math.pi / 2
878
+ elif GeometryOperators.v_norm(GeometryOperators.v_sub(z, [0, 0, -1])) < tol:
879
+ phi = GeometryOperators.atan2(-x1, x2)
880
+ theta = math.pi
881
+ psi = math.pi / 2
882
+ else:
883
+ phi = GeometryOperators.atan2(z2, z1)
884
+ theta = math.acos(z3)
885
+ psi = GeometryOperators.atan2(y3, -x3)
886
+ return phi, theta, psi
887
+
888
+ @staticmethod
889
+ @pyedb_function_handler()
890
+ def quaternion_to_axis(q): # pragma: no cover
891
+ """Convert a quaternion to a rotated frame defined by X, Y, and Z axes.
892
+
893
+ Parameters
894
+ ----------
895
+ q : List
896
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
897
+
898
+ Returns
899
+ -------
900
+ tuple
901
+ [Xx, Xy, Xz], [Yx, Yy, Yz], [Zx, Zy, Zz] of the three axes (normalized).
902
+
903
+ """
904
+ q1 = q[0]
905
+ q2 = q[1]
906
+ q3 = q[2]
907
+ q4 = q[3]
908
+
909
+ m11 = q1 * q1 + q2 * q2 - q3 * q3 - q4 * q4
910
+ m12 = 2.0 * (q2 * q3 - q1 * q4)
911
+ m13 = 2.0 * (q2 * q4 + q1 * q3)
912
+
913
+ m21 = 2.0 * (q2 * q3 + q1 * q4)
914
+ m22 = q1 * q1 - q2 * q2 + q3 * q3 - q4 * q4
915
+ m23 = 2.0 * (q3 * q4 - q1 * q2)
916
+
917
+ m31 = 2.0 * (q2 * q4 - q1 * q3)
918
+ m32 = 2.0 * (q3 * q4 + q1 * q2)
919
+ m33 = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
920
+
921
+ x = GeometryOperators.normalize_vector([m11, m21, m31])
922
+ y = GeometryOperators.normalize_vector([m12, m22, m32])
923
+ z = GeometryOperators.normalize_vector([m13, m23, m33])
924
+
925
+ return x, y, z
926
+
927
+ @staticmethod
928
+ @pyedb_function_handler()
929
+ def quaternion_to_axis_angle(q): # pragma: no cover
930
+ """Convert a quaternion to the axis angle rotation formulation.
931
+
932
+ Parameters
933
+ ----------
934
+ q : List
935
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
936
+
937
+ Returns
938
+ -------
939
+ tuple
940
+ ([ux, uy, uz], theta) containing the rotation axes expressed as X, Y, Z components of
941
+ the unit vector ``u`` and the rotation angle theta expressed in radians.
942
+
943
+ """
944
+ q1 = q[0]
945
+ q2 = q[1]
946
+ q3 = q[2]
947
+ q4 = q[3]
948
+ n = (q2 * q2 + q3 * q3 + q4 * q4) ** 0.5
949
+ u = [q2 / n, q3 / n, q4 / n]
950
+ theta = 2.0 * GeometryOperators.atan2(n, q1)
951
+ return u, theta
952
+
953
+ @staticmethod
954
+ @pyedb_function_handler()
955
+ def axis_angle_to_quaternion(u, theta): # pragma: no cover
956
+ """Convert the axis angle rotation formulation to a quaternion.
957
+
958
+ Parameters
959
+ ----------
960
+ u : List
961
+ List of ``[ux, uy, uz]`` coordinates for the rotation axis.
962
+
963
+ theta : float
964
+ Angle of rotation in radians.
965
+
966
+ Returns
967
+ -------
968
+ List
969
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
970
+
971
+ """
972
+ un = GeometryOperators.normalize_vector(u)
973
+ s = math.sin(theta * 0.5)
974
+ q1 = math.cos(theta * 0.5)
975
+ q2 = un[0] * s
976
+ q3 = un[1] * s
977
+ q4 = un[2] * s
978
+ return [q1, q2, q3, q4]
979
+
980
+ @staticmethod
981
+ @pyedb_function_handler()
982
+ def quaternion_to_euler_zxz(q): # pragma: no cover
983
+ """Convert a quaternion to Euler angles following rotation sequence ZXZ.
984
+
985
+ Parameters
986
+ ----------
987
+ q : List
988
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
989
+
990
+ Returns
991
+ -------
992
+ tuple
993
+ (phi, theta, psi) containing the Euler angles in radians.
994
+
995
+ """
996
+ q1 = q[0]
997
+ q2 = q[1]
998
+ q3 = q[2]
999
+ q4 = q[3]
1000
+ m13 = 2.0 * (q2 * q4 + q1 * q3)
1001
+ m23 = 2.0 * (q3 * q4 - q1 * q2)
1002
+ m33 = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
1003
+ m31 = 2.0 * (q2 * q4 - q1 * q3)
1004
+ m32 = 2.0 * (q3 * q4 + q1 * q2)
1005
+ phi = GeometryOperators.atan2(m13, -m23)
1006
+ theta = GeometryOperators.atan2((1.0 - m33 * m33) ** 0.5, m33)
1007
+ psi = GeometryOperators.atan2(m31, m32)
1008
+ return phi, theta, psi
1009
+
1010
+ @staticmethod
1011
+ @pyedb_function_handler()
1012
+ def euler_zxz_to_quaternion(phi, theta, psi): # pragma: no cover
1013
+ """Convert the Euler angles following rotation sequence ZXZ to a quaternion.
1014
+
1015
+ Parameters
1016
+ ----------
1017
+ phi : float
1018
+ Euler angle psi in radians.
1019
+ theta : float
1020
+ Euler angle theta in radians.
1021
+ psi : float
1022
+ Euler angle phi in radians.
1023
+
1024
+ Returns
1025
+ -------
1026
+ List
1027
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
1028
+
1029
+ """
1030
+ t1 = phi
1031
+ t2 = theta
1032
+ t3 = psi
1033
+ c = math.cos(t2 * 0.5)
1034
+ s = math.sin(t2 * 0.5)
1035
+ q1 = c * math.cos((t1 + t3) * 0.5)
1036
+ q2 = s * math.cos((t1 - t3) * 0.5)
1037
+ q3 = s * math.sin((t1 - t3) * 0.5)
1038
+ q4 = c * math.sin((t1 + t3) * 0.5)
1039
+ return [q1, q2, q3, q4]
1040
+
1041
+ @staticmethod
1042
+ @pyedb_function_handler()
1043
+ def quaternion_to_euler_zyz(q): # pragma: no cover
1044
+ """Convert a quaternion to Euler angles following rotation sequence ZYZ.
1045
+
1046
+ Parameters
1047
+ ----------
1048
+ q : List
1049
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
1050
+
1051
+ Returns
1052
+ -------
1053
+ tuple
1054
+ (phi, theta, psi) containing the Euler angles in radians.
1055
+
1056
+ """
1057
+ q1 = q[0]
1058
+ q2 = q[1]
1059
+ q3 = q[2]
1060
+ q4 = q[3]
1061
+ m13 = 2.0 * (q2 * q4 + q1 * q3)
1062
+ m23 = 2.0 * (q3 * q4 - q1 * q2)
1063
+ m33 = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
1064
+ m31 = 2.0 * (q2 * q4 - q1 * q3)
1065
+ m32 = 2.0 * (q3 * q4 + q1 * q2)
1066
+ phi = GeometryOperators.atan2(m23, m13)
1067
+ theta = GeometryOperators.atan2((1.0 - m33 * m33) ** 0.5, m33)
1068
+ psi = GeometryOperators.atan2(m32, -m31)
1069
+ return phi, theta, psi
1070
+
1071
+ @staticmethod
1072
+ @pyedb_function_handler()
1073
+ def euler_zyz_to_quaternion(phi, theta, psi): # pragma: no cover
1074
+ """Convert the Euler angles following rotation sequence ZYZ to a quaternion.
1075
+
1076
+ Parameters
1077
+ ----------
1078
+ phi : float
1079
+ Euler angle psi in radians.
1080
+ theta : float
1081
+ Euler angle theta in radians.
1082
+ psi : float
1083
+ Euler angle phi in radians.
1084
+
1085
+ Returns
1086
+ -------
1087
+ List
1088
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
1089
+
1090
+ """
1091
+ t1 = phi
1092
+ t2 = theta
1093
+ t3 = psi
1094
+ c = math.cos(t2 * 0.5)
1095
+ s = math.sin(t2 * 0.5)
1096
+ q1 = c * math.cos((t1 + t3) * 0.5)
1097
+ q2 = -s * math.sin((t1 - t3) * 0.5)
1098
+ q3 = s * math.cos((t1 - t3) * 0.5)
1099
+ q4 = c * math.sin((t1 + t3) * 0.5)
1100
+ return [q1, q2, q3, q4]
1101
+
1102
+ @staticmethod
1103
+ @pyedb_function_handler()
1104
+ def deg2rad(angle):
1105
+ """Convert the angle from degrees to radians.
1106
+
1107
+ Parameters
1108
+ ----------
1109
+ angle : float
1110
+ Angle in degrees.
1111
+
1112
+ Returns
1113
+ -------
1114
+ float
1115
+ Angle in radians.
1116
+
1117
+ """
1118
+ pi = math.pi
1119
+ return angle / 180.0 * pi
1120
+
1121
+ @staticmethod
1122
+ @pyedb_function_handler()
1123
+ def rad2deg(angle):
1124
+ """Convert the angle from radians to degrees.
1125
+
1126
+ Parameters
1127
+ ----------
1128
+ angle : float
1129
+ Angle in radians.
1130
+
1131
+ Returns
1132
+ -------
1133
+ float
1134
+ Angle in degrees.
1135
+
1136
+ """
1137
+ pi = math.pi
1138
+ return angle * 180.0 / pi
1139
+
1140
+ @staticmethod
1141
+ @pyedb_function_handler()
1142
+ def atan2(y, x): # pragma: no cover
1143
+ """Implementation of atan2 that does not suffer from the following issues:
1144
+ math.atan2(0.0, 0.0) = 0.0
1145
+ math.atan2(-0.0, 0.0) = -0.0
1146
+ math.atan2(0.0, -0.0) = 3.141592653589793
1147
+ math.atan2(-0.0, -0.0) = -3.141592653589793
1148
+ and returns always 0.0.
1149
+
1150
+ Parameters
1151
+ ----------
1152
+ y : float
1153
+ Y-axis value for atan2.
1154
+
1155
+ x : float
1156
+ X-axis value for atan2.
1157
+
1158
+ Returns
1159
+ -------
1160
+ float
1161
+
1162
+ """
1163
+ eps = 7.0 / 3.0 - 4.0 / 3.0 - 1.0
1164
+ if abs(y) < eps:
1165
+ y = 0.0
1166
+ if abs(x) < eps:
1167
+ x = 0.0
1168
+ return math.atan2(y, x)
1169
+
1170
+ @staticmethod
1171
+ @pyedb_function_handler()
1172
+ def q_prod(p, q): # pragma: no cover
1173
+ """Evaluate the product of two quaternions, ``p`` and ``q``, defined as:
1174
+ p = p0 + p' = p0 + ip1 + jp2 + kp3.
1175
+ q = q0 + q' = q0 + iq1 + jq2 + kq3.
1176
+ r = pq = p0q0 - p' • q' + p0q' + q0p' + p' x q'.
1177
+
1178
+ Parameters
1179
+ ----------
1180
+ p : List
1181
+ List of ``[p1, p2, p3, p4]`` coordinates for quaternion ``p``.
1182
+
1183
+ q : List
1184
+ List of ``[p1, p2, p3, p4]`` coordinates for quaternion ``q``.
1185
+
1186
+ Returns
1187
+ -------
1188
+ List
1189
+ List of [r1, r2, r3, r4] coordinates for the result quaternion.
1190
+
1191
+ """
1192
+ p0 = p[0]
1193
+ pv = p[1:4]
1194
+ q0 = q[0]
1195
+ qv = q[1:4]
1196
+
1197
+ r0 = p0 * q0 - GeometryOperators.v_dot(pv, qv)
1198
+
1199
+ t1 = GeometryOperators.v_prod(p0, qv)
1200
+ t2 = GeometryOperators.v_prod(q0, pv)
1201
+ t3 = GeometryOperators.v_cross(pv, qv)
1202
+ rv = GeometryOperators.v_sum(t1, GeometryOperators.v_sum(t2, t3))
1203
+
1204
+ return [r0, rv[0], rv[1], rv[2]]
1205
+
1206
+ @staticmethod
1207
+ @pyedb_function_handler()
1208
+ def q_rotation(v, q): # pragma: no cover
1209
+ """Evaluate the rotation of a vector, defined by a quaternion.
1210
+ Evaluated as:
1211
+ ``"q = q0 + q' = q0 + iq1 + jq2 + kq3"``,
1212
+ ``"w = qvq* = (q0^2 - |q'|^2)v + 2(q' • v)q' + 2q0(q' x v)"``.
1213
+
1214
+ Parameters
1215
+ ----------
1216
+ v : List
1217
+ List of ``[v1, v2, v3]`` coordinates for the vector.
1218
+ q : List
1219
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
1220
+
1221
+ Returns
1222
+ -------
1223
+ List
1224
+ List of ``[w1, w2, w3]`` coordinates for the result vector ``w``.
1225
+ """
1226
+ q0 = q[0]
1227
+ qv = q[1:4]
1228
+
1229
+ c1 = q0 * q0 - (qv[0] * qv[0] + qv[1] * qv[1] + qv[2] * qv[2])
1230
+ t1 = GeometryOperators.v_prod(c1, v)
1231
+
1232
+ c2 = 2.0 * GeometryOperators.v_dot(qv, v)
1233
+ t2 = GeometryOperators.v_prod(c2, qv)
1234
+
1235
+ t3 = GeometryOperators.v_cross(qv, v)
1236
+ t4 = GeometryOperators.v_prod(2.0 * q0, t3)
1237
+
1238
+ w = GeometryOperators.v_sum(t1, GeometryOperators.v_sum(t2, t4))
1239
+
1240
+ return w
1241
+
1242
+ @staticmethod
1243
+ @pyedb_function_handler()
1244
+ def q_rotation_inv(v, q):
1245
+ """Evaluate the inverse rotation of a vector that is defined by a quaternion.
1246
+
1247
+ It can also be the rotation of the coordinate frame with respect to the vector.
1248
+
1249
+ q = q0 + q' = q0 + iq1 + jq2 + kq3
1250
+ q* = q0 - q' = q0 - iq1 - jq2 - kq3
1251
+ w = q*vq
1252
+
1253
+ Parameters
1254
+ ----------
1255
+ v : List
1256
+ List of ``[v1, v2, v3]`` coordinates for the vector.
1257
+
1258
+ q : List
1259
+ List of ``[q1, q2, q3, q4]`` coordinates for the quaternion.
1260
+
1261
+ Returns
1262
+ -------
1263
+ List
1264
+ List of ``[w1, w2, w3]`` coordinates for the vector.
1265
+
1266
+ """
1267
+ q1 = [q[0], -q[1], -q[2], -q[3]]
1268
+ return GeometryOperators.q_rotation(v, q1)
1269
+
1270
+ @staticmethod
1271
+ @pyedb_function_handler()
1272
+ def get_polygon_centroid(pts): # pragma: no cover
1273
+ """Evaluate the centroid of a polygon defined by its points.
1274
+
1275
+ Parameters
1276
+ ----------
1277
+ pts : List
1278
+ List of points, with each point defined by its ``[x,y,z]`` coordinates.
1279
+
1280
+ Returns
1281
+ -------
1282
+ List
1283
+ List of [x,y,z] coordinates for the centroid of the polygon.
1284
+
1285
+ """
1286
+ if len(pts) == 0: # pragma: no cover
1287
+ raise ValueError("pts must contain at list one point")
1288
+ sx = sy = sz = sl = sl2 = 0
1289
+ x1, y1, z1 = pts[0]
1290
+ for i in range(len(pts)): # counts from 0 to len(points)-1
1291
+ x0, y0, z0 = pts[i - 1] # in Python points[-1] is last element of points
1292
+ x1, y1, z1 = pts[i]
1293
+ L = ((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5
1294
+ sx += (x0 + x1) / 2 * L
1295
+ sy += (y0 + y1) / 2 * L
1296
+ L2 = ((z1 - z0) ** 2 + (x1 - x0) ** 2) ** 0.5
1297
+ sz += (z0 + z1) / 2 * L2
1298
+ sl += L
1299
+ sl2 += L2
1300
+ xc = sx / sl if sl != 0.0 else x1
1301
+ yc = sy / sl if sl != 0.0 else y1
1302
+ zc = sz / sl2 if sl2 != 0.0 else z1
1303
+
1304
+ return [xc, yc, zc]
1305
+
1306
+ @staticmethod
1307
+ @pyedb_function_handler()
1308
+ def cs_xy_pointing_expression(yaw, pitch, roll): # pragma: no cover
1309
+ """Return x_pointing and y_pointing vectors as expressions from
1310
+ the yaw, ptich, and roll input (as strings).
1311
+
1312
+ Parameters
1313
+ ----------
1314
+ yaw : str, required
1315
+ String expression for the yaw angle (rotation about Z-axis)
1316
+ pitch : str
1317
+ String expression for the pitch angle (rotation about Y-axis)
1318
+ roll : str
1319
+ String expression for the roll angle (rotation about X-axis)
1320
+
1321
+ Returns
1322
+ -------
1323
+ [x_pointing, y_pointing] vector expressions.
1324
+ """
1325
+ # X-Pointing
1326
+ xx = "cos(" + yaw + ")*cos(" + pitch + ")"
1327
+ xy = "sin(" + yaw + ")*cos(" + pitch + ")"
1328
+ xz = "sin(" + pitch + ")"
1329
+
1330
+ # Y-Pointing
1331
+ yx = "sin(" + roll + ")*sin(" + pitch + ")*cos(" + yaw + ") - "
1332
+ yx += "sin(" + yaw + ")*cos(" + roll + ")"
1333
+
1334
+ yy = "sin(" + roll + ")*sin(" + yaw + ")*sin(" + pitch + ") + "
1335
+ yy += "cos(" + roll + ")*cos(" + yaw + ")"
1336
+
1337
+ yz = "sin(" + roll + " + pi)*cos(" + pitch + ")" # use pi to avoid negative sign.
1338
+
1339
+ # x, y pointing vectors for CS
1340
+ x_pointing = [xx, xy, xz]
1341
+ y_pointing = [yx, yy, yz]
1342
+
1343
+ return [x_pointing, y_pointing]
1344
+
1345
+ @staticmethod
1346
+ @pyedb_function_handler()
1347
+ def get_numeric(s):
1348
+ """Convert a string to a numeric value. Discard the suffix."""
1349
+ if type(s) == str:
1350
+ if s == "Global":
1351
+ return 0.0
1352
+ else:
1353
+ return float("".join(c for c in s if c.isdigit() or c == "."))
1354
+ elif s is None:
1355
+ return 0.0
1356
+ else:
1357
+ return float(s)
1358
+
1359
+ @staticmethod
1360
+ @pyedb_function_handler()
1361
+ def is_small(s):
1362
+ """Return ``True`` if the number represented by s is zero (i.e very small).
1363
+
1364
+ Parameters
1365
+ ----------
1366
+ s : numeric or str
1367
+ Variable value.
1368
+
1369
+ Returns
1370
+ -------
1371
+ bool
1372
+
1373
+ """
1374
+ n = GeometryOperators.get_numeric(s)
1375
+ return True if math.fabs(n) < 2.0 * abs(sys.float_info.epsilon) else False
1376
+
1377
+ @staticmethod
1378
+ @pyedb_function_handler()
1379
+ def numeric_cs(cs_in): # pragma: no cover
1380
+ """Return a list of [x,y,z] numeric values given a coordinate system as input.
1381
+
1382
+ Parameters
1383
+ ----------
1384
+ cs_in : List of str or str
1385
+ ``["x", "y", "z"]`` or "Global".
1386
+ """
1387
+ if type(cs_in) is str:
1388
+ if cs_in == "Global":
1389
+ return [0.0, 0.0, 0.0]
1390
+ else:
1391
+ return None
1392
+ elif type(cs_in) is list:
1393
+ if len(cs_in) == 3:
1394
+ return [GeometryOperators.get_numeric(s) if type(s) is str else s for s in cs_in]
1395
+ else:
1396
+ return [0, 0, 0]
1397
+
1398
+ @staticmethod
1399
+ @pyedb_function_handler()
1400
+ def orient_polygon(x, y, clockwise=True):
1401
+ """
1402
+ Orient a polygon clockwise or counterclockwise. The vertices should be already ordered either way.
1403
+ Use this function to change the orientation.
1404
+ The polygon is represented by its vertices coordinates.
1405
+
1406
+
1407
+ Parameters
1408
+ ----------
1409
+ x : List
1410
+ List of x coordinates of the vertices. Length must be >= 1.
1411
+ Degenerate polygon with only 2 points is also accepted, in this case the points are returned unchanged.
1412
+ y : List
1413
+ List of y coordinates of the vertices. Must be of the same length as x.
1414
+ clockwise : bool
1415
+ If ``True`` the polygon is oriented clockwise, if ``False`` it is oriented counterclockwise.
1416
+ Default is ``True``.
1417
+
1418
+ Returns
1419
+ -------
1420
+ List of List
1421
+ Lists of oriented vertices.
1422
+ """
1423
+ x_ret = x[:]
1424
+ y_ret = y[:]
1425
+ if len(x) < 2: # pragma: no cover
1426
+ raise ValueError("'x' length must be >= 2")
1427
+ if len(y) != len(x): # pragma: no cover
1428
+ raise ValueError("'y' must be same length as 'x'")
1429
+ if len(x) == 2:
1430
+ return x_ret, y_ret
1431
+ # fmt: off
1432
+ # select a vertex on the hull
1433
+ xmin = min(x)
1434
+ ixmin = [i for i, el in enumerate(x) if xmin == el]
1435
+ if len(ixmin) == 1:
1436
+ imin = ixmin[0]
1437
+ else: # searching for the minimum y
1438
+ tmpy = [(i, el) for i, el in enumerate(y) if i in ixmin]
1439
+ min_tmpy = min(tmpy, key=lambda t: t[1])
1440
+ imin = min_tmpy[0]
1441
+ ymin = y[imin]
1442
+ if imin == 0: # the minimum is the first point of the polygon
1443
+ xa = x[-1]
1444
+ ya = y[-1]
1445
+ xb = xmin
1446
+ yb = ymin
1447
+ xc = x[1]
1448
+ yc = y[1]
1449
+ elif imin == len(x)-1: # the minimum is the last point of the polygon
1450
+ xa = x[imin-1]
1451
+ ya = y[imin-1]
1452
+ xb = xmin
1453
+ yb = ymin
1454
+ xc = x[0]
1455
+ yc = y[0]
1456
+ else:
1457
+ xa = x[imin-1]
1458
+ ya = y[imin-1]
1459
+ xb = xmin
1460
+ yb = ymin
1461
+ xc = x[imin+1]
1462
+ yc = y[imin+1]
1463
+ det = (xb-xa) * (yc-ya) - (xc-xa) * (yb-ya)
1464
+ if det > 0: # counterclockwise
1465
+ is_CW = False
1466
+ else: # clockwise
1467
+ is_CW = True
1468
+ # fmt: on
1469
+ if (clockwise and not is_CW) or (not clockwise and is_CW):
1470
+ x_ret.reverse()
1471
+ y_ret.reverse()
1472
+ return x_ret, y_ret
1473
+
1474
+ @staticmethod
1475
+ @pyedb_function_handler()
1476
+ def v_angle_sign(va, vb, vn, right_handed=True): # pragma: no cover
1477
+ """Evaluate the signed angle between two geometry vectors.
1478
+ The sign is evaluated respect to the normal to the plane containing the two vectors as per the following rule.
1479
+ In case of opposite vectors, it returns an angle equal to 180deg (always positive).
1480
+ Assuming that the plane normal is normalized (vb == 1), the signed angle is simplified.
1481
+ For the right-handed rotation from Va to Vb:
1482
+ - atan2((va x Vb) . vn, va . vb).
1483
+ For the left-handed rotation from Va to Vb:
1484
+ - atan2((Vb x va) . vn, va . vb).
1485
+
1486
+ Parameters
1487
+ ----------
1488
+ va : List
1489
+ List of ``[x, y, z]`` coordinates for the first vector.
1490
+ vb : List
1491
+ List of ``[x, y, z]`` coordinates for the second vector.
1492
+ vn : List
1493
+ List of ``[x, y, z]`` coordinates for the plane normal.
1494
+ right_handed : bool
1495
+ Whether to consider the right-handed rotation from va to vb. The default is ``True``.
1496
+ When ``False``, left-hand rotation from va to vb is considered.
1497
+
1498
+ Returns
1499
+ -------
1500
+ float
1501
+ Angle in radians.
1502
+
1503
+ """
1504
+ tol = 1e-12
1505
+ cross = GeometryOperators.v_cross(va, vb)
1506
+ if GeometryOperators.v_norm(cross) < tol:
1507
+ return math.pi
1508
+ assert GeometryOperators.is_collinear(cross, vn), (
1509
+ "vn must be the normal to the " "plane containing va and vb."
1510
+ ) # pragma: no cover
1511
+
1512
+ vnn = GeometryOperators.normalize_vector(vn)
1513
+ if right_handed:
1514
+ return math.atan2(GeometryOperators.v_dot(cross, vnn), GeometryOperators.v_dot(va, vb))
1515
+ else:
1516
+ mcross = GeometryOperators.v_cross(vb, va)
1517
+ return math.atan2(GeometryOperators.v_dot(mcross, vnn), GeometryOperators.v_dot(va, vb))
1518
+
1519
+ @staticmethod
1520
+ @pyedb_function_handler()
1521
+ def v_angle_sign_2D(va, vb, right_handed=True):
1522
+ """Evaluate the signed angle between two 2D geometry vectors.
1523
+ Iit the 2D version of the ``GeometryOperators.v_angle_sign`` considering vn = [0,0,1].
1524
+ In case of opposite vectors, it returns an angle equal to 180deg (always positive).
1525
+
1526
+ Parameters
1527
+ ----------
1528
+ va : List
1529
+ List of ``[x, y]`` coordinates for the first vector.
1530
+ vb : List
1531
+ List of ``[x, y]`` coordinates for the second vector.
1532
+ right_handed : bool
1533
+ Whether to consider the right-handed rotation from Va to Vb. The default is ``True``.
1534
+ When ``False``, left-hand rotation from Va to Vb is considered.
1535
+
1536
+ Returns
1537
+ -------
1538
+ float
1539
+ Angle in radians.
1540
+
1541
+ """
1542
+ c = va[0] * vb[1] - va[1] * vb[0]
1543
+
1544
+ if right_handed:
1545
+ return math.atan2(c, GeometryOperators.v_dot(va, vb))
1546
+ else:
1547
+ return math.atan2(-c, GeometryOperators.v_dot(va, vb))
1548
+
1549
+ @staticmethod
1550
+ @pyedb_function_handler()
1551
+ def point_in_polygon(point, polygon, tolerance=1e-8):
1552
+ """Determine if a point is inside, outside the polygon or at exactly at the border.
1553
+
1554
+ The method implements the radial algorithm (https://es.wikipedia.org/wiki/Algoritmo_radial)
1555
+
1556
+ point : List
1557
+ List of ``[x, y]`` coordinates.
1558
+ polygon : List
1559
+ [[x1, x2, ..., xn],[y1, y2, ..., yn]]
1560
+ tolerance : float
1561
+ tolerance used for the algorithm. Default value is 1e-8.
1562
+
1563
+ Returns
1564
+ -------
1565
+ int
1566
+ - ``-1`` When the point is outside the polygon.
1567
+ - ``0`` When the point is exactly on one of the sides of the polygon.
1568
+ - ``1`` When the point is inside the polygon.
1569
+ """
1570
+ # fmt: off
1571
+ tol = tolerance
1572
+ if len(point) != 2: # pragma: no cover
1573
+ raise ValueError("Point must be a list in the form [x, y].")
1574
+ pl = len(polygon[0])
1575
+ if len(polygon[1]) != pl: # pragma: no cover
1576
+ raise ValueError("Polygon x and y lists must be the same length")
1577
+ asum = 0
1578
+ for i in range(pl):
1579
+ vj = [polygon[0][i-1], polygon[1][i-1]]
1580
+ vi = [polygon[0][i], polygon[1][i]]
1581
+ if GeometryOperators.points_distance(point, vi) < tol:
1582
+ return 0 # point is one of polyline vertices
1583
+ vpj = GeometryOperators.v_points(point, vj)
1584
+ vpi = GeometryOperators.v_points(point, vi)
1585
+ a = GeometryOperators.v_angle_sign_2D(vpj, vpi)
1586
+ if abs(abs(a) - math.pi) < tol:
1587
+ return 0
1588
+ asum += a
1589
+ if abs(asum) < tol:
1590
+ return -1
1591
+ elif abs(abs(asum) - 2*math.pi) < tol:
1592
+ return 1
1593
+ else: # pragma: no cover
1594
+ raise Exception("Unexpected error!")
1595
+ # fmt: on
1596
+
1597
+ @staticmethod
1598
+ @pyedb_function_handler()
1599
+ def is_point_in_polygon(point, polygon):
1600
+ """Determine if a point is inside or outside a polygon, both located on the same plane.
1601
+
1602
+ The method implements the radial algorithm (https://es.wikipedia.org/wiki/Algoritmo_radial)
1603
+
1604
+ point : List
1605
+ List of ``[x, y]`` coordinates.
1606
+ polygon : List
1607
+ [[x1, x2, ..., xn],[y1, y2, ..., yn]]
1608
+
1609
+ Returns
1610
+ -------
1611
+ bool
1612
+ ``True`` if the point is inside the polygon or exactly on one of its sides.
1613
+ ``False`` otherwise.
1614
+ """
1615
+ r = GeometryOperators.point_in_polygon(point, polygon)
1616
+ if r == -1:
1617
+ return False
1618
+ else:
1619
+ return True
1620
+
1621
+ @staticmethod
1622
+ @pyedb_function_handler()
1623
+ def are_segments_intersecting(a1, a2, b1, b2, include_collinear=True):
1624
+ """
1625
+ Determine if the two segments a and b are intersecting.
1626
+
1627
+ a1 : List
1628
+ First point of segment a. List of ``[x, y]`` coordinates.
1629
+ a2 : List
1630
+ Second point of segment a. List of ``[x, y]`` coordinates.
1631
+ b1 : List
1632
+ First point of segment b. List of ``[x, y]`` coordinates.
1633
+ b2 : List
1634
+ Second point of segment b. List of ``[x, y]`` coordinates.
1635
+ include_collinear : bool
1636
+ If ``True`` two segments are considered intersecting also if just one end lies on the other segment.
1637
+ Default is ``True``.
1638
+
1639
+ Returns
1640
+ -------
1641
+ bool
1642
+ ``True`` if the segments are intersecting.
1643
+ ``False`` otherwise.
1644
+ """
1645
+ # fmt: off
1646
+ def on_segment(p, q, r):
1647
+ # Given three collinear points p, q, r, the function checks if point q lies on line-segment 'pr'
1648
+ if ((q[0] <= max(p[0], r[0])) and (q[0] >= min(p[0], r[0])) and
1649
+ (q[1] <= max(p[1], r[1])) and (q[1] >= min(p[1], r[1]))):
1650
+ return True
1651
+ return False
1652
+
1653
+ def orientation(p, q, r):
1654
+ # Find the orientation of an ordered triplet (p,q,r) using the slope evaluation.
1655
+ # The function returns the following values:
1656
+ # 0 : Collinear points
1657
+ # 1 : Clockwise points
1658
+ # -1 : Counterclockwise
1659
+ val = float(q[1]-p[1]) * float(r[0]-q[0]) - float(q[0]-p[0]) * float(r[1]-q[1])
1660
+ if val > 0:
1661
+ return 1 # Clockwise orientation
1662
+ elif val < 0:
1663
+ return -1 # Counterclockwise orientation
1664
+ else:
1665
+ return 0 # Collinear orientation
1666
+
1667
+ # MAIN
1668
+ # Find the 4 orientations
1669
+ o1 = orientation(a1, a2, b1)
1670
+ o2 = orientation(a1, a2, b2)
1671
+ o3 = orientation(b1, b2, a1)
1672
+ o4 = orientation(b1, b2, a2)
1673
+
1674
+ # General case
1675
+ if (o1 != o2) and (o3 != o4):
1676
+ if include_collinear:
1677
+ return True
1678
+ else:
1679
+ # a1 , a2 and b1 are collinear and b1 lies on segment a1a2
1680
+ if (o1 == 0) and on_segment(a1, b1, a2):
1681
+ return False
1682
+ # a1 , a2 and b2 are collinear and b2 lies on segment a1a2
1683
+ if (o2 == 0) and on_segment(a1, b2, a2):
1684
+ return False
1685
+ # b1 , b2 and a1 are collinear and a1 lies on segment b1b2
1686
+ if (o3 == 0) and on_segment(b1, a1, b2):
1687
+ return False
1688
+ # b1 , b2 and a2 are collinear and a2 lies on segment b1b2
1689
+ if (o4 == 0) and on_segment(b1, a2, b2):
1690
+ return False
1691
+ return True
1692
+
1693
+ # Special Cases
1694
+ # a1 , a2 and b1 are collinear and b1 lies on segment a1a2
1695
+ if (o1 == 0) and on_segment(a1, b1, a2):
1696
+ return include_collinear
1697
+ # a1 , a2 and b2 are collinear and b2 lies on segment a1a2
1698
+ if (o2 == 0) and on_segment(a1, b2, a2):
1699
+ return include_collinear
1700
+ # b1 , b2 and a1 are collinear and a1 lies on segment b1b2
1701
+ if (o3 == 0) and on_segment(b1, a1, b2):
1702
+ return include_collinear
1703
+ # b1 , b2 and a2 are collinear and a2 lies on segment b1b2
1704
+ if (o4 == 0) and on_segment(b1, a2, b2):
1705
+ return include_collinear
1706
+ # If none of the cases
1707
+ return False
1708
+ # fmt: on
1709
+
1710
+ @staticmethod
1711
+ @pyedb_function_handler()
1712
+ def is_segment_intersecting_polygon(a, b, polygon):
1713
+ """
1714
+ Determine if a segment defined by two points ``a`` and ``b`` intersects a polygon.
1715
+ Points on the vertices and on the polygon boundaries are not considered intersecting.
1716
+
1717
+ a : List
1718
+ First point of the segment. List of ``[x, y]`` coordinates.
1719
+ b : List
1720
+ Second point of the segment. List of ``[x, y]`` coordinates.
1721
+ polygon : List
1722
+ [[x1, x2, ..., xn],[y1, y2, ..., yn]]
1723
+
1724
+ Returns
1725
+ -------
1726
+ float
1727
+ ``True`` if the segment intersect the polygon. ``False`` otherwise.
1728
+ """
1729
+ assert len(a) == 2, "point must be a list in the form [x, y]"
1730
+ assert len(b) == 2, "point must be a list in the form [x, y]"
1731
+ pl = len(polygon[0])
1732
+ assert len(polygon[1]) == pl, "Polygon x and y lists must be the same length"
1733
+
1734
+ a_in = GeometryOperators.is_point_in_polygon(a, polygon)
1735
+ b_in = GeometryOperators.is_point_in_polygon(b, polygon)
1736
+ if a_in != b_in:
1737
+ return True # one point is inside and one is outside, no need for further investigation.
1738
+ for i in range(pl):
1739
+ vj = [polygon[0][i - 1], polygon[1][i - 1]]
1740
+ vi = [polygon[0][i], polygon[1][i]]
1741
+ if GeometryOperators.are_segments_intersecting(a, b, vi, vj, include_collinear=False):
1742
+ return True
1743
+ return False
1744
+
1745
+ @staticmethod
1746
+ @pyedb_function_handler()
1747
+ def is_perpendicular(a, b, tol=1e-6):
1748
+ """Check if two vectors are perpendicular.
1749
+
1750
+ Parameters
1751
+ ----------
1752
+ a : List
1753
+ List of ``[x, y, z]`` coordinates for the first vector.
1754
+ b : List
1755
+ List of ``[x, y, z]`` coordinates for the second vector.
1756
+ tol : float
1757
+ Linear tolerance. The default value is ``1e-6``.
1758
+
1759
+ Returns
1760
+ -------
1761
+ bool
1762
+ ``True`` if vectors are perpendicular, ``False`` otherwise.
1763
+
1764
+ """
1765
+ var = GeometryOperators._v_dot(a, b)
1766
+ if abs(var) < tol * tol:
1767
+ return True
1768
+ else:
1769
+ return False
1770
+
1771
+ @staticmethod
1772
+ @pyedb_function_handler()
1773
+ def is_point_projection_in_segment(p, a, b):
1774
+ """Check if a point projection lies on the segment defined by two points.
1775
+
1776
+ Parameters
1777
+ ----------
1778
+ p : List
1779
+ List of ``[x, y, z]`` coordinates for the reference point ``p``.
1780
+ a : List
1781
+ List of ``[x, y, z]`` coordinates for the first point of the segment.
1782
+ b : List
1783
+ List of ``[x, y, z]`` coordinates for the second point of the segment.
1784
+
1785
+ Returns
1786
+ -------
1787
+ bool
1788
+ ``True`` when the projection point lies on the segment defined by the two points, ``False`` otherwise.
1789
+
1790
+ """
1791
+ # fmt: off
1792
+ dx = b[0]-a[0]
1793
+ dy = b[1]-a[1]
1794
+ inner_product = (p[0]-a[0])*dx + (p[1]-a[1])*dy
1795
+ return 0 <= inner_product <= dx*dx + dy*dy
1796
+ # fmt: on
1797
+
1798
+ @staticmethod
1799
+ @pyedb_function_handler()
1800
+ def point_segment_distance(p, a, b): # pragma: no cover
1801
+ """Calculate the distance between a point ``p`` and a segment defined by two points ``a`` and ``b``.
1802
+
1803
+ Parameters
1804
+ ----------
1805
+ p : List
1806
+ List of ``[x, y, z]`` coordinates for the reference point ``p``.
1807
+ a : List
1808
+ List of ``[x, y, z]`` coordinates for the first point of the segment.
1809
+ b : List
1810
+ List of ``[x, y, z]`` coordinates for the second point of the segment.
1811
+
1812
+ Returns
1813
+ -------
1814
+ float
1815
+ Distance between the point and the segment.
1816
+ """
1817
+ # fmt: off
1818
+ den = math.sqrt((b[0] - a[0])**2 + (b[1] - a[1])**2)
1819
+ num = (b[0] - a[0])*(a[1] - p[1]) - (a[0] - p[0])*(b[1] - a[1])
1820
+ d = abs(num)/den
1821
+ return d
1822
+ # fmt: on
1823
+
1824
+ @staticmethod
1825
+ @pyedb_function_handler()
1826
+ def find_largest_rectangle_inside_polygon(polygon, partition_max_order=16):
1827
+ """Find the largest area rectangles of arbitrary orientation in a polygon.
1828
+
1829
+ Implements the algorithm described by Rubén Molano, et al.
1830
+ *"Finding the largest area rectangle of arbitrary orientation in a closed contour"*, published in
1831
+ *Applied Mathematics and Computation*.
1832
+ https://doi.org/10.1016/j.amc.2012.03.063.
1833
+ (https://www.sciencedirect.com/science/article/pii/S0096300312003207)
1834
+
1835
+ Parameters
1836
+ ----------
1837
+ polygon : List
1838
+ [[x1, x2, ..., xn],[y1, y2, ..., yn]]
1839
+ partition_max_order : float, optional
1840
+ Order of the lattice partition used to find the quasi-lattice polygon that approximates ``polygon``.
1841
+ Default is ``16``.
1842
+
1843
+ Returns
1844
+ -------
1845
+ List of List
1846
+ List containing the rectangles points. Return all rectangles found.
1847
+ List is in the form: [[[x1, y1],[x2, y2],...],[[x1, y1],[x2, y2],...],...].
1848
+ """
1849
+
1850
+ # fmt: off
1851
+ def evaluate_partition_size(polygon, partition_max_order):
1852
+ x, y = polygon
1853
+ max_size = max(max(x)-min(x), max(y)-min(y))
1854
+ L = max_size/partition_max_order
1855
+ return L
1856
+
1857
+ def build_s_ploygon_points(vertices, L):
1858
+ x, y = vertices
1859
+
1860
+ # build the lattice
1861
+ xmin = min(x)
1862
+ r = int(math.ceil(float(max(x)-xmin)/L))
1863
+ ymin = min(y)
1864
+ s = int(math.ceil(float(max(y)-ymin)/L))
1865
+
1866
+ # get the lattice points S inside the polygon
1867
+ Spoints = []
1868
+ for i in range(r + 1):
1869
+ xi = xmin + L * i
1870
+ for j in range(s + 1):
1871
+ yj = ymin + L * j
1872
+ if GeometryOperators.is_point_in_polygon([xi, yj], [x, y]):
1873
+ Spoints.append([xi, yj])
1874
+ return Spoints
1875
+
1876
+ def build_u_matrix(S, polygon):
1877
+ N = len(S)
1878
+ # preallocate the matrix
1879
+ Umatrix = [[None for j in range(N)] for i in range(N)]
1880
+ for i in range(N):
1881
+ for j in range(N):
1882
+ if i >= j:
1883
+ Umatrix[i][j] = 0
1884
+ else:
1885
+ if GeometryOperators.is_segment_intersecting_polygon(S[i], S[j], polygon):
1886
+ Umatrix[i][j] = 0
1887
+ else:
1888
+ p = GeometryOperators.get_mid_point(S[i], S[j])
1889
+ if not GeometryOperators.is_point_in_polygon(p, polygon):
1890
+ Umatrix[i][j] = 0
1891
+ else:
1892
+ Umatrix[i][j] = GeometryOperators.v_points(S[i], S[j])
1893
+ return Umatrix
1894
+
1895
+ def inside(i, j):
1896
+ if U[i][j] == 0 and isinstance(U[i][j], int):
1897
+ return False
1898
+ else:
1899
+ return True
1900
+
1901
+ def compute_largest_rectangle(S):
1902
+ max_area = 0
1903
+ rectangles = []
1904
+ N = len(S)
1905
+ for i in range(N-3):
1906
+ for j in range(i+1, N-2):
1907
+ if inside(i, j):
1908
+ for k in range(j+1, N-1):
1909
+ if inside(i, k) and GeometryOperators.is_perpendicular(U[i][j], U[i][k]):
1910
+ ps = GeometryOperators.v_sum(GeometryOperators.v_sub(S[j], S[i]), S[k])
1911
+ try:
1912
+ s = S.index(ps)
1913
+ except ValueError:
1914
+ break
1915
+ if inside(k, s) and inside(j, s):
1916
+ area = GeometryOperators.v_norm(U[i][j]) * GeometryOperators.v_norm(U[i][k])
1917
+ if area > max_area:
1918
+ max_area = area
1919
+ R = [S[i], S[j], S[s], S[k]]
1920
+ rectangles = [R]
1921
+ elif area == max_area:
1922
+ R = [S[i], S[j], S[s], S[k]]
1923
+ rectangles.append(R)
1924
+ return rectangles
1925
+
1926
+ L = evaluate_partition_size(polygon, partition_max_order=partition_max_order)
1927
+ S = build_s_ploygon_points(polygon, L)
1928
+ U = build_u_matrix(S, polygon)
1929
+ R = compute_largest_rectangle(S)
1930
+ return R
1931
+ # fmt: on
1932
+
1933
+ @staticmethod
1934
+ @pyedb_function_handler()
1935
+ def degrees_over_rounded(angle, digits):
1936
+ """Ceil of angle.
1937
+
1938
+ Parameters
1939
+ ----------
1940
+ angle : float
1941
+ Angle in radians which will be converted to degrees and will be over-rounded to the next "digits" decimal.
1942
+ digits : int
1943
+ Integer number which is the number of decimals.
1944
+
1945
+ Returns
1946
+ -------
1947
+ float
1948
+
1949
+ """
1950
+ return math.ceil(math.degrees(angle) * 10**digits) / (10**digits)
1951
+
1952
+ @staticmethod
1953
+ @pyedb_function_handler()
1954
+ def radians_over_rounded(angle, digits):
1955
+ """Radian angle ceiling.
1956
+
1957
+ Parameters
1958
+ ----------
1959
+ angle : float
1960
+ Angle in degrees which will be converted to radians and will be over-rounded to the next "digits" decimal.
1961
+ digits : int
1962
+ Integer number which is the number of decimals.
1963
+
1964
+ Returns
1965
+ -------
1966
+ float
1967
+
1968
+ """
1969
+ return math.ceil(math.radians(angle) * 10**digits) / (10**digits)
1970
+
1971
+ @staticmethod
1972
+ @pyedb_function_handler()
1973
+ def degrees_default_rounded(angle, digits):
1974
+ """Convert angle to degree with given digits rounding.
1975
+
1976
+ Parameters
1977
+ ----------
1978
+ angle : float
1979
+ Angle in radians which will be converted to degrees and will be under-rounded to the next "digits" decimal.
1980
+ digits : int
1981
+ Integer number which is the number of decimals.
1982
+
1983
+ Returns
1984
+ -------
1985
+ float
1986
+
1987
+ """
1988
+ return math.floor(math.degrees(angle) * 10**digits) / (10**digits)
1989
+
1990
+ @staticmethod
1991
+ @pyedb_function_handler()
1992
+ def radians_default_rounded(angle, digits):
1993
+ """Convert to radians with given round.
1994
+
1995
+ Parameters
1996
+ ----------
1997
+ angle : float
1998
+ Angle in degrees which will be converted to radians and will be under-rounded to the next "digits" decimal.
1999
+ digits : int
2000
+ Integer number which is the number of decimals.
2001
+
2002
+ Returns
2003
+ -------
2004
+ float
2005
+
2006
+ """
2007
+ return math.floor(math.radians(angle) * 10**digits) / (10**digits)
2008
+
2009
+ @staticmethod
2010
+ @pyedb_function_handler()
2011
+ def find_closest_points(points_list, reference_point, tol=1e-6): # pragma: no cover
2012
+ """Given a list of points, finds the closest points to a reference point.
2013
+ It returns a list of points because more than one can be found.
2014
+ It works with 2D or 3D points. The tolerance used to evaluate the distance
2015
+ to the reference point can be specified.
2016
+
2017
+ Parameters
2018
+ ----------
2019
+ points_list : List of List
2020
+ List of points. The points can be defined in 2D or 3D space.
2021
+ reference_point : List
2022
+ The reference point. The point can be defined in 2D or 3D space (same as points_list).
2023
+ tol : float, optional
2024
+ The tolerance used to evaluate the distance. Default is ``1e-6``.
2025
+
2026
+ Returns
2027
+ -------
2028
+ List of List
2029
+
2030
+ """
2031
+ # fmt: off
2032
+ if not isinstance(points_list, list) or not isinstance(points_list[0], list):
2033
+ raise AttributeError("points_list must be a list of points")
2034
+ if len(points_list[0]) < 2 or len(points_list[0]) > 3:
2035
+ raise AttributeError("points must be defined in either 2D or 3D space.")
2036
+ if len(points_list[0]) != len(reference_point):
2037
+ raise AttributeError("Points in points_list attribute and reference_point must have the same length.")
2038
+ # make copy of the input points
2039
+ pl = [i[:] for i in points_list]
2040
+ pr = reference_point[:]
2041
+ # find the closest points
2042
+ dm = 1e12
2043
+ close_points = []
2044
+ for p in pl:
2045
+ d = GeometryOperators.points_distance(p, pr)
2046
+ if abs(d-dm) < tol:
2047
+ close_points.append(p)
2048
+ elif d < dm:
2049
+ dm = d
2050
+ close_points = [p]
2051
+ if close_points:
2052
+ return close_points
2053
+ else: # pragma: no cover
2054
+ return False
2055
+ # fmt: on
2056
+
2057
+ @staticmethod
2058
+ @pyedb_function_handler()
2059
+ def mirror_point(start, reference, vector): # pragma: no cover
2060
+ """Mirror point about a plane defining by a point on the plane and a normal point.
2061
+
2062
+ Parameters
2063
+ ----------
2064
+ start : list
2065
+ Point to be mirrored
2066
+ reference : list
2067
+ The reference point. Point on the plane around which you want to mirror the object.
2068
+ vector : list
2069
+ Normalized vector used for the mirroring.
2070
+
2071
+ Returns
2072
+ -------
2073
+ List
2074
+ List of the reflected point.
2075
+
2076
+ """
2077
+ distance = [start[i] - reference[i] for i in range(3)]
2078
+ vector_norm = GeometryOperators.v_norm(vector)
2079
+ vector = [vector[i] / vector_norm for i in range(3)]
2080
+ dot_product = sum([distance[i] * vector[i] for i in range(3)])
2081
+ reflection = [-dot_product * vector[i] * 2 + start[i] for i in range(3)]
2082
+ return reflection