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.
- pyedb/__init__.py +17 -0
- pyedb/dotnet/__init__.py +0 -0
- pyedb/dotnet/application/Variables.py +2261 -0
- pyedb/dotnet/application/__init__.py +0 -0
- pyedb/dotnet/clr_module.py +103 -0
- pyedb/dotnet/edb.py +4237 -0
- pyedb/dotnet/edb_core/__init__.py +1 -0
- pyedb/dotnet/edb_core/cell/__init__.py +0 -0
- pyedb/dotnet/edb_core/cell/hierarchy/__init__.py +0 -0
- pyedb/dotnet/edb_core/cell/hierarchy/model.py +66 -0
- pyedb/dotnet/edb_core/components.py +2669 -0
- pyedb/dotnet/edb_core/configuration.py +423 -0
- pyedb/dotnet/edb_core/definition/__init__.py +0 -0
- pyedb/dotnet/edb_core/definition/component_def.py +166 -0
- pyedb/dotnet/edb_core/definition/component_model.py +30 -0
- pyedb/dotnet/edb_core/definition/definition_obj.py +18 -0
- pyedb/dotnet/edb_core/definition/definitions.py +12 -0
- pyedb/dotnet/edb_core/dotnet/__init__.py +0 -0
- pyedb/dotnet/edb_core/dotnet/database.py +1218 -0
- pyedb/dotnet/edb_core/dotnet/layout.py +238 -0
- pyedb/dotnet/edb_core/dotnet/primitive.py +1517 -0
- pyedb/dotnet/edb_core/edb_data/__init__.py +0 -0
- pyedb/dotnet/edb_core/edb_data/components_data.py +938 -0
- pyedb/dotnet/edb_core/edb_data/connectable.py +113 -0
- pyedb/dotnet/edb_core/edb_data/control_file.py +1268 -0
- pyedb/dotnet/edb_core/edb_data/design_options.py +35 -0
- pyedb/dotnet/edb_core/edb_data/edbvalue.py +45 -0
- pyedb/dotnet/edb_core/edb_data/hfss_extent_info.py +330 -0
- pyedb/dotnet/edb_core/edb_data/hfss_simulation_setup_data.py +1607 -0
- pyedb/dotnet/edb_core/edb_data/layer_data.py +576 -0
- pyedb/dotnet/edb_core/edb_data/nets_data.py +281 -0
- pyedb/dotnet/edb_core/edb_data/obj_base.py +19 -0
- pyedb/dotnet/edb_core/edb_data/padstacks_data.py +2080 -0
- pyedb/dotnet/edb_core/edb_data/ports.py +287 -0
- pyedb/dotnet/edb_core/edb_data/primitives_data.py +1397 -0
- pyedb/dotnet/edb_core/edb_data/simulation_configuration.py +2914 -0
- pyedb/dotnet/edb_core/edb_data/simulation_setup.py +716 -0
- pyedb/dotnet/edb_core/edb_data/siwave_simulation_setup_data.py +1205 -0
- pyedb/dotnet/edb_core/edb_data/sources.py +514 -0
- pyedb/dotnet/edb_core/edb_data/terminals.py +632 -0
- pyedb/dotnet/edb_core/edb_data/utilities.py +148 -0
- pyedb/dotnet/edb_core/edb_data/variables.py +91 -0
- pyedb/dotnet/edb_core/general.py +181 -0
- pyedb/dotnet/edb_core/hfss.py +1646 -0
- pyedb/dotnet/edb_core/layout.py +1244 -0
- pyedb/dotnet/edb_core/layout_validation.py +272 -0
- pyedb/dotnet/edb_core/materials.py +939 -0
- pyedb/dotnet/edb_core/net_class.py +335 -0
- pyedb/dotnet/edb_core/nets.py +1215 -0
- pyedb/dotnet/edb_core/padstack.py +1389 -0
- pyedb/dotnet/edb_core/siwave.py +1427 -0
- pyedb/dotnet/edb_core/stackup.py +2703 -0
- pyedb/edb_logger.py +396 -0
- pyedb/generic/__init__.py +0 -0
- pyedb/generic/constants.py +1063 -0
- pyedb/generic/data_handlers.py +320 -0
- pyedb/generic/design_types.py +104 -0
- pyedb/generic/filesystem.py +150 -0
- pyedb/generic/general_methods.py +1535 -0
- pyedb/generic/plot.py +1840 -0
- pyedb/generic/process.py +285 -0
- pyedb/generic/settings.py +224 -0
- pyedb/ipc2581/__init__.py +0 -0
- pyedb/ipc2581/bom/__init__.py +0 -0
- pyedb/ipc2581/bom/bom.py +21 -0
- pyedb/ipc2581/bom/bom_item.py +32 -0
- pyedb/ipc2581/bom/characteristics.py +37 -0
- pyedb/ipc2581/bom/refdes.py +16 -0
- pyedb/ipc2581/content/__init__.py +0 -0
- pyedb/ipc2581/content/color.py +38 -0
- pyedb/ipc2581/content/content.py +55 -0
- pyedb/ipc2581/content/dictionary_color.py +29 -0
- pyedb/ipc2581/content/dictionary_fill.py +28 -0
- pyedb/ipc2581/content/dictionary_line.py +30 -0
- pyedb/ipc2581/content/entry_color.py +13 -0
- pyedb/ipc2581/content/entry_line.py +14 -0
- pyedb/ipc2581/content/fill.py +15 -0
- pyedb/ipc2581/content/layer_ref.py +10 -0
- pyedb/ipc2581/content/standard_geometries_dictionary.py +72 -0
- pyedb/ipc2581/ecad/__init__.py +0 -0
- pyedb/ipc2581/ecad/cad_data/__init__.py +0 -0
- pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +26 -0
- pyedb/ipc2581/ecad/cad_data/cad_data.py +37 -0
- pyedb/ipc2581/ecad/cad_data/component.py +41 -0
- pyedb/ipc2581/ecad/cad_data/drill.py +30 -0
- pyedb/ipc2581/ecad/cad_data/feature.py +54 -0
- pyedb/ipc2581/ecad/cad_data/layer.py +41 -0
- pyedb/ipc2581/ecad/cad_data/layer_feature.py +151 -0
- pyedb/ipc2581/ecad/cad_data/logical_net.py +32 -0
- pyedb/ipc2581/ecad/cad_data/outline.py +25 -0
- pyedb/ipc2581/ecad/cad_data/package.py +104 -0
- pyedb/ipc2581/ecad/cad_data/padstack_def.py +38 -0
- pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +24 -0
- pyedb/ipc2581/ecad/cad_data/padstack_instance.py +62 -0
- pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +26 -0
- pyedb/ipc2581/ecad/cad_data/path.py +89 -0
- pyedb/ipc2581/ecad/cad_data/phy_net.py +80 -0
- pyedb/ipc2581/ecad/cad_data/pin.py +31 -0
- pyedb/ipc2581/ecad/cad_data/polygon.py +169 -0
- pyedb/ipc2581/ecad/cad_data/profile.py +40 -0
- pyedb/ipc2581/ecad/cad_data/stackup.py +31 -0
- pyedb/ipc2581/ecad/cad_data/stackup_group.py +42 -0
- pyedb/ipc2581/ecad/cad_data/stackup_layer.py +21 -0
- pyedb/ipc2581/ecad/cad_data/step.py +275 -0
- pyedb/ipc2581/ecad/cad_header.py +33 -0
- pyedb/ipc2581/ecad/ecad.py +19 -0
- pyedb/ipc2581/ecad/spec.py +46 -0
- pyedb/ipc2581/history_record.py +37 -0
- pyedb/ipc2581/ipc2581.py +387 -0
- pyedb/ipc2581/logistic_header.py +25 -0
- pyedb/misc/__init__.py +0 -0
- pyedb/misc/aedtlib_personalib_install.py +14 -0
- pyedb/misc/downloads.py +322 -0
- pyedb/misc/misc.py +67 -0
- pyedb/misc/pyedb.runtimeconfig.json +13 -0
- pyedb/misc/siw_feature_config/__init__.py +0 -0
- pyedb/misc/siw_feature_config/emc/__init__.py +0 -0
- pyedb/misc/siw_feature_config/emc/component_tags.py +46 -0
- pyedb/misc/siw_feature_config/emc/net_tags.py +37 -0
- pyedb/misc/siw_feature_config/emc/tag_library.py +62 -0
- pyedb/misc/siw_feature_config/emc/xml_generic.py +78 -0
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +179 -0
- pyedb/misc/utilities.py +27 -0
- pyedb/modeler/geometry_operators.py +2082 -0
- pyedb-0.2.0.dist-info/LICENSE +21 -0
- pyedb-0.2.0.dist-info/METADATA +208 -0
- pyedb-0.2.0.dist-info/RECORD +128 -0
- 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
|