flcpy 0.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.
flcpy/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .flcpy import Controller
flcpy/flcpy.py ADDED
@@ -0,0 +1,342 @@
1
+ import socket
2
+ import struct
3
+ import sys
4
+ from typing import NamedTuple
5
+
6
+ class Output(NamedTuple):
7
+ outputs: dict
8
+
9
+ def get_output_of(self, obj: int) -> tuple:
10
+ if obj in self.outputs:
11
+ return self.outputs[obj]
12
+
13
+ return None
14
+
15
+
16
+ class SimulationResult(NamedTuple):
17
+ failed: int
18
+ outputs: dict
19
+
20
+ def is_failed(self) -> bool:
21
+ return self.failed != 0
22
+
23
+ def get_output_of(self, obj: int) -> tuple:
24
+ if obj in self.outputs:
25
+ return self.outputs[obj]
26
+
27
+ return None
28
+
29
+ buffer_size = 4096
30
+ uint_max = 4294967295
31
+
32
+ def bool_to_bytes(val: bool) -> bytes:
33
+ if val:
34
+ return b'\x00'
35
+ return b'\x01'
36
+
37
+ def sint_to_bytes(val : int) -> bytes:
38
+ return int.to_bytes(val, 4, byteorder=sys.byteorder, signed=True)
39
+
40
+ def uint_to_bytes(val : int) -> bytes:
41
+ return int.to_bytes(val, 4, byteorder=sys.byteorder, signed=False)
42
+
43
+ def bytes_to_uint(data : bytes, offset : int) -> int:
44
+ return int.from_bytes(data[offset:offset+4], byteorder=sys.byteorder, signed=False)
45
+
46
+ def double_to_bytes(val: float) -> bytes:
47
+ return struct.pack('d', val)
48
+
49
+ def bytes_to_double(data: bytes, offset: int) -> float:
50
+ return struct.unpack('d', data[offset:offset+8])[0]
51
+
52
+ def string_to_bytes(string: str) -> bytes:
53
+ return string.encode("utf-8")
54
+
55
+ class MessagingClient:
56
+ def __init__(self, host : str, port : int):
57
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
58
+ self.socket.connect((host, port))
59
+
60
+ def request(self, data : bytes) -> bytes:
61
+ self.socket.send(data)
62
+ return self.socket.recv(buffer_size)
63
+
64
+ def close(self):
65
+ self.socket.close()
66
+
67
+ class Controller:
68
+ def __init__(self, host : str = 'localhost', port : int = 25556, recover_world = True):
69
+ self.messaging = MessagingClient(host, port)
70
+ if recover_world:
71
+ self.clear()
72
+ self.recover_world()
73
+
74
+ # Parameters: none
75
+ # Returns: time step
76
+ def get_time_step(self) -> float:
77
+ reply = self.messaging.request(uint_to_bytes(0))
78
+ return bytes_to_double(reply, 0)
79
+
80
+ # Parameters: object path, relative to object
81
+ # Returns: None or object id
82
+ def get_object_by_path(self, path : str, relative_to_object : int=None) -> int:
83
+ request = uint_to_bytes(1)
84
+ if relative_to_object is None:
85
+ request += uint_to_bytes(uint_max)
86
+ else:
87
+ request += uint_to_bytes(relative_to_object)
88
+ array = string_to_bytes(path)
89
+ request += uint_to_bytes(len(array))
90
+ request += array
91
+ reply = self.messaging.request(request)
92
+ obj = bytes_to_uint(reply, 0)
93
+ if obj == uint_max:
94
+ return None
95
+ else:
96
+ return obj
97
+
98
+ # See 'get_object_by_path'
99
+ def get_object(self, path: str, relative_to_object: int = None) -> int:
100
+ return self.get_object_by_path(path, relative_to_object)
101
+
102
+ # Parameters: list of object to retrieve output
103
+ # Returns: Output
104
+ def get_output(self, output_objs: list) -> Output:
105
+ request = uint_to_bytes(2)
106
+ request += uint_to_bytes(len(output_objs))
107
+ for obj in output_objs:
108
+ request += uint_to_bytes(obj)
109
+
110
+ reply = self.messaging.request(request)
111
+ count = bytes_to_uint(reply, 0)
112
+ outputs = {}
113
+ start = 4
114
+ for i in range(count):
115
+ obj = bytes_to_uint(reply, start)
116
+ start += 4
117
+ dof = bytes_to_uint(reply, start)
118
+ start += 4
119
+ output = []
120
+ for j in range(dof):
121
+ output.append(bytes_to_double(reply, start))
122
+ start += 8
123
+ outputs[obj] = tuple(output)
124
+
125
+ return Output(outputs)
126
+
127
+ # Parameters: none
128
+ # Returns: (success, current state), where state = 0 for not running, 1 for running.
129
+ def start(self) -> tuple:
130
+ request = uint_to_bytes(3)
131
+ request += b'\x00'
132
+ reply = self.messaging.request(request)
133
+ success = reply[0:1] == b'\x00'
134
+ state = int.from_bytes(reply[1:2], byteorder=sys.byteorder, signed=False)
135
+ return success, state
136
+
137
+ # Parameters: none
138
+ # Returns: (success, current state), where state = 0 for not running, 1 for running.
139
+ def reset(self) -> tuple:
140
+ request = uint_to_bytes(3)
141
+ request += b'\x01'
142
+ reply = self.messaging.request(request)
143
+ success = reply[0:1] == b'\x00'
144
+ state = int.from_bytes(reply[1:2], byteorder=sys.byteorder, signed=False)
145
+ return success, state
146
+
147
+ # Parameters: none
148
+ # Returns: (success, current state), where state = 0 for not running, 1 for running.
149
+ def clear(self) -> tuple:
150
+ request = uint_to_bytes(3)
151
+ request += b'\x02'
152
+ reply = self.messaging.request(request)
153
+ success = reply[0:1] == b'\x00'
154
+ state = int.from_bytes(reply[1:2], byteorder=sys.byteorder, signed=False)
155
+ return success, state
156
+
157
+ def stop(self) -> tuple:
158
+ return self.clear()
159
+
160
+ # Parameters: steps to simulate, inputs, output_objs
161
+ # (inputs: obj (int) -> input (tuple of float))
162
+ # (output_objs: list of obj (int))
163
+ # Returns: (status code, sensor outputs), where status code = 0 for success, 1 for inpur error, 2 for simulation error.
164
+ # (sensor outputs: index (int) -> output (tuple of float))
165
+ def simulate(self, steps : int, inputs : dict, output_objs : list = []) -> SimulationResult:
166
+ request = uint_to_bytes(4)
167
+ request += uint_to_bytes(steps)
168
+ request += uint_to_bytes(len(inputs))
169
+ for obj in inputs:
170
+ request += uint_to_bytes(obj)
171
+ input = inputs[obj]
172
+ if not hasattr(input, '__len__'):
173
+ input = (input,)
174
+ request += uint_to_bytes(len(input))
175
+ for val in input:
176
+ request += struct.pack('d', val)
177
+ request += uint_to_bytes(len(output_objs))
178
+ for obj in output_objs:
179
+ request += uint_to_bytes(obj)
180
+
181
+ reply = self.messaging.request(request)
182
+ failed = int.from_bytes(reply[0:1], byteorder=sys.byteorder, signed=False)
183
+ if failed != 0:
184
+ return SimulationResult(failed, dict())
185
+
186
+ output_count = bytes_to_uint(reply, 1)
187
+ start = 5
188
+ outputs = {}
189
+ for i in range(output_count):
190
+ obj = bytes_to_uint(reply, start)
191
+ start += 4
192
+ dof = bytes_to_uint(reply, start)
193
+ start += 4
194
+ output = []
195
+ for j in range(dof):
196
+ output.append(bytes_to_double(reply, start))
197
+ start += 8
198
+ outputs[obj] = tuple(output)
199
+
200
+ return SimulationResult(0, outputs)
201
+
202
+ # Parameters: Simulate until time, inputs, output_objs
203
+ # (inputs: obj (int) -> input (tuple of float))
204
+ # (output_objs: list of obj (int))
205
+ # Returns: (status code, sensor outputs), where status code = 0 for success, 1 for inpur error, 2 for simulation error.
206
+ # (sensor outputs: index (int) -> output (tuple of float))
207
+ def simulate_until(self, until_time : float, inputs : dict, output_objs : list = []) -> SimulationResult:
208
+ request = uint_to_bytes(11)
209
+ request += double_to_bytes(until_time)
210
+ request += uint_to_bytes(len(inputs))
211
+ for obj in inputs:
212
+ request += uint_to_bytes(obj)
213
+ input = inputs[obj]
214
+ if not hasattr(input, '__len__'):
215
+ input = (input,)
216
+ request += uint_to_bytes(len(input))
217
+ for val in input:
218
+ request += struct.pack('d', val)
219
+ request += uint_to_bytes(len(output_objs))
220
+ for obj in output_objs:
221
+ request += uint_to_bytes(obj)
222
+
223
+ reply = self.messaging.request(request)
224
+ failed = int.from_bytes(reply[0:1], byteorder=sys.byteorder, signed=False)
225
+ if failed != 0:
226
+ return SimulationResult(failed, dict())
227
+
228
+ output_count = bytes_to_uint(reply, 1)
229
+ start = 5
230
+ outputs = {}
231
+ for i in range(output_count):
232
+ obj = bytes_to_uint(reply, start)
233
+ start += 4
234
+ dof = bytes_to_uint(reply, start)
235
+ start += 4
236
+ output = []
237
+ for j in range(dof):
238
+ output.append(bytes_to_double(reply, start))
239
+ start += 8
240
+ outputs[obj] = tuple(output)
241
+
242
+ return SimulationResult(0, outputs)
243
+
244
+ # Parameters: sequential number
245
+ # Returns: success or not
246
+ def export(self, seq_num : int) -> bool:
247
+ request = uint_to_bytes(5)
248
+ request += uint_to_bytes(seq_num)
249
+ reply = self.messaging.request(request)
250
+ return reply[0:1] == b'\x00'
251
+
252
+ # parameters: object to clone, position, rotation, name
253
+ # (position: a tuple of 3 float, in x, y, z)
254
+ # (rotation: a tuple of 4 float, in quaternion w, x, y, z)
255
+ # Returns: None if failed, otherwise the cloned object
256
+ def clone_object(self, source_obj : int, position: tuple, rotation: tuple, name: str) -> int:
257
+ request = uint_to_bytes(6)
258
+ request += uint_to_bytes(source_obj)
259
+ for i in range(3):
260
+ request += double_to_bytes(position[i])
261
+ for i in range(4):
262
+ request += double_to_bytes(rotation[i])
263
+ array = string_to_bytes(name)
264
+ request += uint_to_bytes(len(array))
265
+ request += array
266
+
267
+ reply = self.messaging.request(request)
268
+ failed = int.from_bytes(reply[0:1], byteorder=sys.byteorder, signed=False)
269
+ if failed != 0:
270
+ return None
271
+ return bytes_to_uint(reply, 1)
272
+
273
+ def set_object_property(self, obj: int, property_path: str, value: bytes) -> bool:
274
+ request = uint_to_bytes(7)
275
+ request += uint_to_bytes(obj)
276
+ path_arr = string_to_bytes(property_path)
277
+ request += uint_to_bytes(len(path_arr))
278
+ request += path_arr
279
+ request += uint_to_bytes(len(value))
280
+ request += value
281
+
282
+ reply = self.messaging.request(request)
283
+ return reply[0:1] == b'\x00'
284
+
285
+ def set_object_property_float(self, obj: int, property_path: str, value: float) -> bool:
286
+ return self.set_object_property(obj, property_path, double_to_bytes(value))
287
+
288
+ def set_object_property_int(self, obj: int, property_path: str, value: int) -> bool:
289
+ return self.set_object_property(obj, property_path, sint_to_bytes(value))
290
+
291
+ def set_object_property_uint(self, obj: int, property_path: str, value: int) -> bool:
292
+ return self.set_object_property(obj, property_path, uint_to_bytes(value))
293
+
294
+ def set_object_property_bool(self, obj: int, property_path: str, value: bool) -> bool:
295
+ return self.set_object_property(obj, property_path, bool_to_bytes(value))
296
+
297
+ def set_object_property_string(self, obj: int, property_path: str, value: str) -> bool:
298
+ return self.set_object_property(obj, property_path, string_to_bytes(value))
299
+
300
+ def set_object_property_reference(self, obj: int, property_path: str, value: int) -> bool:
301
+ return self.set_object_property(obj, property_path, uint_to_bytes(value))
302
+
303
+ # Recover the world to initial state. Not available when simulation is started.
304
+ # Returns: success or not
305
+ def recover_world(self) -> bool:
306
+ request = uint_to_bytes(8)
307
+ reply = self.messaging.request(request)
308
+ return reply[0:1] == b'\x00'
309
+
310
+ # Parameters: preset path
311
+ # Returns: None or preset's object id
312
+ def get_preset_by_path(self, path : str) -> int:
313
+ request = uint_to_bytes(9)
314
+ array = string_to_bytes(path)
315
+ request += uint_to_bytes(len(array))
316
+ request += array
317
+ reply = self.messaging.request(request)
318
+ obj = bytes_to_uint(reply, 0)
319
+ if obj == uint_max:
320
+ return None
321
+ else:
322
+ return obj
323
+
324
+ # Parameters: system name
325
+ # Returns: None or system's object id
326
+ def get_system_by_name(self, name : str) -> int:
327
+ request = uint_to_bytes(10)
328
+ array = string_to_bytes(name)
329
+ request += uint_to_bytes(len(array))
330
+ request += array
331
+ reply = self.messaging.request(request)
332
+ obj = bytes_to_uint(reply, 0)
333
+ if obj == uint_max:
334
+ return None
335
+ else:
336
+ return obj
337
+
338
+ def close(self, recover_world = False):
339
+ if recover_world:
340
+ self.clear()
341
+ self.recover_world()
342
+ self.messaging.close()
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: flcpy
3
+ Version: 0.2.4
4
+ Summary: Python control interface for Flare Live / Flare Real
5
+ Author-email: Jiwei Wang <zmh78879@gmail.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # Flare Python Controller (flcpy)
15
+
16
+ Python networking interface for Flare Live / Flare Real.
17
+
18
+ ## Install & Upgrade
19
+
20
+ ```
21
+ pip install --upgrade flcpy
22
+ ```
23
+
24
+ ## Use
25
+
26
+ ```
27
+ import flcpy
28
+
29
+ c = flcpy.Controller()
30
+ obj = c.get_object(...)
31
+ ...
32
+ ```
@@ -0,0 +1,7 @@
1
+ flcpy/__init__.py,sha256=WcVxn_YRoSsV4APNAAzYGCeKF8YIc1rUF15PDwkAk8o,29
2
+ flcpy/flcpy.py,sha256=x0ErbwjAuAwiFxu__7hqLDwg2Dq14Qm1kq-t2hu_B9o,12875
3
+ flcpy-0.2.4.dist-info/licenses/LICENSE,sha256=WdC21H3GYeYSJhDX93szPIiP73ICkoub2kns1MWhAbE,1067
4
+ flcpy-0.2.4.dist-info/METADATA,sha256=7cguAfEYnJjt8wVfW_l8EOhl61QFq5x28sMend086H0,676
5
+ flcpy-0.2.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ flcpy-0.2.4.dist-info/top_level.txt,sha256=tsxUVN6BBzIIfL4670mbM8bqLO5WhlQcUHh6nSnk_ms,6
7
+ flcpy-0.2.4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Jiwei Wang
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ flcpy