[tools] Visualizer Rotating Wing (#3070)

* Visualizer Rotating Wing

Rotwing Monitor

* executable + tool

* removed STL

* Apply suggestions from code review
This commit is contained in:
Christophe De Wagter
2023-09-18 15:00:42 +02:00
committed by GitHub
parent fd3479c2d7
commit 3da0992fd5
6 changed files with 818 additions and 1 deletions
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
#
# Copyright (C) 2023 TUDelft
#
# This file is part of paparazzi.
#
# paparazzi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# paparazzi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with paparazzi. If not, see <http://www.gnu.org/licenses/>.
#
import wx
import rot_wing_viewer
class RotWingApp(wx.App):
def OnInit(self):
self.main = rot_wing_viewer.RotWingFrame()
self.main.Show()
self.SetTopWindow(self.main)
return True
def main():
application = RotWingApp(0)
application.MainLoop()
if __name__ == '__main__':
main()
@@ -0,0 +1,284 @@
#
# Copyright (C) 2023 TUDelft
#
# This file is part of paparazzi.
#
# paparazzi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# paparazzi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with paparazzi. If not, see <http://www.gnu.org/licenses/>.
#
import wx
import sys
import os
import math
import datetime
import numpy as np
PPRZ_HOME = os.getenv("PAPARAZZI_HOME", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../../..')))
PPRZ_SRC = os.getenv("PAPARAZZI_SRC", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../../..')))
sys.path.append(PPRZ_HOME + "/var/lib/python")
from pprzlink.ivy import IvyMessagesInterface
WIDTH = 600
BARH = 140
class EscMessage(object):
def __init__(self, msg):
self.id = int(msg['motor_id'])
self.amp = float(msg['amps'])
self.rpm = float(msg['rpm'])
self.volt_b = float(msg['bat_volts'])
self.volt_m = float(msg['motor_volts'])
self.temperature = float(msg['temperature']) - 273.15
self.temperature_dev = float(msg['temperature_dev']) - 273.15
self.energy = float(msg['energy'])
def get_current(self):
return str(round(self.amp ,1)) + "A"
def get_current_perc(self):
return self.amp / 30
def get_rpm(self):
return str(round(self.rpm ,0)) + " rpm"
def get_rpm_perc(self):
return self.rpm / 5150
def get_rpm_color(self):
if self.rpm < 4000:
return 1
return 0.5
def get_volt(self):
if (self.id in [6,7,8,9,16,17,18,19]):
return "Servo " + str(self.id) + " " +str(round(self.volt_m ,1)) + "V"
else:
return "Mot " + str(self.id) + " " +str(round(self.volt_m ,1)) + "V"
def get_volt_perc(self):
return self.volt_b / (6*4.2)
def get_temp(self):
if self.temperature < -200:
return "xxx"
return str(round(self.temperature ,1)) + "C"
def get_temp_perc(self):
return self.temperature / 120.0
def get_temp_dev(self):
if self.temperature_dev < -200:
return "xxx"
return str(round(self.temperature_dev, 1)) + "C"
def get_temp_dev_perc(self):
return self.temperature_dev / 120.0
def get_temp_color(self):
if self.temperature < 0:
return 0
elif self.temperature < 60:
return 1
else:
return 0.5
def get_temp_dev_color(self):
if self.temperature_dev < 0:
return 0
elif self.temperature_dev < 100:
return 1
else:
return 0.5
class INDIMessage(object):
def __init__(self, msg):
self.u = np.array(msg['u'], dtype=np.float)
def get_u(self, id):
return str(round(self.u[id], 0)) + " PPRZ"
def get_u_perc(self, id):
return self.u[id] / 9600.
def get_u_color(self, id):
if self.u[id] < 9600 / 0.8:
return 1
else:
return 0.5
class MotorList(object):
def __init__(self):
self.mot = []
def fill_from_esc_msg(self, esc):
added = False
for i in range(len(self.mot)):
if self.mot[i].id == esc.id:
self.mot[i] = esc
added = True
break
if not added:
self.mot.append(esc)
class RotWingFrame(wx.Frame):
def message_recv(self, ac_id, msg):
if msg.name == "ESC":
self.esc = EscMessage(msg)
self.motors.fill_from_esc_msg(self.esc)
wx.CallAfter(self.update)
if msg.name == "STAB_ATTITUDE_FULL_INDI":
self.indi = INDIMessage(msg)
wx.CallAfter(self.update)
def update(self):
self.Refresh()
def OnSize(self, event):
self.w = event.GetSize().x
self.h = event.GetSize().y
self.cfg.Write("width", str(self.w))
self.cfg.Write("height", str(self.h))
self.Refresh()
def OnMove(self, event):
self.x = event.GetPosition().x
self.y = event.GetPosition().y
self.cfg.Write("left", str(self.x))
self.cfg.Write("top", str(self.y))
def StatusBox(self, dc, dx, dy, row, col, txt, percent, color):
if percent < 0:
percent = 0
if percent > 1:
percent = 1
boxw = self.stat
tdx = int(boxw * 10.0 / 300.0)
tdy = int(boxw * 6.0 / 300.0)
boxh = int(boxw * 40.0 / 300.0)
boxw = self.stat - 2*tdx
spacing = boxh+10
dc.SetPen(wx.Pen(wx.Colour(0,0,0)))
dc.SetBrush(wx.Brush(wx.Colour(220,220,220)))
dc.DrawRectangle(tdx + col * 200 + dx, int(row*spacing+tdx) +dy, int(boxw), boxh)
dc.SetTextForeground(wx.Colour(0, 0, 0))
if color < 0.2:
dc.SetTextForeground(wx.Colour(255, 255, 255))
dc.SetBrush(wx.Brush(wx.Colour(250,0,0)))
elif color < 0.6:
dc.SetBrush(wx.Brush(wx.Colour(250,180,0)))
else:
dc.SetBrush(wx.Brush(wx.Colour(0,250,0)))
# dc.DrawLine(200,50,350,50)
dc.DrawRectangle(tdx + col * 200 + dx, int(row*spacing+tdx+dy), int(boxw * percent), boxh)
dc.DrawText(txt,18 + col * 200 + dx,int(row*spacing+tdy+tdx+dy))
def OnPaint(self, e):
# Automatic Scaling
w = self.w
h = self.h - 25
dc = wx.PaintDC(self)
brush = wx.Brush("white")
dc.SetBackground(brush)
dc.Clear()
# Background
dc.SetBrush(wx.Brush(wx.Colour(0, 0, 0), wx.TRANSPARENT))
fontscale = int(w * 11.0 / 1500.0)
if fontscale < 6:
fontscale = 6
font = wx.Font(fontscale, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
dc.SetFont(font)
# Draw Drone
dc.SetPen(wx.Pen(wx.Colour(0,0,0)))
dc.SetBrush(wx.Brush(wx.Colour(240,240,220)))
# Fuselage
dc.DrawRoundedRectangle(int(0.425*w), int(0.05*h),int(0.15*w), int(0.4*h), int(0.05*w))
# Front Wing
#dc.DrawRectangle(int(0.05*w), int(0.25*h),int(0.9*w), int(0.15*h))
# Back Wing
#dc.DrawRectangle(int(0.01*w), int(0.65*h),int(0.98*w), int(0.15*h))
# Motors
self.stat = int(0.10*w)
dc.SetBrush(wx.Brush(wx.Colour(200,200,100)))
w1 = 0.03
w2 = 0.2
w3 = 0.37
w4 = 0.54
dw = 0.11
mw = 0.1
mh = 0.15
mm = [(0.5-0.5*mw,w1), (0.5+dw,w2), (0.5-0.5*mw,w3), (0.5-dw-mw,w2), (0.5-0.5*mw,w4)]
for m in mm:
dc.DrawRectangle(int(m[0]*w), int(m[1]*h),int(mw*w), int(mh*h))
for m in self.motors.mot:
if m.id>4:
continue
mo_co = mm[m.id]
#print(m.id, mo_co)
dx = int(mo_co[0]*w)
dy = int(mo_co[1]*h)
self.StatusBox(dc, dx, dy, 0, 0, m.get_volt(), m.get_volt_perc(), 1)
self.StatusBox(dc, dx, dy, 1, 0, m.get_current(), m.get_current_perc(), 1)
self.StatusBox(dc, dx, dy, 2, 0, m.get_rpm(), m.get_rpm_perc(), m.get_rpm_color())
self.StatusBox(dc, dx, dy, 3, 0, m.get_temp(), m.get_temp_perc(), m.get_temp_color())
self.StatusBox(dc, dx, dy, 4, 0, m.get_temp_dev(), m.get_temp_dev_perc(), m.get_temp_dev_color())
try:
self.StatusBox(dc, dx, dy, 5, 0, self.indi.get_u(m.id), self.indi.get_u_perc(m.id), self.indi.get_u_color(m.id))
except:
pass
def __init__(self):
self.w = WIDTH
self.h = WIDTH + BARH
self.cfg = wx.Config('rot_wing_conf')
if self.cfg.Exists('width'):
self.w = int(self.cfg.Read('width'))
self.h = int(self.cfg.Read('height'))
wx.Frame.__init__(self, id=-1, parent=None, name=u'RotWingFrame',
size=wx.Size(self.w, self.h), title=u'Rot Wing Monitoring')
if self.cfg.Exists('left'):
self.x = int(self.cfg.Read('left'))
self.y = int(self.cfg.Read('top'))
self.SetPosition(wx.Point(self.x,self.y))
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_MOVE, self.OnMove)
self.Bind(wx.EVT_CLOSE, self.OnClose)
#ico = wx.Icon(PPRZ_SRC + "/sw/ground_segment/python/energy_mon/energy_mon.ico", wx.BITMAP_TYPE_ICO)
#self.SetIcon(ico)
self.motors = MotorList()
self.interface = IvyMessagesInterface("rotwingframe")
self.interface.subscribe(self.message_recv)
def OnClose(self, event):
self.interface.shutdown()
self.Destroy()
@@ -0,0 +1,495 @@
#!/usr/bin/env python3
#
# Copyright (C) 2023 TUDelft
#
# This file is part of paparazzi.
#
# paparazzi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# paparazzi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with paparazzi. If not, see <http://www.gnu.org/licenses/>.
#
import sys
import os
import open3d as o3d
import numpy as np
import time
import platform
import random
import threading
import copy
isMacOS = (platform.system() == "Darwin")
PPRZ_HOME = os.getenv("PAPARAZZI_HOME", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../../..')))
PPRZ_SRC = os.getenv("PAPARAZZI_SRC", os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'../../../..')))
sys.path.append(PPRZ_HOME + "/var/lib/python")
from pprzlink.ivy import IvyMessagesInterface
class AHRSRefQuatMessage(object):
def __init__(self, msg):
self.body_qi = msg['body_qi']
self.body_qx = msg['body_qx']
self.body_qy = msg['body_qy']
self.body_qz = msg['body_qz']
self.ref_qi = msg['ref_qi']
self.ref_qx = msg['ref_qx']
self.ref_qy = msg['ref_qy']
self.ref_qz = msg['ref_qz']
def get_body_quaternion(self):
body_qi = float(self.body_qi) / 2**15
body_qx = float(self.body_qx) / 2**15
body_qy = float(self.body_qy) / 2**15
body_qz = float(self.body_qz) / 2**15
return np.array([[body_qi], [body_qx], [body_qy], [body_qz]])
def get_ref_quaternion(self):
ref_qi = float(self.ref_qi) / 2**15
ref_qx = float(self.ref_qx) / 2**15
ref_qy = float(self.ref_qy) / 2**15
ref_qz = float(self.ref_qz) / 2**15
return np.array([[ref_qi], [ref_qx], [ref_qy], [ref_qz]])
class RotWingControllerMessage(object):
def __init__(self, msg):
self.wing_angle_deg = msg['wing_angle_deg']
self.wing_angle_deg_sp = msg['wing_angle_deg_sp']
def get_wing_angle(self):
return np.deg2rad(float(self.wing_angle_deg))
def get_wing_angle_sp(self):
return np.deg2rad(float(self.wing_angle_deg_sp))
class ActuatorsMessage(object):
def __init__(self, msg):
self.values = msg['values']
def get_actuator_values(self):
return np.array(self.values).astype(dtype = float)
def get_actuator_value(self, idx):
return float(self.values[idx])
class EscMessage(object):
def __init__(self, msg):
self.id = int(msg['motor_id'])
self.amp = float(msg['amps'])
self.rpm = float(msg['rpm'])
self.volt_b = float(msg['bat_volts'])
self.volt_m = float(msg['motor_volts'])
self.temperature = float(msg['temperature']) - 273.15
self.energy = float(msg['energy'])
def get_id(self):
return self.id
def get_temp(self):
return self.temperature
class Viewer3D(object):
MENU_SPHERE = 1
MENU_RANDOM = 2
MENU_QUIT = 3
def __init__(self, title):
self._id = 0
app = o3d.visualization.gui.Application.instance
app.initialize()
self.w = app.create_window(title)
self.main_vis = o3d.visualization.gui.SceneWidget()
self.main_vis.scene = o3d.visualization.rendering.Open3DScene(self.w.renderer)
self.main_vis.scene.set_background(np.array([[0],[0],[0],[0]], dtype=np.float32))
self.main_vis.scene.scene.set_sun_light(
[1, 1, 1], # direction
[1, 1, 1], # color
100000) # intensity
self.main_vis.scene.scene.enable_sun_light(True)
#self.main_vis.scene.view.set_post_processing(False)
self.mat = o3d.visualization.rendering.MaterialRecord()
self.mat.shader = "defaultLit"
self.mat.point_size = 50 * self.w.scaling
self.mat_ref = o3d.visualization.rendering.MaterialRecord()
self.mat_ref.shader = 'defaultLitSSR'
self.mat_ref.transmission = 1.0
self.mat_ref.point_size = 50 * self.w.scaling
self.add_meshes()
bbox = self.main_vis.scene.bounding_box
#bbox = o3d.geometry.AxisAlignedBoundingBox([-100, -100, -100],
# [100, 100, 100])
self.main_vis.setup_camera(60.0, bbox, bbox.get_center())
self.main_vis.look_at(bbox.get_center(), np.array([[-2500],[0],[-500]]), np.array([[0],[0],[-1]]))
self.w.add_child(self.main_vis)
# # The menu is global (because the macOS menu is global), so only create
# # it once, no matter how many windows are created
# if o3d.visualization.gui.Application.instance.menubar is None:
# if isMacOS:
# app_menu = o3d.visualization.gui.Menu()
# app_menu.add_item("Quit", Viewer3D.MENU_QUIT)
# debug_menu = o3d.visualization.gui.Menu()
# debug_menu.add_item("Add Sphere", Viewer3D.MENU_SPHERE)
# debug_menu.add_item("Add Random Spheres", Viewer3D.MENU_RANDOM)
# if not isMacOS:
# debug_menu.add_separator()
# debug_menu.add_item("Quit", Viewer3D.MENU_QUIT)
# menu = o3d.visualization.gui.Menu()
# if isMacOS:
# # macOS will name the first menu item for the running application
# # (in our case, probably "Python"), regardless of what we call
# # it. This is the application menu, and it is where the
# # About..., Preferences..., and Quit menu items typically go.
# menu.add_menu("Example", app_menu)
# menu.add_menu("Debug", debug_menu)
# else:
# menu.add_menu("Debug", debug_menu)
# o3d.visualization.gui.Application.instance.menubar = menu
# # The menubar is global, but we need to connect the menu items to the
# # window, so that the window can call the appropriate function when the
# # menu item is activated.
# self.w.set_on_menu_item_activated(Viewer3D.MENU_SPHERE,
# self._on_menu_sphere)
# self.w.set_on_menu_item_activated(Viewer3D.MENU_RANDOM,
# self._on_menu_random)
# self.w.set_on_menu_item_activated(Viewer3D.MENU_QUIT,
# self._on_menu_quit)
test_label = self.main_vis.add_3d_label([0,0,-400], 'TEST')
test_label.color = o3d.visualization.gui.Color(1.0, 1.0, 1.0, 1.0)
self.interface = IvyMessagesInterface("rotwingviewer")
self.interface.subscribe(self.message_recv)
self.ahrs_ref_quat = None
self.wing_rotation_controller = None
self.actuators = None
self.T_rotation = np.matrix(np.eye(4))
self.T_rotation_ref = np.matrix(np.eye(4))
self.T_rotation_wing = np.matrix(np.eye(4))
self.T_rotation_wing_ref = np.matrix(np.eye(4))
self.T_rotating_wing_final = np.matrix(np.eye(4))
self.T_rotating_wing_final_ref = np.matrix(np.eye(4))
def add_meshes(self):
R_standard = o3d.geometry.get_rotation_matrix_from_zxy(np.array([[0],[np.deg2rad(-90)],[np.deg2rad(-90)]]))
T_standard = np.array([[680],[0],[0]])
self.mesh_wing = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/WingQuad.ply")
self.mesh_wing.compute_vertex_normals()
self.mesh_wing.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_wing.translate(T_standard)
self.mesh_fuselage = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/RWV4 NonMoving.ply")
self.mesh_fuselage.compute_vertex_normals()
self.mesh_fuselage.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_fuselage.translate(T_standard)
self.mesh_vert_tail = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/RudderSolid.ply")
self.mesh_vert_tail.compute_vertex_normals()
self.mesh_vert_tail.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_vert_tail.translate(T_standard)
self.mesh_hor_tail = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/HoriTail.ply")
self.mesh_hor_tail.compute_vertex_normals()
self.mesh_hor_tail.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_hor_tail.translate(T_standard)
self.mesh_motor_F = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/15 inch prop Disc.ply")
self.mesh_motor_F.compute_vertex_normals()
self.mesh_motor_F.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_motor_F.translate(np.array([[-232],[0],[124]]))
self.mesh_motor_F.translate(T_standard)
self.mesh_motor_B = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/15 inch prop Disc.ply")
self.mesh_motor_B.compute_vertex_normals()
self.mesh_motor_B.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_motor_B.translate(np.array([[-1129],[0],[124]]))
self.mesh_motor_B.translate(T_standard)
self.mesh_motor_R = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/15 inch prop Disc.ply")
self.mesh_motor_R.compute_vertex_normals()
self.mesh_motor_R.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_motor_R.translate(np.array([[-1050],[0],[-152]]))
self.mesh_motor_R.translate(T_standard)
self.mesh_motor_L = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/15 inch prop Disc.ply")
self.mesh_motor_L.compute_vertex_normals()
self.mesh_motor_L.rotate(R_standard, np.array([[0],[0],[0]]))
self.mesh_motor_L.translate(np.array([[-310],[0],[-152]]))
self.mesh_motor_L.translate(T_standard)
# self.mesh_motor_pusher = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/15 inch prop Disc.ply")
# self.mesh_motor_pusher.compute_vertex_normals()
# self.mesh_motor_pusher.rotate(R_standard, np.array([[0],[0],[0]]))
# self.mesh_motor_pusher.translate(np.array([[-1050],[0],[-152]]))
# self.mesh_motor_pusher.translate(T_standard)
# self.mesh_rudder = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/Rudder.ply")
# self.mesh_rudder.compute_vertex_normals()
# self.mesh_rudder.rotate(R_standard, np.array([[0],[0],[0]]))
# self.mesh_rudder.translate(np.array([[-806.6],[0],[-152]]))
# self.mesh_rudder.translate(T_standard)
# self.mesh_l_flap = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/LeftFlap.ply")
# self.mesh_l_flap.rotate(R_standard, np.array([[0],[0],[0]]))
# self.mesh_l_flap.compute_vertex_normals()
# self.mesh_l_aileron = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/LeftAileron.ply")
# self.mesh_l_aileron.rotate(R_standard, np.array([[0],[0],[0]]))
# self.mesh_l_aileron.compute_vertex_normals()
# self.mesh_r_flap = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/RightFlap.ply")
# self.mesh_r_flap.rotate(R_standard, np.array([[0],[0],[0]]))
# self.mesh_r_flap.compute_vertex_normals()
# self.mesh_r_aileron = o3d.io.read_triangle_mesh("sw/ext/tudelft_gazebo_models/src/ply/RotatingWingV3/RightAileron.ply")
# self.mesh_r_aileron.rotate(R_standard, np.array([[0],[0],[0]]))
# self.mesh_r_aileron.compute_vertex_normals()
mesh_coordinate = o3d.geometry.TriangleMesh.create_coordinate_frame(size=1000, origin=np.array([0.0, 0.0, 0.0]))
self.main_vis.scene.add_geometry("wing", self.mesh_wing, self.mat)
self.main_vis.scene.add_geometry("fuselage", self.mesh_fuselage, self.mat)
self.main_vis.scene.add_geometry("coordinate", mesh_coordinate, self.mat)
self.main_vis.scene.add_geometry("vert_tail", self.mesh_vert_tail, self.mat)
self.main_vis.scene.add_geometry("ḧor_tail", self.mesh_hor_tail, self.mat)
self.main_vis.scene.add_geometry("motor_F", self.mesh_motor_F, self.mat)
self.main_vis.scene.add_geometry("motor_B", self.mesh_motor_B, self.mat)
self.main_vis.scene.add_geometry("motor_R", self.mesh_motor_R, self.mat)
self.main_vis.scene.add_geometry("motor_L", self.mesh_motor_L, self.mat)
# self.main_vis.scene.add_geometry("rudder", self.mesh_rudder, self.mat)
# self.main_vis.scene.add_geometry("l_flap", self.mesh_l_flap, self.mat)
# self.main_vis.scene.add_geometry("l_aileron", self.mesh_l_aileron, self.mat)
# self.main_vis.scene.add_geometry("r_flap", self.mesh_r_flap, self.mat)
# self.main_vis.scene.add_geometry("r_aileron", self.mesh_r_aileron, self.mat)
# Add reference
self.main_vis.scene.add_geometry("fuselage_ref", self.mesh_fuselage, self.mat_ref)
self.main_vis.scene.add_geometry("wing_ref", self.mesh_wing, self.mat_ref)
def add_sphere(self):
self._id += 1
mat = o3d.visualization.rendering.MaterialRecord()
mat.base_color = [
random.random(),
random.random(),
random.random(), 1.0
]
mat.shader = "defaultLit"
sphere = o3d.geometry.TriangleMesh.create_sphere(0.5)
sphere.compute_vertex_normals()
sphere.translate([
10.0 * random.uniform(-1.0, 1.0), 10.0 * random.uniform(-1.0, 1.0),
10.0 * random.uniform(-1.0, 1.0)
])
self.main_vis.scene.add_geometry("sphere" + str(self._id), sphere, mat)
def _on_menu_sphere(self):
# GUI callbacks happen on the main thread, so we can do everything
# normally here.
self.add_sphere()
def _on_menu_random(self):
# This adds spheres asynchronously. This pattern is useful if you have
# data coming in from another source than user interaction.
def thread_main():
for _ in range(0, 20):
# We can only modify GUI objects on the main thread, so we
# need to post the function to call to the main thread.
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.add_sphere)
time.sleep(1)
threading.Thread(target=thread_main).start()
def _on_menu_quit(self):
o3d.visualization.gui.Application.instance.quit()
def update_actuator_color(self, name, min_cmd, max_cmd, cmd, transparant = True):
cmd_fraction = (cmd - min_cmd) / (max_cmd - min_cmd)
# Color min cmd = Blue
# Color neutral = Green
# Color max cmd = red
color_rgb = np.ones(4)
if cmd_fraction > 0.5:
color_rgb[0] = (cmd_fraction - 0.5) * 2.
color_rgb[1] = 1. - color_rgb[0]
color_rgb[2] = 0.
else:
color_rgb[0] = 0.
color_rgb[2] = (0.5 - cmd_fraction) * 2.
color_rgb[1] = 1. - color_rgb[2]
mat = o3d.visualization.rendering.MaterialRecord()
mat.base_color = color_rgb
if transparant:
mat.shader = 'defaultLitSSR'
mat.transmission = 0.5
else:
mat.shader = "defaultLit"
self.main_vis.scene.modify_geometry_material(name, mat)
def update_actuator_colors(self):
# update motors
self.update_actuator_color("motor_F", 1000, 8191, self.actuators.get_actuator_value(3))
self.update_actuator_color("motor_R", 1000, 8191, self.actuators.get_actuator_value(4))
self.update_actuator_color("motor_B", 1000, 8191, self.actuators.get_actuator_value(5))
self.update_actuator_color("motor_L", 1000, 8191, self.actuators.get_actuator_value(6))
# update elevator
self.update_actuator_color("ḧor_tail", 8191, -1395, self.actuators.get_actuator_value(8), transparant=False)
def update_fuselage(self):
self.main_vis.scene.set_geometry_transform("fuselage", self.T_rotation)
self.main_vis.scene.set_geometry_transform("fuselage_ref", self.T_rotation_ref)
def update_vert_tail(self):
self.main_vis.scene.set_geometry_transform("vert_tail", self.T_rotation)
def update_wing(self):
if self.wing_rotation_controller == None:
return
wing_rotation_matrix = np.matrix(o3d.geometry.get_rotation_matrix_from_axis_angle(np.array([[0],[0],[self.wing_rotation_controller.get_wing_angle() - np.pi/2]])))
wing_rotation_matrix_ref = np.matrix(o3d.geometry.get_rotation_matrix_from_axis_angle(np.array([[0],[0],[self.wing_rotation_controller.get_wing_angle_sp() - np.pi/2]])))
self.T_rotation_wing = np.matrix(np.eye(4))
self.T_rotation_wing[:3,:3] = wing_rotation_matrix
self.T_rotating_wing_final = self.T_rotation * self.T_rotation_wing
self.T_rotation_wing_ref = np.matrix(np.eye(4))
self.T_rotation_wing_ref[:3,:3] = wing_rotation_matrix_ref
self.T_rotating_wing_final_ref = self.T_rotation * self.T_rotation_wing_ref
self.main_vis.scene.set_geometry_transform("wing", self.T_rotating_wing_final)
self.main_vis.scene.set_geometry_transform("wing_ref", self.T_rotating_wing_final_ref)
def thread_main():
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_motor_R)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_motor_L)
threading.Thread(target=thread_main).start()
def update_elevator(self):
cmd_elevator = self.actuators.get_actuator_value(8)
min_angle = np.deg2rad(-36.6)
max_angle = np.deg2rad(10.3)
min_cmd = 8191.
max_cmd = -1395.
# Calc angle from command
angle_elevator = cmd_elevator * (max_angle - min_angle) / (max_cmd - min_cmd)
elevator_rotation_matrix = np.matrix(o3d.geometry.get_rotation_matrix_from_axis_angle(np.array([[0],[-angle_elevator],[0]])))
T_offset_hinge = np.matrix(np.eye(4))
T_offset_hinge[:3,3] = np.matrix([[1486.6-680.],[8.3],[-32]])
T_elevator = np.eye(4)
T_elevator[:3,:3] = np.matrix(elevator_rotation_matrix)
T_elevator_final = self.T_rotation * np.linalg.inv(T_offset_hinge) * T_elevator * T_offset_hinge
self.main_vis.scene.set_geometry_transform("ḧor_tail", T_elevator_final)
def update_motor_F(self):
self.main_vis.scene.set_geometry_transform("motor_F", self.T_rotation)
def update_motor_R(self):
if self.wing_rotation_controller == None:
return
self.main_vis.scene.set_geometry_transform("motor_R", self.T_rotating_wing_final)
def update_motor_B(self):
self.main_vis.scene.set_geometry_transform("motor_B", self.T_rotation)
def update_motor_L(self):
if self.wing_rotation_controller == None:
return
self.main_vis.scene.set_geometry_transform("motor_L", self.T_rotating_wing_final)
def update_actuators(self):
if self.actuators == None:
return
def thread_main():
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_elevator)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_motor_F)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_motor_B)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_motor_R)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_motor_L)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_actuator_colors)
threading.Thread(target=thread_main).start()
def update_orientation_from_quat(self):
if self.ahrs_ref_quat == None:
return
# Calculate drone rotation matrix
rotation_matrix = np.matrix(o3d.geometry.get_rotation_matrix_from_quaternion(self.ahrs_ref_quat.get_body_quaternion()))
rotation_matrix_ref = np.matrix(o3d.geometry.get_rotation_matrix_from_quaternion(self.ahrs_ref_quat.get_ref_quaternion()))
self.T_rotation = np.matrix(np.eye(4))
self.T_rotation[:3,:3] = rotation_matrix
self.T_rotation_ref = np.matrix(np.eye(4))
self.T_rotation_ref[:3,:3] = rotation_matrix_ref
def thread_main():
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_fuselage)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_vert_tail)
o3d.visualization.gui.Application.instance.post_to_main_thread(
self.w, self.update_wing)
threading.Thread(target=thread_main).start()
self.update_actuators()
def message_recv(self, ac_id, msg):
if msg.name == "AHRS_REF_QUAT":
self.ahrs_ref_quat = AHRSRefQuatMessage(msg)
self.update_orientation_from_quat()
if msg.name == "ROT_WING_CONTROLLER":
self.wing_rotation_controller = RotWingControllerMessage(msg)
if msg.name == "ACTUATORS":
self.actuators = ActuatorsMessage(msg)
self.update_actuators()
def main():
o3d.visualization.gui.Application.instance.initialize()
Viewer3D("Rot wing visualizer")
o3d.visualization.gui.Application.instance.run()
if __name__ == "__main__":
main()