yostlabs 2025.10.29.2__py3-none-any.whl → 2026.2.4__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.
- yostlabs/math/quaternion.py +169 -2
- yostlabs/math/vector.py +3 -2
- yostlabs/tss3/api.py +66 -1
- yostlabs/tss3/consts.py +2 -0
- {yostlabs-2025.10.29.2.dist-info → yostlabs-2026.2.4.dist-info}/METADATA +1 -1
- {yostlabs-2025.10.29.2.dist-info → yostlabs-2026.2.4.dist-info}/RECORD +8 -8
- {yostlabs-2025.10.29.2.dist-info → yostlabs-2026.2.4.dist-info}/WHEEL +1 -1
- {yostlabs-2025.10.29.2.dist-info → yostlabs-2026.2.4.dist-info}/licenses/LICENSE +0 -0
yostlabs/math/quaternion.py
CHANGED
|
@@ -30,7 +30,7 @@ def quat_rotate_vec(quat: list[float], vec: list[float]):
|
|
|
30
30
|
final = quat_mul(halfway, inv)
|
|
31
31
|
return [final[0], final[1], final[2]]
|
|
32
32
|
|
|
33
|
-
def angles_to_quaternion(angles: list[float], order: str, degrees=True):
|
|
33
|
+
def angles_to_quaternion(angles: list[float], order: str, degrees=True, extrinsic=False):
|
|
34
34
|
quat = [0, 0, 0, 1]
|
|
35
35
|
for i in range(len(angles)):
|
|
36
36
|
axis = order[i]
|
|
@@ -42,9 +42,18 @@ def angles_to_quaternion(angles: list[float], order: str, degrees=True):
|
|
|
42
42
|
imaginary = math.sin(angle / 2)
|
|
43
43
|
unit_vec = [v * imaginary for v in unit_vec]
|
|
44
44
|
angle_quat = [*unit_vec, w]
|
|
45
|
-
|
|
45
|
+
if extrinsic:
|
|
46
|
+
quat = quat_mul(angle_quat, quat)
|
|
47
|
+
else:
|
|
48
|
+
quat = quat_mul(quat, angle_quat)
|
|
46
49
|
return quat
|
|
47
50
|
|
|
51
|
+
def quat_from_angles(angles: list[float], order: str, degrees=True, extrinsic=False):
|
|
52
|
+
return angles_to_quaternion(angles, order, degrees=degrees, extrinsic=extrinsic)
|
|
53
|
+
|
|
54
|
+
def quat_from_euler(angles: list[float], order: list[int], degrees=False, extrinsic=False):
|
|
55
|
+
return angles_to_quaternion(angles, order, degrees=degrees, extrinsic=extrinsic)
|
|
56
|
+
|
|
48
57
|
def quat_from_axis_angle(axis: list[float], angle: float):
|
|
49
58
|
imaginary = math.sin(angle / 2)
|
|
50
59
|
quat = [imaginary * v for v in axis]
|
|
@@ -134,6 +143,164 @@ def quaternion_to_3x3_rotation_matrix(quat):
|
|
|
134
143
|
[out[6], out[7], out[8]],
|
|
135
144
|
]
|
|
136
145
|
|
|
146
|
+
#Quat is expected in XYZW order
|
|
147
|
+
#https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/quat_2_euler_paper_ver2-1.pdf
|
|
148
|
+
def q2ea(in_quat: list[float], order: list[int]) -> list[float]:
|
|
149
|
+
X, Y, Z, W = 0, 1, 2, 3 #Index order helpers
|
|
150
|
+
if len(order) != 3: raise Exception()
|
|
151
|
+
if len(in_quat) != 4: raise Exception()
|
|
152
|
+
out = [0, 0, 0]
|
|
153
|
+
|
|
154
|
+
i1, i2, i3 = order
|
|
155
|
+
i1n = (i1+ 1) % 3
|
|
156
|
+
i1nn = (i1n + 1) % 3
|
|
157
|
+
|
|
158
|
+
#Find the direction the final axis ends up pointing after the full rotation
|
|
159
|
+
#Note that this vector does not change when the third rotation is being applied.
|
|
160
|
+
v3_rot = [0, 0, 0]
|
|
161
|
+
v3_rot[i3] = 1
|
|
162
|
+
v3_rot = quat_rotate_vec(in_quat, v3_rot)
|
|
163
|
+
|
|
164
|
+
#NOTE: Whenever using asin/acos, ensure the input is in range of -1 <= x <= 1
|
|
165
|
+
#All this math should result in that, but floating point sometimes causes values like 1.0000002 which can cause NANs
|
|
166
|
+
v3_rot = [max(-1, min(1, v)) for v in v3_rot]
|
|
167
|
+
|
|
168
|
+
#Can now discover the first 2 rotations
|
|
169
|
+
#This is because the first two rotations determine the direction of the final axis,
|
|
170
|
+
#and each rotation only affects 1 plane, therefore think of it like the first rotation
|
|
171
|
+
#positions the third axis underneath its final spot, and the second rotation swings it up to its final position.
|
|
172
|
+
#These can be calculated using trig and the known axis pattern.
|
|
173
|
+
|
|
174
|
+
#Non-Circular, Repeated Axes
|
|
175
|
+
#XZX, YXY, ZYZ
|
|
176
|
+
if (i1 == 0 and i2 == 2 and i3 == 0) or \
|
|
177
|
+
(i1 == 1 and i2 == 0 and i3 == 1) or \
|
|
178
|
+
(i1 == 2 and i2 == 1 and i3 == 2):
|
|
179
|
+
out[0] = math.atan2(v3_rot[i1nn], v3_rot[i1n])
|
|
180
|
+
out[1] = math.acos(v3_rot[i1])
|
|
181
|
+
#Non-Circular, Non-Repeated Axes
|
|
182
|
+
#XZY, YXZ, ZYX
|
|
183
|
+
elif (i1 == 0 and i2 == 2 and i3 == 1) or \
|
|
184
|
+
(i1 == 1 and i2 == 0 and i3 == 2) or \
|
|
185
|
+
(i1 == 2 and i2 == 1 and i3 == 0):
|
|
186
|
+
out[0] = math.atan2(v3_rot[i1nn], v3_rot[i1n])
|
|
187
|
+
out[1] = -math.asin(v3_rot[i1])
|
|
188
|
+
#Circular, Repeated Axes
|
|
189
|
+
#XYX, YZY, ZXZ
|
|
190
|
+
elif (i1 == 0 and i2 == 1 and i3 == 0) or \
|
|
191
|
+
(i1 == 1 and i2 == 2 and i3 == 1) or \
|
|
192
|
+
(i1 == 2 and i2 == 0 and i3 == 2):
|
|
193
|
+
out[0] = math.atan2(v3_rot[i1n], -v3_rot[i1nn])
|
|
194
|
+
out[1] = math.acos(v3_rot[i1])
|
|
195
|
+
#Circular, Non-Repeated Axes
|
|
196
|
+
#XYZ, YZX, ZXY
|
|
197
|
+
elif (i1 == 0 and i2 == 1 and i3 == 2) or \
|
|
198
|
+
(i1 == 1 and i2 == 2 and i3 == 0) or \
|
|
199
|
+
(i1 == 2 and i2 == 0 and i3 == 1):
|
|
200
|
+
out[0] = math.atan2(-v3_rot[i1n], v3_rot[i1nn])
|
|
201
|
+
out[1] = math.asin(v3_rot[i1]) #Note, the paper incorrectly says -asin
|
|
202
|
+
else:
|
|
203
|
+
raise ValueError("Invalid Order")
|
|
204
|
+
|
|
205
|
+
#Now compute the third angle
|
|
206
|
+
|
|
207
|
+
#Create a quaternion that applies the first two rotations
|
|
208
|
+
q1_rotation = [0, 0, 0, 0]
|
|
209
|
+
q1_rotation[W] = math.cos(out[0] / 2)
|
|
210
|
+
q1_rotation[i1] = math.sin(out[0] / 2)
|
|
211
|
+
|
|
212
|
+
q2_rotation = [0, 0, 0, 0]
|
|
213
|
+
q2_rotation[W] = math.cos(out[1] / 2)
|
|
214
|
+
q2_rotation[i2] = math.sin(out[1] / 2)
|
|
215
|
+
|
|
216
|
+
q12 = quat_mul(q1_rotation, q2_rotation)
|
|
217
|
+
|
|
218
|
+
#Now apply that quaternion and the original quaternion to the axis that will be rotated by the last axis
|
|
219
|
+
#(Rotating along Axis 3 does not change the position of Axis 3, this is done to obtain an axis that will be rotated
|
|
220
|
+
#so that the difference can be computed to decide how much rotation is needed for the last angle)
|
|
221
|
+
i3n = (i3 + 1) % 3
|
|
222
|
+
v3n = [0, 0, 0]
|
|
223
|
+
v3n[i3n] = 1
|
|
224
|
+
|
|
225
|
+
v3_q12 = v3n.copy()
|
|
226
|
+
v3_q = v3n.copy()
|
|
227
|
+
v3_q12 = quat_rotate_vec(q12, v3_q12)
|
|
228
|
+
v3_q = quat_rotate_vec(in_quat, v3_q)
|
|
229
|
+
|
|
230
|
+
#Now do trig to figure out how much rotation, and what direction, is needed to rotate v3_q12 to v3_q
|
|
231
|
+
d = _vec.vec_dot(v3_q12, v3_q) #Get angle between the two, ensure in range of acos
|
|
232
|
+
d = max(-1, min(1, d))
|
|
233
|
+
magnitude = math.acos(d)
|
|
234
|
+
|
|
235
|
+
#Determine the sign of the rotation
|
|
236
|
+
cross = _vec.vec_cross(v3_q12, v3_q)
|
|
237
|
+
sign = _vec.vec_dot(cross, v3_rot)
|
|
238
|
+
if sign < 0: sign = -1
|
|
239
|
+
else: sign = 1
|
|
240
|
+
|
|
241
|
+
out[2] = sign * abs(magnitude)
|
|
242
|
+
|
|
243
|
+
return out
|
|
244
|
+
|
|
245
|
+
def string_order_to_indices(order: str):
|
|
246
|
+
order = order.lower()
|
|
247
|
+
int_order = []
|
|
248
|
+
if len(order) != 3: raise ValueError()
|
|
249
|
+
for c in order:
|
|
250
|
+
if c == 'x': int_order.append(0)
|
|
251
|
+
elif c == 'y': int_order.append(1)
|
|
252
|
+
elif c == 'z': int_order.append(2)
|
|
253
|
+
else: raise ValueError()
|
|
254
|
+
|
|
255
|
+
return int_order
|
|
256
|
+
|
|
257
|
+
def quat_to_euler_angles(in_quat: list[float], order: str|list[int], extrinsic=False):
|
|
258
|
+
if isinstance(order, str):
|
|
259
|
+
if 'e' in order.lower():
|
|
260
|
+
extrinsic = True
|
|
261
|
+
elif 'i' in order.lower():
|
|
262
|
+
extrinsic = False
|
|
263
|
+
#Only care about the first 3 chars, the 4th optional char is extrinsic vs intrinsic
|
|
264
|
+
order = string_order_to_indices(order[:3])
|
|
265
|
+
|
|
266
|
+
#Extrinsic is performing the rotations around the axes of the original fixed system (Rotates around world axes)
|
|
267
|
+
#Intrinsic is performing the rotations around the axes of a coordinate system that rotates with each step (It rotates around the local axes of the rotating object).
|
|
268
|
+
#These two representations are very similar, and even related. An extrinsic rotation is the same as an intrinsic rotation by the same angles, but with the order inverted.
|
|
269
|
+
if extrinsic:
|
|
270
|
+
#Taking advantage of the above info that intrinsic = extrinsic but in reverse and vice versa.
|
|
271
|
+
order[0], order[2] = order[2], order[0]
|
|
272
|
+
|
|
273
|
+
#Actual conversion, everything else is just setup
|
|
274
|
+
angles = q2ea(in_quat, order)
|
|
275
|
+
|
|
276
|
+
if extrinsic:
|
|
277
|
+
#Swap back to the original desired order
|
|
278
|
+
angles[0], angles[2] = angles[2], angles[0]
|
|
279
|
+
|
|
280
|
+
return angles
|
|
281
|
+
|
|
282
|
+
def quat_from_euler_angles(angles: list[float], order: list[int], degrees=False, extrinsic=False):
|
|
283
|
+
if isinstance(order, str):
|
|
284
|
+
order = _vec.parse_axis_string(order)[0]
|
|
285
|
+
quat = [0, 0, 0, 1]
|
|
286
|
+
for i in range(len(angles)):
|
|
287
|
+
axis = order[i]
|
|
288
|
+
angle = angles[i]
|
|
289
|
+
if degrees:
|
|
290
|
+
angle = math.radians(angle)
|
|
291
|
+
|
|
292
|
+
#Create unit vector for this
|
|
293
|
+
unit_vec = [0, 0, 0]
|
|
294
|
+
unit_vec[axis] = 1
|
|
295
|
+
|
|
296
|
+
#Create quaternion for this rotation and apply to overall rotation
|
|
297
|
+
angle_quat = quat_from_axis_angle(unit_vec, angle)
|
|
298
|
+
if extrinsic:
|
|
299
|
+
quat = quat_mul(angle_quat, quat)
|
|
300
|
+
else:
|
|
301
|
+
quat = quat_mul(quat, angle_quat)
|
|
302
|
+
return quat
|
|
303
|
+
|
|
137
304
|
def quaternion_global_to_local(quat, vec):
|
|
138
305
|
inverse = quat_inverse(quat)
|
|
139
306
|
return quat_rotate_vec(inverse, vec)
|
yostlabs/math/vector.py
CHANGED
|
@@ -33,7 +33,8 @@ def vec_is_right_handed(order: str, negations: list[bool] = None):
|
|
|
33
33
|
return right_handed
|
|
34
34
|
|
|
35
35
|
def axis_to_unit_vector(axis: str):
|
|
36
|
-
axis
|
|
36
|
+
if isinstance(axis, str):
|
|
37
|
+
axis = axis.lower()
|
|
37
38
|
if axis == 'x' or axis == 0: return [1, 0, 0]
|
|
38
39
|
if axis == 'y' or axis == 1: return [0, 1, 0]
|
|
39
40
|
if axis == 'z' or axis == 2: return [0, 0, 1]
|
|
@@ -45,7 +46,7 @@ def parse_axis_string(axis: str):
|
|
|
45
46
|
axis = axis.lower()
|
|
46
47
|
order = [0, 1, 2]
|
|
47
48
|
multipliers = [1, 1, 1]
|
|
48
|
-
if 'x'
|
|
49
|
+
if any(c in axis for c in ['x', 'y', 'z']): #Using XYZ notation
|
|
49
50
|
index = 0
|
|
50
51
|
for c in axis:
|
|
51
52
|
if c == '-':
|
yostlabs/tss3/api.py
CHANGED
|
@@ -63,6 +63,9 @@ class ThreespaceCommand:
|
|
|
63
63
|
BINARY_START_BYTE = 0xf7
|
|
64
64
|
BINARY_START_BYTE_HEADER = 0xf9
|
|
65
65
|
|
|
66
|
+
BINARY_READ_SETTINGS_START_BYTE = 0xFA
|
|
67
|
+
BINARY_READ_SETTINGS_START_BYTE_HEADER = 0xFC
|
|
68
|
+
|
|
66
69
|
def __init__(self, name: str, num: int, in_format: str, out_format: str, custom_func: Callable = None):
|
|
67
70
|
self.info = ThreespaceCommandInfo(name, num, in_format, out_format)
|
|
68
71
|
self.in_format = _3space_format_to_external(self.info.in_format)
|
|
@@ -82,7 +85,7 @@ class ThreespaceCommand:
|
|
|
82
85
|
|
|
83
86
|
def send_command(self, com: ThreespaceOutputStream, *args, header_enabled = False):
|
|
84
87
|
cmd = self.format_cmd(*args, header_enabled=header_enabled)
|
|
85
|
-
com.write(cmd)
|
|
88
|
+
com.write(cmd)
|
|
86
89
|
|
|
87
90
|
#Read the command result from an already read buffer. This will modify the given buffer to remove
|
|
88
91
|
#that data as well
|
|
@@ -415,6 +418,7 @@ class StreamableCommands(Enum):
|
|
|
415
418
|
GetBatteryPercent = 202
|
|
416
419
|
GetBatteryStatus = 203
|
|
417
420
|
|
|
421
|
+
GetGpsActiveState = 214
|
|
418
422
|
GetGpsCoord = 215
|
|
419
423
|
GetGpsAltitude = 216
|
|
420
424
|
GetGpsFixState = 217
|
|
@@ -917,6 +921,40 @@ class ThreespaceSensor:
|
|
|
917
921
|
return list(response_dict.values())[0]
|
|
918
922
|
return response_dict
|
|
919
923
|
|
|
924
|
+
"""
|
|
925
|
+
WIP. Currently does not work with string values or have full validation.
|
|
926
|
+
"""
|
|
927
|
+
def get_setting_binary(self, key: str, format: str, use_threespace_format=True):
|
|
928
|
+
if use_threespace_format:
|
|
929
|
+
format = _3space_format_to_external(format)
|
|
930
|
+
checksum = sum(ord(v) for v in key) % 256
|
|
931
|
+
#Format = StartByte + Key + Null Terminator of key + Checksum
|
|
932
|
+
cmd = struct.pack(f"<B{len(key)}sBB", ThreespaceCommand.BINARY_READ_SETTINGS_START_BYTE_HEADER, key.encode(), 0, checksum)
|
|
933
|
+
self.com.write(cmd)
|
|
934
|
+
try:
|
|
935
|
+
min_response_len = key.index(';')
|
|
936
|
+
except ValueError:
|
|
937
|
+
min_response_len = len(key)
|
|
938
|
+
if min_response_len < len(THREESPACE_GET_SETTINGS_ERROR_RESPONSE):
|
|
939
|
+
min_response_len = len(THREESPACE_GET_SETTINGS_ERROR_RESPONSE)
|
|
940
|
+
min_response_len += (1 + THREESPACE_BINARY_SETTINGS_ID_SIZE) #Null terminator and header size
|
|
941
|
+
if self.__await_get_settings_binary(min_response_len) != THREESPACE_AWAIT_COMMAND_FOUND:
|
|
942
|
+
raise RuntimeError(f"Failed to get binary setting: {key}")
|
|
943
|
+
self.com.read(THREESPACE_BINARY_SETTINGS_ID_SIZE) #Read pass the Header ID
|
|
944
|
+
|
|
945
|
+
try:
|
|
946
|
+
echoed_key = self.com.read_until(b'\0')[:-1].decode()
|
|
947
|
+
if echoed_key != key.lower():
|
|
948
|
+
raise ValueError()
|
|
949
|
+
except:
|
|
950
|
+
raise ValueError()
|
|
951
|
+
result = struct.unpack(f"<{format}", self.com.read(struct.calcsize(format)))
|
|
952
|
+
self.com.read(2) #Remove the null terminator and the checksum
|
|
953
|
+
if len(result) == 1:
|
|
954
|
+
return result[0]
|
|
955
|
+
return result
|
|
956
|
+
|
|
957
|
+
|
|
920
958
|
#-----------Base Settings Parsing----------------
|
|
921
959
|
|
|
922
960
|
def __await_set_settings(self, timeout=2):
|
|
@@ -1004,6 +1042,31 @@ class ThreespaceSensor:
|
|
|
1004
1042
|
self.misaligned = False
|
|
1005
1043
|
return THREESPACE_AWAIT_COMMAND_FOUND
|
|
1006
1044
|
|
|
1045
|
+
"""
|
|
1046
|
+
Incomplete, binary settings protocol is Work In Progress
|
|
1047
|
+
"""
|
|
1048
|
+
def __await_get_settings_binary(self, min_resp_length: int, timeout=2, check_bootloader=False):
|
|
1049
|
+
start_time = time.time()
|
|
1050
|
+
|
|
1051
|
+
while True:
|
|
1052
|
+
remaining_time = timeout - (time.time() - start_time)
|
|
1053
|
+
if remaining_time <= 0:
|
|
1054
|
+
return THREESPACE_AWAIT_COMMAND_TIMEOUT
|
|
1055
|
+
|
|
1056
|
+
if self.com.length < min_resp_length: continue
|
|
1057
|
+
if check_bootloader and self.com.peek(2) == b'OK':
|
|
1058
|
+
return THREESPACE_AWAIT_BOOTLOADER
|
|
1059
|
+
|
|
1060
|
+
id = self.com.peek(THREESPACE_BINARY_SETTINGS_ID_SIZE)
|
|
1061
|
+
if struct.unpack("<L", id)[0] != THREESPACE_BINARY_READ_SETTINGS_ID:
|
|
1062
|
+
self.__internal_update(self.__try_peek_header())
|
|
1063
|
+
continue
|
|
1064
|
+
|
|
1065
|
+
#TODO: Check the rest of the message structure, not just the ID
|
|
1066
|
+
|
|
1067
|
+
self.misaligned = False
|
|
1068
|
+
return THREESPACE_AWAIT_COMMAND_FOUND
|
|
1069
|
+
|
|
1007
1070
|
#---------------------------------BASE COMMAND PARSING--------------------------------------
|
|
1008
1071
|
def __try_peek_header(self):
|
|
1009
1072
|
"""
|
|
@@ -1651,6 +1714,7 @@ class ThreespaceSensor:
|
|
|
1651
1714
|
def getBatteryVoltage(self) -> ThreespaceCmdResult[float]: ...
|
|
1652
1715
|
def getBatteryPercent(self) -> ThreespaceCmdResult[int]: ...
|
|
1653
1716
|
def getBatteryStatus(self) -> ThreespaceCmdResult[int]: ...
|
|
1717
|
+
def getGpsActiveState(self) -> ThreespaceCmdResult[bool]: ...
|
|
1654
1718
|
def getGpsCoord(self) -> ThreespaceCmdResult[list[float]]: ...
|
|
1655
1719
|
def getGpsAltitude(self) -> ThreespaceCmdResult[float]: ...
|
|
1656
1720
|
def getGpsFixState(self) -> ThreespaceCmdResult[int]: ...
|
|
@@ -1810,6 +1874,7 @@ _threespace_commands: list[ThreespaceCommand] = [
|
|
|
1810
1874
|
ThreespaceCommand("getBatteryPercent", 202, "", "b"),
|
|
1811
1875
|
ThreespaceCommand("getBatteryStatus", 203, "", "b"),
|
|
1812
1876
|
|
|
1877
|
+
ThreespaceCommand("getGpsActiveState", 214, "", "b"),
|
|
1813
1878
|
ThreespaceCommand("getGpsCoord", 215, "", "dd"),
|
|
1814
1879
|
ThreespaceCommand("getGpsAltitude", 216, "", "f"),
|
|
1815
1880
|
ThreespaceCommand("getGpsFixState", 217, "", "b"),
|
yostlabs/tss3/consts.py
CHANGED
|
@@ -24,6 +24,8 @@ THREESPACE_OUTPUT_MODE_ASCII = 1
|
|
|
24
24
|
THREESPACE_OUTPUT_MODE_BINARY = 2
|
|
25
25
|
|
|
26
26
|
THREESPACE_GET_SETTINGS_ERROR_RESPONSE = "<KEY_ERROR>"
|
|
27
|
+
THREESPACE_BINARY_SETTINGS_ID_SIZE = 4
|
|
28
|
+
THREESPACE_BINARY_READ_SETTINGS_ID = 0xC695B5E1
|
|
27
29
|
|
|
28
30
|
#This is not comprehensive, just enough to seperate keys from debug messages
|
|
29
31
|
THREESPACE_SETTING_KEY_INVALID_CHARS = " :;"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yostlabs
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2026.2.4
|
|
4
4
|
Summary: Python resources and API for 3Space sensors from Yost Labs Inc.
|
|
5
5
|
Project-URL: Homepage, https://yostlabs.com/
|
|
6
6
|
Project-URL: Repository, https://github.com/YostLabs/3SpacePythonPackage/tree/main
|
|
@@ -6,18 +6,18 @@ yostlabs/communication/bluetooth.py,sha256=GuqCRauGxOt0smh9qLA1NCfs7s5onZJjngXZs
|
|
|
6
6
|
yostlabs/communication/serial.py,sha256=Ex41l3ePXWrZDF8ZGxWoQs1tR0yfwZaSdG7a597zBsA,6505
|
|
7
7
|
yostlabs/communication/socket.py,sha256=mZw_9-5teIPUMxLXQh7kVLu6CJxCNFC7a9CnhSAI2Og,3822
|
|
8
8
|
yostlabs/math/__init__.py,sha256=JFzsPQ4AbsX1AH1brBpn1c_Pa_ItF43__D3mlPvA2a4,34
|
|
9
|
-
yostlabs/math/quaternion.py,sha256=
|
|
10
|
-
yostlabs/math/vector.py,sha256=
|
|
9
|
+
yostlabs/math/quaternion.py,sha256=E61BcyN6vn6UdCqwQEpqs-YHElOj5GXuLH981xZmNEc,13765
|
|
10
|
+
yostlabs/math/vector.py,sha256=s-bNK9Qum8wgVKDfRUJ_Hz5rs7Pqbg9-wFb1XUAOe04,2788
|
|
11
11
|
yostlabs/tss3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
yostlabs/tss3/api.py,sha256=
|
|
13
|
-
yostlabs/tss3/consts.py,sha256=
|
|
12
|
+
yostlabs/tss3/api.py,sha256=mSBWjCrT-Re5xw4QR9QJjHxSYNTzy6nUEd7bAwVBfw8,89257
|
|
13
|
+
yostlabs/tss3/consts.py,sha256=HU72GAEb3aAi2be3Fny1tOwHPwhqomSAWgpVGXxMHIQ,2592
|
|
14
14
|
yostlabs/tss3/eepts.py,sha256=7A7sCyOfDiJgw5Y9pGneg-5YgNvcfKtqeS9FoVWfJO8,9540
|
|
15
15
|
yostlabs/tss3/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
yostlabs/tss3/utils/calibration.py,sha256=J7RnY23MUUo4lmfapHavUqrTArFo4PxGKw68sL4DfOM,14070
|
|
17
17
|
yostlabs/tss3/utils/parser.py,sha256=ij_kQpB3EukOq3O8wQTYhkqBP-OneiHMUcsHLuL6m-8,11347
|
|
18
18
|
yostlabs/tss3/utils/streaming.py,sha256=G2OjSIL9zub0EbkgDGDWaqSXoRY6MJzMD4mazWOCUOA,22419
|
|
19
19
|
yostlabs/tss3/utils/version.py,sha256=NT2H9l-oIRCYhV_yjf5UjkadoJQ0IN4eLl8y__pyTPc,3001
|
|
20
|
-
yostlabs-
|
|
21
|
-
yostlabs-
|
|
22
|
-
yostlabs-
|
|
23
|
-
yostlabs-
|
|
20
|
+
yostlabs-2026.2.4.dist-info/METADATA,sha256=ReMVDqVO5CzGLXI-z1qzRRNk_Ez8lMHv7KTV3s-ZCac,2774
|
|
21
|
+
yostlabs-2026.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
22
|
+
yostlabs-2026.2.4.dist-info/licenses/LICENSE,sha256=PtF8EXRlVhm1_ve52_3GHixSPwMn0tGajFxF3xKS-j0,1090
|
|
23
|
+
yostlabs-2026.2.4.dist-info/RECORD,,
|
|
File without changes
|