jumeaux-numeriques/twin.py

601 lines
22 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <philippe.roy@ac-grenoble.fr>
# @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']['script'][:-4]) # Système
# Memory
sys.setrecursionlimit(10**5) # Limite sur la récursivité (valeur par défaut : 1000) -> segfault de Blender
# EEVEE
eevee = bpy.context.scene.eevee
# 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
color_passive = (0.800, 0.005, 0.315,1) # bouton non activable : magenta
color_active = (0.799, 0.130, 0.063,1) # bouton activable : orange
color_hl = (0.8, 0.8, 0.8, 1) # bouton focus : blanc
color_activated = (0.8, 0.619, 0.021, 1) # bouton activé : 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)
twin_config_tree[0][2].text=str(scene.objects['About']['quality'])
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))
quality_eevee=('LOW', 'MEDIUM','HIGH','ULTRA')
scene.objects['About']['quality'] = int(twin_config_tree[0][2].text)
bpy.context.scene.eevee.smaa_quality= quality_eevee[scene.objects['About']['quality']]
# Ajout du Hud
# scene.active_camera = scene.objects["Camera"]
# scene.objects['Sun'].setVisible(True,True)
# scene.addOverlayCollection(scene.cameras['Camera-Hud'], bpy.data.collections['Hud'])
# UI : Commands
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)
# scene.objects['Twins-icon'].setVisible(False,True)
# UI : Text, ...
scene.objects['Cmd-text']['Text']=""
scene.objects['Cmd-text'].setVisible(True,False)
scene.objects['Twins-text']['Text']="Connection fermée."
# scene.objects['Twins-text'].setVisible(False,False)
# Windows
windows=("Doc", "Doc_chap-general", "Doc_chap-system", "Doc_chap-python", "About")
for window in windows:
scene.objects[window].setVisible(False,True)
##
# 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)",
"Doc-cmd-colbox":"Documentation",
"ResetView": "Reset de la vue (Touche Début)",
"About-cmd": "A propos"}
scene.objects['Cmd-text']['modal']= False
scene.objects['Cmd-text']['Text']= text_hl[obj.name]
if scene.objects['Doc']['page_chap']== "" and obj.name =="Doc-cmd-colbox" :
scene.objects['Cmd-text']['Text']= "Chargement de la documentation ..."
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):
if scene.objects['Cmd-text']['modal'] != True:
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
# Ajout du Hud
scene.active_camera = scene.objects["Camera"]
scene.objects['Sun'].setVisible(True,True)
scene.addOverlayCollection(scene.cameras['Camera-Hud'], bpy.data.collections['Hud'])
# 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-Hud'].worldPosition.x
y0 =scene.objects['Orbit-Hud'].worldPosition.y
z0 =scene.objects['Orbit-Hud'].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<dist_orbit_y : # Orbit sur xz
circle ([x0,y0,z0], radius_orbit_y_base, color_cmd)
n=10
# pas_x=(delta_x*40*sensibilite_orbit)/n
pas_x=(delta_x*4*sensibilite_orbit)/n
# pas_y=(((width/2)-cont.sensors['DownM'].position[0])+((height/2)-cont.sensors['DownM'].position[1]))*0.005
pas_y=((((width/2)-cont.sensors['DownM'].position[0])+((height/2)-cont.sensors['DownM'].position[1]))*0.005)/10
# pas_z=(delta_y*40*sensibilite_orbit)/n
pas_z=(delta_y*4*sensibilite_orbit)/n
for i in range (n):
bge.render.drawLine([x0+pas_x*i, y0+abs(pas_y*math.sin((3.14*i)/n)), z0-pas_z*i],
[x0+pas_x*(i+1), y0+abs(pas_y*math.sin((3.14*(i+1))/n)), z0-pas_z*(i+1)],
[0.8, 0.619, 0.021])
scene.objects['System'].applyRotation((delta_y*sensibilite_orbit, 0, delta_x*sensibilite_orbit), False)
else: # Orbit sur y
circle ([x0,y0,z0], radius_orbit_y_base, color_cmd_hl)
if abs(delta_x) >= 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
###############################################################################
##
# Validation du code Python
##
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
# UI : information
scene.objects['Cmd-text']['modal']= True
scene.objects['Cmd-text']['Text']= "Erreur dans le script ... "
scene.objects['Cmd-text'].setVisible(True,False)
print(stdout) # Affichage console
return False
else:
scene.objects['Cmd-text']['modal']= False
scene.objects['Cmd-text']['Text']= ""
scene.objects['Cmd-text'].setVisible(False,False)
return True
##
# Mise en route et pause du cycle
##
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']['script']+'.py'):
runpy.run_module(scene.objects['System']['script'], run_name='start') # Execution du script utilisateur
# Arrêt de la pause
else:
# FIXME : Relancer la maquette
pass
def light_refresh(cont):
pass
# scene.objects['Sun'].applyMovement((0, 0, 0), True)
# scene.objects['System'].applyRotation((0, 0, 0), False)
# scene.objects['System'].applyMovement((0,0,0), False)
# 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']['script']+'.py'):
runpy.run_module(scene.objects['System']['script'], 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()
##
# Highlight sur les éléments cliquables du systèmes
##
def cycle_hl(cont):
obj = cont.owner
# name=obj.name
# Passif
if "active" in obj.getPropertyNames():
if obj['mo'] == False:
return
# Activation
if cont.sensors['MO'].status == JUST_ACTIVATED and scene.objects['System']['run']:
obj.color = color_hl
obj['mo'] = True
# scene.objects[name].color = color_hl
# scene.objects[name]['mo'] = True
# Désactivation
if cont.sensors['MO'].status == JUST_RELEASED:
obj.color = color_active
obj['mo'] = False
# scene.objects[name].color = color_active
# scene.objects[name]['mo'] = False
##
# Click sur les éléments cliquables du systèmes (activation numérique)
##
def cycle_click(cont):
obj = cont.owner
if scene.objects['System']['run'] ==False:
return
# Passif
if "active" in obj.getPropertyNames():
if obj['mo'] == False:
return
# Activation
if cont.sensors['Click'].status == JUST_ACTIVATED and cont.sensors['MO'].positive and scene.objects['System']['manip_mode']==0:
obj.color = color_activated
# scene.objects[name].color = color_activated
obj['activated'] = True
obj['click'] = True
# Modele 3d -> Arduino : FIXME
# Désactivation
if cont.sensors['Click'].status == JUST_RELEASED:
obj['activated'] = False
obj['click'] = False
# Modele 3d -> Arduino : FIXME
if cont.sensors['MO'].positive:
obj.color = color_hl
# scene.objects[name].color = color_hl
else:
obj.color = color_active
# scene.objects[name].color = color_active