import bge # Blender Game Engine (UPBGE) import bpy # Blender import sys import math import time import xml.etree.ElementTree as ET # Creating/parsing XML file import importlib import runpy # Exécution de script Python légère (sans import) from pylint import epylint as lint # Mesure de la qualité d'un code Python import twin_doc # Documentation import twin_about # About ############################################################################### # twin.py # @title: Bibliothèque générale de l'environnement 3D pour le développement de jumeau numérique # @project: Blender-EduTech # @lang: fr # @authors: Philippe Roy # @copyright: Copyright (C) 2020-2022 Philippe Roy # @license: GNU GPL # # Cet environnement 3D est programmable en Python. Il est destiné à la découverte de la programmation de système pluritechnologique. # ############################################################################### # UPBGE scene scene = bge.logic.getCurrentScene() system=importlib.import_module(scene.objects['System']['name']) # Système # scene.objects['Commands']['debug_fps']=False # Memory sys.setrecursionlimit(10**5) # Limite sur la récursivité (valeur par défaut : 1000) -> segfault de Blender # EEVEE eevee = bpy.context.scene.eevee # fps_time=0.0 # Config file twin_config = ET.parse('twin_config.xml') twin_config_tree = twin_config.getroot() # Couleurs color_cmd = [0.8, 0.8, 0.8, 1] # Blanc color_cmd_hl = [0.8, 0.619, 0.021, 1] # Jaune # Constantes JUST_ACTIVATED = bge.logic.KX_INPUT_JUST_ACTIVATED JUST_RELEASED = bge.logic.KX_INPUT_JUST_RELEASED ACTIVATE = bge.logic.KX_INPUT_ACTIVE # JUST_DEACTIVATED = bge.logic.KX_SENSOR_JUST_DEACTIVATED ############################################################################### # Gestion du clavier ############################################################################### # Mode : Orbit(0) par défaut, Pan(1) avec Shift, Zoom (2) avec Ctrl, Eclaté (1) avec Shift def keyboard(cont): obj = scene.objects['System'] keyboard = bge.logic.keyboard # Touche ESC if JUST_ACTIVATED in keyboard.inputs[bge.events.ESCKEY].queue: # Maj du fichier de config (screen size : data/config/screen/width-> [0][0].text) screen_width = bge.render.getWindowWidth() screen_height = bge.render.getWindowHeight() twin_config_tree[0][0].text=str(screen_width) twin_config_tree[0][1].text=str(screen_height) buffer_xml = ET.tostring(twin_config_tree) with open("twin_config.xml", "wb") as f: f.write(buffer_xml) # Sortir bge.logic.endGame() # Shift -> mode 1 : Pan (clic milieu) ou Eclaté (clic gauche) if JUST_ACTIVATED in keyboard.inputs[bge.events.LEFTSHIFTKEY].queue: obj['manip_mode']=1 if JUST_ACTIVATED in keyboard.inputs[bge.events.RIGHTSHIFTKEY].queue: obj['manip_mode']=1 # Ctrl -> mode 2 : Zoom (clic milieu) if JUST_ACTIVATED in keyboard.inputs[bge.events.LEFTCTRLKEY].queue: obj['manip_mode']=2 if JUST_ACTIVATED in keyboard.inputs[bge.events.RIGHTCTRLKEY].queue: obj['manip_mode']=2 # Pas de modificateur -> mode 0 : Orbit (clic milieu) if JUST_RELEASED in keyboard.inputs[bge.events.LEFTSHIFTKEY].queue: obj['manip_mode']=0 if JUST_RELEASED in keyboard.inputs[bge.events.RIGHTSHIFTKEY].queue: obj['manip_mode']=0 if JUST_RELEASED in keyboard.inputs[bge.events.LEFTCTRLKEY].queue: obj['manip_mode']=0 if JUST_RELEASED in keyboard.inputs[bge.events.RIGHTCTRLKEY].queue: obj['manip_mode']=0 # Touche Home -> Reset de la vue if JUST_ACTIVATED in keyboard.inputs[bge.events.HOMEKEY].queue: manip_reset() # Touche F5 -> Run et Pause if JUST_ACTIVATED in keyboard.inputs[bge.events.F5KEY].queue: cycle_run () # Touche F6 -> Stop / Init if JUST_ACTIVATED in keyboard.inputs[bge.events.F6KEY].queue: cycle_stop () ############################################################################### # Commandes ############################################################################### ## # Initialisation des commandes ## def cmd_init(): # Fichier de config (screen size : data/config/screen/width-> [0][0].text, height-> [0][1].text) bge.render.setWindowSize(int(twin_config_tree[0][0].text),int(twin_config_tree[0][1].text)) scene.objects['Run-Hl'].setVisible(False,False) scene.objects['Pause-Hl'].setVisible(False,False) scene.objects['ResetView-Hl'].setVisible(False,False) scene.objects['Doc-cmd-Hl'].setVisible(False,False) scene.objects['About-cmd-Hl'].setVisible(False,False) ## # Highlight des commandes ## def cmd_hl(cont): obj = cont.owner # Activation if cont.sensors['MO'].status == JUST_ACTIVATED and scene.objects['System']['manip_mode']==0: if obj.name!="Run" and obj.name!="Pause" and obj.name!="Stop" and obj.name!="Doc-cmd-colbox": # if obj.name!="Run" and obj.name!="Pause" and obj.name!="Run-Hl" and obj.name!="Pause-Hl" and obj.name!="Pause-Hl": obj.setVisible(False,True) scene.objects[obj.name+'-Hl'].setVisible(True,True) # Run et pause if obj.name=="Pause" or obj.name=="Run": if scene.objects['System']['run'] == True: pass # scene.objects['Pause'].setVisible(False,False) FIXME pause pas implémenté # scene.objects['Pause-Hl'].setVisible(True,False) FIXME pause pas implémenté else: scene.objects['Run'].setVisible(False,False) scene.objects['Run-Hl'].setVisible(True,False) # Stop if obj.name=="Stop": scene.objects['Stop'].setVisible(False,False) scene.objects['Stop-Hl'].setVisible(True,False) # Doc if obj.name=="Doc-cmd-colbox": scene.objects['Doc-cmd'].setVisible(False,False) scene.objects['Doc-cmd-Hl'].setVisible(True,False) # Text # text_hl ={"Run":"Exécuter (F5)", # "Stop":"Stop et initialisation (F6)", # "Pause":"Pause (F5)", # "Aim-cmd":"Afficher/cacher l'objectif", # "Doc-cmd-colbox":"Documentation", # "Store-cmd":"Boutique", # "Task-cmd":"Liste des tâches", # "Task_close-cmd":"Fermer la liste des tâches", # "ResetView": "Reset de la vue (Touche Début)", # "About-cmd": "A propos", # "Speed_down": "Moins vite (-)", # "Speed_up": "Plus vite (+)", # "Sound-cmd": "Muet", # "NoSound-cmd": "Rétablir le son"} # scene.objects['Cmd-text']['Text']= text_hl[obj.name] # scene.objects['Cmd-text'].setVisible(True,False) # Désactivation if cont.sensors['MO'].status == JUST_RELEASED and (scene.objects['System']['manip_mode']==0 or scene.objects['System']['manip_mode']==9): # scene.objects['Cmd-text']['Text']= "" # scene.objects['Cmd-text'].setVisible(False,False) if obj.name!="Run" and obj.name!="Pause" and obj.name!="Stop" and obj.name!="Doc-cmd-colbox": scene.objects[obj.name+'-Hl'].setVisible(False,True) obj.setVisible(True,True) # Run et pause if obj.name=="Pause" or obj.name=="Run": if scene.objects['System']['run'] == True: pass # scene.objects['Pause-Hl'].setVisible(False,False) FIXME pause pas implémenté # scene.objects['Pause'].setVisible(True,False) FIXME pause pas implémenté else: scene.objects['Run-Hl'].setVisible(False,False) scene.objects['Run'].setVisible(True,False) # Stop if obj.name=="Stop": scene.objects['Stop-Hl'].setVisible(False,False) scene.objects['Stop'].setVisible(True,False) # Doc if obj.name=="Doc-cmd-colbox": scene.objects['Doc-cmd-Hl'].setVisible(False,False) scene.objects['Doc-cmd'].setVisible(True,False) ## # Click sur les commandes ## def cmd_click(cont): obj = cont.owner if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive and scene.objects['System']['manip_mode']==0: # Play et pause if obj.name=="Pause" or obj.name=="Run": cycle_run () # Stop if obj.name=="Stop": cycle_stop () # Reset-view if obj.name=="ResetView" : manip_reset() # About if obj.name=="About-cmd" : twin_about.open() # Aide if obj.name=="Doc-cmd-colbox" : twin_doc.open() ############################################################################### # Manipulation du mécanisme ############################################################################### ## # Dessiner une cercle (bas niveau) ## def circle (center, radius, color): ang = 0 ang_step = 0.1 while ang< 2 * math.pi: x0 = center[0]+float(radius*math.cos(ang)*0.95) y0 = center[1] z0 = center[2]+float(radius*math.sin(ang)) x1 = center[0]+float(radius*math.cos(ang+ang_step)*0.95) y1 = center[1] z1 = center[2]+float(radius*math.sin(ang+ang_step)) bge.render.drawLine([x0,y0,z0],[x1,y1,z1],color) ang += ang_step ## # Initialisation de la vue 3D ## # def manip_init(cont): def manip_init(): # Configuration du moteur de rendu eevee.use_eevee_smaa = True # Fenêtres scene.objects['About'].setVisible(False,True) scene.objects['Doc'].setVisible(False,True) # Mémorisation de la position de la caméra scene.objects['Camera']['init_lx']=scene.objects['Camera'].worldPosition.x scene.objects['Camera']['init_ly']=scene.objects['Camera'].worldPosition.y scene.objects['Camera']['init_lz']=scene.objects['Camera'].worldPosition.z # Mémorisation de la position du modèle scene.objects['System']['init_lx']=scene.objects['System'].worldPosition.x scene.objects['System']['init_ly']=scene.objects['System'].worldPosition.y scene.objects['System']['init_lz']=scene.objects['System'].worldPosition.z scene.objects['System']['init_rx']=scene.objects['System'].worldOrientation.to_euler().x scene.objects['System']['init_ry']=scene.objects['System'].worldOrientation.to_euler().y scene.objects['System']['init_rz']=scene.objects['System'].worldOrientation.to_euler().z ## # Atteindre une orientation (bas niveau) ## def applyRotationTo(obj, rx=None, ry=None, rz=None): rres=0.001 # resolution rotation # x if rx is not None: while (abs(rx-obj.worldOrientation.to_euler().x) > rres) : if obj.worldOrientation.to_euler().x-rx > rres: obj.applyRotation((-rres, 0, 0), True) if rx-obj.worldOrientation.to_euler().x > rres: obj.applyRotation((rres, 0, 0), True) # print ("delta x ",rx-obj.worldOrientation.to_euler().x) # y if ry is not None: while (abs(ry-obj.worldOrientation.to_euler().y) > rres) : if obj.worldOrientation.to_euler().y-ry > rres: obj.applyRotation((0, -rres, 0), True) if ry-obj.worldOrientation.to_euler().y > rres: obj.applyRotation((0, rres, 0), True) # print ("delta y ",ry-obj.worldOrientation.to_euler().y) # z if rz is not None: while (abs(rz-obj.worldOrientation.to_euler().z) > rres) : if obj.worldOrientation.to_euler().z-rz > rres: obj.applyRotation((0, 0, -rres), True) if rz-obj.worldOrientation.to_euler().z > rres: obj.applyRotation((0, 0, rres), True) # print ("delta z ",rz-obj.worldOrientation.to_euler().z) ## # Reset de la manipulation de la vue ## def manip_reset(): scene.objects['Camera'].worldPosition.x = scene.objects['Camera']['init_lx'] scene.objects['Camera'].worldPosition.y = scene.objects['Camera']['init_ly'] scene.objects['Camera'].worldPosition.z = scene.objects['Camera']['init_lz'] applyRotationTo(scene.objects['System'], 0, 0, 0) scene.objects['System'].worldPosition.x = scene.objects['System']['init_lx'] scene.objects['System'].worldPosition.y = scene.objects['System']['init_ly'] scene.objects['System'].worldPosition.z = scene.objects['System']['init_lz'] for objet in scene.objects['System']['objects'] : scene.objects[objet].setVisible(True,False) scene.objects[objet].restorePhysics() if objet+"_Lines.GP" in scene.objects: scene.objects[objet+"_Lines.GP"].setVisible(True,False) ## # Position de départ pour la manipulation de la vue ## def manip_start(cont): obj = cont.owner obj['click_x']=cont.sensors['ClickM'].position[0] obj['click_y']=cont.sensors['ClickM'].position[1] ## # Manipulation du modèle ou de la caméra ## def manip(cont): obj = cont.owner sensibilite_orbit=0.0005 sensibilite_pan=0.005 sensibilite_zoom=0.01 delta_x=cont.sensors['DownM'].position[0]-obj['click_x'] delta_y=cont.sensors['DownM'].position[1]-obj['click_y'] # Orbit if obj['manip_mode']==0: x0 = scene.objects['Orbit'].worldPosition.x y0 =scene.objects['Orbit'].worldPosition.y z0 =scene.objects['Orbit'].worldPosition.z width = bge.render.getWindowWidth() height = bge.render.getWindowHeight() dist_orbit_y_base=200 # Pour 1280 x 720 # radius_orbit_y_base=5 radius_orbit_y_base=0.875 diag_base= math.sqrt(1280**2+720**2) diag= math.sqrt(width**2+height**2) dist_orbit_y = dist_orbit_y_base*(diag/diag_base) radius_orbit_y=radius_orbit_y_base*(diag/diag_base) dist_orbit = math.sqrt(((width/2)-obj['click_x'])**2+((height/2)-obj['click_y'])**2) if obj['click_y']> height/2 : dist_orbit = dist_orbit+((math.sqrt(((height/2)-obj['click_y'])**2)/175)*25) # Correction des Y sous le centre ? if dist_orbit= abs(delta_y): scene.objects['System'].applyRotation((0, delta_x*sensibilite_orbit, 0), False) else: scene.objects['System'].applyRotation((0, delta_y*sensibilite_orbit, 0), False) # Pan if obj['manip_mode']==1: # Shift scene.objects['System'].applyMovement((delta_x*sensibilite_pan, -delta_y*sensibilite_pan, -delta_y*sensibilite_pan), False) # scene.objects['Camera'].applyMovement((delta_x*-sensibilite_pan, delta_y*sensibilite_pan, 0), True) # Zoom if obj['manip_mode']==2: # Ctrl scene.objects['Camera'].applyMovement((0, 0, (delta_x+delta_y)*sensibilite_zoom), True) scene.objects['Camera'].applyMovement((0, 0, (delta_x+delta_y)*sensibilite_zoom), True) ## # Zoom ## def manip_wheel(cont): obj = cont.owner sensibilite_wheel = 20 if cont.sensors['WheelUp'].positive: scene.objects['Camera'].applyMovement((0, 0, -sensibilite_wheel), True) if cont.sensors['WheelDown'].positive: scene.objects['Camera'].applyMovement((0, 0, sensibilite_wheel), True) ############################################################################### # Cycle ############################################################################### ## # Mise en route et pause du cycle ## def python_validation(file): (pylint_stdout, pylint_stderr) = lint.py_run(file+' --disable=C --disable=W --disable=R', return_std=True) stdout = pylint_stdout.read() stderr = pylint_stderr.read() if " error (" in stdout: # Présence d'erreur print(stdout) return False else: return True def cycle_run (): # Pause if scene.objects['System']['run'] == True: scene.objects['System']['run']=False scene.objects['Pause'].setVisible(False,False) scene.objects['Pause'].suspendPhysics() scene.objects['Pause-Hl'].setVisible(False,False) scene.objects['Run'].restorePhysics() scene.objects['Run-Hl'].setVisible(True,False) # Run else : scene.objects['System']['run']=True scene.objects['Run'].setVisible(False,False) scene.objects['Run'].suspendPhysics() scene.objects['Run-Hl'].setVisible(False,False) # scene.objects['Pause']. restorePhysics() # FIXME pause pas implémentée # scene.objects['Pause'].setVisible(True,False) # FIXME pause pas implémentée # Démarrage du cycle if scene.objects['System']['thread_cmd']==False: time.sleep(0.125) scene.objects['System']['thread_cmd']=True system.system_reset() time.sleep(0.125) if python_validation(scene.objects['System']['name']+'_cmd'): runpy.run_module(scene.objects['System']['name']+'_cmd', run_name='start') # Execution du script utilisateur # Arrêt de la pause else: # FIXME : Relancer Ropy pass ## # Arrêt et réinitialisation du cycle (forçage) ## def cycle_stop (): scene.objects['System']['thread_cmd']=False system.system_reset() ## # Fin naturelle du cycle ## def cycle_end (cont): if cont.sensors['End cycle'].positive: scene.objects['System']['run']=False if python_validation(scene.objects['System']['name']+'_cmd'): runpy.run_module(scene.objects['System']['name']+'_cmd', run_name='stop') # Fin du script utilisateur # Commandes scene.objects['Pause'].setVisible(False,False) scene.objects['Pause'].suspendPhysics() scene.objects['Pause-Hl'].setVisible(False,False) scene.objects['Run'].setVisible(True,False) scene.objects['Run'].restorePhysics()