source: framspy/gui/framsutils/FramsSocket.py @ 1198

Last change on this file since 1198 was 1198, checked in by Maciej Komosinski, 15 months ago

Added simple Python GUI for Framsticks library/server

File size: 18.4 KB
Line 
1from typing import List, Dict, Any, Tuple, Callable
2from .comm import CommWrapper
3from .creature import Creature
4from gui.framsutils.framsProperty import Property
5from framsfiles import reader
6import threading
7import asyncio
8from functools import partial
9import time
10import glm
11from .utils import parseNeuronDToOrient
12
13class FramsSocket:
14        def __init__(self):
15                self.comm = CommWrapper()
16                self.refreshControlButtonsCallback = None
17                self.refreshTreeviewCallback = None
18                self.loop = asyncio.get_event_loop()
19                self.loop_thread = threading.Thread(target=self.loop.run_forever)
20                self.loop_thread.start()
21               
22        def initConnection(self, address: str = '127.0.0.1', port: int = 9009) -> None:
23                self.comm = CommWrapper()
24                i = 0
25                time_delta = 0.1
26                total_time = 10
27                self.comm.start(address, port)
28                while self.comm.client.connecting == True and i < total_time / time_delta:
29                        time.sleep(time_delta)
30                        i += 1
31                response = self.comm.client.connected
32                if response:
33                        self.comm.client.consumer.runningChangeEventCallback = self._runningChangeEventCallback
34                        self.comm.client.consumer.populationsGroupChangeEventCallback = self._populationsGroupChangeEventCallback
35                        self.comm.client.consumer.genepoolsGroupChangeEventCallback = self._genepoolsGroupChangeEventCallback
36                        self.sendRequest("reg /simulator/populations/groups_changed")
37                        self.sendRequest("reg /simulator/genepools/groups_changed")
38                        self.sendRequest("reg /simulator/running_changed")
39                        self.sendRequest("reg /cli/messages")
40                else:
41                        self.comm.close()
42                        raise ConnectionError()
43
44        def closeConnection(self) -> None:
45                if self.comm:
46                        self.comm.stop()
47                        self.loop.call_soon_threadsafe(self.loop.stop)
48                        self.loop_thread.join()
49
50        def sendRequest(self, request: str, timeout = 1.0, return_index = False, index=-1) -> List[str]:
51                response = []
52
53                if self.comm.connected:
54                        if self.comm:
55                                idx = self.comm.write(request, index=index)
56                                future = asyncio.run_coroutine_threadsafe(self.comm.read(idx, timeout), self.loop)
57                                try:
58                                        response = future.result(timeout)
59                                        response = [e+'\n' for e in response.split('\n')]
60                                except asyncio.TimeoutError:
61                                        future.cancel()
62                                except Exception as ex:
63                                        print("dropped request id {}, exception: {}".format(idx, ex))
64
65                append = False
66                change = False
67                for i in range(len(response) - 2, -1, -1):
68                        if response[i].find('~\n') >= 0 and response[i].find('\~\n') < 0:
69                                response[i].replace('~\n', '')
70                                change = True
71
72                        if append and not change:
73                                response[i] = response[i] + response.pop(i + 1) #merge middle line
74                        elif append and change:
75                                response[i] = response[i] + response.pop(i + 1) #merge last line
76
77                        if change:
78                                append = not append
79                                change = False
80                if return_index:
81                        return response, idx
82                return response
83
84        def writeCreatureFromString(self, groupNo: int, creature: str) -> None:
85                creatureString = "call /simulator/populations/groups/{} createFromString {}".format(groupNo, creature)
86                self.sendRequest(creatureString)
87
88        def step(self):
89                self.sendRequest("call /simulator step")
90
91        def start(self):
92                self.sendRequest("call /simulator start")
93
94        def stop(self):
95                self.sendRequest("call /simulator stop")
96
97        def info(self, path: str = "/") -> List[Property]:
98                response = self.sendRequest("info {}".format(path))
99                properties = self._infoParser(response)
100                return properties
101
102        def infoList(self, path: str) -> List[Property]:
103                response = self.sendRequest("get {}".format(path))
104                properties = self._infoParser(response)
105                return properties
106
107        def _infoParser(self, response: List[str], packToProperty = True) -> List[Property]:
108                properties: List[Property] = []
109
110                tmp = reader.loads(''.join(response))
111                if packToProperty:
112                        properties = [Property(p=x) for x in tmp]
113                        return properties
114                return tmp
115
116        def _VstyleParserColor(self, style: str):
117                f = "color=0x"
118                idx = style.find(f)
119                if idx > -1:
120                        hx = style[idx+len(f):idx+len(f)+6]
121                        r = int(hx[0:2], 16) / 255.0
122                        g = int(hx[2:4], 16) / 255.0
123                        b = int(hx[4:6], 16) / 255.0
124                        return r, g, b
125                return None, None, None
126
127        def readCreatures(self, groupNo: int, creatureNo: int, color):
128                creatures: List[Creature] = []
129                if color:
130                        creatureString = "get /simulator/populations/groups/{}/creatures/{}(/mechparts{{x,y,z,orient}},/joints{{p1,p2,vr,vg,vb,Vstyle}},/parts{{vr,vg,vb,Vstyle}},/neurodefs{{p,j,d}},{{model_Vstyle}})".format(groupNo, creatureNo)
131                else:
132                        creatureString = "get /simulator/populations/groups/{}/creatures/{}(/mechparts{{x,y,z,orient}},/joints{{p1,p2}},/neurodefs{{p,j,d}})".format(groupNo, creatureNo)
133                response = self.sendRequest(creatureString)
134
135                mechParts = []
136                joints = []
137                partColors = []
138                neurons = []
139                neuronStyles = []
140                creature: Creature = None
141
142                mechPart: List[float] = [0,0,0]
143                joint: List[int] = [0,0]
144                partColor: glm.vec3 = glm.vec3(0,0,0)
145                neuron: Tuple(int, int) = (-1,-1)
146
147                files = [i for i, x in enumerate(response) if x.startswith("file")]
148                files.append(-1)
149
150                for i in range(len(files)-1):
151                        file = response[files[i]]
152
153                        groupsStr = "groups/"
154                        groupBegin = file.find(groupsStr) + len(groupsStr)
155                        groupEnd = file.find("/", groupBegin)
156                        group = int(file[groupBegin:groupEnd])
157
158                        creaturesStr = "creatures/"
159                        creatureIndexBegin = file.find(creaturesStr) + len(creaturesStr)
160                        creatureIndexEnd = file.find("/", creatureIndexBegin)
161                        creatureIndex = int(file[creatureIndexBegin:creatureIndexEnd])
162
163                        creature = next((x for x in creatures if x.group == group and x.index == creatureIndex), None)
164                        if not creature:
165                                creature = Creature(group, creatureIndex)
166                                creatures.append(creature)
167
168                        mechParts = []
169                        joints = []
170                        partColors = []
171                        jointColors = []
172                        neurons = []
173                        neuronStyles = []
174                        partOrient = []
175                        neuroRelativeOrient = []
176
177                        resp = self._infoParser(response[files[i]:files[i+1]], False)
178
179                        for p in resp:
180                                if p["_classname"] == "MechPart":
181                                        mechPart = [p['x'], p['y'], p['z']]
182                                        orient = p["orient"]
183                                        orient = orient[orient.find('[')+1:orient.find(']')]
184                                        orient = glm.mat4(glm.mat3(*[float(x) for x in orient.split(',')]))
185                                        mechParts.append(mechPart)
186                                        partOrient.append(orient)
187                                elif p["_classname"] == "Joint":
188                                        joint = [p["p1"], p["p2"]]
189                                        joints.append(joint)
190                                       
191                                        if color:
192                                                jointColor = glm.vec3(p["vr"], p["vg"], p["vb"])
193                                                colors = self._VstyleParserColor(p["Vstyle"])
194                                                if colors and colors[0] != None:
195                                                        jointColor = glm.vec3(colors)
196
197                                                jointColors.append(jointColor)
198                                elif p["_classname"] == "Part":
199                                        partColor = glm.vec3(p["vr"], p["vg"], p["vb"])
200
201                                        colors = self._VstyleParserColor(p["Vstyle"])
202                                        if colors and colors[0] != None:
203                                                partColor = glm.vec3(colors)
204
205                                        partColors.append(partColor)
206                                elif p["_classname"] == "NeuroDef":
207                                        neuron = (p['p'], p['j'])
208                                        neurons.append(neuron)
209                                        neuronStyles.append(p['d'].split(':')[0])
210
211                                        '''ix = p['d'].find(':')
212                                        rorient = glm.mat4(1)
213                                        if ix != -1:
214                                                ds = p['d'][ix+1:].split(',')
215                                                ds = [x.split('=') for x in ds]
216                                                rots = [x for x in ds if x[0].startswith('r')]
217
218                                                if not any("ry" in x[0] for x in rots):
219                                                        rots.append(["ry", '0'])
220                                                if not any("rz" in x[0] for x in rots):
221                                                        rots.append(["rz", '0'])
222
223                                                angle = next(float(x[1]) for x in rots if x[0] == "rz")
224                                                rorient = glm.rotate(rorient, angle, glm.vec3(0,0,1))
225                                                angle = -next(float(x[1]) for x in rots if x[0] == "ry")
226                                                rorient = glm.rotate(rorient, angle, glm.vec3(0,1,0))'''
227                                               
228                                        rorient = parseNeuronDToOrient(p['d'])
229                                        neuroRelativeOrient.append(rorient)
230                                elif p["_classname"] == "Creature":
231                                        colors = self._VstyleParserColor(p["model_Vstyle"])
232                                        if colors and colors[0] != None:
233                                                creature.colorsPart = [glm.vec3(colors) for i in creature.colorsPart]
234
235                        creature.mechParts.extend(mechParts.copy())
236                        creature.joints.extend(joints.copy())
237                        creature.colorsPart.extend(partColors.copy())
238                        creature.colorsJoint.extend(jointColors.copy())
239                        creature.neurons.extend(neurons.copy())
240                        creature.styleNeuron.extend(neuronStyles.copy())
241                        creature.partOrient.extend(partOrient.copy())
242                        creature.neuronRelativeOrient.extend(neuroRelativeOrient.copy())
243
244                invalidCreatures = []
245                for creature in creatures:
246                        if len(creature.mechParts) == 0:
247                                invalidCreatures.append(creature)
248                for creature in invalidCreatures:
249                        creatures.remove(creature)
250
251                return creatures
252
253        def readGenePoolsGroups(self) -> Dict[str, int]:
254                genotypes: Dict[str, int] = {}
255                tmp = []
256                for i in range(3):
257                        response = self.sendRequest("get /simulator/genepools/groups/+ index,name")
258                        tmp = self._infoParser(response)
259                        if len(tmp) > 0:
260                                break
261
262                for i in tmp:
263                        genotypes[i.p["name"]] = i.p["index"]
264
265                return genotypes
266
267        def readGenePools(self, props: List[str]) -> Dict[str, List[Dict[str, Any]]]:
268                genotypes: Dict[str, List[Dict[str, Any]]] = {}
269
270                for i in range(3):
271                        response = self.sendRequest("get /simulator/genepools/groups/+ index,name")
272                        tmp = self._infoParser(response)
273                        if len(tmp) > 0:
274                                break
275                else:
276                        return genotypes
277
278                tu = {}
279                for i in tmp:
280                        tu[i.p["index"]] = i.p["name"]
281                        genotypes[i.p["index"]] = []
282
283                p = ",".join(props)
284                response = self.sendRequest("get /simulator/genepools/groups/+/genotypes/+ {}".format(p))
285
286                files = [i for i, x in enumerate(response) if x.startswith("file")]
287                files.append(-1)
288
289                for i in range(len(files)-1):
290                        file = response[files[i]]
291
292                        groupsStr = "groups/"
293                        groupBegin = file.find(groupsStr) + len(groupsStr)
294                        groupEnd = file.find("/", groupBegin)
295                        group = int(file[groupBegin:groupEnd])
296
297                        genotypesStr = "genotypes/"
298                        genotypeIndexBegin = file.find(genotypesStr) + len(genotypesStr)
299                        genotypeIndexEnd = file.find("/", genotypeIndexBegin-1) +2
300                        genotypeIndex = int(file[genotypeIndexBegin:genotypeIndexEnd])
301
302                        resp = self._infoParser(response[files[i]:files[i+1]], False)
303
304                        genotypes[group].append(resp[0])
305                        genotypes[group][-1].update({"group": group, "index": genotypeIndex})
306
307                g = {}
308                for (i, gen) in genotypes.items():
309                        g[tu[i]] = gen
310
311                return g
312
313        def readPopulationsGroups(self) -> Dict[str, int]:
314                creatures: Dict[str, int] = {}
315                tmp = []
316                for i in range(3):
317                        response = self.sendRequest("get /simulator/populations/groups/+ index,name")
318                        tmp = self._infoParser(response)
319                        if len(tmp) > 0:
320                                break
321
322                for i in tmp:
323                        creatures[i.p["name"]] = i.p["index"]
324
325                return creatures
326
327        def readPopulations(self, props: List[str]) -> Dict[str, List[Dict[str, Any]]]:
328                creatures: Dict[str, List[Dict[str, Any]]] = {}
329                for i in range(3):
330                        response = self.sendRequest("get /simulator/populations/groups/+ index,name")
331                        tmp = self._infoParser(response)
332                        if len(tmp) > 0:
333                                break
334                else:
335                        return creatures
336
337                tu = {}
338                for i in tmp:
339                        tu[i.p["index"]] = i.p["name"]
340                        creatures[i.p["index"]] = []
341
342                p = ",".join(props)
343                response = self.sendRequest("get /simulator/populations/groups/+/creatures/+ {}".format(p))
344
345                files = [i for i, x in enumerate(response) if x.startswith("file")]
346                files.append(-1)
347
348                for i in range(len(files)-1):
349                        file = response[files[i]]
350
351                        groupsStr = "groups/"
352                        groupBegin = file.find(groupsStr) + len(groupsStr)
353                        groupEnd = file.find("/", groupBegin)
354                        group = int(file[groupBegin:groupEnd])
355
356                        creaturesStr = "creatures/"
357                        creatureIndexBegin = file.find(creaturesStr) + len(creaturesStr)
358                        creatureIndexEnd = file.find("/", creatureIndexBegin-1) +2
359                        creatureIndex = int(file[creatureIndexBegin:creatureIndexEnd])
360
361                        resp = self._infoParser(response[files[i]:files[i+1]], False)
362
363                        creatures[group].append(resp[0])
364                        creatures[group][-1].update({"group": group, "index": creatureIndex})
365
366                c = {}
367                for (i, cre) in creatures.items():
368                        c[tu[i]] = cre
369
370                return c
371
372        def readCreatureInfo(self, groupNo: int, creatureNo: int) -> List[Property]:
373                response = self.sendRequest("info /simulator/populations/groups/{}/creatures/{}".format(groupNo, creatureNo))
374                response2 = self.sendRequest("get /simulator/populations/groups/{}/creatures/{}".format(groupNo, creatureNo))
375
376                tmpProperties = self._infoParser(response)
377                tmpValues = self._infoParser(response2)
378                properties: List[Property] = []
379                for prop in tmpProperties:
380                        if "type" in prop.p and "id" in prop.p and len(prop.p["type"]) > 0:
381                                if prop.p["type"][0] not in "xole": #xol is a list of unwanted types
382                                        if prop.p["type"][0] == 'p':
383                                                prop.p["value"] = partial(self.call, "/simulator/populations/groups/{}/creatures/{} {}".format(groupNo, creatureNo, prop.p["id"]))
384                                        else:
385                                                prop.p["value"] = next(v for k, v in tmpValues[0].p.items() if k == prop.p["id"])
386                                        properties.append(prop)
387
388                return properties
389
390        def readGenotypeInfo(self, groupNo: int, genotypeNo: int) -> List[Property]:
391                response = self.sendRequest("info /simulator/genepools/groups/{}/genotypes/{}".format(groupNo, genotypeNo))
392                response2 = self.sendRequest("get /simulator/genepools/groups/{}/genotypes/{}".format(groupNo, genotypeNo))
393
394                tmpProperties = self._infoParser(response)
395                tmpValues = self._infoParser(response2)
396                properties: List[Property] = []
397                for prop in tmpProperties:
398                        if "type" in prop.p and "id" in prop.p and len(prop.p["type"]) > 0:
399                                if prop.p["type"][0] not in "xole": #xol is a list of unwanted types
400                                        if prop.p["type"][0] == 'p':
401                                                prop.p["value"] = partial(self.call, "/simulator/genepools/groups/{}/genotypes/{} {}".format(groupNo, genotypeNo, prop.p["id"]))
402                                        else:
403                                                prop.p["value"] = next(v for k, v in tmpValues[0].p.items() if k == prop.p["id"])
404                                        properties.append(prop)
405
406                return properties
407
408        def readPropertyInfo(self, path) -> List[Property]:
409                response = self.sendRequest("info {}".format(path))
410                response2 = self.sendRequest("get {}".format(path))
411
412                tmpProperties = self._infoParser(response)
413                tmpValues = self._infoParser(response2)
414                properties: List[Property] = []
415                for prop in tmpProperties:
416                        if "type" in prop.p and "id" in prop.p and len(prop.p["type"]) > 0:
417                                if prop.p["type"][0] and prop.p["type"][0] not in "xole": #xole is a list of unwanted types
418                                        if prop.p["type"][0] == 'p':
419                                                prop.p["value"] = partial(self.call, "{} {}".format(path, prop.p["id"]))
420                                        else:
421                                                prop.p["value"] = next((v for k, v in tmpValues[0].p.items() if k == prop.p["id"]), "")
422                                        properties.append(prop)
423
424                return properties
425
426        def writeCreatureInfo(self, uid: str, prop: str, value: str) -> bool:
427                response = self.sendRequest("get /simulator/populations size")
428                size = next(int(x[x.find(":")+1:-1]) for x in response if x.startswith("size"))
429                if response:
430                        for i in range(size):
431                                res = self.sendRequest("set /simulator/populations/groups/{}/creatures/{} {} \"{}\"".format(i, uid, prop, value))
432                        return True
433                return False
434
435        def writeGenotypeInfo(self, uid: str, prop: str, value: str) -> bool:
436                response = self.sendRequest("get /simulator/genepools size")
437                size = next(int(x[x.find(":")+1:-1]) for x in response if x.startswith("size"))
438                if response:
439                        for i in range(size):
440                                res = self.sendRequest("set /simulator/genepools/groups/{}/genotypes/{} {} \"{}\"".format(i, uid, prop, value))
441                        return True
442                return False
443
444        def writePropertyInfo(self, path: str, prop: str, value: str):
445                response = self.sendRequest("set {} {} \"{}\"".format(path, prop, value))
446                if response:
447                        return True
448                return False
449
450        def call(self, path: str):
451                self.sendRequest("call {}".format(path))
452
453        def _runningChangeEventCallback(self, block: str, header: str):
454                prop = self._infoParser([block])[0]
455                if self.refreshControlButtonsCallback:
456                        self.refreshControlButtonsCallback(prop.p["value"])
457               
458        def _populationsGroupChangeEventCallback(self, block: str, header: str):
459                if self.refreshTreeviewCallback:
460                        self.refreshTreeviewCallback()
461
462        def _genepoolsGroupChangeEventCallback(self, block: str, header: str):
463                if self.refreshTreeviewCallback:
464                        self.refreshTreeviewCallback()
465
466        def registerMessageEventCallback(self, callback: Callable[[str, str], None]):
467                self.comm.client.consumer.messagesEventCallback = callback
468
469        def loadFile(self, path: str) -> None:
470                response, idx = self.sendRequest("call /simulator netload", return_index=True)
471                if response:
472                        if response[0].startswith("needfile"):
473                                with open(path) as file:
474                                        data = "file \n" + file.read() + "eof"
475                                        self.sendRequest(data, index=idx)
476
477        def importFile(self, path: str, options: int) -> None:
478                response, idx = self.sendRequest("call /simulator netimport {}".format(options), return_index=True)
479                if response:
480                        if response[0].startswith("needfile"):
481                                with open(path) as file:
482                                        data = "file \n" + file.read() + "eof"
483                                        self.sendRequest(data, index=idx)
484
485        def saveFile(self, path: str, options: int) -> None:
486                response = self.sendRequest("call /simulator netexport {} -1 -1".format(options))
487                if response:
488                        start = 0
489                        end = len(response) - 1
490                        if response[0].startswith("file"):
491                                start = 1
492
493                        while response[end].strip() == '':
494                                end -= 1
495
496                        if response[end].startswith("ok"):
497                                end -= 1
498                        if response[end].startswith("eof"):
499                                end -= 1
500
501                        while response[end].strip() == '':
502                                end -= 1
503                        end += 1
504
505                with open(path, 'w') as file:
506                        file.write(''.join(response[start:end]))
507                        file.write("\n")
508
509        def getWorldType(self) -> int:
510                info = []
511                while not info:
512                        response = self.sendRequest("get /simulator/world wrldtyp")
513                        info = self._infoParser(response)
514                return info[0].p["wrldtyp"]
515
516        def getWorldSize(self) -> float:
517                info = []
518                while not info:
519                        response = self.sendRequest("get /simulator/world wrldsiz")
520                        info = self._infoParser(response)
521                return info[0].p["wrldsiz"]
522
523        def getWorldWaterLevel(self) -> float:
524                info = []
525                while not info:
526                        response = self.sendRequest("get /simulator/world wrldwat")
527                        info = self._infoParser(response)
528                return info[0].p["wrldwat"]
529
530        def getWorldBoundaries(self) -> int:
531                info = []
532                while not info:
533                        response = self.sendRequest("get /simulator/world wrldbnd")
534                        info = self._infoParser(response)
535                return info[0].p["wrldbnd"]
536
537        def getWorldMap(self) -> str:
538                info = []
539                while not info:
540                        response = self.sendRequest("get /simulator/world geometry")
541                        info = self._infoParser(response)
542                return info[0].p["geometry"]
543
544        def getSimtype(self) -> int:
545                info = []
546                while not info:
547                        response = self.sendRequest("get /simulator/world simtype")
548                        info = self._infoParser(response)
549                return info[0].p["simtype"]
Note: See TracBrowser for help on using the repository browser.